Salesforce TLS 1.0 Support

As of June 25th 2016 sandbox orgs no longer support TLS 1.0 encryption. The previously optional critical update “Require TLS 1.1 or higher for HTTPS connections” was auto-activated on this date. Production orgs will be auto-activated on the 4th March 2017.

Inbound API connections requesting TLS 1.0 will receive the error message “TLS 1.0 has been disabled in this organization. Please use TLS 1.1 or higher when connecting to Salesforce using https.”.

Eclipse IDE Error

For Force.com IDE (Eclipse) users on Java 7 the eclipse.ini file must be modified to add the line below. This is not required for Java 8.

-Dhttps.protocols=TLSv1.1,TLSv1.2

Further information here.

Two-Factor Authentication

Winter ’14 introduces two-factor authentication (2FA) for both User Interface and API logins, a long-awaited security feature enabled through User Profile, or Permission Set. The relevant permissions are:

Two-Factor Authentication for User Interface Logins
Two-Factor Authentication for API Logins

The second factor in question being a time-based token generated by a Salesforce supplied, device specific authenticator app (something similar to the RSA SecurID soft-token mobile app I would expect).

User Interface Login

For User Interface logins the authentication process is shown in the screenshot below. A Salesforce generated code is entered (or scanned via QR Code) into the authenticator app which generates the 6-digit time-based token to authenticate with. At the time of writing the authenticator app download link redirects to salesforce.com/mobile path, so the end-to-end process can’t be tested as yet.

auth_time_based_token_entry

API Login

Two-factor authentication for User Interface logins is a pre-requisite for API logins. In this context the time-based token replaces the standard security token in the API login call.

The time-based token is generated via the mobile authenticator app and added to a field on the User record, as shown below.

user_time_vased_token_input

Identity Confirmation
Time-based tokens can also be used during login verification to activate a computer/device.

I’ll update this post once the authenticator apps become available and the end to end process can be tested. I need to do some investigation also into the specifics of the persisted tokens (how long-lived are they etc.?).

Update 2013-10-23
I notice that the authenticator app information is at the bottom of the salesforce.com/mobile page. Instead of providing a proprietary approach the commonly used Google Authenticator service and related apps are used. iOS screenshot below.

photo

For more information on Google Authenticator refer to here and here.

Salesforce Analytics API

With Winter ’14 the new Analytics API turns GA. This RESTful style of API consumes standard API call limits, uses OAuth authentication and JSON request/response message formats (representations).

The key functionality of the Analytics API is the execution of reports (in synchronous or asynchronous modes), the application of dynamic filtering and the inspection of report metadata.

Note, in experimenting/testing any of the RESTful APIs (Force.com REST API, Chatter REST API, Analytics API etc.) the Apigee Salesforce API Consoles are an extremely convenient and useful tool – screenshot below.

apigee_screenshot

Core API Resources

– Report metadata (GET /services/data/v29.0/analytics/reports/report_ID/describe)

– List recently viewed reports (GET /services/data/v29.0/analytics/reports/list)
Note to obtain a full list of reports the standard REST API should be used to query the Report resource (via Soql).

– Synchronous report execution (/services/data/v29.0/analytics/reports/report_ID)
GET for a simple execution (add includeDetail=true to the Querystring if required)
POST to apply filtering.

– Asynchronous report execution (/services/data/v29.0/analytics/reports/report_ID/instances) – POST to request an execution.
GET to retrieve a list of the execution instances.
GET to the ../instances/instance_ID to request the results of a single execution.

– Fact Maps
The JSON message format for a report execution, synchronous or asynchronous, provides the detail and summary data in a Fact Map structure. In short, the keys provided in the structured groupings (across and down) data provided in the embedded report metadata are concatenated to provide a composite lookup key to detail row (hasDetailRows response flag indicates the existence of detail rows) and aggregate data held in the data map.

For more on Fact Maps refer to the “Decode the Fact Map” section in the Analytics API Reference.

Use Cases
The Analytics API is a long awaited key enabler for mobile/tablet analytics use cases, allowing on-platform reports to be executed and the results accessed in a constrained-device friendly lightweight format. Without this API mobile app developers have been limited to the execution of Soql queries via the standard REST API. With the API such developers have access to the recently viewed reports for a specific user, the report metadata, the ability to execute reports and then to pass the report results up to the graphics library of choice using a standard data format.

Outside of the mobile context, the API offers the same patterns to composite applications such as complementary web applications and 3rd party tools where datasets can be defined on-platform using reports (by end-users perhaps) and the data picked up for services such as mass email marketing etc.

Key Limitations
– 500 synchronous report executions per hour
– 1200 synchronous report execution requests per hour
– Asynchronous report results are available for 24 hours from execution

