/apps

APIs that provide details about an app.

The specific app's shortcode.

For all these APIs, the core asserts that:

  • this parameter is the same as the consuming app's shortcode.
get

Get details about an app, e.g. its SSO setup. Normally an app would have this information at its fingertips, but this is useful for proxies and helpers that work on behalf of an app, but were not coded by the app developer.

get

Get a list of shortCodes of every tenant that has this app installed.

A tenant's shortcode.

For all APIs beneath here, the core asserts that:

  • the app has been installed by the tenant

Apps that are SSO-protected (i.e., SPs in SAML lingo) require their own SAML metadata

get

Get the metadata for this app (the SP) as installed at this tenant.

The core asserts that:

  • the app is linked to any principal type (i.e., it uses SSO)

/devs

APIs that provide details about a developer or their objects.

Any app can call these APIs.

A specific developer

APIs that provide details about a developer's APIs.

get

Get a list of URI templates for every API this developer has defined.

get

Get details for a specific API that this developer has defined.

/OAuthPublicKey

The OAuth public key is used by apps to verify that incoming OAuth tokens were indeed issued by TAS.

get

Get the current public key used by TASA to sign OAuth tokens.

/patchSets

post

This endpoint behaves exactly as per /tenants/{tenant}/patchSets, except that it creates a tenant rather than applying patches to an existing tenant.

The core asserts that:

  • the app is marked as "storeFront"
post

This endpoint behaves exactly as per /tenants/{tenant}/patchSets/{launchKey}, except that it works against a tenant-creating patchSet, rather than a patchSet that is modifying an existing tenant.

The core asserts that:

  • the app is marked as "storeFront"

/ping

A ping is the simplest call an app can make against the TAS core

get

Always return http 200. Useful for testing app is working OK (has correct secret key etc.).

/routes

About routes

Routes reflect a possible point to point API call between two apps on behalf of a tenant. They are usages:

  • of a tenant API
  • as produced in a specific way (source of truth or not)
  • by a consuming app
  • installed at a tenant
  • on a producing app (currently, always the same as the consuming tenant)
  • installed at a tenant

