From b1c09bf4f982e119f123ca1d7f19f17f13062ca9 Mon Sep 17 00:00:00 2001 From: Matthew O'Riordan Date: Thu, 24 Nov 2016 12:53:25 +0000 Subject: [PATCH 01/38] spec: Reject invalid authCallback / authUrl content We have seen authUrl retrieving HTML files greater than 500kb in size which should have been rejected due to the content type, but also because of the size. --- content/client-lib-development-guide/features.textile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/content/client-lib-development-guide/features.textile b/content/client-lib-development-guide/features.textile index 88b13124c0..e3f155f9aa 100644 --- a/content/client-lib-development-guide/features.textile +++ b/content/client-lib-development-guide/features.textile @@ -104,11 +104,13 @@ h3(#rest-auth). Auth * @(RSA4)@ Token Auth is used if @useTokenAuth@ is set to true, or if @useTokenAuth@ is unspecified and any one of the following conditions are met: a @clientId@ is specified; @authUrl@ or @authCallback@ is provided; an explicit @token@ or @TokenDetails@ is provided ** @(RSA4a)@ When a @token@ or @tokenDetails@ is used to instance the library, and no means to renew the token is provided (either an API key, @authCallback@ or @authUrl@), if the server responds with a token error (401 HTTP status code and an Ably error value @40140 <= code < 40150@), then the client library should indicate an error, not retry the request and in the case of the realtime library, transition the connection to the @FAILED@ state ** @(RSA4b)@ When the client does have a means to renew the token automatically, and the token has expired or the server has responded with a token error (@statusCode@ value of 401 and error @code@ value in the range @40140 <= code < 40150@), then the client should automatically make a single attempt to reissue the token and resend the request using the new token. If the token creation failed or the subsequent request with the new token failed due to a token error, then the request should result in an error -** @(RSA4c)@ If an attempt by the realtime client library to authenticate is made using the @authUrl@ or @authCallback@, and the request to @authUrl@ fails (unless @RSA4d@ applies), the callback @authCallback@ results in an error (unless @RSA4d@ applies), an attempt to exchange a @TokenRequest@ for a @TokenDetails@ results in an error (unless @RSA4d@ applies), the provided token is in an invalid format, or the attempt times out after "@realtimeRequestTimeout@":#DF1b, then: +** @(RSA4c)@ If an attempt by the realtime client library to authenticate is made using the @authUrl@ or @authCallback@, and the request to @authUrl@ fails (unless @RSA4d@ applies), the callback @authCallback@ results in an error (unless @RSA4d@ applies), an attempt to exchange a @TokenRequest@ for a @TokenDetails@ results in an error (unless @RSA4d@ applies), the provided token is in an invalid format (per "@RSA4e@":#RSA4e), or the attempt times out after "@realtimeRequestTimeout@":#DF1b, then: *** @(RSA4c1)@An @ErrorInfo@ with @code@ @80019@ and description of the underlying failure should be emitted with the state change, in the @errorReason@ and/or in the callback as appropriate *** @(RSA4c2)@If the connection is @CONNECTING@, then the connection attempt should be treated as unsuccessful, and as such the connection should transition to the @DISCONNECTED@ or @SUSPENDED@ state as defined in "RTN14":#RTN14 and "RTN15":#RTN15 *** @(RSA4c3)@If the connection is @CONNECTED@, then the connection should remain @CONNECTED@ ** @(RSA4d)@ If a request by a realtime client to an @authUrl@ results in an HTTP 403 response, or any of an @authUrl@ request, an @authCallback@, or a request to Ably to exchange a @TokenRequest@ for a @TokenDetails@ result in an @ErrorInfo@ with @statusCode@ 403, then the client library should transition to the @FAILED@ state, with the connection @errorReason@ should be set to the @ErrorInfo@ (or where there is none, as for a 403 @authUrl@ response with no body, an @ErrorInfo@ with @code@ @40300@ and an appropriate message) +** @(RSA4e)@ If in the course of a REST request (or explicit call to @requestToken@) an attempt to authenticate using @authUrl@ or @authCallback@ fails due to a timeout, network error, a token in an invalid format (per "@RSA4e@":#RSA4e), or some other auth error condition other than an explicit @ErrorInfo@ from Ably, the request should result in an error with @code@ 40170, @statusCode@ 401, and a suitable error message +** @(RSA4f)@ A token is in an invalid format if: it is requested using an@authUrl@ and the content type is neither @text/plain@ or @application/json@; with an @authCallback@ and the object passed in is neither a @String@, @JsonObject@, @TokenRequest@ object or a @TokenDetails@ object; is greater than 384 bytes (for a text token string), or 128kB (for a JSON stringified @JsonObject@, @TokenRequest@ or @TokenDetails@) * @(RSA14)@ If Token Auth is selected, yet a token is not provided and there is no means to generate a token, then this will result in an error. For example, if only the option @useTokenAuth@ is specified, and a @key@ is not provided, then the client library is unable to authenticate or issue a token * @(RSA15)@ If Token Auth is selected and @clientId@ has been set in the @ClientOptions@ when the library was instanced: ** @(RSA15a)@ Any @clientId@ provided in @ClientOptions@ must match any non wildcard (@'*'@) @clientId@ value in @TokenDetails@ or @connectionDetails@ of the @CONNECTED@ @ProtocolMessage@, where applicable From 1acf1e46699a2d7dc52349f4698ef6f4eb6bea6d Mon Sep 17 00:00:00 2001 From: Matthew O'Riordan Date: Fri, 25 Nov 2016 08:47:19 +0000 Subject: [PATCH 02/38] spec: authCallback can take a JSON-like object --- content/client-lib-development-guide/features.textile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/content/client-lib-development-guide/features.textile b/content/client-lib-development-guide/features.textile index e3f155f9aa..d6fdbfea38 100644 --- a/content/client-lib-development-guide/features.textile +++ b/content/client-lib-development-guide/features.textile @@ -1156,7 +1156,7 @@ h4. ClientOptions *** @(TO3j2)@ @token@ string - An authentication token string issued for this application *** @(TO3j3)@ @tokenDetails@ @TokenDetails@ - An authentication token issued for this application *** @(TO3j4)@ @useTokenAuth@ boolean - When true, token authentication will always be used by the client. If @clientId@ is unspecified, then the token issued will inherently be anonymous i.e. it will contain an empty @clientId@ -*** @(TO3j5)@ @authCallback@ - A callback to call to obtain a signed @TokenRequest@, @TokenDetails@ or a token string. This enables a client to obtain token requests or tokens from another entity, so tokens can be renewed without the client requiring a key +*** @(TO3j5)@ @authCallback@ - A callback to call to obtain a signed @TokenRequest@, @TokenDetails@ or a token string. This enables a client to obtain token requests or tokens from another entity, so tokens can be renewed without the client requiring a key. If a JSON-like object is provided in the callback, then the library will determine if it's a @TokenRequest@ or @TokenDetails@ and use the @fromJson@ method ("TD7":#TD7, "TE6":#TE6) to construct an object *** @(TO3j6)@ @authUrl@ string - A URL to query to obtain a signed @TokenRequest@, @TokenDetails@ or a token string. This enables a client to obtain token request or token from another entity, so tokens can be renewed without the client requiring a key *** @(TO3j7)@ @authMethod@ - The HTTP verb to be used when a request is made by the library to the @authUrl@. Defaults to @GET@, supports @GET@ and @POST@ *** @(TO3j8)@ @authHeaders@ - Headers to be included in any request made by the library to the @authUrl@ @@ -1199,7 +1199,7 @@ h4. AuthOptions * @(AO1)@ A class providing configurable authentication options used when authenticating or issuing tokens explicitly. These options are used when invoking @Auth#authorize@, @Auth#requestToken@, @Auth#createTokenRequest@ and @Auth#authorize@ * @(AO2)@ The attributes of @AuthOptions@ consist of: ** @(AO2a)@ @key@ string - Full Ably key string, as obtained from dashboard, used when signing token requests locally -** @(AO2b)@ @authCallback@ - A callback to call to obtain a signed @TokenRequest@, @TokenDetails@ or a token string. This enables a client to obtain token requests or tokens from another entity, so tokens can be renewed without the client requiring a key +** @(AO2b)@ @authCallback@ - A callback to call to obtain a signed @TokenRequest@, @TokenDetails@ or a token string. This enables a client to obtain token requests or tokens from another entity, so tokens can be renewed without the client requiring a key. If a JSON-like object is provided in the callback, then the library will determine if it's a @TokenRequest@ or @TokenDetails@ and use the @fromJson@ method ("TD7":#TD7, "TE6":#TE6) to construct an object ** @(AO2c)@ @authUrl@ string - A URL to query to obtain a signed @TokenRequest@, @TokenDetails@ or a token string. This enables a client to obtain token request or token from another entity, so tokens can be renewed without the client requiring a key ** @(AO2d)@ @authMethod@ - The HTTP verb to be used when a request is made by the library to the @authUrl@. Defaults to @GET@, supports @GET@ and @POST@ ** @(AO2e)@ @authHeaders@ - Headers to be included in any request made by the library to the @authUrl@ @@ -1333,7 +1333,7 @@ class ClientOptions: maxFrameSize: Int default 524288 // TO3l8 class AuthOptions: // RSA8e - authCallback: (() -> io (String | TokenDetails | TokenRequest))? // RSA4a, RSA4, TO3j5, AO2b + authCallback: (() -> io (String | TokenDetails | TokenRequest | JsonObject))? // RSA4a, RSA4, TO3j5, AO2b authHeaders: [String: Stringifiable]? // RSA8c3, TO3j8, AO2e authMethod: .GET | .POST default .GET // RSA8c, TO3j7, AO2d authParams: [String: Stringifiable]? // RSA8c3, RSA8c1, TO3j9, AO2f From 55642a28dc6bf94ab021532dec0a3170f3e13c10 Mon Sep 17 00:00:00 2001 From: Matthew O'Riordan Date: Fri, 25 Nov 2016 08:54:26 +0000 Subject: [PATCH 03/38] spec: Consistent spec reference formatting --- content/client-lib-development-guide/features.textile | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/content/client-lib-development-guide/features.textile b/content/client-lib-development-guide/features.textile index d6fdbfea38..9ff8273bd1 100644 --- a/content/client-lib-development-guide/features.textile +++ b/content/client-lib-development-guide/features.textile @@ -82,7 +82,7 @@ h3(#restclient). RestClient * @(RSC15)@ Host Fallback ** @(RSC15b)@ The fallback behavior described below only applies when the default @rest.ably.io@ endpoint is being used and has not been overriden (see "RSC11":#RSC11), @ClientOptions#fallbackHostsUseDefault@ is @true@, or an array of @ClientOptions#fallbackHosts@ is provided. If host fallback is not supported, failing HTTP requests that would have "qualified for a retry against a fallback host (see RSC15d)":#RSC15d, will instead result in an error immediately ** @(RSC15e)@ By default, every new HTTP request is first attempted to the default primary host @rest.ably.io@ (unless overriden in @ClientOptions#restHost@), which, through DNS, is automatically routed to the client's closest data center. The client library must always prefer the default endpoint (closest data center), even if a previous request to that endpoint has failed -** @(RSC15a)@ In the case of an error necessitating use of an alternative host (see "RSC15d":#RSC15d), try fallback hosts (with a matching Host header as this is necessary when fallbacks are proxied through a CDN) in random order, continuing to try further hosts if "qualifying errors":#RSC15d occur, failing when all have been tried or the configured @httpMaxRetryCount@ has been reached (see "@TO3l5@":#TO3l5). This ensures that a client library is able to work around routing or other problems for the user's closest data center. For example, if a @POST@ request to @rest.ably.io@ fails because the default endpoint is unreachable or unserviceable, then the @POST@ request should be retried again against the fallback hosts in attempt to find an alternate healthy data center to service the request. The five default fallback hosts are @[a-e].ably-realtime.com@. If an array of custom fallback hosts are provided in @ClientOptions#fallbackHosts@, then they will be used instead. If an empty array of fallback hosts is provided, then fallback host functionality is disabled +** @(RSC15a)@ In the case of an error necessitating use of an alternative host (see "RSC15d":#RSC15d), try fallback hosts (with a matching Host header as this is necessary when fallbacks are proxied through a CDN) in random order, continuing to try further hosts if "qualifying errors":#RSC15d occur, failing when all have been tried or the configured @httpMaxRetryCount@ has been reached (see "TO3l@":#TO3l5). This ensures that a client library is able to work around routing or other problems for the user's closest data center. For example, if a @POST@ request to @rest.ably.io@ fails because the default endpoint is unreachable or unserviceable, then the @POST@ request should be retried again against the fallback hosts in attempt to find an alternate healthy data center to service the request. The five default fallback hosts are @[a-e].ably-realtime.com@. If an array of custom fallback hosts are provided in @ClientOptions#fallbackHosts@, then they will be used instead. If an empty array of fallback hosts is provided, then fallback host functionality is disabled ** @(RSC15d)@ Errors that necessitate use of an alternative host include: host unresolvable or unreachable, request timeout, or a response but with an applicable HTTP status code in the range @500 <= code <= 504@. Resending requests that have failed for other failure conditions will not fix the problem and will simply increase the load on other data-centers unnecessarily * @(RSC17)@ When instancing the library, if a @clientId@ attribute is set in @ClientOptions@, then the @Auth#clientId@ attribute will contain the provided @clientId@ * @(RSC19)@ @RestClient#request@ function is provided as a convenience for customers who wish to use bleeding edge REST API functionality that is either not documented or is not included in the API for our client libraries. The REST client library provides a function to issue HTTP requests to the Ably endpoints with all the built in functionality of the library such as authentication, paging, fallback hosts, MsgPack and JSON support etc. The function: @@ -104,12 +104,12 @@ h3(#rest-auth). Auth * @(RSA4)@ Token Auth is used if @useTokenAuth@ is set to true, or if @useTokenAuth@ is unspecified and any one of the following conditions are met: a @clientId@ is specified; @authUrl@ or @authCallback@ is provided; an explicit @token@ or @TokenDetails@ is provided ** @(RSA4a)@ When a @token@ or @tokenDetails@ is used to instance the library, and no means to renew the token is provided (either an API key, @authCallback@ or @authUrl@), if the server responds with a token error (401 HTTP status code and an Ably error value @40140 <= code < 40150@), then the client library should indicate an error, not retry the request and in the case of the realtime library, transition the connection to the @FAILED@ state ** @(RSA4b)@ When the client does have a means to renew the token automatically, and the token has expired or the server has responded with a token error (@statusCode@ value of 401 and error @code@ value in the range @40140 <= code < 40150@), then the client should automatically make a single attempt to reissue the token and resend the request using the new token. If the token creation failed or the subsequent request with the new token failed due to a token error, then the request should result in an error -** @(RSA4c)@ If an attempt by the realtime client library to authenticate is made using the @authUrl@ or @authCallback@, and the request to @authUrl@ fails (unless @RSA4d@ applies), the callback @authCallback@ results in an error (unless @RSA4d@ applies), an attempt to exchange a @TokenRequest@ for a @TokenDetails@ results in an error (unless @RSA4d@ applies), the provided token is in an invalid format (per "@RSA4e@":#RSA4e), or the attempt times out after "@realtimeRequestTimeout@":#DF1b, then: +** @(RSA4c)@ If an attempt by the realtime client library to authenticate is made using the @authUrl@ or @authCallback@, and the request to @authUrl@ fails (unless @RSA4d@ applies), the callback @authCallback@ results in an error (unless "RSA4d":#RSA4 applies), an attempt to exchange a @TokenRequest@ for a @TokenDetails@ results in an error (unless "RSA4d":#RSA4 applies), the provided token is in an invalid format (per "RSA4e":#RSA4e), or the attempt times out after "@realtimeRequestTimeout@":#DF1b, then: *** @(RSA4c1)@An @ErrorInfo@ with @code@ @80019@ and description of the underlying failure should be emitted with the state change, in the @errorReason@ and/or in the callback as appropriate *** @(RSA4c2)@If the connection is @CONNECTING@, then the connection attempt should be treated as unsuccessful, and as such the connection should transition to the @DISCONNECTED@ or @SUSPENDED@ state as defined in "RTN14":#RTN14 and "RTN15":#RTN15 *** @(RSA4c3)@If the connection is @CONNECTED@, then the connection should remain @CONNECTED@ ** @(RSA4d)@ If a request by a realtime client to an @authUrl@ results in an HTTP 403 response, or any of an @authUrl@ request, an @authCallback@, or a request to Ably to exchange a @TokenRequest@ for a @TokenDetails@ result in an @ErrorInfo@ with @statusCode@ 403, then the client library should transition to the @FAILED@ state, with the connection @errorReason@ should be set to the @ErrorInfo@ (or where there is none, as for a 403 @authUrl@ response with no body, an @ErrorInfo@ with @code@ @40300@ and an appropriate message) -** @(RSA4e)@ If in the course of a REST request (or explicit call to @requestToken@) an attempt to authenticate using @authUrl@ or @authCallback@ fails due to a timeout, network error, a token in an invalid format (per "@RSA4e@":#RSA4e), or some other auth error condition other than an explicit @ErrorInfo@ from Ably, the request should result in an error with @code@ 40170, @statusCode@ 401, and a suitable error message +** @(RSA4e)@ If in the course of a REST request (or explicit call to @requestToken@) an attempt to authenticate using @authUrl@ or @authCallback@ fails due to a timeout, network error, a token in an invalid format (per "RSA4e":#RSA4e), or some other auth error condition other than an explicit @ErrorInfo@ from Ably, the request should result in an error with @code@ 40170, @statusCode@ 401, and a suitable error message ** @(RSA4f)@ A token is in an invalid format if: it is requested using an@authUrl@ and the content type is neither @text/plain@ or @application/json@; with an @authCallback@ and the object passed in is neither a @String@, @JsonObject@, @TokenRequest@ object or a @TokenDetails@ object; is greater than 384 bytes (for a text token string), or 128kB (for a JSON stringified @JsonObject@, @TokenRequest@ or @TokenDetails@) * @(RSA14)@ If Token Auth is selected, yet a token is not provided and there is no means to generate a token, then this will result in an error. For example, if only the option @useTokenAuth@ is specified, and a @key@ is not provided, then the client library is unable to authenticate or issue a token * @(RSA15)@ If Token Auth is selected and @clientId@ has been set in the @ClientOptions@ when the library was instanced: @@ -278,7 +278,7 @@ h3(#realtimeclient). RealtimeClient * @(RTC2)@ @RealtimeClient#connection@ attribute provides access to the underlying @Connection@ object * @(RTC3)@ @RealtimeClient#channels@ attribute provides access to the underlying @Channels@ object * @(RTC4)@ @RealtimeClient#auth@ attribute provides access to the @Auth@ object that was instanced with the @ClientOptions@ provided in the @RealtimeClient@ constructor -** @(RTC4a)@ Unlike the stateless REST client library, the @Auth#clientId@ is populated when the connection is established. The @CONNECTED@ @ProtocolMessage@ contains the confirmed @clientId@ for this connected client i.e. the client is considered identified. See "@RSA7b@":#RSA7b and "@RSA12@":#RSA12 for further info +** @(RTC4a)@ Unlike the stateless REST client library, the @Auth#clientId@ is populated when the connection is established. The @CONNECTED@ @ProtocolMessage@ contains the confirmed @clientId@ for this connected client i.e. the client is considered identified. See "@RSA7b@":#RSA7b and "@RSA12@":#RSA12 for futher info * @(RTC5)@ @RealtimeClient#stats@ function: ** @(RTC5a)@ Proxy to @RestClient#stats@ presented with an async or threaded interface as appropriate ** @(RTC5b)@ Accepts all the same params as @RestClient#stats@ and provides all the same functionality @@ -373,7 +373,7 @@ h3(#realtime-connection). Connection *** @(RTN15b2)@ @connectionSerial@ is the most recent @ProtocolMessage#connectionSerial@ received from Ably or @Connection#serial@ which should be identical ** @(RTN15c)@ The system's response to a resume request will be one of the following: **** @(RTN15c1)@ @CONNECTED@ @ProtocolMessage@ with the same @connectionId@ as the current client, and no @error@. In this case, the server is indicating that the resume succeeded, all channels are still attached, and all backlog messages are available. The client should not change the state of attached channels, and immediately process any queued messages for that channel -**** @(RTN15c2)@ @CONNECTED@ @ProtocolMessage@ with the same @connectionId@ as the current client, and an @error@. In this case, the server is indicating that the resume succeeded but with a non-fatal error, all channels are still attached, and some backlog messages may be unavailable. The @ErrorInfo@ received should be set as the @reason@ in the @CONNECTED@ event, and the @Connection#errorReason@ should be set. The client should not change the state of attached channels, and immediately process any queued messages for that channel. Any channels that are not resumed in full may receive an @ATTACHED@ @ProtocolMessage@ with an @error@, see "@RTL12@":#RTL12 +**** @(RTN15c2)@ @CONNECTED@ @ProtocolMessage@ with the same @connectionId@ as the current client, and an @error@. In this case, the server is indicating that the resume succeeded but with a non-fatal error, all channels are still attached, and some backlog messages may be unavailable. The @ErrorInfo@ received should be set as the @reason@ in the @CONNECTED@ event, and the @Connection#errorReason@ should be set. The client should not change the state of attached channels, and immediately process any queued messages for that channel. Any channels that are not resumed in full may receive an @ATTACHED@ @ProtocolMessage@ with an @error@, see "RTL12":#RTL12 **** @(RTN15c3)@ @CONNECTED@ @ProtocolMessage@ with a new @connectionId@, and an error in @error@. In this case, a new connection has been established, the resume was unsuccessful, the channels are no longer attached, and the error indicates the cause of the unsuccessful resume. The @ErrorInfo@ should be set as the @reason@ in the @CONNECTED@ event, and the @Connection#errorReason@ should be set. The client library should initiate an attach for channels that are in the @SUSPENDED@ state. For all channels in the @ATTACHING@ or @ATTACHED@ state, the client library should fail any previously queued messages for that channel and initiate a new attach i.e. a new @ATTACH@ @ProtocolMessage@ must be sent for each channel. Finally, the internal @msgSerial@ counter is reset so that the first message published to Ably will contain a @msgSerial@ value of @0@ **** @(RTN15c5)@ @ERROR@ @ProtocolMessage@ indicating a failure to authenticate as a result of a token error (see "RTN15h":#RTN15h). The transport will be closed by the server. The spec described in "RTN15h":#RTN15h must be followed for a connection being resumed with a token error **** @(RTN15c4)@ Any other @ERROR@ @ProtocolMessage@ indicating a fatal error in the connection. The server will close the transport immediately after. The client should transition to the @FAILED@ state triggering all attached channels to transition to the @FAILED@ state as well. Additionally the @Connection#errorReason@ will be set should be set with the error received from Ably From 47606aad5b0a9b3c17275e1a50894eeaa136fe5e Mon Sep 17 00:00:00 2001 From: Matthew O'Riordan Date: Fri, 25 Nov 2016 09:03:12 +0000 Subject: [PATCH 04/38] api: Add details on authUrl / authCallback payload restrictions --- content/types/_auth_options.textile | 4 ++-- content/types/_client_options.textile | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/content/types/_auth_options.textile b/content/types/_auth_options.textile index 5fdf788ceb..8efacd4b25 100644 --- a/content/types/_auth_options.textile +++ b/content/types/_auth_options.textile @@ -19,9 +19,9 @@ h4. ruby: Attributes Python: Attributes -- authCallbackAuthCallbackauth_callback:auth_callback := A functionfunction with the form @function(tokenParams, callback(err, tokenOrTokenRequest))@@TokenCallback@ instancecallable (eg a lambda)proc / lambda (called synchronously in REST and Realtime but does not block EventMachine in the latter) which is called when a new token is required. The role of the callback is to either generate a signed "@TokenRequest@":/realtime/types#token-request which may then be submitted automatically by the library to the "Ably REST API @requestToken@":/rest-api#request-token; or to provide a valid token in as a "@TokenDetails@":/realtime/types#token-details object. See "an authentication callback example":<%= JsBins.url_for('authentication/auth-callback') %> or "our authentication documentation":/rest/authentication for details of the token request format and associated API calls.
__Type: @Callable@@TokenCallback@@Proc@@Func>@__ +- authCallbackAuthCallbackauth_callback:auth_callback := A functionfunction with the form @function(tokenParams, callback(err, tokenOrTokenRequest))@@TokenCallback@ instancecallable (eg a lambda)proc / lambda (called synchronously in REST and Realtime but does not block EventMachine in the latter) which is called when a new token is required. The role of the callback is to either generate a signed "@TokenRequest@":/realtime/types#token-request which may then be submitted automatically by the library to the "Ably REST API @requestToken@":/rest-api#request-token; or to provide a valid token in as a "@TokenDetails@":/realtime/types#token-details object. See "an authentication callback example":<%= JsBins.url_for('authentication/auth-callback') %> or "our authentication documentation":/rest/authentication for details of the token request format and associated API calls. Please note that the JSON stringified version of @TokenDetails@ or @TokenRequest@ must be less than 128kb.
__Type: @Callable@@TokenCallback@@Proc@@Func>@__ -- authUrlAuthUrl:auth_urlauth_url := A URL that the library may use to obtain a token string (in plain text format), or a signed "@TokenRequest@":/realtime/types#token-request or "@TokenDetails@":/realtime/types#token-details (in JSON format). For example, this can be used by a client to obtain signed token requests from an application server.
__Type: @String@@Uri@@NSURL@__ +- authUrlAuthUrl:auth_urlauth_url := A URL that the library may use to obtain a token string (in plain text format), or a signed "@TokenRequest@":/realtime/types#token-request or "@TokenDetails@":/realtime/types#token-details (in JSON format). For example, this can be used by a client to obtain signed token requests from an application server. Please note that the JSON stringified version of @TokenDetails@ or @TokenRequest@ must be less than 128kb.
__Type: @String@@Uri@@NSURL@__ - authMethodAuthMethod:auth_methodauth_method := _@GET@@:get@_ The HTTP verb to use for the request, either @GET@@:get@ or @POST@@:post@
__Type: @String@@Symbol@@HttpMethod@__ diff --git a/content/types/_client_options.textile b/content/types/_client_options.textile index 85839efa52..893d2941f7 100644 --- a/content/types/_client_options.textile +++ b/content/types/_client_options.textile @@ -26,9 +26,9 @@ h4. - clientIdClientIdclient_id:client_id := A client ID, used for identifying this client when publishing messages or for presence purposes. The @clientId@@client_id@@ClientId@ can be any non-empty string. This option is primarily intended to be used in situations where the library is instanced with a key; note that a @clientId@@client_id@@ClientId@ may also be implicit in a token used to instance the library; an error will be raised if a @clientId@@client_id@ specified here conflicts with the @clientId@@client_id@@ClientId@ implicit in the token. "Find out more about client identities":/how-ably-works#client-identity
__Type: @String@__ -- authCallbackAuthCallbackauth_callback:auth_callback := A functionfunction with the form @function(tokenParams, callback(err, tokenOrTokenRequest))@@TokenCallback@ instancecallable (eg a lambda)proc / lambda (called synchronously in REST and Realtime but does not block EventMachine in the latter) which is called when a new token is required. The role of the callback is to either generate a signed "@TokenRequest@":/realtime/types#token-request which may then be submitted automatically by the library to the "Ably REST API @requestToken@":/rest-api#request-token; or to provide a valid token in as a "@TokenDetails@":/realtime/types#token-details object. See "an authentication callback example":<%= JsBins.url_for('authentication/auth-callback') %> or "our authentication documentation":/rest/authentication for details of the token request format and associated API calls.
__Type: @Callable@@TokenCallback@@Proc@@Func>@__ +- authCallbackAuthCallbackauth_callback:auth_callback := A functionfunction with the form @function(tokenParams, callback(err, tokenOrTokenRequest))@@TokenCallback@ instancecallable (eg a lambda)proc / lambda (called synchronously in REST and Realtime but does not block EventMachine in the latter) which is called when a new token is required. The role of the callback is to either generate a signed "@TokenRequest@":/realtime/types#token-request which may then be submitted automatically by the library to the "Ably REST API @requestToken@":/rest-api#request-token; or to provide a valid token in as a "@TokenDetails@":/realtime/types#token-details object. See "an authentication callback example":<%= JsBins.url_for('authentication/auth-callback') %> or "our authentication documentation":/rest/authentication for details of the token request format and associated API calls. Please note that the JSON stringified version of @TokenDetails@ or @TokenRequest@ must be less than 128kb.
__Type: @Callable@@TokenCallback@@Proc@@Func>@__ -- authUrlAuthUrlauth_url:auth_url := A URL that the library may use to obtain a token string (in plain text format), or a signed "@TokenRequest@":/realtime/types#token-request or "@TokenDetails@":/realtime/types#token-details (in JSON format). For example, this can be used by a client to obtain signed token requests from an application server.
__Type: @String@@Uri@__ +- authUrlAuthUrlauth_url:auth_url := A URL that the library may use to obtain a token string (in plain text format), or a signed "@TokenRequest@":/realtime/types#token-request or "@TokenDetails@":/realtime/types#token-details (in JSON format). For example, this can be used by a client to obtain signed token requests from an application server. Please note that the JSON stringified version of @TokenDetails@ or @TokenRequest@ must be less than 128kb.
__Type: @String@@Uri@__ - authMethodAuthMethodauth_method:auth_method := _@GET@@:get@_ The HTTP verb to use for the request, either @GET@@:get@ or @POST@@:post@
__Type: @String@@Symbol@@HttpMethod@__ From 92bd442dae2a57ac55f1e8146baae94fca4ad01f Mon Sep 17 00:00:00 2001 From: Matthew O'Riordan Date: Fri, 25 Nov 2016 09:14:05 +0000 Subject: [PATCH 05/38] spec: Prefer JSON-encodeable See https://github.com/ably/docs/pull/203#discussion_r89470286 --- content/client-lib-development-guide/features.textile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/client-lib-development-guide/features.textile b/content/client-lib-development-guide/features.textile index 9ff8273bd1..5f9e37c40c 100644 --- a/content/client-lib-development-guide/features.textile +++ b/content/client-lib-development-guide/features.textile @@ -1156,7 +1156,7 @@ h4. ClientOptions *** @(TO3j2)@ @token@ string - An authentication token string issued for this application *** @(TO3j3)@ @tokenDetails@ @TokenDetails@ - An authentication token issued for this application *** @(TO3j4)@ @useTokenAuth@ boolean - When true, token authentication will always be used by the client. If @clientId@ is unspecified, then the token issued will inherently be anonymous i.e. it will contain an empty @clientId@ -*** @(TO3j5)@ @authCallback@ - A callback to call to obtain a signed @TokenRequest@, @TokenDetails@ or a token string. This enables a client to obtain token requests or tokens from another entity, so tokens can be renewed without the client requiring a key. If a JSON-like object is provided in the callback, then the library will determine if it's a @TokenRequest@ or @TokenDetails@ and use the @fromJson@ method ("TD7":#TD7, "TE6":#TE6) to construct an object +*** @(TO3j5)@ @authCallback@ - A callback to call to obtain a signed @TokenRequest@, @TokenDetails@ or a token string. This enables a client to obtain token requests or tokens from another entity, so tokens can be renewed without the client requiring a key. If a JSON-encodable object is provided in the callback, then the library will determine if it's a @TokenDetails@ (if it contains a @token@ key) or @TokenRequest@ (no @token@ key) and use the @fromJson@ method ("TD7":#TD7, "TE6":#TE6) to construct an object *** @(TO3j6)@ @authUrl@ string - A URL to query to obtain a signed @TokenRequest@, @TokenDetails@ or a token string. This enables a client to obtain token request or token from another entity, so tokens can be renewed without the client requiring a key *** @(TO3j7)@ @authMethod@ - The HTTP verb to be used when a request is made by the library to the @authUrl@. Defaults to @GET@, supports @GET@ and @POST@ *** @(TO3j8)@ @authHeaders@ - Headers to be included in any request made by the library to the @authUrl@ @@ -1199,7 +1199,7 @@ h4. AuthOptions * @(AO1)@ A class providing configurable authentication options used when authenticating or issuing tokens explicitly. These options are used when invoking @Auth#authorize@, @Auth#requestToken@, @Auth#createTokenRequest@ and @Auth#authorize@ * @(AO2)@ The attributes of @AuthOptions@ consist of: ** @(AO2a)@ @key@ string - Full Ably key string, as obtained from dashboard, used when signing token requests locally -** @(AO2b)@ @authCallback@ - A callback to call to obtain a signed @TokenRequest@, @TokenDetails@ or a token string. This enables a client to obtain token requests or tokens from another entity, so tokens can be renewed without the client requiring a key. If a JSON-like object is provided in the callback, then the library will determine if it's a @TokenRequest@ or @TokenDetails@ and use the @fromJson@ method ("TD7":#TD7, "TE6":#TE6) to construct an object +** @(AO2b)@ @authCallback@ - A callback to call to obtain a signed @TokenRequest@, @TokenDetails@ or a token string. This enables a client to obtain token requests or tokens from another entity, so tokens can be renewed without the client requiring a key. If a JSON-encodeable object is provided in the callback, then the library will determine if it's a @TokenRequest@ or @TokenDetails@ and use the @fromJson@ method ("TD7":#TD7, "TE6":#TE6) to construct an object ** @(AO2c)@ @authUrl@ string - A URL to query to obtain a signed @TokenRequest@, @TokenDetails@ or a token string. This enables a client to obtain token request or token from another entity, so tokens can be renewed without the client requiring a key ** @(AO2d)@ @authMethod@ - The HTTP verb to be used when a request is made by the library to the @authUrl@. Defaults to @GET@, supports @GET@ and @POST@ ** @(AO2e)@ @authHeaders@ - Headers to be included in any request made by the library to the @authUrl@ From 593d983ec6d8ddc6e6aab81095c99f358d9bae39 Mon Sep 17 00:00:00 2001 From: Matthew O'Riordan Date: Fri, 25 Nov 2016 09:26:41 +0000 Subject: [PATCH 06/38] spec: Describe how TokenDetails are detected --- content/client-lib-development-guide/features.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/client-lib-development-guide/features.textile b/content/client-lib-development-guide/features.textile index 5f9e37c40c..deb48a7c28 100644 --- a/content/client-lib-development-guide/features.textile +++ b/content/client-lib-development-guide/features.textile @@ -1199,7 +1199,7 @@ h4. AuthOptions * @(AO1)@ A class providing configurable authentication options used when authenticating or issuing tokens explicitly. These options are used when invoking @Auth#authorize@, @Auth#requestToken@, @Auth#createTokenRequest@ and @Auth#authorize@ * @(AO2)@ The attributes of @AuthOptions@ consist of: ** @(AO2a)@ @key@ string - Full Ably key string, as obtained from dashboard, used when signing token requests locally -** @(AO2b)@ @authCallback@ - A callback to call to obtain a signed @TokenRequest@, @TokenDetails@ or a token string. This enables a client to obtain token requests or tokens from another entity, so tokens can be renewed without the client requiring a key. If a JSON-encodeable object is provided in the callback, then the library will determine if it's a @TokenRequest@ or @TokenDetails@ and use the @fromJson@ method ("TD7":#TD7, "TE6":#TE6) to construct an object +** @(AO2b)@ @authCallback@ - A callback to call to obtain a signed @TokenRequest@, @TokenDetails@ or a token string. This enables a client to obtain token requests or tokens from another entity, so tokens can be renewed without the client requiring a key. If a JSON-encodable object is provided in the callback, then the library will determine if it's a @TokenDetails@ (if it contains a @token@ key) or @TokenRequest@ (no @token@ key) and use the @fromJson@ method ("TD7":#TD7, "TE6":#TE6) to construct an object ** @(AO2c)@ @authUrl@ string - A URL to query to obtain a signed @TokenRequest@, @TokenDetails@ or a token string. This enables a client to obtain token request or token from another entity, so tokens can be renewed without the client requiring a key ** @(AO2d)@ @authMethod@ - The HTTP verb to be used when a request is made by the library to the @authUrl@. Defaults to @GET@, supports @GET@ and @POST@ ** @(AO2e)@ @authHeaders@ - Headers to be included in any request made by the library to the @authUrl@ From 2f99cdad2411cfb5e0bdb6e7a0a1b1fabcad1a2c Mon Sep 17 00:00:00 2001 From: Simon Woolf Date: Tue, 13 Dec 2016 09:28:56 +0000 Subject: [PATCH 07/38] Be explicit about failure conditions vs result in realtime vs rest libs --- content/client-lib-development-guide/features.textile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/client-lib-development-guide/features.textile b/content/client-lib-development-guide/features.textile index deb48a7c28..0618763314 100644 --- a/content/client-lib-development-guide/features.textile +++ b/content/client-lib-development-guide/features.textile @@ -104,13 +104,13 @@ h3(#rest-auth). Auth * @(RSA4)@ Token Auth is used if @useTokenAuth@ is set to true, or if @useTokenAuth@ is unspecified and any one of the following conditions are met: a @clientId@ is specified; @authUrl@ or @authCallback@ is provided; an explicit @token@ or @TokenDetails@ is provided ** @(RSA4a)@ When a @token@ or @tokenDetails@ is used to instance the library, and no means to renew the token is provided (either an API key, @authCallback@ or @authUrl@), if the server responds with a token error (401 HTTP status code and an Ably error value @40140 <= code < 40150@), then the client library should indicate an error, not retry the request and in the case of the realtime library, transition the connection to the @FAILED@ state ** @(RSA4b)@ When the client does have a means to renew the token automatically, and the token has expired or the server has responded with a token error (@statusCode@ value of 401 and error @code@ value in the range @40140 <= code < 40150@), then the client should automatically make a single attempt to reissue the token and resend the request using the new token. If the token creation failed or the subsequent request with the new token failed due to a token error, then the request should result in an error -** @(RSA4c)@ If an attempt by the realtime client library to authenticate is made using the @authUrl@ or @authCallback@, and the request to @authUrl@ fails (unless @RSA4d@ applies), the callback @authCallback@ results in an error (unless "RSA4d":#RSA4 applies), an attempt to exchange a @TokenRequest@ for a @TokenDetails@ results in an error (unless "RSA4d":#RSA4 applies), the provided token is in an invalid format (per "RSA4e":#RSA4e), or the attempt times out after "@realtimeRequestTimeout@":#DF1b, then: +** @(RSA4c)@ If an attempt by the realtime client library to authenticate is made using the @authUrl@ or @authCallback@, and the request to @authUrl@ fails (unless @RSA4d@ applies), the callback @authCallback@ results in an error (unless "RSA4d":#RSA4 applies), an attempt to exchange a @TokenRequest@ for a @TokenDetails@ results in an error (unless "RSA4d":#RSA4 applies), the provided token is in an invalid format (as defined in "RSA4e":#RSA4e), or the attempt times out after "@realtimeRequestTimeout@":#DF1b, then: *** @(RSA4c1)@An @ErrorInfo@ with @code@ @80019@ and description of the underlying failure should be emitted with the state change, in the @errorReason@ and/or in the callback as appropriate *** @(RSA4c2)@If the connection is @CONNECTING@, then the connection attempt should be treated as unsuccessful, and as such the connection should transition to the @DISCONNECTED@ or @SUSPENDED@ state as defined in "RTN14":#RTN14 and "RTN15":#RTN15 *** @(RSA4c3)@If the connection is @CONNECTED@, then the connection should remain @CONNECTED@ ** @(RSA4d)@ If a request by a realtime client to an @authUrl@ results in an HTTP 403 response, or any of an @authUrl@ request, an @authCallback@, or a request to Ably to exchange a @TokenRequest@ for a @TokenDetails@ result in an @ErrorInfo@ with @statusCode@ 403, then the client library should transition to the @FAILED@ state, with the connection @errorReason@ should be set to the @ErrorInfo@ (or where there is none, as for a 403 @authUrl@ response with no body, an @ErrorInfo@ with @code@ @40300@ and an appropriate message) ** @(RSA4e)@ If in the course of a REST request (or explicit call to @requestToken@) an attempt to authenticate using @authUrl@ or @authCallback@ fails due to a timeout, network error, a token in an invalid format (per "RSA4e":#RSA4e), or some other auth error condition other than an explicit @ErrorInfo@ from Ably, the request should result in an error with @code@ 40170, @statusCode@ 401, and a suitable error message -** @(RSA4f)@ A token is in an invalid format if: it is requested using an@authUrl@ and the content type is neither @text/plain@ or @application/json@; with an @authCallback@ and the object passed in is neither a @String@, @JsonObject@, @TokenRequest@ object or a @TokenDetails@ object; is greater than 384 bytes (for a text token string), or 128kB (for a JSON stringified @JsonObject@, @TokenRequest@ or @TokenDetails@) +** @(RSA4f)@ The following conditions imply that the token is in an invalid format: the @authUrl@ response content type is neither @text/plain@ nor @application/json@; the object passed by @authCallback@ is neither a @String@, @JsonObject@, @TokenRequest@ object, nor @TokenDetails@ object; the text token string is greater than 384 bytes; the JSON stringified @JsonObject@, @TokenRequest@ or @TokenDetails@ is greater than 128kb. * @(RSA14)@ If Token Auth is selected, yet a token is not provided and there is no means to generate a token, then this will result in an error. For example, if only the option @useTokenAuth@ is specified, and a @key@ is not provided, then the client library is unable to authenticate or issue a token * @(RSA15)@ If Token Auth is selected and @clientId@ has been set in the @ClientOptions@ when the library was instanced: ** @(RSA15a)@ Any @clientId@ provided in @ClientOptions@ must match any non wildcard (@'*'@) @clientId@ value in @TokenDetails@ or @connectionDetails@ of the @CONNECTED@ @ProtocolMessage@, where applicable From 0805df1899c8b9e2a794812b17f86e198fa7ad3b Mon Sep 17 00:00:00 2001 From: Matthew O'Riordan Date: Mon, 2 Jan 2017 21:38:34 +0100 Subject: [PATCH 08/38] EventEmitter emit spec update (#240) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * spec: Clarify EventEmitter emit behaviour when new listeners registered * spec: Update to reflect Simon’s suggested wording --- content/client-lib-development-guide/features.textile | 1 + 1 file changed, 1 insertion(+) diff --git a/content/client-lib-development-guide/features.textile b/content/client-lib-development-guide/features.textile index 0618763314..6526c48ffa 100644 --- a/content/client-lib-development-guide/features.textile +++ b/content/client-lib-development-guide/features.textile @@ -605,6 +605,7 @@ h3(#eventemitter). EventEmitter mixin / interface * @(RTE4)@ @EventEmitter#once@ registers the provided listener for either the first event that is emitted when no @event@ argument is provided, or for only the first occurrence of a single named event when an @event@ argument is provided. If @once@ is called more than once with the same listener, the listener is added multiple times to its listener registry. Therefore, as an example, assuming the same listener is registered twice using @once@, and an event is emitted once, the listener would be invoked twice. However, all subsequent events emitted would not invoke the listener as @once@ ensures that each registration is only invoked once * @(RTE5)@ @EventEmitter#off@ deregisters a listener. If called with a specific event and a listener, it removes all registrations that match both the given listener and the given event; if called only with a listener, it removes all registrations matching the given listener, regardless of whether they are associated with an event or not; if called with no arguments, it removes all registrations, for all events and listeners * @(RTE6)@ @EventEmitter#emit@ emits an event, calling registered listeners with the given event name and any other given arguments. If an exception is raised in any of the listeners, the exception is caught by the @EventEmitter@ and the exception is logged to the Ably logger. Tests must exist to ensure exceptions raised in client code do not propagate and inhibit other event processing within the client library +** @(RTE6a)@ The set of listeners called by @emit@ must not change over the course of the @emit@. That is: If a listener being called by @emit@ registers another listener, that second listener should not be called by that invocation of @emit@ (even if it would have been called had it already been present); and if a listener being called by @emit@ removes other listeners, but those other listeners would otherwise have been called during that @emit@ invocation, they should still be called. Tests should exist for both adding and removing. See "https://goo.gl/OVTtjO":https://goo.gl/OVTtjO h2(#state-conditions-and-operations). State conditions and operations From 8cc09fd90ce1e6f1da74abf35462c1f7cfd3ff60 Mon Sep 17 00:00:00 2001 From: Simon Woolf Date: Thu, 24 Nov 2016 14:42:37 +0000 Subject: [PATCH 09/38] Allow clientoptions.token to be a string, tokendetails, or tokenrequest --- content/client-lib-development-guide/features.textile | 4 ++-- content/types/_client_options.textile | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/content/client-lib-development-guide/features.textile b/content/client-lib-development-guide/features.textile index 6526c48ffa..64da848133 100644 --- a/content/client-lib-development-guide/features.textile +++ b/content/client-lib-development-guide/features.textile @@ -1154,7 +1154,7 @@ h4. ClientOptions ** @(TO3i)@ @recover@ string - A connection recovery string, specified with the intention of inheriting the state of an earlier connection ** @(TO3j)@ Auth option attributes: *** @(TO3j1)@ @key@ string - Full Ably key string as obtained from dashboard -*** @(TO3j2)@ @token@ string - An authentication token string issued for this application +*** @(TO3j2)@ @token@ string | @TokenDetails@ | @TokenRequest@ - An authentication token issued for this application, either in the form of a token string, a @TokenDetails@ object, or a @TokenRequest@ object *** @(TO3j3)@ @tokenDetails@ @TokenDetails@ - An authentication token issued for this application *** @(TO3j4)@ @useTokenAuth@ boolean - When true, token authentication will always be used by the client. If @clientId@ is unspecified, then the token issued will inherently be anonymous i.e. it will contain an empty @clientId@ *** @(TO3j5)@ @authCallback@ - A callback to call to obtain a signed @TokenRequest@, @TokenDetails@ or a token string. This enables a client to obtain token requests or tokens from another entity, so tokens can be renewed without the client requiring a key. If a JSON-encodable object is provided in the callback, then the library will determine if it's a @TokenDetails@ (if it contains a @token@ key) or @TokenRequest@ (no @token@ key) and use the @fromJson@ method ("TD7":#TD7, "TE6":#TE6) to construct an object @@ -1341,7 +1341,7 @@ class AuthOptions: // RSA8e authUrl: String? // RSA4a, RSA4, RSA8c, TO3j6, AO2c key: String? // RSA11, RSA14, TO3j1, AO2a queryTime: Bool default false // RSA9d, TO3j10, AO2a - token: String? | TokenDetails? // RSA4a, RSA4, TO3j2 + token: String? | TokenDetails? | TokenRequest? // RSA4a, RSA4, TO3j2 tokenDetails: TokenDetails? // RSA4a, RSA4, TO3j3 useTokenAuth: Bool? // RSA4, RSA14, TO3j4 diff --git a/content/types/_client_options.textile b/content/types/_client_options.textile index 893d2941f7..a3f9cb764c 100644 --- a/content/types/_client_options.textile +++ b/content/types/_client_options.textile @@ -18,7 +18,7 @@ h4. - keyKey:key := The full key string, as obtained from the "application dashboard":http://support.ably.io/solution/articles/3000030053-how-do-i-access-my-app-dashboard. Use this option if you wish to use Basic authentication, or wish to be able to issue tokens without needing to defer to a separate entity to sign token requests. Read more about "Basic authentication":/general/authentication#basic-authentication
__Type: @String@__ -- tokenToken:token := An authenticated token string that is most commonly obtained from the @token@@Token@ property of a "@TokenDetails@":/realtime/types#token-details component of a token request response. Use this option if you wish to use Token authentication. Read more about "Token authentication":/general/authentication#token-authentication
__Type: @String@__ +- tokenToken:token := An authenticated token. This can either be a "@TokenDetails@":/realtime/types#token-details object, a "@TokenRequest@":/realtime/types#token-request object, or token string obtained from the @token@>@Token@ property of a "@TokenDetails@":/realtime/types#token-details component of a token request response. Use this option if you wish to use Token authentication. Read more about "Token authentication":/general/authentication#token-authentication
__Type: @String@, @TokenDetails@ or @TokenRequest@__ - tokenDetailsTokenDetailstoken_details:token_details := An authenticated "@TokenDetails@":/realtime/types/#token-details object that is most commonly obtained from a token request's response. Use this option if you wish to use Token authentication. Read more about "Token authentication":/general/authentication#token-authentication
__Type: @TokenDetails@__ From c571644c0c15d94aeedfcda122167c3550dc68bb Mon Sep 17 00:00:00 2001 From: Simon Woolf Date: Mon, 28 Aug 2017 19:42:41 +0200 Subject: [PATCH 10/38] Add spec item for transportParams overriding default params --- content/client-lib-development-guide/features.textile | 1 + 1 file changed, 1 insertion(+) diff --git a/content/client-lib-development-guide/features.textile b/content/client-lib-development-guide/features.textile index 64da848133..e3dac291b9 100644 --- a/content/client-lib-development-guide/features.textile +++ b/content/client-lib-development-guide/features.textile @@ -275,6 +275,7 @@ h3(#realtimeclient). RealtimeClient ** @(RTC1d)@ @realtimeHost@ string, when set, will modify the realtime endpoint host used by this client library ** @(RTC1e)@ @environment@ string, when set, will modify both the REST and realtime endpoint hosts by prefixing the environment to the default endpoint host with a hypen delimiter. For example, a @RealtimeClient@ with an @environment@ of "sandbox", would use "sandbox-rest.ably.io" as the @restHost@ and @sandbox-realtime.ably.io@ as the @realtimeHost@. See "TO3k3":#TO3k3 for constraints. ** @(RTC1f)@ @transportParams@ map or equivalent, additional parameters to be sent in the querystring when initiating a realtime connection. Keys are @Strings@, values are @Stringifiable@ (a value that can be coerced to a string in order to be sent as a querystring parameter. Supported values should be at least strings, numbers, and booleans, with booleans stringified as @true@ and @false@. If this is unidiomatic to the language, the implementer may consider this as equivalent to @String@). +*** @(RTC1f1)@ If a key in @transportParams@ is one the library sends by default (for example, @v@ or @heartbeats@), the value in @transportParams@ takes precedence. * @(RTC2)@ @RealtimeClient#connection@ attribute provides access to the underlying @Connection@ object * @(RTC3)@ @RealtimeClient#channels@ attribute provides access to the underlying @Channels@ object * @(RTC4)@ @RealtimeClient#auth@ attribute provides access to the @Auth@ object that was instanced with the @ClientOptions@ provided in the @RealtimeClient@ constructor From 9f3bc39415858cbed97979655c1b753c185cc1f2 Mon Sep 17 00:00:00 2001 From: Simon Woolf Date: Tue, 1 Nov 2016 15:47:11 +0000 Subject: [PATCH 11/38] Clarify RSA10e (Auth#authorize requesting tokens) Add missing token and tokenDetails properties to authOptions Clarify TokenDetails --- content/client-lib-development-guide/features.textile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/content/client-lib-development-guide/features.textile b/content/client-lib-development-guide/features.textile index e3dac291b9..8b7c92746a 100644 --- a/content/client-lib-development-guide/features.textile +++ b/content/client-lib-development-guide/features.textile @@ -111,6 +111,7 @@ h3(#rest-auth). Auth ** @(RSA4d)@ If a request by a realtime client to an @authUrl@ results in an HTTP 403 response, or any of an @authUrl@ request, an @authCallback@, or a request to Ably to exchange a @TokenRequest@ for a @TokenDetails@ result in an @ErrorInfo@ with @statusCode@ 403, then the client library should transition to the @FAILED@ state, with the connection @errorReason@ should be set to the @ErrorInfo@ (or where there is none, as for a 403 @authUrl@ response with no body, an @ErrorInfo@ with @code@ @40300@ and an appropriate message) ** @(RSA4e)@ If in the course of a REST request (or explicit call to @requestToken@) an attempt to authenticate using @authUrl@ or @authCallback@ fails due to a timeout, network error, a token in an invalid format (per "RSA4e":#RSA4e), or some other auth error condition other than an explicit @ErrorInfo@ from Ably, the request should result in an error with @code@ 40170, @statusCode@ 401, and a suitable error message ** @(RSA4f)@ The following conditions imply that the token is in an invalid format: the @authUrl@ response content type is neither @text/plain@ nor @application/json@; the object passed by @authCallback@ is neither a @String@, @JsonObject@, @TokenRequest@ object, nor @TokenDetails@ object; the text token string is greater than 384 bytes; the JSON stringified @JsonObject@, @TokenRequest@ or @TokenDetails@ is greater than 128kb. +** @(RSA4g)@ If multiple @authOptions@ are used to initialize the library, the preference ordering among them is identical to @Auth#authorize@, defined in @RSA10e@ * @(RSA14)@ If Token Auth is selected, yet a token is not provided and there is no means to generate a token, then this will result in an error. For example, if only the option @useTokenAuth@ is specified, and a @key@ is not provided, then the client library is unable to authenticate or issue a token * @(RSA15)@ If Token Auth is selected and @clientId@ has been set in the @ClientOptions@ when the library was instanced: ** @(RSA15a)@ Any @clientId@ provided in @ClientOptions@ must match any non wildcard (@'*'@) @clientId@ value in @TokenDetails@ or @connectionDetails@ of the @CONNECTED@ @ProtocolMessage@, where applicable @@ -164,7 +165,7 @@ h3(#rest-auth). Auth ** @(RSA10j)@ Method signature is @authorize(TokenParams, AuthOptions)@. @TokenParams@ and @AuthOptions@ are optional. When the arguments are present, even if empty, the @TokenParams@ and @AuthOptions@ supersede any previously client library configured @TokenParams@ and @AuthOptions@. For example, if a client is initialized with @TokenParams#ttl@ configured with a custom value, and a @TokenParams@ object is passed in as an argument to @#authorize@ with a @null@ or missing value for @ttl@, then the @ttl@ used for every subsequent authorization will be @null@ ** @(RSA10b)@ Supports all @AuthOptions@ and @TokenParams@ in the function arguments ** @(RSA10k)@ If the @AuthOption@ argument's @queryTime@ attribute is true, it will obtain the server time once and persist the offset from the local clock. All future token requests generated directly or indirectly via a call to @authorize@ will not obtain the server time, but instead use the local clock offset to calculate the server time. The client library itself MAY internally discard the cached local clock offset in situations in which it may have been invalidated, such as if there is a local change to the date, time, or timezone, of the client device. For clarity however, there is no requirement for this cache invalidation to be available to consumers of the client library API. -** @(RSA10e)@ Adheres to the implementation of @requestToken@ when issuing new tokens +** @(RSA10e)@ If the @authOptions@ contains a way of obtaining a token (an @authCallback@, @authUrl@, or @key@), that should be used to obtain a new token, as per @requestToken@ (@RSA8@). If it contains a token (@token@ or @tokenDetails@), that should be used as-is. If it contains both a token and a way of obtaining a token, the token should be used, with the way of obtaining a token being stored per @RSA10g@ for when the token expires. (Ordering of preference within those groups is not defined and is left up to individual implementations) ** @(RSA10f)@ Returns a @TokenDetails@ object that contains the token string + token metadata ** @(RSA10g)@ Stores the @AuthOptions@ and @TokenParams@ arguments as defaults for subsequent authorizations with the exception of the attributes @TokenParams#timestamp@ and @AuthOptions#queryTime@ ** @(RSA10h)@ Will use the value from @Auth#clientId@ by default, if not @null@ @@ -1202,6 +1203,8 @@ h4. AuthOptions * @(AO2)@ The attributes of @AuthOptions@ consist of: ** @(AO2a)@ @key@ string - Full Ably key string, as obtained from dashboard, used when signing token requests locally ** @(AO2b)@ @authCallback@ - A callback to call to obtain a signed @TokenRequest@, @TokenDetails@ or a token string. This enables a client to obtain token requests or tokens from another entity, so tokens can be renewed without the client requiring a key. If a JSON-encodable object is provided in the callback, then the library will determine if it's a @TokenDetails@ (if it contains a @token@ key) or @TokenRequest@ (no @token@ key) and use the @fromJson@ method ("TD7":#TD7, "TE6":#TE6) to construct an object +** @(AO2h)@ @token@ string - An authentication token string issued for this application +** @(AO2i)@ @tokenDetails@ @TokenDetails@ - An authentication token (token string plus associated details, per @TD1@) issued for this application ** @(AO2c)@ @authUrl@ string - A URL to query to obtain a signed @TokenRequest@, @TokenDetails@ or a token string. This enables a client to obtain token request or token from another entity, so tokens can be renewed without the client requiring a key ** @(AO2d)@ @authMethod@ - The HTTP verb to be used when a request is made by the library to the @authUrl@. Defaults to @GET@, supports @GET@ and @POST@ ** @(AO2e)@ @authHeaders@ - Headers to be included in any request made by the library to the @authUrl@ From f20b888581ce14ca0fec222ee2c9079ff1702975 Mon Sep 17 00:00:00 2001 From: Matthew O'Riordan Date: Tue, 2 May 2017 18:10:37 +0100 Subject: [PATCH 12/38] spec: Send exceptions to ably error reporting service In order to improve libraries for all customers, we are proposing that all libraries send unhandled exceptions to our central error reporting service. --- .../client-lib-development-guide/features.textile | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/content/client-lib-development-guide/features.textile b/content/client-lib-development-guide/features.textile index 8b7c92746a..6fe52c0143 100644 --- a/content/client-lib-development-guide/features.textile +++ b/content/client-lib-development-guide/features.textile @@ -91,6 +91,15 @@ h3(#restclient). RestClient ** @(RSC19c)@ The library will configure the @Accept@ and @Content-Type@ type headers to reflect whether the client is configured to use a binary or JSON based protocol (see "RSC8":#RSC8). All requests are encoded and decoded into Json or MsgPack as appropriate automatically by the library. Binary @body@ payloads are not supported at this time ** @(RSC19d)@ @request@ method returns an @HttpPaginatedResponse@ object that inherits from the @PaginatedResult@ object to provide details on the response plus paging support where applicable. See "HP1":#HP1 for more details ** @(RSC19e)@ If the HTTP network request fails for reasons such as a timeout (after the underlying fallback host attempts have failed where applicable, see "RSC15":#RSC15), then the @request@ method should indicate an error in an idiomatic way for the platform +* @(RSC20)@ Unexpected internal library exception handling: +** @(RSC20a)@ The library must make every attempt to handle unexpected internal exceptions as gracefully as possible, and where appropriate, report these failures to Ably's error tracking service. For the avoidance of doubt, unexpected internal exceptions do not include request timeouts, invalid argument values, invalid responses from third parties or exceptions in code run in callbacks registered by customers. However, any unexpected failures or unhandled exceptions in our own code that typically could trigger a crash or raise an exception in our customer's code, is considered an unexpected internal exception +** @(RSC20b)@ Exception reporting is enabled by default, but can be disabled using the @logExceptionReportingUrl@ @ClientOption@ documented in "@TO3m@":#TO3m. When enabled, the client library must: +*** @(RSC20b1)@ At startup log a message with the equivalent of @info@ log level with the message "Ably client library exception@reporting service started. Unhandled failures will be automatically submitted to errors.ably.io to help improve our service. To find out more about this feature, see https://help.ably.io/exceptions". The @info@ log level is preferred as customers can hide this entry by configuring the client library log level to @warning@, yet will, by default, see this notice when using a client library with exception reporting enabled +*** @(RSC20b2)@ Any unhandled internal exceptions should automatically submit bug reports to the @logExceptionReportingUrl@ using the "@Sentry API@":https://docs.sentry.io/clientdev/ either via a raw HTTP request or by using an embedded "Sentry exception reporting client library":https://docs.sentry.io/clients/. A message at @info@ log level should be logged if the request succeeds or fails i.e. a failure to submit an exception should not be logged as a failure / error. Where possible, the exception GUID returned from the Sentry API should be displayed to the user so that the specific error can be tracked +** @(RSC20c)@ Exceptions reported must additionally include the following tags: @ably-lib@ with the value defined in "@RSC7b@":RSC7b, @ably-version@ with the value defined in "@RSC7a@":RSC7a, @appId@ if known from either the token or API key currently being used. +** @(RSC20d)@ All personally identifiable information, as much as is practicable, must be redacted or stripped completely before being submitted to Ably. Our intent is only to capture necessary information to debug issues in our own code +** @(RSC20e)@ Failures to log exceptions to the @errors.ably.io@ endpoint must be handled gracefully. This includes for example DNS failures, TCP/HTTP requests rejected, slow requests and internal failure errors. Additionally, as specified in @RSC20b2@, a failure to log an exception is logged with log level @info@ i.e. an exception reporting failure is not consider a client library @error@ or @warning@ +** @(RSC20f)@ Any errors emitted by the library as a result of an internal failure must contain status code @500@, an error code in the range @51000@ to @51999@ and a suitable error message h3(#rest-auth). Auth @@ -297,6 +306,7 @@ h3(#realtimeclient). RealtimeClient ** @(RTC8c)@ If the connection is in the @DISCONNECTED@, @SUSPENDED@, @FAILED@, or @CLOSED@ state when @auth#authorize@ is called, after obtaining a token the library should move to the @CONNECTING@ state and initiate a connection attempt using the new token, and @RTC8b1@ applies. * @(RTC9)@ @RealtimeClient#request@ is a wrapper around @RestClient#request@ (see "RSC19":#RSC19) delivered in an idiomatic way for the realtime library, e.g. in the case of Ruby, with an evented async callback interface * @(RTC10)@ The client library should never register any listeners for internal use with the public @EventEmitter@ interfaces (such as @Connection#on@) or message/event subscription interfaces (such as @Channel#subscribe@) in such a way that a user of the library calling @Connection#off()@ or @Channel#unsubscribe()@ to remove all listeners would result in the library not working as expected +* @(RTC11)@ Unexpected internal exceptions, as defined in "@RSC20@":RSC20, must be handled as gracefully as possible and reported to Ably's error reporting service when enabled. Additionally, the client library developer must consider whether an exception should result in an error for the operation requested by the customer (when applicable), or transition the client library connection state to the @FAILED@ state (with a suitable error message and code) as ongoing use of the instanced library could now result in unpredictable results h3(#realtime-connection). Connection @@ -1148,6 +1158,7 @@ h4. ClientOptions ** @(TO3a)@ @clientId@ string - the id of the client represented by this instance ** @(TO3b)@ @logLevel@ - controls the level of verbosity of log messages from the library. The implementation of this is likely to vary by platform ** @(TO3c)@ @logHandler@ - allows the client to intercept log messages and handle them in a client-specific way. The implementation of this is likely to vary by platform +** @(TO3m)@ @logExceptionReportingUrl@ - defaults to a string value for an Ably error reporting DSN (Data Source Name), which is typically a URL in the format https://[KEY]:[SECRET]@errors.ably.io/[ID]. When set to @null@, @false@ or an empty string (depending on what is idiomatic for the platform), exception reporting is disabled ** @(TO3d)@ @tls@ boolean - defaults to true. If false, will not use TLS for all connections ** @(TO3e)@ @autoConnect@ boolean - defaults to true. If false, suppresses the automatic initiation of a connection when the library is instanced ** @(TO3f)@ @useBinaryProtocol@ boolean - defaults to true. If false, forces the library to use the JSON encoding for REST and Realtime operations, instead of the default binary msgpack encoding @@ -1316,6 +1327,7 @@ class ClientOptions: environment: String? // RSC15b, TO3k1 logHandler: // platform specific - TO3c logLevel: // platform specific - TO3b + logExceptionReportingUrl: String default "[library specific]" // TO3c port: Int default 80 // TO3k4 queueMessages: Bool default true // RTP16b, TO3g restHost: String default "rest.ably.io" // RSC12, TO3k2 From e1ba83700b94c3e958fff71119d8a15dff4c4991 Mon Sep 17 00:00:00 2001 From: Matthew O'Riordan Date: Tue, 2 May 2017 18:22:58 +0100 Subject: [PATCH 13/38] spec: Href and source for ErrorInfo See https://github.com/ably/wiki/issues/142 and https://github.com/ably/ably-common/pull/21/files --- content/client-lib-development-guide/features.textile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/content/client-lib-development-guide/features.textile b/content/client-lib-development-guide/features.textile index 6fe52c0143..cf3a2c7371 100644 --- a/content/client-lib-development-guide/features.textile +++ b/content/client-lib-development-guide/features.textile @@ -1108,9 +1108,11 @@ h4. Stats h4. ErrorInfo -* @(TI1)@ Provides a generic Ably @ErrorInfo@ object that contains Ably @code@, @statusCode@ (analogous to HTTP status code) and @message@ attributes +* @(TI1)@ Provides a generic Ably @ErrorInfo@ object that contains Ably @code@, @statusCode@ (analogous to HTTP status code), @message@ and @source@ attributes * @(TI2)@ Errors returned from the Ably server are compatible with the @ErrorInfo@ structure and should result in errors that inherit from @ErrorInfo@ * @(TI3)@ "Ably-common":https://github.com/ably/ably-common should be included as a submodule so that "consistent error codes":https://github.com/ably/ably-common/blob/master/protocol/errors.json can be used +* @(TI4)@ Ably may additionally include a @href@ attribute with a string value. This is included for REST responses to provide a URL for customers to find more help on the error code +* @(TI5)@ When errors are logged that contain a @code@, the following text should be included in all log entries: "See https://help.ably.io/error/@[CODE]@" h4. ConnectionStateChange @@ -1740,7 +1742,9 @@ class PushChannelSubscription: class ErrorInfo: code: Int // TI1 + href: String // TI4 message: String // TI1 + source: ErrorInfo // TI1 statusCode: Int // TI1 class EventEmitter: From 1af4d392f1ae2ab9fa8f465e818c5494ba4d8eec Mon Sep 17 00:00:00 2001 From: Matthew O'Riordan Date: Tue, 2 May 2017 21:26:47 +0100 Subject: [PATCH 14/38] spec: Address feedback --- content/client-lib-development-guide/features.textile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/content/client-lib-development-guide/features.textile b/content/client-lib-development-guide/features.textile index cf3a2c7371..d7a0e4aba7 100644 --- a/content/client-lib-development-guide/features.textile +++ b/content/client-lib-development-guide/features.textile @@ -92,14 +92,14 @@ h3(#restclient). RestClient ** @(RSC19d)@ @request@ method returns an @HttpPaginatedResponse@ object that inherits from the @PaginatedResult@ object to provide details on the response plus paging support where applicable. See "HP1":#HP1 for more details ** @(RSC19e)@ If the HTTP network request fails for reasons such as a timeout (after the underlying fallback host attempts have failed where applicable, see "RSC15":#RSC15), then the @request@ method should indicate an error in an idiomatic way for the platform * @(RSC20)@ Unexpected internal library exception handling: -** @(RSC20a)@ The library must make every attempt to handle unexpected internal exceptions as gracefully as possible, and where appropriate, report these failures to Ably's error tracking service. For the avoidance of doubt, unexpected internal exceptions do not include request timeouts, invalid argument values, invalid responses from third parties or exceptions in code run in callbacks registered by customers. However, any unexpected failures or unhandled exceptions in our own code that typically could trigger a crash or raise an exception in our customer's code, is considered an unexpected internal exception +** @(RSC20a)@ The library must make every attempt to handle unexpected internal exceptions as gracefully as possible, and where appropriate, report these failures to Ably's error tracking service. For the avoidance of doubt, unexpected internal exceptions do not include request timeouts, invalid argument values, invalid responses from third parties or exceptions in code run in callbacks registered by applications. However, any unexpected failures or unhandled exceptions in our own code that typically could trigger a crash or raise an exception in our customer's code, is considered an unexpected internal exception ** @(RSC20b)@ Exception reporting is enabled by default, but can be disabled using the @logExceptionReportingUrl@ @ClientOption@ documented in "@TO3m@":#TO3m. When enabled, the client library must: -*** @(RSC20b1)@ At startup log a message with the equivalent of @info@ log level with the message "Ably client library exception@reporting service started. Unhandled failures will be automatically submitted to errors.ably.io to help improve our service. To find out more about this feature, see https://help.ably.io/exceptions". The @info@ log level is preferred as customers can hide this entry by configuring the client library log level to @warning@, yet will, by default, see this notice when using a client library with exception reporting enabled +*** @(RSC20b1)@ At startup log a message with the equivalent of @info@ log level with the message "Ably client library exception reporting enabled. Unhandled failures will be automatically submitted to errors.ably.io to help improve our service. To find out more about this feature, see https://help.ably.io/exceptions". The @info@ log level is preferred as customers can hide this entry by configuring the client library log level to @warning@, yet will, by default, see this notice when using a client library with exception reporting enabled *** @(RSC20b2)@ Any unhandled internal exceptions should automatically submit bug reports to the @logExceptionReportingUrl@ using the "@Sentry API@":https://docs.sentry.io/clientdev/ either via a raw HTTP request or by using an embedded "Sentry exception reporting client library":https://docs.sentry.io/clients/. A message at @info@ log level should be logged if the request succeeds or fails i.e. a failure to submit an exception should not be logged as a failure / error. Where possible, the exception GUID returned from the Sentry API should be displayed to the user so that the specific error can be tracked ** @(RSC20c)@ Exceptions reported must additionally include the following tags: @ably-lib@ with the value defined in "@RSC7b@":RSC7b, @ably-version@ with the value defined in "@RSC7a@":RSC7a, @appId@ if known from either the token or API key currently being used. ** @(RSC20d)@ All personally identifiable information, as much as is practicable, must be redacted or stripped completely before being submitted to Ably. Our intent is only to capture necessary information to debug issues in our own code ** @(RSC20e)@ Failures to log exceptions to the @errors.ably.io@ endpoint must be handled gracefully. This includes for example DNS failures, TCP/HTTP requests rejected, slow requests and internal failure errors. Additionally, as specified in @RSC20b2@, a failure to log an exception is logged with log level @info@ i.e. an exception reporting failure is not consider a client library @error@ or @warning@ -** @(RSC20f)@ Any errors emitted by the library as a result of an internal failure must contain status code @500@, an error code in the range @51000@ to @51999@ and a suitable error message +** @(RSC20f)@ Any errors emitted by the library as a result of an internal failure must contain a status code @500@, an error code in the range @51000@ to @51999@ and a suitable error message. The error code must match one of "our common error codes":https://github.com/ably/ably-common/blob/master/protocol/errors.json h3(#rest-auth). Auth @@ -306,7 +306,7 @@ h3(#realtimeclient). RealtimeClient ** @(RTC8c)@ If the connection is in the @DISCONNECTED@, @SUSPENDED@, @FAILED@, or @CLOSED@ state when @auth#authorize@ is called, after obtaining a token the library should move to the @CONNECTING@ state and initiate a connection attempt using the new token, and @RTC8b1@ applies. * @(RTC9)@ @RealtimeClient#request@ is a wrapper around @RestClient#request@ (see "RSC19":#RSC19) delivered in an idiomatic way for the realtime library, e.g. in the case of Ruby, with an evented async callback interface * @(RTC10)@ The client library should never register any listeners for internal use with the public @EventEmitter@ interfaces (such as @Connection#on@) or message/event subscription interfaces (such as @Channel#subscribe@) in such a way that a user of the library calling @Connection#off()@ or @Channel#unsubscribe()@ to remove all listeners would result in the library not working as expected -* @(RTC11)@ Unexpected internal exceptions, as defined in "@RSC20@":RSC20, must be handled as gracefully as possible and reported to Ably's error reporting service when enabled. Additionally, the client library developer must consider whether an exception should result in an error for the operation requested by the customer (when applicable), or transition the client library connection state to the @FAILED@ state (with a suitable error message and code) as ongoing use of the instanced library could now result in unpredictable results +* @(RTC11)@ Unexpected internal exceptions, as defined in "@RSC20@":RSC20, must be handled as gracefully as possible and reported to Ably's error reporting service when enabled. The aim when handling unexpected exceptions should be to ensure that no invalid or inconsistent state can potentially be left after handling the exception; depending on circumstances the remedial action could include failing the transport, failing the connection, rejecting a message, reinitialising the library completely, etc. h3(#realtime-connection). Connection From 52e89475b7e4245ab702d8e36a535ff2a1aac6ed Mon Sep 17 00:00:00 2001 From: Matthew O'Riordan Date: Tue, 2 May 2017 23:23:10 +0100 Subject: [PATCH 15/38] spec: New ErrorInfo attributes are optional --- content/client-lib-development-guide/features.textile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/client-lib-development-guide/features.textile b/content/client-lib-development-guide/features.textile index d7a0e4aba7..65a01e7228 100644 --- a/content/client-lib-development-guide/features.textile +++ b/content/client-lib-development-guide/features.textile @@ -1742,9 +1742,9 @@ class PushChannelSubscription: class ErrorInfo: code: Int // TI1 - href: String // TI4 + href: String? // TI4 message: String // TI1 - source: ErrorInfo // TI1 + source: ErrorInfo? // TI1 statusCode: Int // TI1 class EventEmitter: From dce7bb8a766afaec382f18afdeaee5b233d53b66 Mon Sep 17 00:00:00 2001 From: Matthew O'Riordan Date: Wed, 3 May 2017 00:35:19 +0100 Subject: [PATCH 16/38] spec: Replace source with cause See https://github.com/ably/docs/issues/242 --- content/client-lib-development-guide/features.textile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/client-lib-development-guide/features.textile b/content/client-lib-development-guide/features.textile index 65a01e7228..7a8cad1774 100644 --- a/content/client-lib-development-guide/features.textile +++ b/content/client-lib-development-guide/features.textile @@ -1108,7 +1108,7 @@ h4. Stats h4. ErrorInfo -* @(TI1)@ Provides a generic Ably @ErrorInfo@ object that contains Ably @code@, @statusCode@ (analogous to HTTP status code), @message@ and @source@ attributes +* @(TI1)@ Provides a generic Ably @ErrorInfo@ object that contains Ably @code@, @statusCode@ (analogous to HTTP status code), @message@ and @cause@ attributes * @(TI2)@ Errors returned from the Ably server are compatible with the @ErrorInfo@ structure and should result in errors that inherit from @ErrorInfo@ * @(TI3)@ "Ably-common":https://github.com/ably/ably-common should be included as a submodule so that "consistent error codes":https://github.com/ably/ably-common/blob/master/protocol/errors.json can be used * @(TI4)@ Ably may additionally include a @href@ attribute with a string value. This is included for REST responses to provide a URL for customers to find more help on the error code @@ -1744,7 +1744,7 @@ class ErrorInfo: code: Int // TI1 href: String? // TI4 message: String // TI1 - source: ErrorInfo? // TI1 + cause: ErrorInfo? // TI1 statusCode: Int // TI1 class EventEmitter: From 584eaad25d8679c2ee88555d72bede6051ae4fef Mon Sep 17 00:00:00 2001 From: Simon Woolf Date: Thu, 16 Nov 2017 22:45:15 +0100 Subject: [PATCH 17/38] Clarify spec on client auth failures --- content/client-lib-development-guide/features.textile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/client-lib-development-guide/features.textile b/content/client-lib-development-guide/features.textile index 7a8cad1774..b879e3322b 100644 --- a/content/client-lib-development-guide/features.textile +++ b/content/client-lib-development-guide/features.textile @@ -114,10 +114,10 @@ h3(#rest-auth). Auth ** @(RSA4a)@ When a @token@ or @tokenDetails@ is used to instance the library, and no means to renew the token is provided (either an API key, @authCallback@ or @authUrl@), if the server responds with a token error (401 HTTP status code and an Ably error value @40140 <= code < 40150@), then the client library should indicate an error, not retry the request and in the case of the realtime library, transition the connection to the @FAILED@ state ** @(RSA4b)@ When the client does have a means to renew the token automatically, and the token has expired or the server has responded with a token error (@statusCode@ value of 401 and error @code@ value in the range @40140 <= code < 40150@), then the client should automatically make a single attempt to reissue the token and resend the request using the new token. If the token creation failed or the subsequent request with the new token failed due to a token error, then the request should result in an error ** @(RSA4c)@ If an attempt by the realtime client library to authenticate is made using the @authUrl@ or @authCallback@, and the request to @authUrl@ fails (unless @RSA4d@ applies), the callback @authCallback@ results in an error (unless "RSA4d":#RSA4 applies), an attempt to exchange a @TokenRequest@ for a @TokenDetails@ results in an error (unless "RSA4d":#RSA4 applies), the provided token is in an invalid format (as defined in "RSA4e":#RSA4e), or the attempt times out after "@realtimeRequestTimeout@":#DF1b, then: -*** @(RSA4c1)@An @ErrorInfo@ with @code@ @80019@ and description of the underlying failure should be emitted with the state change, in the @errorReason@ and/or in the callback as appropriate +*** @(RSA4c1)@An @ErrorInfo@ with @code@ @80019@, @statusCode@ 401, and @cause@ set to the underlying cause should be emitted with the state change if there is one (per @RSA4c2/3@) and set as the connection @errorReason@ *** @(RSA4c2)@If the connection is @CONNECTING@, then the connection attempt should be treated as unsuccessful, and as such the connection should transition to the @DISCONNECTED@ or @SUSPENDED@ state as defined in "RTN14":#RTN14 and "RTN15":#RTN15 *** @(RSA4c3)@If the connection is @CONNECTED@, then the connection should remain @CONNECTED@ -** @(RSA4d)@ If a request by a realtime client to an @authUrl@ results in an HTTP 403 response, or any of an @authUrl@ request, an @authCallback@, or a request to Ably to exchange a @TokenRequest@ for a @TokenDetails@ result in an @ErrorInfo@ with @statusCode@ 403, then the client library should transition to the @FAILED@ state, with the connection @errorReason@ should be set to the @ErrorInfo@ (or where there is none, as for a 403 @authUrl@ response with no body, an @ErrorInfo@ with @code@ @40300@ and an appropriate message) +** @(RSA4d)@ If a request by a realtime client to an @authUrl@ results in an HTTP 403 response, or any of an @authUrl@ request, an @authCallback@, or a request to Ably to exchange a @TokenRequest@ for a @TokenDetails@ result in an @ErrorInfo@ with @statusCode@ 403, then the client library should transition to the @FAILED@ state, with an @ErrorInfo@ (with @code@ @80019@, @statusCode@ 403, and @cause@ set to the underlying cause) emitted with the state change and set as the connection @errorReason@ ** @(RSA4e)@ If in the course of a REST request (or explicit call to @requestToken@) an attempt to authenticate using @authUrl@ or @authCallback@ fails due to a timeout, network error, a token in an invalid format (per "RSA4e":#RSA4e), or some other auth error condition other than an explicit @ErrorInfo@ from Ably, the request should result in an error with @code@ 40170, @statusCode@ 401, and a suitable error message ** @(RSA4f)@ The following conditions imply that the token is in an invalid format: the @authUrl@ response content type is neither @text/plain@ nor @application/json@; the object passed by @authCallback@ is neither a @String@, @JsonObject@, @TokenRequest@ object, nor @TokenDetails@ object; the text token string is greater than 384 bytes; the JSON stringified @JsonObject@, @TokenRequest@ or @TokenDetails@ is greater than 128kb. ** @(RSA4g)@ If multiple @authOptions@ are used to initialize the library, the preference ordering among them is identical to @Auth#authorize@, defined in @RSA10e@ From 7ed0beb258291ebacff3bab6ee26522768d5f523 Mon Sep 17 00:00:00 2001 From: Paddy Byers Date: Wed, 28 Mar 2018 22:45:50 +0100 Subject: [PATCH 18/38] Support for the Sentry exception reporting service is optional in 1.1 --- content/client-lib-development-guide/features.textile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/content/client-lib-development-guide/features.textile b/content/client-lib-development-guide/features.textile index b879e3322b..3cd249e086 100644 --- a/content/client-lib-development-guide/features.textile +++ b/content/client-lib-development-guide/features.textile @@ -92,10 +92,10 @@ h3(#restclient). RestClient ** @(RSC19d)@ @request@ method returns an @HttpPaginatedResponse@ object that inherits from the @PaginatedResult@ object to provide details on the response plus paging support where applicable. See "HP1":#HP1 for more details ** @(RSC19e)@ If the HTTP network request fails for reasons such as a timeout (after the underlying fallback host attempts have failed where applicable, see "RSC15":#RSC15), then the @request@ method should indicate an error in an idiomatic way for the platform * @(RSC20)@ Unexpected internal library exception handling: -** @(RSC20a)@ The library must make every attempt to handle unexpected internal exceptions as gracefully as possible, and where appropriate, report these failures to Ably's error tracking service. For the avoidance of doubt, unexpected internal exceptions do not include request timeouts, invalid argument values, invalid responses from third parties or exceptions in code run in callbacks registered by applications. However, any unexpected failures or unhandled exceptions in our own code that typically could trigger a crash or raise an exception in our customer's code, is considered an unexpected internal exception -** @(RSC20b)@ Exception reporting is enabled by default, but can be disabled using the @logExceptionReportingUrl@ @ClientOption@ documented in "@TO3m@":#TO3m. When enabled, the client library must: +** @(RSC20a)@ The library must make every attempt to handle unexpected internal exceptions as gracefully as possible. For the avoidance of doubt, unexpected internal exceptions do not include request timeouts, invalid argument values, invalid responses from third parties or exceptions in code run in callbacks registered by applications. However, any unexpected failures or unhandled exceptions in our own code that typically could trigger a crash or raise an exception in our customer's code, is considered an unexpected internal exception +** @(RSC20b)@ The library may optionally report unexpected internal exceptions to the Ably exception reporting service. Exception reporting, when supported, is enabled by default, but can be disabled using the @logExceptionReportingUrl@ @ClientOption@ documented in "@TO3m@":#TO3m. When enabled, the client library must: *** @(RSC20b1)@ At startup log a message with the equivalent of @info@ log level with the message "Ably client library exception reporting enabled. Unhandled failures will be automatically submitted to errors.ably.io to help improve our service. To find out more about this feature, see https://help.ably.io/exceptions". The @info@ log level is preferred as customers can hide this entry by configuring the client library log level to @warning@, yet will, by default, see this notice when using a client library with exception reporting enabled -*** @(RSC20b2)@ Any unhandled internal exceptions should automatically submit bug reports to the @logExceptionReportingUrl@ using the "@Sentry API@":https://docs.sentry.io/clientdev/ either via a raw HTTP request or by using an embedded "Sentry exception reporting client library":https://docs.sentry.io/clients/. A message at @info@ log level should be logged if the request succeeds or fails i.e. a failure to submit an exception should not be logged as a failure / error. Where possible, the exception GUID returned from the Sentry API should be displayed to the user so that the specific error can be tracked +*** @(RSC20b2)@ Any unhandled internal exceptions should automatically submit bug reports to the @logExceptionReportingUrl@ using the "@Sentry API@":https://docs.sentry.io/clientdev/ either via a raw HTTP request or by using an embedded "Sentry exception reporting client library":https://docs.sentry.io/clients/. A message at @info@ log level should be logged if the request succeeds or fails i.e. a failure to submit an exception should not be logged as a failure / error. Where possible, the exception GUID returned from the Sentry API should be logged so that the specific error can be tracked ** @(RSC20c)@ Exceptions reported must additionally include the following tags: @ably-lib@ with the value defined in "@RSC7b@":RSC7b, @ably-version@ with the value defined in "@RSC7a@":RSC7a, @appId@ if known from either the token or API key currently being used. ** @(RSC20d)@ All personally identifiable information, as much as is practicable, must be redacted or stripped completely before being submitted to Ably. Our intent is only to capture necessary information to debug issues in our own code ** @(RSC20e)@ Failures to log exceptions to the @errors.ably.io@ endpoint must be handled gracefully. This includes for example DNS failures, TCP/HTTP requests rejected, slow requests and internal failure errors. Additionally, as specified in @RSC20b2@, a failure to log an exception is logged with log level @info@ i.e. an exception reporting failure is not consider a client library @error@ or @warning@ From 7eed9c0da046708efedd3dc10a81476fccbd4c5d Mon Sep 17 00:00:00 2001 From: Paddy Byers Date: Wed, 28 Mar 2018 23:26:53 +0100 Subject: [PATCH 19/38] Include extras in PresenceMessage and associated API --- .../features.textile | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/content/client-lib-development-guide/features.textile b/content/client-lib-development-guide/features.textile index b879e3322b..61e7784f6d 100644 --- a/content/client-lib-development-guide/features.textile +++ b/content/client-lib-development-guide/features.textile @@ -561,23 +561,23 @@ h3(#realtime-presence). Presence ** @(RTP7a)@ Unsubscribe with no arguments unsubscribes the listener if previously subscribed with an action-specific subscription ** @(RTP7b)@ Unsubscribe with a single action argument unsubscribes the provided listener to all presence messages for that action * @(RTP8)@ @Presence#enter@ function: -** @(RTP8a)@ Enters the current client into this channel, optionally with the data provided +** @(RTP8a)@ Enters the current client into this channel, optionally with the data and/or extras provided ** @(RTP8b)@ Optionally a callback can be provided that is called for both success or failure to enter ** @(RTP8c)@ A @PRESENCE ProtocolMessage@ with a @PresenceMessage@ with the action @ENTER@ is sent to the Ably service. The @clientId@ attribute of the @PresenceMessage@ must not be present. Entering without an explicit @PresenceMessage#clientId@, implicitly uses the @clientId@ for the current connection ** @(RTP8d)@ Implicitly attaches the @Channel@ if the channel is in the @INITIALIZED@ state. However, if the channel is in the @DETACHED@ or @FAILED@ state, the @enter@ request results in an error -** @(RTP8e)@ Optional data can be included when entering a channel that will be encoded / decoded as with normal messages. A test should exist to ensure data used with enter is encoded & decoded correctly. Also, when data is provided when entering, but no data is provided when leaving, the data attribute should be emitted in the @LEAVE@ event for this client +** @(RTP8e)@ Optional data and/or extras can be included when entering a channel that will be encoded / decoded as with normal messages. A test should exist to ensure data and extras used with enter are encoded & decoded correctly. Also, when data and/or extras is provided when entering, but neither data nor extras are provided when leaving, the data attribute should be emitted in the @LEAVE@ event for this client ** @(RTP8f)@ If the client library is authenticated but unidentified (i.e. @clientId@ is a wildcard @'*'@ or client is anonymous), the @enter@ request results in an error immediately ** @(RTP8g)@ If the channel is @DETACHED@ or @FAILED@, the @enter@ request results in an error immediately ** @(RTP8h)@ If the Ably service determines that the client does not have required presence permission, a @NACK@ is sent to the client resulting in an error ** @(RTP8i)@ If the Ably service determines that the client is unidentified, a @NACK@ is sent to the client resulting in an error * @(RTP9)@ @Presence#update@ function: -** @(RTP9a)@ Updates the data for the present member with a value or @null@ +** @(RTP9a)@ Updates the data and/or extras for the present member with an updated value or empty value (eg @null@) ** @(RTP9b)@ If the client was not already entered, it enters this client into this channel ** @(RTP9c)@ Optionally a callback can be provided that is called for both success or failure to update ** @(RTP9d)@ A @PRESENCE ProtocolMessage@ with a @PresenceMessage@ with the action @UPDATE@ is sent to the Ably service. The @clientId@ attribute of the @PresenceMessage@ must not be present. Updating without an explicit @PresenceMessage#clientId@, implicitly uses the @clientId@ for the current connection ** @(RTP9e)@ In all other ways, this method is identical to @Presence#enter@ and should have matching tests * @(RTP10)@ @Presence#leave@ function: -** @(RTP10a)@ Leaves this client from the channel and the data will be updated with the value provided. If the language permits the data argument to be omitted, then the previously set data value will be sent as a convenience +** @(RTP10a)@ Leaves this client from the channel and the data and/or extras will be updated with the values provided. If the language permits the data argument to be omitted, then the previously set data value will be sent as a convenience ** @(RTP10b)@ Optionally a callback can be provided that is called for both success or failure to leave ** @(RTP10c)@ A @PRESENCE ProtocolMessage@ with a @PresenceMessage@ with the action @LEAVE@ is sent to the Ably service. The @clientId@ attribute of the @PresenceMessage@ must not be present. Leaving without an explicit @PresenceMessage#clientId@, implicitly uses the @clientId@ for the current connection ** @(RTP10d)@ If the client is not currently @ENTERED@, Ably will respond with an @ACK@ and the request will succeed (i.e. the outcome of asking to @LEAVE@ when not present vs being present is the same) @@ -999,7 +999,7 @@ h4. Message ** @(TM2g)@ @name@ string ** @(TM2d)@ @data@ string, buffer or JSON-encodable object or array ** @(TM2e)@ @encoding@ string -** @(TM2i)@ @extras@ JSON-encodable object, used to contain any arbitrary key value pairs which may also contain other primitive JSON types, JSON-encodable objects or JSON-encodable arrays. The extras field is provided to contain message metadata and/or ancillary payloads in support of specific functionality, e.g. push. Each of these supported extensions is documented separately; for 1.0 the only supported extension is @push@, via the @extras.push@ member. The processing of any other members is undefined +** @(TM2i)@ @extras@ JSON-encodable object, used to contain any arbitrary key value pairs which may also contain other primitive JSON types, JSON-encodable objects or JSON-encodable arrays. The extras field is provided to contain message metadata and/or ancillary payloads in support of specific functionality, e.g. push. Each of these supported extensions is documented separately; for 1.1 the only supported extension is @push@, via the @extras.push@ member. The processing of any other members is undefined ** @(TM2f)@ @timestamp@ time in milliseconds since epoch. If a message received from Ably does not contain a @timestamp@, it should be set to the @timestamp@ of the encapsulating @ProtocolMessage@ * @(TM3)@ @fromEncoded@ and @fromEncodedArray@ are alternative constructors that take an (already deserialized) @Message@-like object (or array of such objects), and optionally a @channelOptions@, and return a @Message@ (or array of such @Messages@) that's decoded and decrypted as specified in @RSL6@, using the cipher in the @channelOptions@ if the message is encrypted, with any residual transforms (ones that the library cannot decode or decrypt) left in the @encoding@ property per @RSL6b@. This is intended for users receiving messages other than from a REST or Realtime channel (for example, from a queue), to avoid them having to parse the @encoding@ string themselves. @@ -1014,6 +1014,7 @@ h4. PresenceMessage ** @(TP3d)@ @connectionId@ string. If a presence message received from Ably does not contain a @connectionId@, it should be set to the @connectionId@ of the encapsulating @ProtocolMessage@ ** @(TP3e)@ @data@ string, buffer or JSON-encodable object or array ** @(TP3f)@ @encoding@ string +** @(TP3i)@ @extras@ JSON-encodable object, used to contain any arbitrary key value pairs which may also contain other primitive JSON types, JSON-encodable objects or JSON-encodable arrays. The @extras@ field is provided to contain message metadata and/or ancillary payloads in support of specific functionality. For 1.1 no specific functionality is specified for @extras@ in presence messages; the processing all members is undefined ** @(TP3g)@ @timestamp@ time in milliseconds since epoch. If a presence message received from Ably does not contain a @timestamp@, it should be set to the @timestamp@ of the encapsulating @ProtocolMessage@ ** @(TP3h)@ @memberKey@ string function that combines the @connectionId@ and @clientId@ ensuring multiple connected clients with the same clientId are uniquely identifiable * @(TP4)@ @fromEncoded@ and @fromEncodedArray@ are alternative constructors that take an (already deserialized) @PresenceMessage@-like object (or array of such objects), and optionally a @channelOptions@, and return a @PresenceMessage@ (or array of such @PresenceMessages@) that's decoded and decrypted as specified in @RSL6@, using the cipher in the @channelOptions@ if the message is encrypted, with any residual transforms (ones that the library cannot decode or decrypt) left in the @encoding@ property per @RSL6b@. This is intended for users receiving messages other than from a REST or Realtime channel (for example, from a queue), to avoid them having to parse the @encoding@ string themselves. @@ -1512,12 +1513,12 @@ class RealtimePresence: unsubscribe((PresenceMessage) ->) // RTP7a unsubscribe(PresenceAction, (PresenceMessage) ->) // RTP7b // presence state modifiers - enter(Data?) => io // RTP8 - update(Data?) => io // RTP9 - leave(Data?) => io // RTP10 - enterClient(clientId: String, Data?) => io // RTP4, RTP14, RTP15 - updateClient(clientId: String, Data?) => io // RTP15 - leaveClient(clientId: String, Data?) => io // RTP15 + enter(Data?, extras?: JsonObject) => io // RTP8 + update(Data?, extras?: JsonObject) => io // RTP9 + leave(Data?, extras?: JsonObject) => io // RTP10 + enterClient(clientId: String, Data?, extras?: JsonObject) => io // RTP4, RTP14, RTP15 + updateClient(clientId: String, Data?, extras?: JsonObject) => io // RTP15 + leaveClient(clientId: String, Data?, extras?: JsonObject) => io // RTP15 enum PresenceAction: ABSENT // TP2 @@ -1558,6 +1559,7 @@ class PresenceMessage connectionId: String // TP3d data: Data? // TP3e encoding: String? // TP3f + extras: JsonObject? // TP3i id: String // TP3a timestamp: Time // TP3g memberKey() -> String // TP3h From ea5fa511915bff87081e56fa2ca07d1fcf438e65 Mon Sep 17 00:00:00 2001 From: Paddy Byers Date: Wed, 28 Mar 2018 23:54:20 +0100 Subject: [PATCH 20/38] Clarify definition of JSON-encodable --- content/client-lib-development-guide/features.textile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/content/client-lib-development-guide/features.textile b/content/client-lib-development-guide/features.textile index b879e3322b..64c11d70be 100644 --- a/content/client-lib-development-guide/features.textile +++ b/content/client-lib-development-guide/features.textile @@ -238,7 +238,7 @@ h3(#rest-channel). Channel * @(RSL6)@ Message decoding ** @(RSL6a)@ All messages received will be decoded automatically based on the @encoding@ field and the payloads will be converted into the format they were originally sent using i.e. binary, string, or JSON *** @(RSL6a1)@ A set of tests must exist to ensure that the client library provides data encoding & decoding interoperability with other client libraries. The tests must use the "set of predefined interoperability message fixtures":https://github.com/ably/ably-common/blob/master/test-resources/messages-encoding.json to 1) publish a raw message to the REST API using the JSON transport and subscribe to the message using Realtime to ensure the @data@ attribute matches the fixture; 2) publish a message using the REST client library and retrieve the raw message using the history REST API using the JSON transport ensuring the @data@ matches the fixture; 3) perform the client library operation using both @JSON@ and @MsgPack@ transports. For reference, see the "Ruby":https://github.com/ably/ably-ruby/pull/94 and "iOS":https://github.com/ably/ably-ios/pull/459 implementations -*** @(RSL6a2)@ A set of tests must exist to ensure that the client library provides interoperability for the @extras@ field which is a JSON-encodable object. The test, at a minimum, should publish a message with an @extras@ object such as @{"push":[{"title":"Testing"}]}@ and ensure it is received with an equivalent JSON-encodable object +*** @(RSL6a2)@ A set of tests must exist to ensure that the client library provides interoperability for the @extras@ field which is a JSON-encodable object (ie a value that represents a JSON @object@ value and supports serialisation to and from JSON text). The test, at a minimum, should publish a message with an @extras@ object such as @{"push":[{"title":"Testing"}]}@ and ensure it is received with an equivalent JSON-encodable object ** @(RSL6b)@ If, for example, incompatible encryption details are provided or invalid Base64 is detected in the message payload, an error message will be sent to the logger, but the message will still be delivered with last successful decoding and the @encoding@ field. For example, if a message had a decoding of "utf-8/cipher+aes-128-cbc/base64", and the payload was successfully Base64 decoded but the payload could not be encrypted because the @CipherParam@ details were not configured, the message would be delivered with a binary payload and an @encoding@ with the value "utf-8/cipher+aes-128-cbc" h3(#rest-presence). Presence @@ -1272,6 +1272,7 @@ Please note the following conventions: * @Duration@ and @Time@ types are typically represented as milliseconds since the epoch. Where needed, a more idiomatic language specific duration may be used such as @seconds@ or @Time@ respectively for Ruby * @Data@ is a message payload type, see "RSL4a":#RSL4a for a list of supported payload types * @Stringifiable@ is a type used for unknown configuration parameters that need to be coerced to strings when used, see "RTC1f":#RTC1f for definition +* @JSONObject@ and @JSONArray@ denote any type or interface in the target language that represents an "RFC4627":https://www.ietf.org/rfc/rfc4627.txt @object@ or @array@ value respectively. Such types serialise to, and may be deserialised from, the corresponding JSON text. In the specification text above, values of these types are collectively referred to as "JSON-encodable". ```[python] class Rest: From f679b1832218a9437dcb0193796ed584af87cc81 Mon Sep 17 00:00:00 2001 From: Paddy Byers Date: Thu, 29 Mar 2018 00:23:03 +0100 Subject: [PATCH 21/38] Add explicit requirement for robustness principle for forwards compatibility --- content/client-lib-development-guide/features.textile | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/content/client-lib-development-guide/features.textile b/content/client-lib-development-guide/features.textile index b879e3322b..73f9e659b7 100644 --- a/content/client-lib-development-guide/features.textile +++ b/content/client-lib-development-guide/features.textile @@ -15,6 +15,7 @@ jump_to: - Channel#rest-channel - Presence#rest-presence - Encryption#rest-encryption + - Forwards compatibility#rest-compatibility Realtime client library: - RealtimeClient - Connection#realtime-connection @@ -22,6 +23,7 @@ jump_to: - Channel#realtime-channel - Presence#realtime-presence - EventEmitter#eventemitter + - Forwards compatibility#realtime-compatibility - State conditions and operations#state-conditions-and-operations Push notifications: - Push notifications#push-notifications @@ -268,6 +270,9 @@ h3(#rest-encryption). Encryption ** @(RSE2a)@ Takes an optional @keyLength@ parameter, which is the length in bits of the key to be generated. If unspecified, this is equal to the default @keyLength@ of the default algorithm: for @AES@, 256 bits. ** @(RSE2b)@ Returns (or calls back with, if the language cryptographic randomness primitives are blocking or async) the key as a binary (e.g. a byte array, depending on the language) +h3(#rest-compatibility). Forwards compatibility +* @(RSF1)@ THe library must apply the "robustness principle":https://en.wikipedia.org/wiki/Robustness_principle in its processing of requests and responses with the Ably system. In particular, deserialisation of Messages and related types, and associated enums, must be tolerant to unrecognised attributes or enum values. Such unrecognised values must be ignored. + h2(#realtime). Realtime client library features The Ably Realtime client libraries establish and maintain a persistent connection to Ably and provide methods to publish and subscribe to messages over a low latency realtime connection. @@ -619,6 +624,9 @@ h3(#eventemitter). EventEmitter mixin / interface * @(RTE6)@ @EventEmitter#emit@ emits an event, calling registered listeners with the given event name and any other given arguments. If an exception is raised in any of the listeners, the exception is caught by the @EventEmitter@ and the exception is logged to the Ably logger. Tests must exist to ensure exceptions raised in client code do not propagate and inhibit other event processing within the client library ** @(RTE6a)@ The set of listeners called by @emit@ must not change over the course of the @emit@. That is: If a listener being called by @emit@ registers another listener, that second listener should not be called by that invocation of @emit@ (even if it would have been called had it already been present); and if a listener being called by @emit@ removes other listeners, but those other listeners would otherwise have been called during that @emit@ invocation, they should still be called. Tests should exist for both adding and removing. See "https://goo.gl/OVTtjO":https://goo.gl/OVTtjO +h3(#realtime-compatibility). Forwards compatibility +* @(RTF1)@ THe library must apply the "robustness principle":https://en.wikipedia.org/wiki/Robustness_principle in its processing of requests and responses with the Ably system. In particular, deserialisation of ProtocolMessages and related types, and associated enums, must be tolerant to unrecognised attributes or enum values. Such unrecognised values must be ignored. + h2(#state-conditions-and-operations). State conditions and operations h3(#connection-states-operations). @Connection.state@ effects on realtime operations From 7248dacbf1323a3fe6a3f69b47e84a015aaaf254 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toni=20C=C3=A1rdenas?= Date: Wed, 1 Nov 2017 19:29:59 +0100 Subject: [PATCH 22/38] Add spec for PushChannel. --- .../features.textile | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/content/client-lib-development-guide/features.textile b/content/client-lib-development-guide/features.textile index b879e3322b..7eb231da92 100644 --- a/content/client-lib-development-guide/features.textile +++ b/content/client-lib-development-guide/features.textile @@ -25,6 +25,7 @@ jump_to: - State conditions and operations#state-conditions-and-operations Push notifications: - Push notifications#push-notifications + - Push channels#push-channels - Activation state machine#activation-state-machine Types: - Data types#types @@ -911,6 +912,23 @@ h2(#push-notifications). Push notifications ** @(RSH2b)@ @Push#deactivate@ sends a @CalledDeactivate@ event to "the state machine":#RSH3. ** @(RSH2c)@ Whenever, from the platform's APIs, details for sending push notifications to the local device (e. g. GCM's registration token) is received, a @GotPushDeviceDetails@ event is sent to "the state machine":#RSH3. +h3(#push-channels). Push channels + +* @(RSH4)@ In platforms that support receiving push notifications, @RestChannel@ and @RealtimeChannel@ have an additional @push@ field, as an object with the following interface: +** @(RSH4a)@ @#subscribeDevice()@ +*** @(RSH4a1)@ Fails if the @LocalDevice@ doesn't have an @updateToken@, ie. it isn't registered yet. +*** @(RSH4a2)@ Performs a POST request to "/push/channelSubscriptions":/rest-api#post-channel-subscription with the device's @id@ and the channel name. +** @(RSH4b)@ @#subscribeClient()@ +*** @(RSH4b1)@ Fails if the @LocalDevice@ doesn't have a @clientId@. +*** @(RSH4b2)@ Performs a POST request to "/push/channelSubscriptions":/rest-api#post-channel-subscription with the device's @clientId@ and the channel name. +** @(RSH4c)@ @#unsubscribeDevice()@ +*** @(RSH4c1)@ Fails if the @LocalDevice@ doesn't have an @updateToken@, ie. it isn't registered yet. +*** @(RSH4c2)@ Performs a DELETE request to "/push/channelSubscriptions":/rest-api#delete-channel-subscription with the device's @id@ and the channel name. +** @(RSH4d)@ @#unsubscribeClient()@ +*** @(RSH4d1)@ Fails if the @LocalDevice@ doesn't have a @clientId@. +*** @(RSH4d2)@ Performs a DELETE request to "/push/channelSubscriptions":/rest-api#delete-channel-subscription with the device's @clientId@ and the channel name. +** @(RSH4e)@ @#listSubscriptions(params)@ performs a GET request to "/push/channelSubscriptions":/rest-api#list-channel-subscriptions and returns a paginated result with @PushChannelSubscription@ objects filtered by the provided params, the channel name, the device ID, and the client ID if it exists, as supported by the REST API. A @concatFilters@ param needs to be set to @true@ as well. + h3(#activation-state-machine). Activation state machine * @(RSH3)@ In platforms that support receiving push notifications, in order to connect the device's push features with Ably's, the library must perform the process described in the following abstract state machine. While this process should be implemented in whatever way better fits the concrete platform, it should be taken into account that its lifetime is that of the _app_ that runs it, which outlives that of the @Rest@ instance or (typically) the process running the app. This typically forces some kind of on-disk storage to which the state machine's state must be persisted, so that it can be recovered later by new instances and processes running the app triggered by external events. @@ -1439,11 +1457,11 @@ class RealtimeChannel: unsubscribe(String, (Message) ->) // RTL8a class PushChannel: - subscribeDevice() => io - subscribeClientId() => io - unsubscribeDevice() => io - unsubscribeClientId() => io - listSubscriptions() => io PaginatedResult + subscribeDevice() => io // RSH4a + subscribeClientId() => io // RSH4b + unsubscribeDevice() => io // RSH4c + unsubscribeClientId() => io // RSH4d + listSubscriptions() => io PaginatedResult // RSH4e enum ChannelState: INITIALIZED From 7dfb95813d4ab57b82c67c30ea45c255a4e5ed7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toni=20C=C3=A1rdenas?= Date: Wed, 1 Nov 2017 19:31:06 +0100 Subject: [PATCH 23/38] Minor fixes, clarifications and omissions in push spec. --- .../features.textile | 59 ++++++++++--------- content/rest-api/index.textile | 8 +-- 2 files changed, 35 insertions(+), 32 deletions(-) diff --git a/content/client-lib-development-guide/features.textile b/content/client-lib-development-guide/features.textile index 7eb231da92..90ead6bde0 100644 --- a/content/client-lib-development-guide/features.textile +++ b/content/client-lib-development-guide/features.textile @@ -903,8 +903,8 @@ h2(#push-notifications). Push notifications *** @(RHS1b5)@ @#removeWhere(params)@ issues a @DELETE@ request to "/push/deviceRegistrations":/rest-api#delete-device-registrations and deletes the registered devices matching the provided @params@. A test should exist that deletes devices by @clientId@ and by @deviceId@ separately, then additionally issues a delete for devices with no matching params and checks the operation still succeeds. ** @(RHS1c)@ @#channelSubscriptions@ provides access to the admin @ChannelSubscriptions@ object with the following methods: *** @(RHS1c1)@ @#list(params)@ performs a request to "/push/channelSubscriptions":/rest-api#list-channel-subscriptions and returns a paginated result with @PushChannelSubscription@ objects filtered by the provided params, as supported by the REST API. A test should exist filtering by @channel@ and @deviceId@ and/or @clientId@, and then controlling the pagination with the @limit@ attribute -*** @(RHS1c2)@ @#listChannels(params)@ performs a request to "/push/channels":/rest-api#list-channels and returns a paginated result with @PushChannelSubscription@ objects filtered by the provided params, as supported by the REST API. A test should exist using the @limit@ attribute and pagination -*** @(RHS1c3)@ @#save(pushChannelSubscription)@ issues a @POST@ (this will change to @PUT@ soon) request to "/push/channelSubscriptions":/rest-api#put-channel-subscription using the @PushChannelSubscription@ object (and optionally a JSON-encodable object) argument. A test should exist for a successful save, a successful subsequent save with an update, and a failed save operation +*** @(RHS1c2)@ @#listChannels(params)@ performs a request to "/push/channels":/rest-api#list-channels and returns a paginated result with @String@ objects filtered by the provided params, as supported by the REST API. A test should exist using the @limit@ attribute and pagination +*** @(RHS1c3)@ @#save(pushChannelSubscription)@ issues a @POST@ (this will change to @PUT@ soon) request to "/push/channelSubscriptions":/rest-api#post-channel-subscription using the @PushChannelSubscription@ object (and optionally a JSON-encodable object) argument. A test should exist for a successful save, a successful subsequent save with an update, and a failed save operation *** @(RHS1c4)@ @#remove(push_channel_subscription)@ issues a @DELETE@ request to "/push/channelSubscriptions":/rest-api#delete-channel-subscription and deletes the channel subscription using the attributes as params to the @DELETE@ request. A test should exist that deletes a @clientId@ and @deviceId@ channel subscription separately and both succeed, and then also deletes a subscription that does not exist but still succeeds *** @(RHS1c5)@ @#removeWhere(params)@ issues a @DELETE@ request to "/push/channelSubscriptions":/rest-api#delete-channel-subscription and deletes the matching channel subscriptions provided in @params@. A test should exist that deletes channel subscriptions by @clientId@ and by @deviceId@ separately, then additionally issues a delete for subscriptions with no matching params and checks the operation still succeeds. * @(RSH2)@ The following should only apply to platforms that support receiving push notifications: @@ -937,12 +937,14 @@ h3(#activation-state-machine). Activation state machine **** @(RSH3a1a)@ Makes @Push#deactivate@ return or call its callback with no error. **** @(RSH3a1b)@ Transitions to @NotActivated@. *** @(RSH3a2)@ On event @CalledActivate@: -**** @(RSH3a2a)@ If the local device has @id@ and @updateToken@, enqueues a @CalledActivate@ event and transitions to @WaitingForNewPushDeviceDetails@. +**** @(RSH3a2a)@ If the local device has @id@ and @updateToken@, enqueues a @CalledActivate@ event and transitions to @WaitingForNewPushDeviceDetails@ and #RSH3a2b and #RSH3a2c don't apply. **** @(RSH3a2b)@ If the local device has the necessary push details (registration token, etc.), sends a @GotPushDeviceDetails@ event. **** @(RSH3a2c)@ Transitions to @WaitingForPushDeviceDetails@. +*** @(RSH3a3)@ On event @GotPushDeviceDetails@: +**** @(RSH3a3a)@ Transitions to @NotActivated@. (This consumes the event; #RSH3a2 produces it again once @Push#activate@ is called.) ** @(RSH3b)@ State @WaitingForPushDeviceDetails@: *** @(RSH3b1)@ On event @CalledActivate@: -**** @(RSH3b1a)@ Transitions ot @CalledActivate@. +**** @(RSH3b1a)@ Transitions to @WaitingForPushDeviceDetails@. *** @(RSH3b2)@ On event @CalledDeactivate@: **** @(RSH3b2a)@ Makes @Push#deactivate@ return or call its callback with no error. **** @(RSH3b2b)@ Transitions to @NotActivated@. @@ -966,19 +968,19 @@ h3(#activation-state-machine). Activation state machine **** @(RSH3d1a)@ Makes @Push#activate@ return or call its callback with no error. **** @(RSH3d1b)@ Transitions to @WaitingForNewPushDeviceDetails@. *** @(RSH3d2)@ On event @CalledDeactivate@: -**** @(RSH3d2a)@ If a custom @deregisterCallback@ was provided to @Push#deactivate@, pass it the local @DeviceDetails@'s id. -**** @(RSH3d2b)@ Otherwise, make an asynchronous DELETE HTTP request to "/push/deviceRegistrations":/rest-api/#delete-device-registration using the local @DeviceDetails@'s ID. +**** @(RSH3d2a)@ If a custom @deregisterCallback@ was provided to @Push#deactivate@, pass it the local @DeviceDetails@ 's id. +**** @(RSH3d2b)@ Otherwise, make an asynchronous DELETE HTTP request to "/push/deviceRegistrations":/rest-api/#delete-device-registration using the local @DeviceDetails@ 's ID. **** @(RSH3d2c)@ Either way, when the registration is done, a @Deregistered@ or @DeregistrationFailed@ event should be fired. **** @(RSH3d2d)@ Transitions to @WaitingForDeregistration@. *** @(RSH3d3)@ On event @GotPushDeviceDetails@ (note that this will only happen on platforms whose push device details, after first set, can change, e. g. GCM's registration token refresh): **** @(RSH3d3a)@ If a custom @registerCallback@ was provided to @Push#activate@, pass it the local @DeviceDetails@ updated with the push details. -**** @(RSH3d3b)@ Otherwise, make an asynchronous PUT HTTP request to "/push/deviceRegistrations/:deviceId":/rest-api/#update-device-registration using the local @DeviceDetails@ 's push details as body and its @updateToken@ as authorization token. +**** @(RSH3d3b)@ Otherwise, make an asynchronous PATCH HTTP request to "/push/deviceRegistrations/:deviceId":/rest-api/#update-device-registration using the local @DeviceDetails@ 's push details as body (but only the changed fields, as described in "the REST endpoint documentation":/rest-api/#update-device-registration) and its @updateToken@ as authorization token. **** @(RSH3d3c)@ Either way, when the registration is done, a @RegistrationUpdated@ or @UpdatingRegistrationFailed@ event should be fired. **** @(RSH3d3d)@ Transitions to @WaitingForRegistrationUpdate@. ** @(RSH3e)@ State @WaitingForRegistrationUpdate@: *** @(RSH3e1)@ On event @CalledActivate@: **** @(RSH3e1a)@ Makes @Push#activate@ return or call its callback with no error. -**** @(RSH3e1b)@ Transitions to @WaitingForNewPushDeviceDetails@. +**** @(RSH3e1b)@ Transitions to @WaitingForRegistrationUpdate@. *** @(RSH3e2)@ On event @RegistrationUpdated@: **** @(RSH3e2a)@ Transitions to @WaitingForNewPushDeviceDetails@. *** @(RSH3e3)@ On event @UpdatingRegistrationFailed@: @@ -1422,7 +1424,6 @@ class Channels: class RestChannel: name: String? presence: RestPresence // RSL3 - push: PushChannel history( start: Time, // RSL2b1 end: Time api-default now(), // RSL2b1 @@ -1432,6 +1433,9 @@ class RestChannel: publish([Message]) => io // RSL1 publish(name: String?, data: Data?, clientId?: String, extras?: JsonObject) => io // RSL1, RSL1h + // Only on platforms that support receiving notifications: + push: PushChannel // RSH4 + class RealtimeChannel: embeds EventEmitter // RTL2a, RTL2d, RTL2e errorReason: ErrorInfo? // RTL4e @@ -1691,7 +1695,7 @@ class LocalDevice extends DeviceDetails: reissueUpdateToken() => io class Push: - admin: PushAdmin + admin: PushAdmin // RSH1 // Only on platforms that support receiving notifications: @@ -1700,35 +1704,34 @@ class Push: // Only on platforms that, after first set, can update later its push // device details: updateFailedCallback: ((ErrorInfo) ->) - ) => io ErrorInfo? // RSH3 + ) => io ErrorInfo? // RSH2a deactivate( deregisterCallback: ((ErrorInfo?, deviceId: String?) -> io)? - ) => io ErrorInfo? + ) => io ErrorInfo? // RSH2b class PushAdmin: - deviceRegistrations: PushDeviceRegistrations - channelSubscriptions: PushChannelSubscriptions - publish(recipient: JsonObject, data: JsonObject) => io + publish(recipient: JsonObject, data: JsonObject) => io // RSH1a + deviceRegistrations: PushDeviceRegistrations // RSH1b + channelSubscriptions: PushChannelSubscriptions // RSH1c class JsonObject: // Platform-dependent, typically a Dict-like object class PushDeviceRegistrations: - get(DeviceDetails) => io DeviceDetails - get(deviceId: String) => io DeviceDetails - list(params: Dict) => io PaginatedResult - save(DeviceDetails) => io DeviceDetails - remove(DeviceDetails) => io - remove(deviceId: String) => io - removeWhere(params: Dict) => io + get(DeviceDetails) => io DeviceDetails // RSH1b1 + get(deviceId: String) => io DeviceDetails // RSH1b1 + list(params: Dict) => io PaginatedResult // RSH1b2 + save(DeviceDetails) => io DeviceDetails // RSH1b3 + remove(DeviceDetails) => io // RSH1b4 + remove(deviceId: String) => io // RSH1b4 + removeWhere(params: Dict) => io // RSH1b5 class PushChannelSubscriptions: - get(PushChannelSubscription) => io PushChannelSubscription - list(params: Dict) => io PaginatedResult - listChannels(params: Dict?) => io PaginatedResult - save(PushChannelSubscription) => io PushChannelSubscription - remove(PushChannelSubscription) => io - removeWhere(params: Dict) => io + list(params: Dict) => io PaginatedResult // RSH1c1 + listChannels(params: Dict?) => io PaginatedResult // RSH1c2 + save(PushChannelSubscription) => io PushChannelSubscription // RSH1c3 + remove(PushChannelSubscription) => io // RSH1c4 + removeWhere(params: Dict) => io // RSH1c5 enum DevicePushTransportType: "fcm" // PTT1 diff --git a/content/rest-api/index.textile b/content/rest-api/index.textile index 86ab63b87b..ee8e77cd59 100644 --- a/content/rest-api/index.textile +++ b/content/rest-api/index.textile @@ -22,7 +22,7 @@ jump_to: - reset a device's update token#reset-update-token - unregister device#delete-device-registration - unregister devices#delete-device-registrations - - subscribe to a channel#put-channel-subscription + - subscribe to a channel#post-channel-subscription - unsubscribe from channels#delete-channel-subscription - list channel subscriptions#list-channel-subscriptions - list channels#list-channels @@ -359,7 +359,7 @@ Messages can include an optional @extras@ field, used by extensions to Ably's co h6(#message-extras-push). Send push notification -You can send a push notification to devices "subscribed to the channel":#put-channel-subscription the message is sent to by setting the @push@ field in the @extras@ object, like this: +You can send a push notification to devices "subscribed to the channel":#post-channel-subscription the message is sent to by setting the @push@ field in the @extras@ object, like this: bc[json]. { <... message fields ...> @@ -866,7 +866,7 @@ A successful request returns an empty response. An unsuccessful request returns an error. -h3(#put-channel-subscription). Subscribe to a channel +h3(#post-channel-subscription). Subscribe to a channel Subscribe either a single device or all devices associated with a client ID to receive push notifications from messages sent to a channel. @@ -999,7 +999,7 @@ h3(#push-publish). Publish a push notification to a single device Convenience endpoint to deliver a push notification payload to a single device or set of devices identified by their "client identifier":/general/authentication#identified-clients. -If you want to send a push notification to multiple devices or use a more flexible publish-subscribe architecture so that you don't need to know about recipient devices's details, we recommend you look at "registering devices for push":#post-device-registration, then "subscribe them to channels":#put-channel-subscription, and then "send messages to the channels with push payloads":#message-extras-push. +If you want to send a push notification to multiple devices or use a more flexible publish-subscribe architecture so that you don't need to know about recipient devices's details, we recommend you look at "registering devices for push":#post-device-registration, then "subscribe them to channels":#post-channel-subscription, and then "send messages to the channels with push payloads":#message-extras-push. This direct publish endpoint is designed for customers who typically have legacy devices they wish to push directly to, or if they want to publish to all devices for a single user. From 4e581dbce0e4e1cdf45b85fa7eb5367f7075f088 Mon Sep 17 00:00:00 2001 From: Julien Letessier Date: Fri, 1 Dec 2017 14:46:31 +0100 Subject: [PATCH 24/38] Fixes some iOS Swift docs (handles renames) --- content/realtime/connection.textile | 2 +- content/realtime/push/activate-subscribe.textile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/content/realtime/connection.textile b/content/realtime/connection.textile index 316e4bfb34..dd5f8650ba 100644 --- a/content/realtime/connection.textile +++ b/content/realtime/connection.textile @@ -77,7 +77,7 @@ bc[objc]. ARTRealtime *ably = [[ARTRealtime alloc] initWithKey:@"{{API_KEY}}"]; }]; bc[swift]. let realtime = ARTRealtime(key: "{{API_KEY}}") -realtime.connection.on(.Connected) { change in +realtime.connection.on(.connected) { change in print("Connected, that was easy") } diff --git a/content/realtime/push/activate-subscribe.textile b/content/realtime/push/activate-subscribe.textile index b932660a76..52089ea3e0 100644 --- a/content/realtime/push/activate-subscribe.textile +++ b/content/realtime/push/activate-subscribe.textile @@ -109,7 +109,7 @@ You should now have a couple of methods: "@application(_:didR ```[swift] // In your UIApplicationDelegate class: func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { - ARTPush.didRegisterForRemoteNotificationsWithDeviceToken(deviceToken, realtime: self.getAblyRealtime()) + ARTPush.didRegisterForRemoteNotifications(withDeviceToken: deviceToken, realtime: self.getAblyRealtime()) } func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { From 45184af078273f3ad423fb586f85b5e03d3d64aa Mon Sep 17 00:00:00 2001 From: Paddy Byers Date: Wed, 14 Mar 2018 07:53:48 +0000 Subject: [PATCH 25/38] Add some comments on device authentication for push operations (draft) --- content/realtime/push/admin.textile | 42 ++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/content/realtime/push/admin.textile b/content/realtime/push/admin.textile index 03c68df6eb..9e5c365924 100644 --- a/content/realtime/push/admin.textile +++ b/content/realtime/push/admin.textile @@ -41,14 +41,30 @@ result.errback do |err| end ``` -p(tip). The push notification service is currently in beta. Before you get started, please take a look at the "push service beta notice":/realtime/push#beta. - The push admin API offers three key features: * "@publish@":#publish for "direct publishing to devices and groups of identified devices":/realtime/push/publish#direct-publishing * "@deviceRegistrations@":#device-registrations for registering, updating, listing and deregistering push devices * "@channelSubscriptions@":#channel-subscriptions for subscribing, listing and unsubscribing individual devices or groups of identified devices to push notifications published on channels +h3. Push admin access control and device authentication + +Operations using the push admin API, as with all other REST APIs, require specific permissions to be present in the credentials presented by a client. The push admin API has two modes of authorization: + +* access using the @push-admin@ permission. A client whose credentials contain the @push-admin@ permission has full access to the push admin API, and can manage registrations and subscriptions for all devices; + +* access using the @push-subscribe@ permission. A client with @push-subscribe@ is a push target device, and it can manage its registration and any subscription for itself; it is not able to manage push registrations or channel subscriptions for any other device. The credentials presented, as well as containing the @push-subscribe@ permission, must also authenticate the device itself. + +Every push target device has a @deviceId@ and also has device credentials, which are used as the basis for device authentication. When using the push admin API, a device can authenticate itself in two ways: + +* by using an Ably token that contains its @deviceId@; + +* by using a normal Ably key or token but additionally including a @deviceToken@ - a credential created at registration time that is used to assert the device identity - in a header in the request. + +Management of device credentials is performed by the client library, so unless the push admin API is being accessed directly using HTTP, the client application does not need to worry about managing device credentials. + +p(tip). The push notification service is currently in beta. Before you get started, please take a look at the "push service beta notice":/realtime/push#beta. + h1. API reference inline-toc. @@ -149,7 +165,7 @@ bq(definition#device-get-device). default: get("DeviceDetails":#device-details device, callback("ErrorInfo":/realtime/types#error-info err, "DeviceDetails":#device-details device)) ruby: "Deferrable":/realtime/types#deferrable get("DeviceDetails":#device-details device) -> yields "DeviceDetails":#device-details -Obtain the @DeviceDetails@ for a device registered for receiving push registrations matching the @deviceId@ argument, or the @id@ attribute of the provided @DeviceDetails@ object. +Obtain the @DeviceDetails@ for a device registered for receiving push registrations matching the @deviceId@ argument, or the @id@ attribute of the provided @DeviceDetails@ object. Requires @push-admin@ permission or @push-subscribe@ permission together with device authentication matching the requested @deviceId@. h4. Parameters @@ -182,7 +198,7 @@ bq(definition). default: list(Object params, callback("ErrorInfo":/realtime/types#error-info err, "PaginatedResult":#paginated-result<"DeviceDetails":#device-details device> resultPage)) ruby: "Deferrable":/realtime/types#deferrable list(Hash params) -> yields "PaginatedResult":#paginated-result<"DeviceDetails":#device-details> -Retrieve all devices matching the params filter as a paginated list of "@DeviceDetails@":#device-details objects. +Retrieve all devices matching the params filter as a paginated list of "@DeviceDetails@":#device-details objects. Requires @push-admin@ permission. h4. Parameters @@ -221,7 +237,7 @@ bq(definition). default: save("DeviceDetails":#device-details device, callback("ErrorInfo":/realtime/types#error-info err, "DeviceDetails":#device-details device)) ruby: "Deferrable":/realtime/types#deferrable save("DeviceDetails":#device-details device) -> yields "DeviceDetails":#device-details -Register a new @DeviceDetails@ object, or update an existing @DeviceDetails@ object with the Ably service. +Register a new @DeviceDetails@ object, or update an existing @DeviceDetails@ object with the Ably service. Requires @push-admin@ permission or @push-subscribe@ permission together with device authentication matching the requested @deviceId@. h4. Parameters @@ -257,7 +273,7 @@ bq(definition#device-remove-device). default: remove("DeviceDetails":#device-details device, callback("ErrorInfo":/realtime/types#error-info err)) ruby: "Deferrable":/realtime/types#deferrable remove("DeviceDetails":#device-details device) -> yield -Remove a device registered for receiving push registrations that matches the @deviceId@ argument, or the @id@ attribute of the provided @DeviceDetails@ object. +Remove a device registered for receiving push registrations that matches the @deviceId@ argument, or the @id@ attribute of the provided @DeviceDetails@ object. Requires @push-admin@ permission or @push-subscribe@ permission together with device authentication matching the requested @deviceId@. h4. Parameters @@ -291,7 +307,7 @@ bq(definition). default: removeWhere(Object params, callback("ErrorInfo":/realtime/types#error-info err)) ruby: "Deferrable":/realtime/types#deferrable remove_where(Hash params) -> yield -Delete all devices matching the params filter. +Delete all devices matching the params filter. Requires @push-admin@ permission. h4. Parameters @@ -335,7 +351,7 @@ bq(definition). default: get("PushChannelSubscription":#push-channel-subscription channelSubscription, callback("ErrorInfo":/realtime/types#error-info err, "PushChannelSubscription":#push-channel-subscription channelSubscription)) ruby: "Deferrable":/realtime/types#deferrable get("PushChannelSubscription":#push-channel-subscription channel_subscription) -> yields "PushChannelSubscription":#push-channel-subscription -Retrieve the push channel subscription that matches the provided "@PushChannelSubscription@":#push-channel-subscription argument. Each "@PushChannelSubscription@":#push-channel-subscription represents a device or set of devices sharing the same "client identifier":/general/authentication#identified-clients registered to a channel to receive push notifications. +Retrieve the push channel subscription that matches the provided "@PushChannelSubscription@":#push-channel-subscription argument. Each "@PushChannelSubscription@":#push-channel-subscription represents a device or set of devices sharing the same "client identifier":/general/authentication#identified-clients registered to a channel to receive push notifications. Requires @push-admin@ permission. h4. Parameters @@ -367,7 +383,7 @@ bq(definition). default: list(Object params, callback("ErrorInfo":/realtime/types#error-info err, "PaginatedResult":#paginated-result<"PushChannelSubscription":#push-channel-subscription> resultPage)) ruby: "Deferrable":/realtime/types#deferrable list(Hash params) -> yields "PaginatedResult":#paginated-result<"PushChannelSubscription":#push-channel-subscription> -Retrieve all push channel subscriptions that match the provided params filter as a paginated list of "@PushChannelSubscription@":#push-channel-subscription objects. Each "@PushChannelSubscription@":#push-channel-subscription represents a device or set of devices sharing the same "client identifier":/general/authentication#identified-clients registered to a channel to receive push notifications. +Retrieve all push channel subscriptions that match the provided params filter as a paginated list of "@PushChannelSubscription@":#push-channel-subscription objects. Each "@PushChannelSubscription@":#push-channel-subscription represents a device or set of devices sharing the same "client identifier":/general/authentication#identified-clients registered to a channel to receive push notifications. Requires @push-admin@ permission. h4. Parameters @@ -408,7 +424,7 @@ bq(definition). default: listChannels(Object params, callback("ErrorInfo":/realtime/types#error-info err, "PaginatedResult":#paginated-result resultPage)) ruby: "Deferrable":/realtime/types#deferrable list_channels(Hash params) -> yields "PaginatedResult":#paginated-result -Retrieve a list of channels with at least one subscribed device as a paginated list of channel name @String@ objects. +Retrieve a list of channels with at least one subscribed device as a paginated list of channel name @String@ objects. Requires @push-admin@ permission. h4. Parameters @@ -445,7 +461,7 @@ bq(definition). default: save("PushChannelSubscription":#push-channel-subscription channelSubscription, callback("ErrorInfo":/realtime/types#error-info err, "PushChannelSubscription":#push-channel-subscription channelSubscription)) ruby: "Deferrable":/realtime/types#deferrable save("PushChannelSubscription":#push-channel-subscription channel_subscription) -> yields "PushChannelSubscription":#push-channel-subscription -Subscribe a device or group of devices sharing a "client identifier":/general/authentication#identified-clients for push notifications published on a channel. +Subscribe a device or group of devices sharing a "client identifier":/general/authentication#identified-clients for push notifications published on a channel. Requires @push-admin@ permission or, in the case of a subscription associated with a given @deviceId@, @push-subscribe@ permission together with device authentication matching that @deviceId@. h4. Parameters @@ -477,7 +493,7 @@ bq(definition). default: remove("PushChannelSubscription":#push-channel-subscription channelSubscription, callback("ErrorInfo":/realtime/types#error-info err)) ruby: "Deferrable":/realtime/types#deferrable remove("PushChannelSubscription":#push-channel-subscription channel_subscription) -> yield -Subscribe a device or group of devices sharing a "client identifier":/general/authentication#identified-clients for push notifications on a channel. +Subscribe a device or group of devices sharing a "client identifier":/general/authentication#identified-clients for push notifications on a channel. Requires @push-admin@ permission or, in the case of a subscription associated with a given @deviceId@, @push-subscribe@ permission together with device authentication matching that @deviceId@. h4. Parameters @@ -510,7 +526,7 @@ bq(definition). default: removeWhere(Object params, callback("ErrorInfo":/realtime/types#error-info err)) ruby: "Deferrable":/realtime/types#deferrable remove_where(Hash params) -> yield -Delete all push channel subscriptions matching the params filter. +Delete all push channel subscriptions matching the params filter. Requires @push-admin@ permission. h4. Parameters From 701ca1c91afbd688b52cc53e3a0930020f3e9ec2 Mon Sep 17 00:00:00 2001 From: Paddy Byers Date: Wed, 14 Mar 2018 07:54:15 +0000 Subject: [PATCH 26/38] Updates to reflect changes in device authentication for push --- .../features.textile | 54 +++++++++++-------- 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/content/client-lib-development-guide/features.textile b/content/client-lib-development-guide/features.textile index 90ead6bde0..26e58731a7 100644 --- a/content/client-lib-development-guide/features.textile +++ b/content/client-lib-development-guide/features.textile @@ -896,16 +896,16 @@ h2(#push-notifications). Push notifications * @(RSH1)@ @Push#admin@ object provides the following interface: ** @(RHS1a)@ @#publish(recipient, data)@ performs an HTTP request to "/push/publish":/rest-api#push-publish. Empty values for @recipient@ or @data@ should be immediately rejected. An end-to-end push notification test can be made using the special test-only @ablyChannel@ recipient. Additionally, tests should exist with valid and invalid recipient details ** @(RHS1b)@ @#deviceRegistrations@ provides access to the admin @PushDeviceRegistrations@ object with the following methods: -*** @(RHS1b1)@ @#get(deviceId)@ performs a request to "/push/deviceRegistrations/:deviceId":/rest-api#get-device-registration and returns a @DeviceDetails@ object if the @deviceId@ is found or results in a not found error if the device cannot be found +*** @(RHS1b1)@ @#get(deviceId)@ performs a request to "/push/deviceRegistrations/:deviceId":/rest-api#get-device-registration and returns a @DeviceDetails@ object if the @deviceId@ is found or results in a not found error if the device cannot be found. If the client has been "activated as a push target device":#activation-state-machine, and the specified @deviceId@ is that of the present client, then this request must include "push device authentication":#push-device-authentication. *** @(RHS1b2)@ @#list(params)@ performs a request to "/push/deviceRegistrations":/rest-api#list-device-registrations and returns a paginated result with @DeviceDetails@ objects filtered by the provided params, as supported by the REST API. A test should exist filtering by @deviceId@ and separately by @clientId@, and then controlling the pagination with the @limit@ attribute -*** @(RHS1b3)@ @#save(device)@ issues a @PUT@ request to "/push/deviceRegistrations/:deviceId":/rest-api#update-device-registration using the @DeviceDetails@ object (and optionally a JSON-encodable object) argument. A test should exist for a successful save, a successful subsequent save with an update, and a failed save operation +*** @(RHS1b3)@ @#save(device)@ issues a @PUT@ request to "/push/deviceRegistrations/:deviceId":/rest-api#update-device-registration using the @DeviceDetails@ object (and optionally a JSON-encodable object) argument. A test should exist for a successful save, a successful subsequent save with an update, and a failed save operation. If the client has been "activated as a push target device":#activation-state-machine, and the specified @deviceId@ is that of the present client, then this request must include "push device authentication":#push-device-authentication. *** @(RHS1b4)@ @#remove(deviceId)@ issues a @DELETE@ request to "/push/deviceRegistrations/:deviceId":/rest-api#delete-device-registration and deletes the registered device specified by @deviceId@. A test should exist that deletes a device and succeeds, and then also deletes a device that does not exist but still succeeds -*** @(RHS1b5)@ @#removeWhere(params)@ issues a @DELETE@ request to "/push/deviceRegistrations":/rest-api#delete-device-registrations and deletes the registered devices matching the provided @params@. A test should exist that deletes devices by @clientId@ and by @deviceId@ separately, then additionally issues a delete for devices with no matching params and checks the operation still succeeds. +*** @(RHS1b5)@ @#removeWhere(params)@ issues a @DELETE@ request to "/push/deviceRegistrations":/rest-api#delete-device-registrations and deletes the registered devices matching the provided @params@. A test should exist that deletes devices by @clientId@ and by @deviceId@ separately, then additionally issues a delete for devices with no matching params and checks the operation still succeeds. If the client has been "activated as a push target device":#activation-state-machine, and the specified @deviceId@ is that of the present client, then this request must include "push device authentication":#push-device-authentication. ** @(RHS1c)@ @#channelSubscriptions@ provides access to the admin @ChannelSubscriptions@ object with the following methods: *** @(RHS1c1)@ @#list(params)@ performs a request to "/push/channelSubscriptions":/rest-api#list-channel-subscriptions and returns a paginated result with @PushChannelSubscription@ objects filtered by the provided params, as supported by the REST API. A test should exist filtering by @channel@ and @deviceId@ and/or @clientId@, and then controlling the pagination with the @limit@ attribute *** @(RHS1c2)@ @#listChannels(params)@ performs a request to "/push/channels":/rest-api#list-channels and returns a paginated result with @String@ objects filtered by the provided params, as supported by the REST API. A test should exist using the @limit@ attribute and pagination -*** @(RHS1c3)@ @#save(pushChannelSubscription)@ issues a @POST@ (this will change to @PUT@ soon) request to "/push/channelSubscriptions":/rest-api#post-channel-subscription using the @PushChannelSubscription@ object (and optionally a JSON-encodable object) argument. A test should exist for a successful save, a successful subsequent save with an update, and a failed save operation -*** @(RHS1c4)@ @#remove(push_channel_subscription)@ issues a @DELETE@ request to "/push/channelSubscriptions":/rest-api#delete-channel-subscription and deletes the channel subscription using the attributes as params to the @DELETE@ request. A test should exist that deletes a @clientId@ and @deviceId@ channel subscription separately and both succeed, and then also deletes a subscription that does not exist but still succeeds +*** @(RHS1c3)@ @#save(pushChannelSubscription)@ issues a @POST@ request to "/push/channelSubscriptions":/rest-api#post-channel-subscription using the @PushChannelSubscription@ object (and optionally a JSON-encodable object) argument. If the client has been "activated as a push target device":#activation-state-machine, and the specified @PushChannelSubscription@ contains a @deviceId@ matching that of the present client, then this request must include "push device authentication":#push-device-authentication. A test should exist for a successful save, a successful subsequent save with an update, and a failed save operation +*** @(RHS1c4)@ @#remove(push_channel_subscription)@ issues a @DELETE@ request to "/push/channelSubscriptions":/rest-api#delete-channel-subscription and deletes the channel subscription using the attributes as params to the @DELETE@ request. If the client has been "activated as a push target device":#activation-state-machine, and the specified @PushChannelSubscription@ contains a @deviceId@ matching that of the present client, then this request must include "push device authentication":#push-device-authentication. A test should exist that deletes a @clientId@ and @deviceId@ channel subscription separately and both succeed, and then also deletes a subscription that does not exist but still succeeds *** @(RHS1c5)@ @#removeWhere(params)@ issues a @DELETE@ request to "/push/channelSubscriptions":/rest-api#delete-channel-subscription and deletes the matching channel subscriptions provided in @params@. A test should exist that deletes channel subscriptions by @clientId@ and by @deviceId@ separately, then additionally issues a delete for subscriptions with no matching params and checks the operation still succeeds. * @(RSH2)@ The following should only apply to platforms that support receiving push notifications: ** @(RSH2a)@ @Push#activate@ sends a @CalledActivate@ event to "the state machine":#RSH3. @@ -916,14 +916,16 @@ h3(#push-channels). Push channels * @(RSH4)@ In platforms that support receiving push notifications, @RestChannel@ and @RealtimeChannel@ have an additional @push@ field, as an object with the following interface: ** @(RSH4a)@ @#subscribeDevice()@ -*** @(RSH4a1)@ Fails if the @LocalDevice@ doesn't have an @updateToken@, ie. it isn't registered yet. +*** @(RSH4a1)@ Fails if the @LocalDevice@ doesn't have an @deviceIdentityToken@, ie. it isn't registered yet. *** @(RSH4a2)@ Performs a POST request to "/push/channelSubscriptions":/rest-api#post-channel-subscription with the device's @id@ and the channel name. +*** @(RSH4a3)@ The request must include "push device authentication":#push-device-authentication ** @(RSH4b)@ @#subscribeClient()@ *** @(RSH4b1)@ Fails if the @LocalDevice@ doesn't have a @clientId@. *** @(RSH4b2)@ Performs a POST request to "/push/channelSubscriptions":/rest-api#post-channel-subscription with the device's @clientId@ and the channel name. ** @(RSH4c)@ @#unsubscribeDevice()@ -*** @(RSH4c1)@ Fails if the @LocalDevice@ doesn't have an @updateToken@, ie. it isn't registered yet. +*** @(RSH4c1)@ Fails if the @LocalDevice@ doesn't have a @deviceIdentityToken@, ie. it isn't registered yet. *** @(RSH4c2)@ Performs a DELETE request to "/push/channelSubscriptions":/rest-api#delete-channel-subscription with the device's @id@ and the channel name. +*** @(RSH4c3)@ The request must include "push device authentication":#push-device-authentication ** @(RSH4d)@ @#unsubscribeClient()@ *** @(RSH4d1)@ Fails if the @LocalDevice@ doesn't have a @clientId@. *** @(RSH4d2)@ Performs a DELETE request to "/push/channelSubscriptions":/rest-api#delete-channel-subscription with the device's @clientId@ and the channel name. @@ -937,9 +939,10 @@ h3(#activation-state-machine). Activation state machine **** @(RSH3a1a)@ Makes @Push#deactivate@ return or call its callback with no error. **** @(RSH3a1b)@ Transitions to @NotActivated@. *** @(RSH3a2)@ On event @CalledActivate@: -**** @(RSH3a2a)@ If the local device has @id@ and @updateToken@, enqueues a @CalledActivate@ event and transitions to @WaitingForNewPushDeviceDetails@ and #RSH3a2b and #RSH3a2c don't apply. -**** @(RSH3a2b)@ If the local device has the necessary push details (registration token, etc.), sends a @GotPushDeviceDetails@ event. -**** @(RSH3a2c)@ Transitions to @WaitingForPushDeviceDetails@. +**** @(RSH3a2a)@ If the local device has @deviceIdentityToken@, enqueues a @CalledActivate@ event and transitions to @WaitingForNewPushDeviceDetails@ and #RSH3a2b onwards don't apply. +**** @(RSH3a2b)@ If the local device does not have @id@ and @deviceSecret@, both are generated using random data with sufficient entropy and creating a 32-byte digest (eg using sha256) and encoding that digest with base64. The local @DeviceDetails@ is updated with the resulting @deviceId@ and @deviceSecret@. +**** @(RSH3a2c)@ If the local device has the necessary push details (registration token, etc.), sends a @GotPushDeviceDetails@ event. +**** @(RSH3a2d)@ Transitions to @WaitingForPushDeviceDetails@. *** @(RSH3a3)@ On event @GotPushDeviceDetails@: **** @(RSH3a3a)@ Transitions to @NotActivated@. (This consumes the event; #RSH3a2 produces it again once @Push#activate@ is called.) ** @(RSH3b)@ State @WaitingForPushDeviceDetails@: @@ -950,17 +953,17 @@ h3(#activation-state-machine). Activation state machine **** @(RSH3b2b)@ Transitions to @NotActivated@. *** @(RSH3b3)@ On event @GotPushDeviceDetails@: **** @(RSH3b3a)@ If a custom @registerCallback@ was provided to @Push#activate@, pass it the local @DeviceDetails@ updated with the push details. -**** @(RSH3b3b)@ Otherwise, make an asynchronous HTTP request to "/push/deviceRegistrations":/rest-api/#post-device-registration using the local @DeviceDetails@ updated with the push details as body. -**** @(RSH3b3c)@ Either way, when the registration is done, a @GotUpdateToken@ or @GettingUpdateTokenFailed@ event should be fired. -**** @(RSH3b3d)@ Transitions to @WaitingForUpdateToken@. -** @(RSH3c)@ State @WaitingForUpdateToken@: +**** @(RSH3b3b)@ Otherwise, make an asynchronous HTTP @POST@ request to "/push/deviceRegistrations":/rest-api/#post-device-registration using the local @DeviceDetails@ updated with the push details as body. +**** @(RSH3b3c)@ Either way, when the registration is done, a @GotDeviceRegistration@ or @GettingDeviceRegistrationFailed@ event should be fired. +**** @(RSH3b3d)@ Transitions to @WaitingForDeviceRegistration@. +** @(RSH3c)@ State @WaitingForDeviceRegistration@: *** @(RSH3c1)@ On event @CalledActivate@: -**** @(RSH3c1a)@ Transitions to @WaitingForUpdateToken@. -*** @(RSH3c2)@ On event @GotUpdateToken@: +**** @(RSH3c1a)@ Transitions to @WaitingForDeviceRegistration@. +*** @(RSH3c2)@ On event @GotDeviceRegistration@: **** @(RSH3c2a)@ Updates the local @DeviceDetails@ with it. **** @(RSH3c2b)@ Makes @Push#activate@ return or call its callback with no error. **** @(RSH3c2c)@ Transitions to @WaitingForNewPushDeviceDetails@. -*** @(RSH3c3)@ On event @GettingUpdateTokenFailed@: +*** @(RSH3c3)@ On event @GettingDeviceRegistrationFailed@: **** @(RSH3c3a)@ Makes @Push#activate@ return or call its callback with the error. **** @(RSH3c3b)@ Transitions to @NotActivated@. ** @(RSH3d)@ State @WaitingForNewPushDeviceDetails@: @@ -969,12 +972,12 @@ h3(#activation-state-machine). Activation state machine **** @(RSH3d1b)@ Transitions to @WaitingForNewPushDeviceDetails@. *** @(RSH3d2)@ On event @CalledDeactivate@: **** @(RSH3d2a)@ If a custom @deregisterCallback@ was provided to @Push#deactivate@, pass it the local @DeviceDetails@ 's id. -**** @(RSH3d2b)@ Otherwise, make an asynchronous DELETE HTTP request to "/push/deviceRegistrations":/rest-api/#delete-device-registration using the local @DeviceDetails@ 's ID. +**** @(RSH3d2b)@ Otherwise, make an asynchronous DELETE HTTP request to "/push/deviceRegistrations":/rest-api/#delete-device-registration using the local @DeviceDetails@ 's ID. This operation requires "push device authentication":#push-device-authentication. **** @(RSH3d2c)@ Either way, when the registration is done, a @Deregistered@ or @DeregistrationFailed@ event should be fired. **** @(RSH3d2d)@ Transitions to @WaitingForDeregistration@. *** @(RSH3d3)@ On event @GotPushDeviceDetails@ (note that this will only happen on platforms whose push device details, after first set, can change, e. g. GCM's registration token refresh): **** @(RSH3d3a)@ If a custom @registerCallback@ was provided to @Push#activate@, pass it the local @DeviceDetails@ updated with the push details. -**** @(RSH3d3b)@ Otherwise, make an asynchronous PATCH HTTP request to "/push/deviceRegistrations/:deviceId":/rest-api/#update-device-registration using the local @DeviceDetails@ 's push details as body (but only the changed fields, as described in "the REST endpoint documentation":/rest-api/#update-device-registration) and its @updateToken@ as authorization token. +**** @(RSH3d3b)@ Otherwise, make an asynchronous PATCH HTTP request to "/push/deviceRegistrations/:deviceId":/rest-api/#update-device-registration using the local @DeviceDetails@ 's push details as body (but only the changed fields, as described in "the REST endpoint documentation":/rest-api/#update-device-registration). This operation requires "push device authentication":#push-device-authentication. **** @(RSH3d3c)@ Either way, when the registration is done, a @RegistrationUpdated@ or @UpdatingRegistrationFailed@ event should be fired. **** @(RSH3d3d)@ Transitions to @WaitingForRegistrationUpdate@. ** @(RSH3e)@ State @WaitingForRegistrationUpdate@: @@ -995,7 +998,7 @@ h3(#activation-state-machine). Activation state machine *** @(RSH3g1)@ On event @CalledDeactivate@: **** @(RSH3g1a)@ Transitions to @WaitingForDeregistration@. *** @(RSH3g2)@ On event @Deregistered@: -**** @(RSH3g2a)@ Removes the @updateToken@ from the local @DeviceDetails@. +**** @(RSH3g2a)@ Removes the @deviceIdentityToken@ from the local @DeviceDetails@. **** @(RSH3g2b)@ Makes @Push#deactivate@ return or call its callback with no error. **** @(RSH3g2c)@ Transitions to @NotActivated@. *** @(RSH3g3)@ On event @DeregistrationFailed@: @@ -1004,6 +1007,12 @@ h3(#activation-state-machine). Activation state machine * @(RSH4)@ When an event is fired and a transition from the current state is not defined for such event, the event is put into a queue. Then, whenever a transition happens, an event is dequeued from the queue. If a transition from the new current state is defined for the dequeued event, such transition happens. If not, the event is put back in its place in the queue. E. g. we're @WaitingForDeregistration@, and an event @CalledActivate@ happens. This event will be put in the queue, since there's no transition defined for it. Then, an event @Deregistered@ arrives, causing a transition to @NotActivated@. Now we peek the next item on the queue: @CalledActivate@. Because @NotActivated@ transitions on @CalledActivate@, the event is consumed and the machine transitions. * @(RSH5)@ Event handling is atomic and sequential: while an event is being handled, the next one should be handled only after the current one has caused a state transition or has been put into the pending events queue. +h3(#push-device-authentication). Push device authentication + +* @(RSH6)@ In platforms that support receiving push notifications, and have undergone push registration, are capable of authenticating themselves to the Ably server in order that certain push admin operations can be authorized. +** @(RSH6a)@ If a device has completed activation and has a @deviceIdentityToken@ then push device authentication is performed for a request by adding an @X-Ably-DeviceIdentityToken@ request header whose value is the @deviceIdentityToken@. +** @(RSH6b)@ If a device has not completed but has a @deviceSecret@ then push device authentication is performed for a request by adding an @X-Ably-DeviceSecret@ request header whose value is the @deviceSecret@. + h2. Types h3(#types). Data types @@ -1683,7 +1692,7 @@ class DeviceDetails: metadata: JsonObject platform: DevicePlatform push: DevicePushDetails - updateToken: String + deviceSecret: String? class DevicePushDetails: errorReason: ErrorInfo? @@ -1691,8 +1700,7 @@ class DevicePushDetails: state: .Active | .Failing | .Failed class LocalDevice extends DeviceDetails: - setUpdateToken(String) - reissueUpdateToken() => io + deviceIdentityToken: String class Push: admin: PushAdmin // RSH1 From 4eda3744afc035872fcbd6649f491cf843ccaa5c Mon Sep 17 00:00:00 2001 From: Cesare Rocchi Date: Fri, 23 Mar 2018 09:01:57 +0100 Subject: [PATCH 27/38] Change RHS to RSH As per Slack chat https://ably-real-time.slack.com/archives/C030C5YLY/p1521742090000377 --- .../features.textile | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/content/client-lib-development-guide/features.textile b/content/client-lib-development-guide/features.textile index 26e58731a7..fa3aba73c0 100644 --- a/content/client-lib-development-guide/features.textile +++ b/content/client-lib-development-guide/features.textile @@ -894,19 +894,19 @@ h3(#channel-states-operations). @RealtimeChannel.state@ effects on channel opera h2(#push-notifications). Push notifications * @(RSH1)@ @Push#admin@ object provides the following interface: -** @(RHS1a)@ @#publish(recipient, data)@ performs an HTTP request to "/push/publish":/rest-api#push-publish. Empty values for @recipient@ or @data@ should be immediately rejected. An end-to-end push notification test can be made using the special test-only @ablyChannel@ recipient. Additionally, tests should exist with valid and invalid recipient details -** @(RHS1b)@ @#deviceRegistrations@ provides access to the admin @PushDeviceRegistrations@ object with the following methods: -*** @(RHS1b1)@ @#get(deviceId)@ performs a request to "/push/deviceRegistrations/:deviceId":/rest-api#get-device-registration and returns a @DeviceDetails@ object if the @deviceId@ is found or results in a not found error if the device cannot be found. If the client has been "activated as a push target device":#activation-state-machine, and the specified @deviceId@ is that of the present client, then this request must include "push device authentication":#push-device-authentication. -*** @(RHS1b2)@ @#list(params)@ performs a request to "/push/deviceRegistrations":/rest-api#list-device-registrations and returns a paginated result with @DeviceDetails@ objects filtered by the provided params, as supported by the REST API. A test should exist filtering by @deviceId@ and separately by @clientId@, and then controlling the pagination with the @limit@ attribute -*** @(RHS1b3)@ @#save(device)@ issues a @PUT@ request to "/push/deviceRegistrations/:deviceId":/rest-api#update-device-registration using the @DeviceDetails@ object (and optionally a JSON-encodable object) argument. A test should exist for a successful save, a successful subsequent save with an update, and a failed save operation. If the client has been "activated as a push target device":#activation-state-machine, and the specified @deviceId@ is that of the present client, then this request must include "push device authentication":#push-device-authentication. -*** @(RHS1b4)@ @#remove(deviceId)@ issues a @DELETE@ request to "/push/deviceRegistrations/:deviceId":/rest-api#delete-device-registration and deletes the registered device specified by @deviceId@. A test should exist that deletes a device and succeeds, and then also deletes a device that does not exist but still succeeds -*** @(RHS1b5)@ @#removeWhere(params)@ issues a @DELETE@ request to "/push/deviceRegistrations":/rest-api#delete-device-registrations and deletes the registered devices matching the provided @params@. A test should exist that deletes devices by @clientId@ and by @deviceId@ separately, then additionally issues a delete for devices with no matching params and checks the operation still succeeds. If the client has been "activated as a push target device":#activation-state-machine, and the specified @deviceId@ is that of the present client, then this request must include "push device authentication":#push-device-authentication. -** @(RHS1c)@ @#channelSubscriptions@ provides access to the admin @ChannelSubscriptions@ object with the following methods: -*** @(RHS1c1)@ @#list(params)@ performs a request to "/push/channelSubscriptions":/rest-api#list-channel-subscriptions and returns a paginated result with @PushChannelSubscription@ objects filtered by the provided params, as supported by the REST API. A test should exist filtering by @channel@ and @deviceId@ and/or @clientId@, and then controlling the pagination with the @limit@ attribute -*** @(RHS1c2)@ @#listChannels(params)@ performs a request to "/push/channels":/rest-api#list-channels and returns a paginated result with @String@ objects filtered by the provided params, as supported by the REST API. A test should exist using the @limit@ attribute and pagination -*** @(RHS1c3)@ @#save(pushChannelSubscription)@ issues a @POST@ request to "/push/channelSubscriptions":/rest-api#post-channel-subscription using the @PushChannelSubscription@ object (and optionally a JSON-encodable object) argument. If the client has been "activated as a push target device":#activation-state-machine, and the specified @PushChannelSubscription@ contains a @deviceId@ matching that of the present client, then this request must include "push device authentication":#push-device-authentication. A test should exist for a successful save, a successful subsequent save with an update, and a failed save operation -*** @(RHS1c4)@ @#remove(push_channel_subscription)@ issues a @DELETE@ request to "/push/channelSubscriptions":/rest-api#delete-channel-subscription and deletes the channel subscription using the attributes as params to the @DELETE@ request. If the client has been "activated as a push target device":#activation-state-machine, and the specified @PushChannelSubscription@ contains a @deviceId@ matching that of the present client, then this request must include "push device authentication":#push-device-authentication. A test should exist that deletes a @clientId@ and @deviceId@ channel subscription separately and both succeed, and then also deletes a subscription that does not exist but still succeeds -*** @(RHS1c5)@ @#removeWhere(params)@ issues a @DELETE@ request to "/push/channelSubscriptions":/rest-api#delete-channel-subscription and deletes the matching channel subscriptions provided in @params@. A test should exist that deletes channel subscriptions by @clientId@ and by @deviceId@ separately, then additionally issues a delete for subscriptions with no matching params and checks the operation still succeeds. +** @(RSH1a)@ @#publish(recipient, data)@ performs an HTTP request to "/push/publish":/rest-api#push-publish. Empty values for @recipient@ or @data@ should be immediately rejected. An end-to-end push notification test can be made using the special test-only @ablyChannel@ recipient. Additionally, tests should exist with valid and invalid recipient details +** @(RSH1b)@ @#deviceRegistrations@ provides access to the admin @PushDeviceRegistrations@ object with the following methods: +*** @(RSH1b1)@ @#get(deviceId)@ performs a request to "/push/deviceRegistrations/:deviceId":/rest-api#get-device-registration and returns a @DeviceDetails@ object if the @deviceId@ is found or results in a not found error if the device cannot be found. If the client has been "activated as a push target device":#activation-state-machine, and the specified @deviceId@ is that of the present client, then this request must include "push device authentication":#push-device-authentication. +*** @(RSH1b2)@ @#list(params)@ performs a request to "/push/deviceRegistrations":/rest-api#list-device-registrations and returns a paginated result with @DeviceDetails@ objects filtered by the provided params, as supported by the REST API. A test should exist filtering by @deviceId@ and separately by @clientId@, and then controlling the pagination with the @limit@ attribute +*** @(RSH1b3)@ @#save(device)@ issues a @PUT@ request to "/push/deviceRegistrations/:deviceId":/rest-api#update-device-registration using the @DeviceDetails@ object (and optionally a JSON-encodable object) argument. A test should exist for a successful save, a successful subsequent save with an update, and a failed save operation. If the client has been "activated as a push target device":#activation-state-machine, and the specified @deviceId@ is that of the present client, then this request must include "push device authentication":#push-device-authentication. +*** @(RSH1b4)@ @#remove(deviceId)@ issues a @DELETE@ request to "/push/deviceRegistrations/:deviceId":/rest-api#delete-device-registration and deletes the registered device specified by @deviceId@. A test should exist that deletes a device and succeeds, and then also deletes a device that does not exist but still succeeds +*** @(RSH1b5)@ @#removeWhere(params)@ issues a @DELETE@ request to "/push/deviceRegistrations":/rest-api#delete-device-registrations and deletes the registered devices matching the provided @params@. A test should exist that deletes devices by @clientId@ and by @deviceId@ separately, then additionally issues a delete for devices with no matching params and checks the operation still succeeds. If the client has been "activated as a push target device":#activation-state-machine, and the specified @deviceId@ is that of the present client, then this request must include "push device authentication":#push-device-authentication. +** @(RSH1c)@ @#channelSubscriptions@ provides access to the admin @ChannelSubscriptions@ object with the following methods: +*** @(RSH1c1)@ @#list(params)@ performs a request to "/push/channelSubscriptions":/rest-api#list-channel-subscriptions and returns a paginated result with @PushChannelSubscription@ objects filtered by the provided params, as supported by the REST API. A test should exist filtering by @channel@ and @deviceId@ and/or @clientId@, and then controlling the pagination with the @limit@ attribute +*** @(RSH1c2)@ @#listChannels(params)@ performs a request to "/push/channels":/rest-api#list-channels and returns a paginated result with @String@ objects filtered by the provided params, as supported by the REST API. A test should exist using the @limit@ attribute and pagination +*** @(RSH1c3)@ @#save(pushChannelSubscription)@ issues a @POST@ request to "/push/channelSubscriptions":/rest-api#post-channel-subscription using the @PushChannelSubscription@ object (and optionally a JSON-encodable object) argument. If the client has been "activated as a push target device":#activation-state-machine, and the specified @PushChannelSubscription@ contains a @deviceId@ matching that of the present client, then this request must include "push device authentication":#push-device-authentication. A test should exist for a successful save, a successful subsequent save with an update, and a failed save operation +*** @(RSH1c4)@ @#remove(push_channel_subscription)@ issues a @DELETE@ request to "/push/channelSubscriptions":/rest-api#delete-channel-subscription and deletes the channel subscription using the attributes as params to the @DELETE@ request. If the client has been "activated as a push target device":#activation-state-machine, and the specified @PushChannelSubscription@ contains a @deviceId@ matching that of the present client, then this request must include "push device authentication":#push-device-authentication. A test should exist that deletes a @clientId@ and @deviceId@ channel subscription separately and both succeed, and then also deletes a subscription that does not exist but still succeeds +*** @(RSH1c5)@ @#removeWhere(params)@ issues a @DELETE@ request to "/push/channelSubscriptions":/rest-api#delete-channel-subscription and deletes the matching channel subscriptions provided in @params@. A test should exist that deletes channel subscriptions by @clientId@ and by @deviceId@ separately, then additionally issues a delete for subscriptions with no matching params and checks the operation still succeeds. * @(RSH2)@ The following should only apply to platforms that support receiving push notifications: ** @(RSH2a)@ @Push#activate@ sends a @CalledActivate@ event to "the state machine":#RSH3. ** @(RSH2b)@ @Push#deactivate@ sends a @CalledDeactivate@ event to "the state machine":#RSH3. @@ -1807,4 +1807,4 @@ h2(#old-specs). Old specs Use the version navigation to view older versions. References to diffs for each version are maintained below: -* v0.8 deprecated in Jan 2017. "View 0.8 → 1.0 changes":https://github.com/ably/docs/blob/master/content/client-lib-development-guide/versions/features-0-8__1-0.diff \ No newline at end of file +* v0.8 deprecated in Jan 2017. "View 0.8 → 1.0 changes":https://github.com/ably/docs/blob/master/content/client-lib-development-guide/versions/features-0-8__1-0.diff From 13fcdb85d09f302a754065593f4cd8c5e1a74ab8 Mon Sep 17 00:00:00 2001 From: Cesare Rocchi Date: Fri, 23 Mar 2018 09:52:57 +0100 Subject: [PATCH 28/38] Relabel push channels section with RSH7 --- .../features.textile | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/content/client-lib-development-guide/features.textile b/content/client-lib-development-guide/features.textile index fa3aba73c0..3867136ebd 100644 --- a/content/client-lib-development-guide/features.textile +++ b/content/client-lib-development-guide/features.textile @@ -914,22 +914,22 @@ h2(#push-notifications). Push notifications h3(#push-channels). Push channels -* @(RSH4)@ In platforms that support receiving push notifications, @RestChannel@ and @RealtimeChannel@ have an additional @push@ field, as an object with the following interface: -** @(RSH4a)@ @#subscribeDevice()@ -*** @(RSH4a1)@ Fails if the @LocalDevice@ doesn't have an @deviceIdentityToken@, ie. it isn't registered yet. -*** @(RSH4a2)@ Performs a POST request to "/push/channelSubscriptions":/rest-api#post-channel-subscription with the device's @id@ and the channel name. -*** @(RSH4a3)@ The request must include "push device authentication":#push-device-authentication -** @(RSH4b)@ @#subscribeClient()@ -*** @(RSH4b1)@ Fails if the @LocalDevice@ doesn't have a @clientId@. -*** @(RSH4b2)@ Performs a POST request to "/push/channelSubscriptions":/rest-api#post-channel-subscription with the device's @clientId@ and the channel name. -** @(RSH4c)@ @#unsubscribeDevice()@ -*** @(RSH4c1)@ Fails if the @LocalDevice@ doesn't have a @deviceIdentityToken@, ie. it isn't registered yet. -*** @(RSH4c2)@ Performs a DELETE request to "/push/channelSubscriptions":/rest-api#delete-channel-subscription with the device's @id@ and the channel name. -*** @(RSH4c3)@ The request must include "push device authentication":#push-device-authentication -** @(RSH4d)@ @#unsubscribeClient()@ -*** @(RSH4d1)@ Fails if the @LocalDevice@ doesn't have a @clientId@. -*** @(RSH4d2)@ Performs a DELETE request to "/push/channelSubscriptions":/rest-api#delete-channel-subscription with the device's @clientId@ and the channel name. -** @(RSH4e)@ @#listSubscriptions(params)@ performs a GET request to "/push/channelSubscriptions":/rest-api#list-channel-subscriptions and returns a paginated result with @PushChannelSubscription@ objects filtered by the provided params, the channel name, the device ID, and the client ID if it exists, as supported by the REST API. A @concatFilters@ param needs to be set to @true@ as well. +* @(RSH7)@ In platforms that support receiving push notifications, @RestChannel@ and @RealtimeChannel@ have an additional @push@ field, as an object with the following interface: +** @(RSH7a)@ @#subscribeDevice()@ +*** @(RSH7a1)@ Fails if the @LocalDevice@ doesn't have an @deviceIdentityToken@, ie. it isn't registered yet. +*** @(RSH7a2)@ Performs a POST request to "/push/channelSubscriptions":/rest-api#post-channel-subscription with the device's @id@ and the channel name. +*** @(RSH7a3)@ The request must include "push device authentication":#push-device-authentication +** @(RSH7b)@ @#subscribeClient()@ +*** @(RSH7b1)@ Fails if the @LocalDevice@ doesn't have a @clientId@. +*** @(RSH7b2)@ Performs a POST request to "/push/channelSubscriptions":/rest-api#post-channel-subscription with the device's @clientId@ and the channel name. +** @(RSH7c)@ @#unsubscribeDevice()@ +*** @(RSH7c1)@ Fails if the @LocalDevice@ doesn't have a @deviceIdentityToken@, ie. it isn't registered yet. +*** @(RSH7c2)@ Performs a DELETE request to "/push/channelSubscriptions":/rest-api#delete-channel-subscription with the device's @id@ and the channel name. +*** @(RSH7c3)@ The request must include "push device authentication":#push-device-authentication +** @(RSH7d)@ @#unsubscribeClient()@ +*** @(RSH7d1)@ Fails if the @LocalDevice@ doesn't have a @clientId@. +*** @(RSH7d2)@ Performs a DELETE request to "/push/channelSubscriptions":/rest-api#delete-channel-subscription with the device's @clientId@ and the channel name. +** @(RSH7e)@ @#listSubscriptions(params)@ performs a GET request to "/push/channelSubscriptions":/rest-api#list-channel-subscriptions and returns a paginated result with @PushChannelSubscription@ objects filtered by the provided params, the channel name, the device ID, and the client ID if it exists, as supported by the REST API. A @concatFilters@ param needs to be set to @true@ as well. h3(#activation-state-machine). Activation state machine From 567a1eee67b3f0e751ca42139ef506e900ba5c5b Mon Sep 17 00:00:00 2001 From: Cesare Rocchi Date: Fri, 23 Mar 2018 09:53:57 +0100 Subject: [PATCH 29/38] Reorder sections by label --- .../features.textile | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/content/client-lib-development-guide/features.textile b/content/client-lib-development-guide/features.textile index 3867136ebd..9898b0528d 100644 --- a/content/client-lib-development-guide/features.textile +++ b/content/client-lib-development-guide/features.textile @@ -912,25 +912,6 @@ h2(#push-notifications). Push notifications ** @(RSH2b)@ @Push#deactivate@ sends a @CalledDeactivate@ event to "the state machine":#RSH3. ** @(RSH2c)@ Whenever, from the platform's APIs, details for sending push notifications to the local device (e. g. GCM's registration token) is received, a @GotPushDeviceDetails@ event is sent to "the state machine":#RSH3. -h3(#push-channels). Push channels - -* @(RSH7)@ In platforms that support receiving push notifications, @RestChannel@ and @RealtimeChannel@ have an additional @push@ field, as an object with the following interface: -** @(RSH7a)@ @#subscribeDevice()@ -*** @(RSH7a1)@ Fails if the @LocalDevice@ doesn't have an @deviceIdentityToken@, ie. it isn't registered yet. -*** @(RSH7a2)@ Performs a POST request to "/push/channelSubscriptions":/rest-api#post-channel-subscription with the device's @id@ and the channel name. -*** @(RSH7a3)@ The request must include "push device authentication":#push-device-authentication -** @(RSH7b)@ @#subscribeClient()@ -*** @(RSH7b1)@ Fails if the @LocalDevice@ doesn't have a @clientId@. -*** @(RSH7b2)@ Performs a POST request to "/push/channelSubscriptions":/rest-api#post-channel-subscription with the device's @clientId@ and the channel name. -** @(RSH7c)@ @#unsubscribeDevice()@ -*** @(RSH7c1)@ Fails if the @LocalDevice@ doesn't have a @deviceIdentityToken@, ie. it isn't registered yet. -*** @(RSH7c2)@ Performs a DELETE request to "/push/channelSubscriptions":/rest-api#delete-channel-subscription with the device's @id@ and the channel name. -*** @(RSH7c3)@ The request must include "push device authentication":#push-device-authentication -** @(RSH7d)@ @#unsubscribeClient()@ -*** @(RSH7d1)@ Fails if the @LocalDevice@ doesn't have a @clientId@. -*** @(RSH7d2)@ Performs a DELETE request to "/push/channelSubscriptions":/rest-api#delete-channel-subscription with the device's @clientId@ and the channel name. -** @(RSH7e)@ @#listSubscriptions(params)@ performs a GET request to "/push/channelSubscriptions":/rest-api#list-channel-subscriptions and returns a paginated result with @PushChannelSubscription@ objects filtered by the provided params, the channel name, the device ID, and the client ID if it exists, as supported by the REST API. A @concatFilters@ param needs to be set to @true@ as well. - h3(#activation-state-machine). Activation state machine * @(RSH3)@ In platforms that support receiving push notifications, in order to connect the device's push features with Ably's, the library must perform the process described in the following abstract state machine. While this process should be implemented in whatever way better fits the concrete platform, it should be taken into account that its lifetime is that of the _app_ that runs it, which outlives that of the @Rest@ instance or (typically) the process running the app. This typically forces some kind of on-disk storage to which the state machine's state must be persisted, so that it can be recovered later by new instances and processes running the app triggered by external events. @@ -1013,6 +994,25 @@ h3(#push-device-authentication). Push device authentication ** @(RSH6a)@ If a device has completed activation and has a @deviceIdentityToken@ then push device authentication is performed for a request by adding an @X-Ably-DeviceIdentityToken@ request header whose value is the @deviceIdentityToken@. ** @(RSH6b)@ If a device has not completed but has a @deviceSecret@ then push device authentication is performed for a request by adding an @X-Ably-DeviceSecret@ request header whose value is the @deviceSecret@. +h3(#push-channels). Push channels + +* @(RSH7)@ In platforms that support receiving push notifications, @RestChannel@ and @RealtimeChannel@ have an additional @push@ field, as an object with the following interface: +** @(RSH7a)@ @#subscribeDevice()@ +*** @(RSH7a1)@ Fails if the @LocalDevice@ doesn't have an @deviceIdentityToken@, ie. it isn't registered yet. +*** @(RSH7a2)@ Performs a POST request to "/push/channelSubscriptions":/rest-api#post-channel-subscription with the device's @id@ and the channel name. +*** @(RSH7a3)@ The request must include "push device authentication":#push-device-authentication +** @(RSH7b)@ @#subscribeClient()@ +*** @(RSH7b1)@ Fails if the @LocalDevice@ doesn't have a @clientId@. +*** @(RSH7b2)@ Performs a POST request to "/push/channelSubscriptions":/rest-api#post-channel-subscription with the device's @clientId@ and the channel name. +** @(RSH7c)@ @#unsubscribeDevice()@ +*** @(RSH7c1)@ Fails if the @LocalDevice@ doesn't have a @deviceIdentityToken@, ie. it isn't registered yet. +*** @(RSH7c2)@ Performs a DELETE request to "/push/channelSubscriptions":/rest-api#delete-channel-subscription with the device's @id@ and the channel name. +*** @(RSH7c3)@ The request must include "push device authentication":#push-device-authentication +** @(RSH7d)@ @#unsubscribeClient()@ +*** @(RSH7d1)@ Fails if the @LocalDevice@ doesn't have a @clientId@. +*** @(RSH7d2)@ Performs a DELETE request to "/push/channelSubscriptions":/rest-api#delete-channel-subscription with the device's @clientId@ and the channel name. +** @(RSH7e)@ @#listSubscriptions(params)@ performs a GET request to "/push/channelSubscriptions":/rest-api#list-channel-subscriptions and returns a paginated result with @PushChannelSubscription@ objects filtered by the provided params, the channel name, the device ID, and the client ID if it exists, as supported by the REST API. A @concatFilters@ param needs to be set to @true@ as well. + h2. Types h3(#types). Data types From 46e780a1635948e6d2e3c88a90632748050feefb Mon Sep 17 00:00:00 2001 From: Paddy Byers Date: Wed, 28 Mar 2018 21:40:38 +0100 Subject: [PATCH 30/38] Correct description of device id generation --- content/client-lib-development-guide/features.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/client-lib-development-guide/features.textile b/content/client-lib-development-guide/features.textile index 9898b0528d..77dc465e67 100644 --- a/content/client-lib-development-guide/features.textile +++ b/content/client-lib-development-guide/features.textile @@ -921,7 +921,7 @@ h3(#activation-state-machine). Activation state machine **** @(RSH3a1b)@ Transitions to @NotActivated@. *** @(RSH3a2)@ On event @CalledActivate@: **** @(RSH3a2a)@ If the local device has @deviceIdentityToken@, enqueues a @CalledActivate@ event and transitions to @WaitingForNewPushDeviceDetails@ and #RSH3a2b onwards don't apply. -**** @(RSH3a2b)@ If the local device does not have @id@ and @deviceSecret@, both are generated using random data with sufficient entropy and creating a 32-byte digest (eg using sha256) and encoding that digest with base64. The local @DeviceDetails@ is updated with the resulting @deviceId@ and @deviceSecret@. +**** @(RSH3a2b)@ If the local device does not have @id@ and @deviceSecret@, both are generated locally. The @id@ must be a "ulid":https://github.com/ulid/spec or similar globally-unique identifier. The @deviceSecret@ must be created using secure random data with sufficient entropy to generate a digest of at least 32 bytes (eg using sha256) and encoding that digest with base64. The local @DeviceDetails@ is updated with the resulting @deviceId@ and @deviceSecret@. **** @(RSH3a2c)@ If the local device has the necessary push details (registration token, etc.), sends a @GotPushDeviceDetails@ event. **** @(RSH3a2d)@ Transitions to @WaitingForPushDeviceDetails@. *** @(RSH3a3)@ On event @GotPushDeviceDetails@: From d585dd0374d267955a60040fc279900f75f42fe0 Mon Sep 17 00:00:00 2001 From: Paddy Byers Date: Thu, 29 Mar 2018 10:13:11 +0100 Subject: [PATCH 31/38] Minor spec corrections --- content/client-lib-development-guide/features.textile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/client-lib-development-guide/features.textile b/content/client-lib-development-guide/features.textile index 61e7784f6d..8bd5275663 100644 --- a/content/client-lib-development-guide/features.textile +++ b/content/client-lib-development-guide/features.textile @@ -565,7 +565,7 @@ h3(#realtime-presence). Presence ** @(RTP8b)@ Optionally a callback can be provided that is called for both success or failure to enter ** @(RTP8c)@ A @PRESENCE ProtocolMessage@ with a @PresenceMessage@ with the action @ENTER@ is sent to the Ably service. The @clientId@ attribute of the @PresenceMessage@ must not be present. Entering without an explicit @PresenceMessage#clientId@, implicitly uses the @clientId@ for the current connection ** @(RTP8d)@ Implicitly attaches the @Channel@ if the channel is in the @INITIALIZED@ state. However, if the channel is in the @DETACHED@ or @FAILED@ state, the @enter@ request results in an error -** @(RTP8e)@ Optional data and/or extras can be included when entering a channel that will be encoded / decoded as with normal messages. A test should exist to ensure data and extras used with enter are encoded & decoded correctly. Also, when data and/or extras is provided when entering, but neither data nor extras are provided when leaving, the data attribute should be emitted in the @LEAVE@ event for this client +** @(RTP8e)@ Optional data and/or extras can be included when entering a channel that will be encoded and decoded as with normal messages. A test should exist to ensure data and extras used with enter are encoded & decoded correctly. Also, when data and/or extras is provided when entering, but neither data nor extras are provided when leaving, the data attribute should be emitted in the @LEAVE@ event for this client ** @(RTP8f)@ If the client library is authenticated but unidentified (i.e. @clientId@ is a wildcard @'*'@ or client is anonymous), the @enter@ request results in an error immediately ** @(RTP8g)@ If the channel is @DETACHED@ or @FAILED@, the @enter@ request results in an error immediately ** @(RTP8h)@ If the Ably service determines that the client does not have required presence permission, a @NACK@ is sent to the client resulting in an error @@ -1014,7 +1014,7 @@ h4. PresenceMessage ** @(TP3d)@ @connectionId@ string. If a presence message received from Ably does not contain a @connectionId@, it should be set to the @connectionId@ of the encapsulating @ProtocolMessage@ ** @(TP3e)@ @data@ string, buffer or JSON-encodable object or array ** @(TP3f)@ @encoding@ string -** @(TP3i)@ @extras@ JSON-encodable object, used to contain any arbitrary key value pairs which may also contain other primitive JSON types, JSON-encodable objects or JSON-encodable arrays. The @extras@ field is provided to contain message metadata and/or ancillary payloads in support of specific functionality. For 1.1 no specific functionality is specified for @extras@ in presence messages; the processing all members is undefined +** @(TP3i)@ @extras@ JSON-encodable object, used to contain any arbitrary key value pairs which may also contain other primitive JSON types, JSON-encodable objects or JSON-encodable arrays. The @extras@ field is provided to contain message metadata and/or ancillary payloads in support of specific functionality. For 1.1 no specific functionality is specified for @extras@ in presence messages; the processing of all members is undefined ** @(TP3g)@ @timestamp@ time in milliseconds since epoch. If a presence message received from Ably does not contain a @timestamp@, it should be set to the @timestamp@ of the encapsulating @ProtocolMessage@ ** @(TP3h)@ @memberKey@ string function that combines the @connectionId@ and @clientId@ ensuring multiple connected clients with the same clientId are uniquely identifiable * @(TP4)@ @fromEncoded@ and @fromEncodedArray@ are alternative constructors that take an (already deserialized) @PresenceMessage@-like object (or array of such objects), and optionally a @channelOptions@, and return a @PresenceMessage@ (or array of such @PresenceMessages@) that's decoded and decrypted as specified in @RSL6@, using the cipher in the @channelOptions@ if the message is encrypted, with any residual transforms (ones that the library cannot decode or decrypt) left in the @encoding@ property per @RSL6b@. This is intended for users receiving messages other than from a REST or Realtime channel (for example, from a queue), to avoid them having to parse the @encoding@ string themselves. From 459bfd002995315597e11bb726cd4168e5fbdb30 Mon Sep 17 00:00:00 2001 From: Paddy Byers Date: Thu, 29 Mar 2018 10:15:33 +0100 Subject: [PATCH 32/38] Minor spec corrections --- content/client-lib-development-guide/features.textile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/client-lib-development-guide/features.textile b/content/client-lib-development-guide/features.textile index 73f9e659b7..2ab7b472ee 100644 --- a/content/client-lib-development-guide/features.textile +++ b/content/client-lib-development-guide/features.textile @@ -271,7 +271,7 @@ h3(#rest-encryption). Encryption ** @(RSE2b)@ Returns (or calls back with, if the language cryptographic randomness primitives are blocking or async) the key as a binary (e.g. a byte array, depending on the language) h3(#rest-compatibility). Forwards compatibility -* @(RSF1)@ THe library must apply the "robustness principle":https://en.wikipedia.org/wiki/Robustness_principle in its processing of requests and responses with the Ably system. In particular, deserialisation of Messages and related types, and associated enums, must be tolerant to unrecognised attributes or enum values. Such unrecognised values must be ignored. +* @(RSF1)@ The library must apply the "robustness principle":https://en.wikipedia.org/wiki/Robustness_principle in its processing of requests and responses with the Ably system. In particular, deserialisation of Messages and related types, and associated enums, must be tolerant to unrecognised attributes or enum values. Such unrecognised values must be ignored. h2(#realtime). Realtime client library features @@ -625,7 +625,7 @@ h3(#eventemitter). EventEmitter mixin / interface ** @(RTE6a)@ The set of listeners called by @emit@ must not change over the course of the @emit@. That is: If a listener being called by @emit@ registers another listener, that second listener should not be called by that invocation of @emit@ (even if it would have been called had it already been present); and if a listener being called by @emit@ removes other listeners, but those other listeners would otherwise have been called during that @emit@ invocation, they should still be called. Tests should exist for both adding and removing. See "https://goo.gl/OVTtjO":https://goo.gl/OVTtjO h3(#realtime-compatibility). Forwards compatibility -* @(RTF1)@ THe library must apply the "robustness principle":https://en.wikipedia.org/wiki/Robustness_principle in its processing of requests and responses with the Ably system. In particular, deserialisation of ProtocolMessages and related types, and associated enums, must be tolerant to unrecognised attributes or enum values. Such unrecognised values must be ignored. +* @(RTF1)@ The library must apply the "robustness principle":https://en.wikipedia.org/wiki/Robustness_principle in its processing of requests and responses with the Ably system. In particular, deserialisation of ProtocolMessages and related types, and associated enums, must be tolerant to unrecognised attributes or enum values. Such unrecognised values must be ignored. h2(#state-conditions-and-operations). State conditions and operations From 206e801e914b097efc5353ce38e57bcd13fbc444 Mon Sep 17 00:00:00 2001 From: Paddy Byers Date: Wed, 25 Apr 2018 13:11:26 +0100 Subject: [PATCH 33/38] Add requirements for JWT support --- .../features.textile | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/content/client-lib-development-guide/features.textile b/content/client-lib-development-guide/features.textile index 103efd165d..203a046d18 100644 --- a/content/client-lib-development-guide/features.textile +++ b/content/client-lib-development-guide/features.textile @@ -56,7 +56,10 @@ h2(#rest). REST client library h3(#restclient). RestClient -* @(RSC1)@ The constructor accepts either an API key, a token string, or a set of "@ClientOptions@":#options. If invalid arguments are provided such as a no API key, no token and no means to create a token, then this will result in an error +* @(RSC1)@ The constructor accepts a set of "@ClientOptions@":#options or, in languages that support overloaded constructors, a string which may be a token string or an API key. +** @(RSC1a)@ If a single string argument is supplied when constructing the library then the library must determine whether this is a key or a token by checking for the presence of the ':' (colon) delimiter present in an API key. Any other string must be treated as a token string. +** @(RSC1b)@ If invalid arguments are provided such as a no API key, no token and no means to create a token, then this will result in an error +** @(RSC1c)@ Tests must exist that in each overloaded library constructor the library correctly determines an API key to be a key, and each type of token string is determined to be a token. * @(RSC2)@ The logger by default outputs to @STDOUT@ (or other logging medium as appropriate to the platform) and the log level is set to warning * @(RSC3)@ The log level can be changed * @(RSC4)@ A custom logger can be provided in the constructor @@ -111,8 +114,9 @@ h3(#rest-auth). Auth * @(RSA2)@ "Basic Auth":https://en.wikipedia.org/wiki/Basic_access_authentication is the default authentication scheme if an API key exists * @(RSA3)@ Token Auth: ** @(RSA3a)@ Can be used over HTTP or HTTPs -** @(RSA3b)@ For REST requests, the token string is Base64 encoded and used in the @Authorization: Bearer@ header -** @(RSA3c)@ For Realtime connections, the querystring param @accessToken@ is appended to the URL endpoint +** @(RSA3b)@ For REST requests, the token string is optionally Base64-encoded and used in the @Authorization: Bearer@ header +** @(RSA3c)@ For Realtime websocket connections, the querystring param @accessToken@ is appended to the URL endpoint +** @(RSC3d)@ A test must exist that each type of token string is correctly passed in requests (ie according to @(RSA3b)@ and @(RSA3c)@) * @(RSA4)@ Token Auth is used if @useTokenAuth@ is set to true, or if @useTokenAuth@ is unspecified and any one of the following conditions are met: a @clientId@ is specified; @authUrl@ or @authCallback@ is provided; an explicit @token@ or @TokenDetails@ is provided ** @(RSA4a)@ When a @token@ or @tokenDetails@ is used to instance the library, and no means to renew the token is provided (either an API key, @authCallback@ or @authUrl@), if the server responds with a token error (401 HTTP status code and an Ably error value @40140 <= code < 40150@), then the client library should indicate an error, not retry the request and in the case of the realtime library, transition the connection to the @FAILED@ state ** @(RSA4b)@ When the client does have a means to renew the token automatically, and the token has expired or the server has responded with a token error (@statusCode@ value of 401 and error @code@ value in the range @40140 <= code < 40150@), then the client should automatically make a single attempt to reissue the token and resend the request using the new token. If the token creation failed or the subsequent request with the new token failed due to a token error, then the request should result in an error @@ -122,7 +126,7 @@ h3(#rest-auth). Auth *** @(RSA4c3)@If the connection is @CONNECTED@, then the connection should remain @CONNECTED@ ** @(RSA4d)@ If a request by a realtime client to an @authUrl@ results in an HTTP 403 response, or any of an @authUrl@ request, an @authCallback@, or a request to Ably to exchange a @TokenRequest@ for a @TokenDetails@ result in an @ErrorInfo@ with @statusCode@ 403, then the client library should transition to the @FAILED@ state, with an @ErrorInfo@ (with @code@ @80019@, @statusCode@ 403, and @cause@ set to the underlying cause) emitted with the state change and set as the connection @errorReason@ ** @(RSA4e)@ If in the course of a REST request (or explicit call to @requestToken@) an attempt to authenticate using @authUrl@ or @authCallback@ fails due to a timeout, network error, a token in an invalid format (per "RSA4e":#RSA4e), or some other auth error condition other than an explicit @ErrorInfo@ from Ably, the request should result in an error with @code@ 40170, @statusCode@ 401, and a suitable error message -** @(RSA4f)@ The following conditions imply that the token is in an invalid format: the @authUrl@ response content type is neither @text/plain@ nor @application/json@; the object passed by @authCallback@ is neither a @String@, @JsonObject@, @TokenRequest@ object, nor @TokenDetails@ object; the text token string is greater than 384 bytes; the JSON stringified @JsonObject@, @TokenRequest@ or @TokenDetails@ is greater than 128kb. +** @(RSA4f)@ The following conditions imply that the token is in an invalid format: the @authUrl@ response content type is neither @text/plain@ nor @application/json@; the object passed by @authCallback@ is neither a @String@, @JsonObject@, @TokenRequest@ object, nor @TokenDetails@ object; the token string or the JSON stringified @JsonObject@, @TokenRequest@ or @TokenDetails@ is greater than 128kb. ** @(RSA4g)@ If multiple @authOptions@ are used to initialize the library, the preference ordering among them is identical to @Auth#authorize@, defined in @RSA10e@ * @(RSA14)@ If Token Auth is selected, yet a token is not provided and there is no means to generate a token, then this will result in an error. For example, if only the option @useTokenAuth@ is specified, and a @key@ is not provided, then the client library is unable to authenticate or issue a token * @(RSA15)@ If Token Auth is selected and @clientId@ has been set in the @ClientOptions@ when the library was instanced: @@ -149,7 +153,7 @@ h3(#rest-auth). Auth ** @(RSA8e)@ Method signature is @requestToken(TokenParams, AuthOptions)@. @TokenParams@ and @AuthOptions@ are optional. When @TokenParams@ or @AuthOptions@ are provided, the values of each attribute are not merged with the configured client library defaults, but instead replace all corresponding values, even when @null@. If the object arguments are omitted, the client library configured defaults are used ** @(RSA8a)@ Implicitly creates a @TokenRequest@ if required, and requests a token from Ably if required. Returns a @TokenDetails@ object ** @(RSA8b)@ Supports all @TokenParams@ in the function arguments, which override defaults for @Client@ @Auth@ -** @(RSA8c)@ When @authUrl@ option is set, it will query the provided URL to obtain a @TokenRequest@ or the token itself (either a token string or a @TokenDetails@). The query is performed using the given URL using the HTTP method in @authMethod@, headers (from @authHeaders@) and supplementary params (from @authParams@). The token retrieved is assumed by the library to be a token string if the response has @Content-Type@ @"text/plain"@, or taken to be a @TokenRequest@ or @TokenDetails@ object if the response has @Content-Type@ @"application/json"@. @authMethod@ can be either @GET@ or @POST@, or if not specified, will default to @GET@. It can be quite difficult to add test coverage for these scenarios - as such, we have developed a simple echo server that can be used in your tests, see the "ably-js authUrl echo tests":https://github.com/ably/ably-js/commit/e77b2c6c197bc71f3d27084fe54524724a00bc92 +** @(RSA8c)@ When @authUrl@ option is set, it will query the provided URL to obtain a @TokenRequest@ or the token itself (either a token string or a @TokenDetails@). The query is performed using the given URL using the HTTP method in @authMethod@, headers (from @authHeaders@) and supplementary params (from @authParams@). The token retrieved is assumed by the library to be a token string if the response has @Content-Type@ @"text/plain"@ or @"application/jwt"@, or taken to be a @TokenRequest@ or @TokenDetails@ object if the response has @Content-Type@ @"application/json"@. @authMethod@ can be either @GET@ or @POST@, or if not specified, will default to @GET@. It can be quite difficult to add test coverage for these scenarios - as such, we have developed a simple echo server that can be used in your tests, see the "ably-js authUrl echo tests":https://github.com/ably/ably-js/commit/e77b2c6c197bc71f3d27084fe54524724a00bc92 *** @(RSA8c1)@ @TokenParams@ and any configured @authParams@ and @authHeaders@ are always sent to the @authUrl@ as follows: **** @(RSA8c1a)@ When the @authMethod@ is @GET@ or unspecified, the @TokenParams@ and @authParams@ are merged and appended to the URL as query string params, and the @authHeaders@ are sent as HTTP headers **** @(RSA8c1b)@ When the @authMethod@ is @POST@, the @TokenParams@ and @authParams@ are merged and sent form-encoded in the body of the @POST@ request, and the @authHeaders@ are sent as HTTP headers @@ -162,6 +166,7 @@ h3(#rest-auth). Auth *** @(RSA8f2)@ Request a token with a @null@ value @clientId@, authenticate a client with the token, publish a message with an explicit @clientId@ value, and ensure that the message is rejected *** @(RSA8f3)@ Request a token with a wildcard @'*'@ value @clientId@, authenticate a client with the token, publish a message without an explicit @clientId@, and ensure the message published does not have a @clientId@. Check that @Auth#clientId@ is a string with value @'*'@ *** @(RSA8f4)@ Request a token with a wildcard @'*'@ value @clientId@, authenticate a client with the token, publish a message with an explicit @clientId@ value, and ensure that the message published has the provided @clientId@ +** @(RSA8g)@ Tests must exist to verify both the @authCallback@ and @authURL@ mechanisms where the returned token string value is a JWT token string and an Ably token string. * @(RSA9)@ @Auth#createTokenRequest@ function: ** @(RSA9h)@ Method signature is @createTokenRequest(TokenParams, AuthOptions)@. @TokenParams@ and @AuthOptions@ are optional.When @TokenParams@ or @AuthOptions@ are provided, the values of each attribute are not merged with the configured client library defaults, but instead replace all corresponding values, even when @null@. If the object arguments are omitted, the client library configured defaults are used ** @(RSA9a)@ Returns a signed @TokenRequest@ object that can be used to obtain a token from Ably. This is useful for servers that can create a @TokenRequest@ signed with the API key without communicating with Ably directly. The @TokenRequest@ can then be passed to a designated client that is then responsible for communicating with Ably and requesting a token for authentication from that @TokenRequest@ @@ -307,6 +312,7 @@ h3(#realtimeclient). RealtimeClient *** @(RTC8a1)@ If the authentication token change is successful, then Ably will send a new @CONNECTED@ @ProtocolMessage@. The @connectionDetails@ provided in the @CONNECTED@ @ProtocolMessage@ must override any existing defaults, see "RTN21":#RTN21. The @Connection@ should emit an @UPDATE@ event per @RTN24@. A test should exist that performs an upgrade of capabilities without any loss of continuity or connectivity during the upgrade process. Another test should exist where the capabilities are downgraded resulting in Ably sending an @ERROR@ @ProtocolMessage@ with a @channel@ property, causing the channel to enter the @FAILED@ state. That test must assert that the channel becomes failed soon after the token update and the reason is included in the channel state change event *** @(RTC8a2)@ If the authentication token change fails, then Ably will send an @ERROR@ @ProtocolMessage@ triggering the connection to transition to the @FAILED@ state. A test should exist for a token change that fails (such as sending a new token with an incompatible @clientId@) *** @(RTC8a3)@ The @authorize@ call should be indicated as completed with the new token or error only once realtime has responded to the @AUTH@ with either a @CONNECTED@ or @ERROR@ respectively. +*** @(RTC8a4)@ Tests must exist that verify the inband reauthorization mechanism described in @RTC8a@ succeeds in the cases of an Ably token string and a JWT token string. ** @(RTC8b)@ If the connection is in the @CONNECTING@ state when @auth#authorize@ is called, all current connection attempts should be halted, and after obtaining a new token the library should immediately initiate a connection attempt using the new token *** @(RTC8b1)@ The @authorize@ call should be indicated as completed with the new token once the connection has moved to the @CONNECTED@ state, or with an error if the connection instead moves to the @FAILED@, @SUSPENDED@, or @CLOSED@ states ** @(RTC8c)@ If the connection is in the @DISCONNECTED@, @SUSPENDED@, @FAILED@, or @CLOSED@ state when @auth#authorize@ is called, after obtaining a token the library should move to the @CONNECTING@ state and initiate a connection attempt using the new token, and @RTC8b1@ applies. @@ -1127,6 +1133,12 @@ h4. TokenDetails * @(TD6)@ @TokenDetails#clientId@ attribute contains the @clientId@ assigned to the token. If @clientId@ is @null@ or omitted, then the token is prohibited from assuming a @clientId@ in any operations, however if @clientId@ is a wildcard string @'*'@, then the token is permitted to assume any @clientId@. Any other string value for @clientId@ implies that the @clientId@ is both enforced and assumed for all operations for this token * @(TD7)@ @TokenDetails::fromJson@ static factory method that accepts either a @JsonObject@ or a string (which should be parsed as a JSON string), and returns a new @TokenDetails@. Statically typed languages (that expect the @authCallback@ to result in a typed @TokenDetails@ object, rather than, say, a hashmap) must implement this; others may at their discretion. +h4. Token string + +* @(TN1)@ There is no @TokenString@ type but in this specification "token string" refers to a string containing a token for authentication with Ably. With the exception of "RSC1a":#RSC1a and "RSA4f":#RSA4f, token strings are handled opaquely by the library, and any use of token strings in the API and library must support any type of token string. +* @(TN2)@ A token string (referred to in this specification as an "Ably token string") may be obtained as the @TokenDetails#token@ attribute of a @TokenDetails@ obtained in response to a request to the "REST requestToken endpoint":/rest-api/#request-token +* @(TN3)@ A token string (referred to in this specification as a "JWT token string") may be a "JSON Web Token":https://tools.ietf.org/html/rfc7519 satisfying "the Ably requirements for JWTs":https://fill_in_the_url. + h4. AuthDetails * @(AD1)@ @AuthDetails@ is a type used with an @AUTH@ protocol messages to send authentication details From 74cc4c5f7ec00eb3943b55a2e5063fe7b3b65545 Mon Sep 17 00:00:00 2001 From: Paddy Byers Date: Thu, 26 Apr 2018 14:43:47 +0100 Subject: [PATCH 34/38] Minor corrections following feedback --- content/client-lib-development-guide/features.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/client-lib-development-guide/features.textile b/content/client-lib-development-guide/features.textile index 203a046d18..4de86e16e4 100644 --- a/content/client-lib-development-guide/features.textile +++ b/content/client-lib-development-guide/features.textile @@ -58,7 +58,7 @@ h3(#restclient). RestClient * @(RSC1)@ The constructor accepts a set of "@ClientOptions@":#options or, in languages that support overloaded constructors, a string which may be a token string or an API key. ** @(RSC1a)@ If a single string argument is supplied when constructing the library then the library must determine whether this is a key or a token by checking for the presence of the ':' (colon) delimiter present in an API key. Any other string must be treated as a token string. -** @(RSC1b)@ If invalid arguments are provided such as a no API key, no token and no means to create a token, then this will result in an error +** @(RSC1b)@ If invalid arguments are provided such as no API key, no token and no means to create a token, then this will result in an error with error code 40000 and an informative message. ** @(RSC1c)@ Tests must exist that in each overloaded library constructor the library correctly determines an API key to be a key, and each type of token string is determined to be a token. * @(RSC2)@ The logger by default outputs to @STDOUT@ (or other logging medium as appropriate to the platform) and the log level is set to warning * @(RSC3)@ The log level can be changed From 7e97612a9db1d238d466d7d7c607e064620aebc2 Mon Sep 17 00:00:00 2001 From: Cesare Date: Mon, 28 May 2018 21:23:46 +0200 Subject: [PATCH 35/38] Add missing URL for JWT --- content/client-lib-development-guide/features.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/client-lib-development-guide/features.textile b/content/client-lib-development-guide/features.textile index 4de86e16e4..55d5a3b420 100644 --- a/content/client-lib-development-guide/features.textile +++ b/content/client-lib-development-guide/features.textile @@ -1137,7 +1137,7 @@ h4. Token string * @(TN1)@ There is no @TokenString@ type but in this specification "token string" refers to a string containing a token for authentication with Ably. With the exception of "RSC1a":#RSC1a and "RSA4f":#RSA4f, token strings are handled opaquely by the library, and any use of token strings in the API and library must support any type of token string. * @(TN2)@ A token string (referred to in this specification as an "Ably token string") may be obtained as the @TokenDetails#token@ attribute of a @TokenDetails@ obtained in response to a request to the "REST requestToken endpoint":/rest-api/#request-token -* @(TN3)@ A token string (referred to in this specification as a "JWT token string") may be a "JSON Web Token":https://tools.ietf.org/html/rfc7519 satisfying "the Ably requirements for JWTs":https://fill_in_the_url. +* @(TN3)@ A token string (referred to in this specification as a "JWT token string") may be a "JSON Web Token":https://tools.ietf.org/html/rfc7519 satisfying "the Ably requirements for JWTs":https://www.ably.io/documentation/general/authentication#ably-jwt. h4. AuthDetails From a5abc73ff3453348cd3f2bcd4855bad011a236a5 Mon Sep 17 00:00:00 2001 From: Paddy Byers Date: Sat, 2 Jun 2018 15:58:36 +0100 Subject: [PATCH 36/38] Add 40106 error code for no credentials; clarify possibly authURL response content types --- content/client-lib-development-guide/features.textile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/client-lib-development-guide/features.textile b/content/client-lib-development-guide/features.textile index 55d5a3b420..bfe7e98f53 100644 --- a/content/client-lib-development-guide/features.textile +++ b/content/client-lib-development-guide/features.textile @@ -58,7 +58,7 @@ h3(#restclient). RestClient * @(RSC1)@ The constructor accepts a set of "@ClientOptions@":#options or, in languages that support overloaded constructors, a string which may be a token string or an API key. ** @(RSC1a)@ If a single string argument is supplied when constructing the library then the library must determine whether this is a key or a token by checking for the presence of the ':' (colon) delimiter present in an API key. Any other string must be treated as a token string. -** @(RSC1b)@ If invalid arguments are provided such as no API key, no token and no means to create a token, then this will result in an error with error code 40000 and an informative message. +** @(RSC1b)@ If invalid arguments are provided such as no API key, no token and no means to create a token, then this will result in an error with error code 40106 and an informative message. ** @(RSC1c)@ Tests must exist that in each overloaded library constructor the library correctly determines an API key to be a key, and each type of token string is determined to be a token. * @(RSC2)@ The logger by default outputs to @STDOUT@ (or other logging medium as appropriate to the platform) and the log level is set to warning * @(RSC3)@ The log level can be changed @@ -126,7 +126,7 @@ h3(#rest-auth). Auth *** @(RSA4c3)@If the connection is @CONNECTED@, then the connection should remain @CONNECTED@ ** @(RSA4d)@ If a request by a realtime client to an @authUrl@ results in an HTTP 403 response, or any of an @authUrl@ request, an @authCallback@, or a request to Ably to exchange a @TokenRequest@ for a @TokenDetails@ result in an @ErrorInfo@ with @statusCode@ 403, then the client library should transition to the @FAILED@ state, with an @ErrorInfo@ (with @code@ @80019@, @statusCode@ 403, and @cause@ set to the underlying cause) emitted with the state change and set as the connection @errorReason@ ** @(RSA4e)@ If in the course of a REST request (or explicit call to @requestToken@) an attempt to authenticate using @authUrl@ or @authCallback@ fails due to a timeout, network error, a token in an invalid format (per "RSA4e":#RSA4e), or some other auth error condition other than an explicit @ErrorInfo@ from Ably, the request should result in an error with @code@ 40170, @statusCode@ 401, and a suitable error message -** @(RSA4f)@ The following conditions imply that the token is in an invalid format: the @authUrl@ response content type is neither @text/plain@ nor @application/json@; the object passed by @authCallback@ is neither a @String@, @JsonObject@, @TokenRequest@ object, nor @TokenDetails@ object; the token string or the JSON stringified @JsonObject@, @TokenRequest@ or @TokenDetails@ is greater than 128kb. +** @(RSA4f)@ The following conditions imply that the token is in an invalid format: the @authUrl@ response content type is not one of @text/plain@, @application/json@ or @application/jwt@; the object passed by @authCallback@ is neither a @String@, @JsonObject@, @TokenRequest@ object, nor @TokenDetails@ object; the token string or the JSON stringified @JsonObject@, @TokenRequest@ or @TokenDetails@ is greater than 128kb. ** @(RSA4g)@ If multiple @authOptions@ are used to initialize the library, the preference ordering among them is identical to @Auth#authorize@, defined in @RSA10e@ * @(RSA14)@ If Token Auth is selected, yet a token is not provided and there is no means to generate a token, then this will result in an error. For example, if only the option @useTokenAuth@ is specified, and a @key@ is not provided, then the client library is unable to authenticate or issue a token * @(RSA15)@ If Token Auth is selected and @clientId@ has been set in the @ClientOptions@ when the library was instanced: From 6c021da4c8f6fff7a03bb1d5b420e5eeffe2a69e Mon Sep 17 00:00:00 2001 From: Cesare Date: Mon, 4 Jun 2018 10:43:21 +0200 Subject: [PATCH 37/38] RSC3d -> RSA3d --- content/client-lib-development-guide/features.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/client-lib-development-guide/features.textile b/content/client-lib-development-guide/features.textile index bfe7e98f53..ea433dd0c5 100644 --- a/content/client-lib-development-guide/features.textile +++ b/content/client-lib-development-guide/features.textile @@ -116,7 +116,7 @@ h3(#rest-auth). Auth ** @(RSA3a)@ Can be used over HTTP or HTTPs ** @(RSA3b)@ For REST requests, the token string is optionally Base64-encoded and used in the @Authorization: Bearer@ header ** @(RSA3c)@ For Realtime websocket connections, the querystring param @accessToken@ is appended to the URL endpoint -** @(RSC3d)@ A test must exist that each type of token string is correctly passed in requests (ie according to @(RSA3b)@ and @(RSA3c)@) +** @(RSA3d)@ A test must exist that each type of token string is correctly passed in requests (ie according to @(RSA3b)@ and @(RSA3c)@) * @(RSA4)@ Token Auth is used if @useTokenAuth@ is set to true, or if @useTokenAuth@ is unspecified and any one of the following conditions are met: a @clientId@ is specified; @authUrl@ or @authCallback@ is provided; an explicit @token@ or @TokenDetails@ is provided ** @(RSA4a)@ When a @token@ or @tokenDetails@ is used to instance the library, and no means to renew the token is provided (either an API key, @authCallback@ or @authUrl@), if the server responds with a token error (401 HTTP status code and an Ably error value @40140 <= code < 40150@), then the client library should indicate an error, not retry the request and in the case of the realtime library, transition the connection to the @FAILED@ state ** @(RSA4b)@ When the client does have a means to renew the token automatically, and the token has expired or the server has responded with a token error (@statusCode@ value of 401 and error @code@ value in the range @40140 <= code < 40150@), then the client should automatically make a single attempt to reissue the token and resend the request using the new token. If the token creation failed or the subsequent request with the new token failed due to a token error, then the request should result in an error From 49a57c0a836615718676c159172f6d30229a9518 Mon Sep 17 00:00:00 2001 From: Paddy Byers Date: Tue, 5 Jun 2018 12:21:00 +0100 Subject: [PATCH 38/38] RTN15h: token errors, when the library can renew, result in DISCONNECTED, not FAILED --- content/client-lib-development-guide/features.textile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/content/client-lib-development-guide/features.textile b/content/client-lib-development-guide/features.textile index ea433dd0c5..1ff7c1cac4 100644 --- a/content/client-lib-development-guide/features.textile +++ b/content/client-lib-development-guide/features.textile @@ -385,7 +385,9 @@ h3(#realtime-connection). Connection ** @(RTN14e)@ Once the connection state has been in the @DISCONNECTED@ state for more than the "default @connectionStateTtl@":#defaults, the state will change to @SUSPENDED@ and be emitted with the @reason@, and the @Connection#errorReason@ will be updated. In this state, a new connection attempt will be made periodically as specified within @suspendedRetryTimeout@ of @ClientOptions@ ** @(RTN14f)@ The connection will remain in the @SUSPENDED@ state indefinitely, whilst periodically attempting to reestablish a connection * @(RTN15)@ @Connection@ failures once @CONNECTED@: -** @(RTN15h)@ If a @DISCONNECTED@ message is received from Ably, then that transport will subsequently be closed by Ably. If the @DISCONNECTED@ message contains a token error (@statusCode@ value of 401 and error @code@ value in the range @40140 <= code < 40150@), if the token is renewable, a single attempt to create a new token should be made and a new connection attempt initiated using the new token. If the token creation fails, the next connection attempt fails due to a token error, or the token was not renewable, the connection will transition to the @FAILED@ state and the @Connection#errorReason@ will be set +** @(RTN15h)@ If a @DISCONNECTED@ message is received from Ably, then that transport will subsequently be closed by Ably +*** @(RTN15h1)@ If the @DISCONNECTED@ message contains a token error (@statusCode@ value of 401 and error @code@ value in the range @40140 <= code < 40150@) and the library does not have a means to renew the token, the connection will transition to the @FAILED@ state and the @Connection#errorReason@ will be set +*** @(RTN15h2)@ If the @DISCONNECTED@ message contains a token error (@statusCode@ value of 401 and error @code@ value in the range @40140 <= code < 40150@) and the library has the means to renew the token, a single attempt to create a new token should be made and a new connection attempt initiated using the new token. If the token creation fails or the next connection attempt fails due to a token error, the connection will transition to the @DISCONNECTED@ state and the @Connection#errorReason@ will be set. ** @(RTN15i)@ If an @ERROR@ @ProtocolMessage@ is received, this indicates a fatal error in the connection. The server will close the transport immediately after. The client should transition to the @FAILED@ state triggering all attached channels to transition to the @FAILED@ state as well. Additionally the @Connection#errorReason@ should be set with the error received from Ably ** @(RTN15a)@ If a @Connection@ transport is disconnected unexpectedly or if a token expires, then the @Connection@ manager will immediately attempt to reconnect and restore the connection state. Connection state recovery is provided by the Ably service and ensures that whilst the client is disconnected, all events are queued and channel state is retained on the Ably servers. When a new connection is made with the correct connection recovery key, the client is able to catch up by receiving the queued @ProtocolMessages@ from Ably. ** @(RTN15g)@ Connection state is only maintained serverside for a brief period, given by the @connectionStateTtl@ in the @connectionDetails@, see "CD2f":#CD2f. If a client has been disconnected for longer than the @connectionStateTtl@, it should not attempt to resume. Instead, it should clear the local connection state, and any connection attempts should be made as for a fresh connection