Report Response Example – Matrix Report

HTTP/1.1 200 OK
Sforce-Limit-Info:api-usage=21/5000000
Date:Sat, 28 Sep 2013 18:30:16 GMT
Content-Length:8705
Content-Type:application/json;charset=UTF-8

{
  "hasDetailRows": false,
  "attributes": {
    "describeUrl": "/services/data/v29.0/analytics/reports/00O300000041iFQEAX/describe",
    "instancesUrl": "/services/data/v29.0/analytics/reports/00O300000041iFQEAX/instances",
    "type": "Report",
    "reportId": "00O300000041iFQEAX",
    "reportName": "Example Report"
  },
  "groupingsDown": {
    "groupings": [
      {
        "value": "001Q000000PUcCtIAX",
        "key": "0",
        "groupings": [
          {
            "value": "2013-08-28",
            "key": "0_0",
            "groupings": [],
            "label": "2013/08/28 - 2013/09/03"
          }
        ],
        "label": "New Business"
      },
      {
        "value": "001Q000000Q0wLMIAX",
        "key": "1",
        "groupings": [
          {
            "value": "2013-09-18",
            "key": "1_0",
            "groupings": [],
            "label": "2013/09/18 - 2013/09/24"
          }
        ],
        "label": "Existing Business"
      },
      {
        "value": "001Q000000OwobqIAX",
        "key": "2",
        "groupings": [
          {
            "value": "2013-07-31",
            "key": "2_0",
            "groupings": [],
            "label": "2013/07/31 - 2013/08/06"
          }
        ],
        "label": "Prospects"
      }
    ]
  },
  "groupingsAcross": {
    "groupings": [
      {
        "value": "Product Sales",
        "key": "0",
        "groupings": [
          {
            "value": "a0AQ00000054Q0uMAX",
            "key": "0_0",
            "groupings": [],
            "label": "Product A"
          },
          {
            "value": "a0AQ0000004YV8xMAX",
            "key": "0_1",
            "groupings": [],
            "label": "Product B"
          }
        ],
        "label": "Product Sales"
      },
      {
        "value": "Services",
        "key": "1",
        "groupings": [
          {
            "value": "a0AQ0000005G0OpMAX",
            "key": "1_0",
            "groupings": [],
            "label": "Service A"
          }
        ],
        "label": "Services"
      }
    ]
  },
  "reportMetadata": {
    "name": "Example Report",
    "id": "00O300000041iFQEAX",
    "aggregates": [
      "RowCount"
    ],
    "groupingsDown": [
      {
        "name": "Client__c.Name",
        "sortOrder": "Asc",
        "dateGranularity": "None"
      },
      {
        "name": "Projection__c.Date__c",
        "sortOrder": "Asc",
        "dateGranularity": "Week"
      }
    ],
    "groupingsAcross": [
      {
        "name": "MyProduct__c.Classification__c",
        "sortOrder": "Asc",
        "dateGranularity": "None"
      },
      {
        "name": "MyProduct__c.Name",
        "sortOrder": "Asc",
        "dateGranularity": "None"
      }
    ],
    "reportType": {
      "type": "MyProduct_CRT__c",
      "label": "My Product CRT"
    },
    "reportBooleanFilter": null,
    "reportFilters": [],
    "detailColumns": [],
    "reportFormat": "MATRIX",
    "currency": null,
    "developerName": "Example_Report"
  },
  "factMap": {
    "1_0!T": {
      "aggregates": [
        {
          "value": 1,
          "label": "1"
        }
      ]
    },
    "0!1_0": {
      "aggregates": [
        {
          "value": 0,
          "label": "0"
        }
      ]
    },
    "1!0_0": {
      "aggregates": [
        {
          "value": 0,
          "label": "0"
        }
      ]
    },
    "1!0_1": {
      "aggregates": [
        {
          "value": 0,
          "label": "0"
        }
      ]
    },
    "2!T": {
      "aggregates": [
        {
          "value": 5,
          "label": "5"
        }
      ]
    },
    "0!0_0": {
      "aggregates": [
        {
          "value": 0,
          "label": "0"
        }
      ]
    },
    "0!0_1": {
      "aggregates": [
        {
          "value": 2,
          "label": "2"
        }
      ]
    },
    "1!1": {
      "aggregates": [
        {
          "value": 1,
          "label": "1"
        }
      ]
    },
    "T!1": {
      "aggregates": [
        {
          "value": 1,
          "label": "1"
        }
      ]
    },
    "1!0": {
      "aggregates": [
        {
          "value": 0,
          "label": "0"
        }
      ]
    },
    "2_0!1_0": {
      "aggregates": [
        {
          "value": 0,
          "label": "0"
        }
      ]
    },
    "2_0!0_1": {
      "aggregates": [
        {
          "value": 0,
          "label": "0"
        }
      ]
    },
    "0_0!1": {
      "aggregates": [
        {
          "value": 0,
          "label": "0"
        }
      ]
    },
    "0_0!0": {
      "aggregates": [
        {
          "value": 2,
          "label": "2"
        }
      ]
    },
    "2_0!0_0": {
      "aggregates": [
        {
          "value": 5,
          "label": "5"
        }
      ]
    },
    "T!0": {
      "aggregates": [
        {
          "value": 7,
          "label": "7"
        }
      ]
    },
    "1_0!1_0": {
      "aggregates": [
        {
          "value": 1,
          "label": "1"
        }
      ]
    },
    "2_0!0": {
      "aggregates": [
        {
          "value": 5,
          "label": "5"
        }
      ]
    },
    "2_0!1": {
      "aggregates": [
        {
          "value": 0,
          "label": "0"
        }
      ]
    },
    "1_0!0_0": {
      "aggregates": [
        {
          "value": 0,
          "label": "0"
        }
      ]
    },
    "1_0!0_1": {
      "aggregates": [
        {
          "value": 0,
          "label": "0"
        }
      ]
    },
    "0!1": {
      "aggregates": [
        {
          "value": 0,
          "label": "0"
        }
      ]
    },
    "T!1_0": {
      "aggregates": [
        {
          "value": 1,
          "label": "1"
        }
      ]
    },
    "0!0": {
      "aggregates": [
        {
          "value": 2,
          "label": "2"
        }
      ]
    },
    "1_0!1": {
      "aggregates": [
        {
          "value": 1,
          "label": "1"
        }
      ]
    },
    "1_0!0": {
      "aggregates": [
        {
          "value": 0,
          "label": "0"
        }
      ]
    },
    "2!1": {
      "aggregates": [
        {
          "value": 0,
          "label": "0"
        }
      ]
    },
    "2!0_1": {
      "aggregates": [
        {
          "value": 0,
          "label": "0"
        }
      ]
    },
    "2!0_0": {
      "aggregates": [
        {
          "value": 5,
          "label": "5"
        }
      ]
    },
    "0_0!0_0": {
      "aggregates": [
        {
          "value": 0,
          "label": "0"
        }
      ]
    },
    "0_0!0_1": {
      "aggregates": [
        {
          "value": 2,
          "label": "2"
        }
      ]
    },
    "2!0": {
      "aggregates": [
        {
          "value": 5,
          "label": "5"
        }
      ]
    },
    "T!T": {
      "aggregates": [
        {
          "value": 8,
          "label": "8"
        }
      ]
    },
    "0!T": {
      "aggregates": [
        {
          "value": 2,
          "label": "2"
        }
      ]
    },
    "1!T": {
      "aggregates": [
        {
          "value": 1,
          "label": "1"
        }
      ]
    },
    "2!1_0": {
      "aggregates": [
        {
          "value": 0,
          "label": "0"
        }
      ]
    },
    "0_0!T": {
      "aggregates": [
        {
          "value": 2,
          "label": "2"
        }
      ]
    },
    "T!0_0": {
      "aggregates": [
        {
          "value": 5,
          "label": "5"
        }
      ]
    },
    "1!1_0": {
      "aggregates": [
        {
          "value": 1,
          "label": "1"
        }
      ]
    },
    "T!0_1": {
      "aggregates": [
        {
          "value": 2,
          "label": "2"
        }
      ]
    },
    "2_0!T": {
      "aggregates": [
        {
          "value": 5,
          "label": "5"
        }
      ]
    },
    "0_0!1_0": {
      "aggregates": [
        {
          "value": 0,
          "label": "0"
        }
      ]
    }
  },
  "allData": true
}