To consume a tenant API, the consuming app must first fetch a route in order to locate the actual endpoint (where the producer's server sits on the internet).

Routes are also useful to the producer of an API, e.g. to validate the methods (POST, GET, etc.) that the consumer is allowed to call.

Remote mounting

Remote mounting is a possible future feature of TAS. It is not currently implemented.

With remote mounting, apps can produce APIs (SoT or non-SoT) by remote mounting a specified tenant's SoT production of the same API.

For example:

  • tenant acme has installed a number of job board apps
  • the tenant wants each job board app to produce (non-SoT) the /categories API
  • so that acme's other apps can query each job board's unique category setup, build mappings, etc.
  • the job board "green jobs" provides such a job board app
  • that app produces (non-SoT) /categories
  • however it does so by remote mounting the same API at the central green jobs tenant
  • inside the green jobs tenant, the app "corestuff" produces (SoT) the /categories API
  • therefore corestuff finishes up producing the API when consumed by some other app at the acme tenant

TAS detects and disallows loops.

Consuming tenant; the tenant with the app installed that is consuming the API

Consuming app; the app consuming the API

get

Used by tenant API consumers to fetch the route(s) from the consumer to each producer of a specific API.

The core returns 403 (Forbidden) if:

  • the app making the core API call is the consuming app (i.e., matches {CA})
  • the consuming app is installed at {CT}
  • the consuming app has declared that it consumes the given API, in the given way (SoT or not).

The core returns 200, with an empty array for producingAppInstalls, if there are no routes.

Producing tenant: the tenant with the app installed that is producing the API

Consuming app: the app consuming the API

get

Used by tenant API consumers to fetch the route(s) from each consumer to the producer of a specific API.

The core returns 403 (Forbidden) if:

  • the producing app is installed at {PT}
  • the producing app has declared that it produces the given API, in the given way (SoT or not).

The core returns 200, with an empty array for consumingAppInstalls, if there are no routes.

Consuming tenant: the tenant with the app installed that is consuming the API

Consuming app: the app consuming the API

get

DEPRECATED: use /producers/{CT}/{CA} instead.

Used by tenant API consumers to fetch the route(s) from the consumer to each producer of a specific API.

The core returns 403 (Forbidden) if:

  • the app making the core API call is the consuming app (i.e., matches {CA})
  • the consuming app is installed at {CT}
  • the consuming app has declared that it consumes the given API, in the given way (SoT or not).

The core returns 200, with an empty array for producingAppInstalls, if there are no routes.

Producing tenant: the tenant with the app installed that is producing the API

Producing app: the app producing the API

get

Used by tenant API producers to verify that the consumer is allowed to consume this API.

The core will assert that:

  • the app making the core API call is the producing app (i.e., matches {PA})
  • the producing app is installed at {PT}
  • the consuming app is installed at {CT}
  • the consuming app has declared that it consumes the given API
  • the producing app has declared that it produces the given API
  • the consuming app has declared that it consumes the given API in the same way that the producer has declared that it produces it (SoT or not).

Since both consuming and producing apps are specified, there can only be one or zero routes in the response.

/samlIdPMetadata

Apps that are SSO-protected (i.e., SPs in SAML lingo) require the SAML metadata for the TAS core proxy that acts as a facade in front of all of the identity providers that all of the tenants has set up. In other words there is a single unit of IdP metadata that can be used by all apps at all tenants.

get

description: Get the metadata for the IdP proxy.

The core asserts that:

  • the app is linked to any principal type (i.e., it uses SSO)

/tenants

A tenant is a customer within the multi-tenanted environment of Talent App Store.

APIs concerned with a specific tenant. For all these APIs, the core asserts that:

  • the calling app is installed at the tenant
get

Get details of the tenant.

For all APIs beneath here, the core asserts that:

  • the app is marked as "inspector"
get

Get details about a specific app installed at the tenant, such as its name and developer. Typically used by apps that control or enable other apps.

get

Get the status of the tenant, including the current state and security generation. Normally apps would use this information when restarting to determine whether to flush cached OAuth tokens and routes.

The APIs deal with SAML assertions that were issued to the consuming app, or to an app that directly or indirectly consumes an on-behalf API at this app (i.e., an upstream app, perhaps a web site that the user logs into).

TAS acts as a SAML proxy, and captures and stores assertions in an internal database, partitioned by principal type, prior to handing them off to the app (SP).

For all APIs below here, the core asserts that:

  • the app is linked to a principal type
  • the assertion was issued to this application, as installed at this tenant
get

Fetch a complete SAML assertion via its database key.

get

The APIs below here fetch a SAML assertion via an on-behalf OAuth token. Such a token contains the database key of the user logged in upstream. This can be used by apps that produce on-behalf APIs to learn more about the principal that the call is on behalf of.

For all APIs below here, the core asserts that:

  • the app is linked to a principal type
  • the token is correctly signed and has not expired
  • the token has this app as its consuming app
get

Fetch a SAML assertion via an on-behalf OAuth token.

get
post

Fetch the key of an incoming SAML assertion. Used by apps which include their own SAML SP layer (rather than sitting behind the proxy).

The request body contains the content of the assertion to search for.

The core asserts that:

  • the app is linked to a principal type
  • the assertion was issued to this application, as installed at this tenant

A reboot is the process of TAS performing an orderly shutdown and startup of all of the installed apps for a specific tenant whenever major changes are made to the tenant.

The first step is to prepare the reboot using one of the rebootPrepares endpoints, which happens as a result of a tenant's actions in the storefront app's UI. Specifically;

  • when a tenant clicks "install" or "uninstall" on an app, the storefront app calls POST /tenants/{tenant}/rebootPrepares/patchSet, resulting in a reboot with events like these.
  • when a tenant clicks "reboot" on their tenant, the storefront app calls POST /tenants/{tenant}/rebootPrepares/noop, resulting in a reboot with events like these.
  • when a tenant clicks "flush OAuth tokens" on their tenant, the storefront app calls POST /tenants/{tenant}/rebootPrepares/OAuthFlush, resulting in a reboot with events like these.

The response body from any of the /rebootPrepares endpoints is an unguessable unique "launch key" which is then used with POST /tenants/{tenant}/reboots/{launchKey}.

For all of these endpoints, the core asserts that:

  • the app is marked as "storeFront".
post

A storefront app calls this endpoint to prepare a reboot that will increment the tenant's security generation.

Called when a tenant admin person clicks "Flush tokens" in the storefront.

This effectively invalidates all OAuth tokens for APIs consumed or produced by this tenant (since apps should always check for changed security generation). This is used by a tenant to mitigate an attack where the attacker has obtained an OAuth token.

post

A storefront app calls this endpoint to prepare a reboot that will change the installed apps, e.g. to reflect the tenant clicking install on an app. The endpoint validates the attached patch document.

Called when a tenant admin person clicks to install or uninstall apps in the storefront.

The core asserts that:

  • the lists of apps do not contain any app that does not exist
  • the list of apps to uninstall does not contain any app not installed
  • the list of apps to install does not contain any app already installed
post

A storefront app calls this endpoint to prepare a reboot that does nothing else.

This provides a way to retry on failures that have locked up the tenant, e.g. a bad response from one of the orchestration APIs.

Called when tenant admin person clicks "Reboot" in the storefront.

About reboots

A reboot is the process of TAS performing an orderly shutdown and startup of all of the installed apps for a specific tenant whenever major changes are made to the tenant.

To play its part correctly when a reboot happens, your app's control server should produce the reboot APIs. TAS then consumes the following APIs during the reboot.

  • /tenants/{}/preHalts
  • /tenants/{}/halts
  • /tenants/{}/preStarts
  • /tenants/{}/starts

As a reboot happens, TAS maintains a set of history events that the storefront app can use to feed a progress dialogue.

How the storefront launches a reboot

Once the storefront app has prepared a reboot by calling one of the POST /tenants/{tenant}/rebootPrepares endpoints, the the reboot does not start immediately. Instead, the APIs return a launch key.

To actually start a reboot, the storefront app (typically from within a web page's onClick handler of a "Continue" button) uses this launch key to launch the reboot.

As a reboot happens, TAS maintains the event history, and also a status for;

Order of events during reboot

During a reboot, the order of events is:

  • Halt all apps

    • TAS sets the tenant's status to "preHalting"
    • TAS calls /tenants/{}/preHalts on each installed app
    • When the last app returns 200, TAS sets the tenant's status to "preHalted"
    • TAS sets the tenant's status to "halting"
    • TAS calls /tenants/{}/halts on each installed app
    • When the last app returns 200, TAS sets the tenant's status to "halted"
  • TAS performs the actual operation, i.e. depending on which core-in API call the reboot is in response to:

    • for POST /tenants/{}/reboots
      • increment tenant.incarnation
    • for POST /tenants/{}/patchSets
      • uninstall all relevant apps (calling DELETE /tenants on each)
      • increment tenant.incarnation
      • increment tenant.securityGeneration (only if the patch set is making changes to the tenant's identity apps or assertion hooks)
      • install all relevant apps (calling POST /tenants on each)
      • routes are adjusted
    • for POST /tenants/{}/OAuthFlushes (i.e. the tenant clicked "Flush OAuth tokens")
      • increment tenant.securityGeneration
  • Start all apps

    • TAS sets the tenant's status to "preStarting"
    • TAS calls /tenants/{}/preStarts on each installed app
    • When the last app returns 200, TAS sets the tenant's status to "preStarted"
    • TAS sets the tenant's status to "starting"
    • TAS calls /tenants/{}/starts on each installed app
    • When the last app returns 200, TAS sets the tenant's status to "starting"

For all of these endpoints, the core asserts that:

  • the app is marked as "storeFront".
post

A storefront app's web page uses this API to launch a previously prepared reboot (if not already launched), and get the latest events and the current summary details.

IGNORE THE FOLLOWING (it will be removed shortly - for now the endpoint is a normal core API endpoint secured by HMAC, and typically the storefront app will proxy it so the access is same-domain and CORS is not needed). This endpoint is unauthenticated and has CORS headers that allow access from any domain (since the javascript calling it belongs to a storefront app which does not share the same domain as the tas core).

If the apply is already "complete" then this call will return immediately, with any events that match the filter, up to the maximum events per call (100).

Otherwise, the call blocks for .5 seconds to avoid thrashing, then returns as soon as at least one event is available that matches the filter, or the apply becomes complete (which would always be accompanied by an event). If no events are available after 60 seconds, the call returns indicating a timeout - the client will typically immediately make another call in a typical long-polling pattern, until the apply is marked as complete and the last event has been fetched.

The core asserts:

  • that no reboot is currently underway for the tenant
  • that the launch key has been previously created via a rebootPrepare call

/token

About OAuth tokens

API calls between apps (i.e. tenant API calls) are protected by OAuth access tokens, with TAS acting as the authorization server.

An OAuth token in TAS has this structure. An example token is shown here.

A single OAuth token is scoped by the combination of consumer, producer, API and SoT, and can be used to consume any allowable method on the API. Currently, the consumer and producer will always be at the same tenant.

The flow for consuming a tenant API is as follows;

  • The app consuming the API (the consumer) calls GET /routes to locate the actual endpoint (the producer)
  • The consumer checks its cache to see if it already has an OAuth token for the combination of consumer and producer and API and SoT
  • If not, it calls POST /token to obtain an OAuth token from the TAS core
  • The consumer makes the API call, passing the token in the Authorization header
  • The producer checks that the token is valid and being used correctly
  • For on-behalf tokens, the producer extracts the logs in user's identity
  • The producer produces the API

Rather than being passed as raw json, OAuth tokens are passed as a JWT (json web token; see http://jwt.io/). Because JWTs are self-contained and signed, producers can rely on their content without any network traffic to the authorization server (i.e. TAS core).

For more background on OAuth and tokens, see the [OAuth 2 spec(http://tools.ietf.org/html/rfc6749') and also Google's OAuth documentation for additional context.

On-behalf tokens

OAuth tokens may further be on-behalf, i.e. principal-aware, in which case they also identify some ultimate logged in end user who the API call is on behalf of, in the "sub" field.

Refresh tokens

Some apps need to continue making API calls on behalf of a user, even when the user is no longer logged in.

Any app that is protected via TAS SSO and is marked as "offlineCapable" (TODO: exact name) can use the urn:ietf:params:oauth:client-assertion-type:saml2-bearer grant type (which requires a SAML assertion), and add " offline" to the scope parameter in the call to POST /token to create both an on-behalf OAuth access token and an accompanying refresh token. Your app should store the refresh token in its own long-term storage, and can use the access token immediately.

The access token can be used immediately, and when it expires, the refresh token can be used to generate new access tokens even when the user is no longer logged in.

Note: you only need to use refresh tokens if the offline activity is on behalf of a specific user. Otherwise your app can just use the client_credentials grant type to request a new OAuth token as soon as the existing one expires.

Example:

POST /oauth2/v1/token HTTP/1.1
Host: core.talentappstore.com
Content-Type: application/x-www-form-urlencoded

grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Asaml2-bearer
scope=acme+jobboard+acme+ats+tas+%2fjobs%2f%7bjob%7d+true+offline
principal_type=candidate
assertion=kjh9786gJHGutvfUT765Kkjghf70978klhjhgIUYGkpoiygEciYnjOi...
Response:
{
  "access_token":"iuhy76YT785L0GDF23JH7kjhd",
  "expires_in":3600,
  "token_type":"Bearer",
  "refresh_token":"j089777bhkltyHTRFG78969KJH968d"
}

Internally, TAS stores a copy of refresh tokens that it has issued, so it can validate requests for new access tokens. These refresh tokens are stored keyed by:

  • principal type
  • saml key (i.e. a key for the specific SAML assertion that was passed in the "assertion" parameter user identifier (/E/N)
  • the SSO-protected app the user logged into (the API consuming app in this case)
  • consuming app install
  • producing app install

Your app should save refresh tokens in long-term storage and continue to use them as long as they remain valid. If your app requests another refresh token for the same combination of values above, a new refresh token will be issued and any previous refresh token will be revoked.

There is currently no support for refresh token revocation as per https://tools.ietf.org/html/rfc7009.

Verifying incoming OAuth tokens

To be secure, when an app receives an incoming tenant API call, it must:

  • Check that an incoming OAuth token was provided in the Authorization request header
  • Verify that the OAuth token was correctly signed by TAS core, and matches the public key from GET /OAuthKey.
  • Check that the token has not expired (exp field)
  • Check that it is the correct audience (to prevent a malicious app using a token for your app which was intended for another app), i.e.;
    • the "pt" (producing tenant) field matches the current tenant (e.g. as extracted from the Host header)
    • the "pa" (producing app) field matches the shortCode hard-coded into the app's code
  • Check, using "dev" and "api" fields, that the token is for this API (to prevent a malicious app calling an API on your app which it has not declared that it calls)
  • call GET /routes/{CT}/{CA}/{PT}/{PA} and verify that:
    • the API is being consumed (as per "sot" in the token) in a way (SoT or not) that the consumer has declared
    • whatever method is being used (GET, POST, etc.) is one of the subset of supported methods that the consumer declares that it consumes as Sot or not as appropriate (to prevent a malicious app declaring that it only uses GET but then performing a DELETE).

Because performing the checks above involves making TAS core API calls, the producing app should attempt to cache the results whenever possible.

For intra-tenant calls (i.e., where the producer and the consumer are the same), the producer can cache verifications as long as the "sgen" (security generation) parameter in the token matches the app's own stored value for sgen.

This works because sgen cannot be changed without bouncing the tenant, and every app is always informed when the tenant is bounced, so the producer always knows it has the latest value for sgen.

A typical cache implementation might:

  • store verification outcomes for intra-tenant calls as booleans in a key value store
  • key the outcomes by some synthesised string like /routes/acme/uberapply/acme/ats/tas/%2Fcategories/GET
  • flush the cache whenever sgen changes

For inter-tenant calls (i.e. where the consumer is a remote app likely installed at another tenant), the producer does not receive any notifications when the remote tenant is bounced. Therefore it has no way to maintain an up to date copy of the remote tenant's sgen value. This means that the producer must verify the route every time. This is not an issue until when/if TAS supports remote mounting.

post

Get a new access token, and optionally a refresh token.

The request body must be of content-type application/x-www-form-urlencoded, and contain values for grant_type and optionally other parameters, encoded as per https://tools.ietf.org/html/rfc6749#appendix-B. See also https://tools.ietf.org/html/rfc7522#section-4.

The core asserts that:

  • the calling app (as per the HMAC header) is the same as the consuming app (as per the scope parameter)
  • the consuming app is installed at the consuming tenant
  • the producing app is installed at the producing tenant
  • there exists a route between the consumer and producer, for this API, consumed as SoT or not as relevant (this prevents malicious apps probing for the presence of other apps)
  • when a SAML assertion is passed directly or by key, that the assertion was issued to this application and not any other
  • if the API (as per the scope parameter) is on-behalf (is linked to a principal type) that one of the following grant types is being used:
    • urn:ietf:params:oauth:client-assertion-type:saml2-bearer
    • urn:talentappstore.com:oauth:tas-inherited
    • refresh_token
  • if the API is not on-behalf (is linked to a principal type) that one of the following grant types is being used:
    • client_credentials

Every call to this endpoint results in a new access token being issued, with an expiry time set to one hour, or (as per http://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithSAML.html) to the time specified in the SAML assertion's SessionNotOnOrAfter value (if present), whichever is shorter.

If SessionNotOnOrAfter is in the past then token creation fails, and the app must somehow force the user to reauthenticate.

For efficiency consumers should cache the tokens returned by this endpoint until (a) the tenant's security generation number changes, or (b) the cached token is too close to expiry to serve the needs of the consumer.