Salesforce Tooling API

With the upcoming Spring 13 release (release window dates below) the new Tooling API goes GA.

11th Jan, sandbox instances
18th Jan – 9th Feb, production instances (EU0 8th Feb, EU1/2 9th Feb)

What is the Tooling API?
The full details can be found in the API guide here, in short the API is intended to provide an optimised, standardised, capable and compelling interface for tools developers. Full stop. The latter two points being key – a major complaint I’ve been hearing for a long time is the immaturity of the available developer tools and the lack of choice. Substandard tools (relative to Ruby, Java and particularly .NET etc.) can really hamper adoption within the IT department, it never ceases to surprise me how much power developers can wield particularly where strong technical leadership is absent.

The Tooling API supports both SOAP and REST styles of web service and enables working copy management for the Apex metadata types, update synchronisation and error checking as well as access to heap dumps, debug logs and so forth. All in all the provided functionality looks like all you’d need to build a rich development tool or Force.com augmentation to an existing toolset.

A new open source version of the Force.com IDE, developed with the Tooling API, will follow the GA release, no date announced.

If I had the time, my first task for 2013 would be to design and build an iPad developer app; delivering a super-efficient developer experience, integrated with GitHub etc.. Anyone who has experienced code-editing in the web app in the iPad Safari browser will surely agree, this could be a winner! I suspect other more forward thinking people will already have such things ready to ship as soon as Spring ’13 rolls out. I hope so. Looking forward to 2013 being the year of new and innovative developer tools..

Force.com Streaming API

So, imagine you’re building a modern web app (or page) and need to update some element on the page in near-real time, perhaps an open inventory quantity to prevent data conflicts upstream. The point being that the immediacy of update of the data plays a fundamental role in the business process. What are the options? Historically an interaction of this type would require a full page refresh, and as such would push the application toward a non-web delivery model. Then partial page updates became popular; we could then consider some form of client-initiated polling (JavaScript timer or otherwise). This client-initiated “Pull” model would satisfy the requirement, with a possible benefit of offloading the expensive polling activity to client agent, all the server needs to do is expose a lightweight (probably stateless) endpoint to return the calculated data. Excellent. However, as anyone implementing such an approach will know – there’s something slightly unedifying about the “Pull” model – you can never really tune the poll frequency to the right balance of data update frequency versus the feeling of wasting server resources. We should also strive to minimise how much trust we put in the client. In the Force.com context, unnecessary polling activity also comes with the cost of consumption of limited API calls – this is a key point. In an ideal world perhaps, the server could somehow just notify the client when the data has definitely changed with no redundant polling, wasted callouts or unnecessary server resource consumption. Enter the Force.com Streaming API.

The Force.com Streaming API provides a server-initiated “Push Model”, where notifications of changes to data of interest can be sent to internal pages (Visualforce), external app servers and external clients. The latter point being interesting in that the use cases for the API aren’t limited to simple UI updates, external system-type services can also be be subscribers in the model. The API is founded on the cometD stack and utilises the long polling technique where an almost persistent client-to-server connection used, i.e. the server holds the client request open until a response is available, once returned the client immediately re-requests the data thereby opening a new connection.

To use this API, a PushTopic is defined using a SOQL query construct and exposed via a Channel. Clients then subscribe to the channel and receive notifications when DML events occur that affect records covered by the SOQL query WHERE clause. PushTopics can notify on Insert and/or Update operations, this can be configured. Also, PushTopics can generate notifications for records matching the SOQL WHERE clause in response to any field change, or just changes to fields referenced in the SELECT clause or a WHERE clause predicate – or both. There is currently no UI for the creation of PushTopics, therefore Apex script must be used – executed perhaps via the Developer Console or Execute Anonymous tab in the Force.com IDE. Not ideal.

Supporting technology
Bayeux protocol – standard protocol for transportation of asynchronous messages – typically in an HTTP context.
CometD – implementation of the Bayeux protocol using the AJAX push technology pattern referred to as Comet. Reference: http://cometd.org.
JSON – notifications are formatted as JSON messages.

Key points
– Stateless model – no server persistence of client state – fire-and-forget from the server perspective.
– Bulk API operations do not initiate notifications – for obvious reasons.
– Server processing of new PushTopic notifications occurs every 3 seconds, therefore this becomes the maximum frequency of update from the client perspective.
– PushTopic construct – each PushTopic relates to one object (Custom or Standard). The SELECT clause must include the Id field, other fields are sent via the channel. Join and aggregation operations are not supported – also formula fields are not supported, which is a surprising limitation.
– Browser support is limited to IE8+ and FF4+.
– Client cookie support is required.
– A limit of 10 subscribing clients is enforced per PushTopic, with a maximum of 20 PushTopics in total. This again is surprising, as 10 clients per-topic is unrealistic for usage in a multi-user web-app. The documentation does suggest however that this soft-limit can be increased through contacting salesforce.com support. There may be a cost implication here.
– A number of JavaScript libraries must be added to each Visualforce page, via a static resource preferably. Use a page template, or page composition if possible to isolate the references.
– The API requires a reasonable level of JavaScript expertise.

Summary
The Force.com Streaming API adds native support for server-initiated “Push” models – helping to limit consumption of expensive API callouts in some circumstances. The key imitations to be aware of are that the API does not enable client-control over the scope of the data pushed from the server, and that the subscribers per PushTopic is limited to 10. With the former, it isn’t possible to limit the scope of notifications to a single Account as an example – therefore a web page would receive notifications for all Accounts. Careful consideration must be to the design and purpose of each topic, with a focus on maximising specificity.