Macaroons

Introduction

The Macaroon API provides means to request and verify macaroons that can be used to authenticate requests against the Store.

The Macaroon API is exposed at the following base URLs:

Response Format

JSON will be returned in all responses from the API, including errors.

Errors

The Macaroon API uses conventional HTTP response codes to indicate success or failure of an API request.

In general, codes in the 2xx range indicate success, codes in the 4xx range indicate an error that resulted from the provided information (e.g. a required parameter was missing) and codes in the 5xx range indicate an error with our servers.

Here is detailed the format for API responses that end in error.

This applies to all the 4xx responses, but also to some 5xx ones (if possible, the client should be prepared to handle 5xx responses with no informational body). Note that this structure format does not apply to 2xx and 3xx responses, as those are not errors.

Format

An error response body will contain the following field:

  • error_list: a list of one or several items (never empty), each item described by…

    • message: a text in English describing the error that happened, ready to show to the user.

    • code: a short (but representative) string indicating concisely the error; it’s aimed for clients to take specific actions and react to the problem. See below for the list of existing codes.

Additionally and for backwards compatibility reasons, some other fields may be present as well, but are considered deprecated and will be removed in the near future.

No status or success indication is returned inside the response body, the client should react properly to the received HTTP return code according to its well established semantics.

Codes

These are the codes used in the response and their meanings:

  • assertion-creation-failed: the request asked for an assertion to be created, but this failed.

  • bad-request: there is a problem in the structure of the request.

  • feature-disabled: the request cannot be fulfilled because the feature is currently disabled on the server side.

  • internal-server-error: some unexpected problem server side.

  • invalid-field: the field received in the request has format problems (e.g.: must be a number and it’s not) or value problems (e.g.: it contains an account-key-request assertion which does not match the user’s account).

  • macaroon-permission-required: the macaroon authorization is missing in the received request or not enough for it to be fulfilled.

  • missing-field: the request received must include a field which was not present.

  • user-not-ready: the user is not ready to issue the received request; normally some actions would need to be done in the user account before repeating the request.

Examples

A simple error:

{
  "error_list": [{
     "message": "The 'foo' field must not be empty",
     "code": "invalid-field"
  }]
}

A multiple error:

{
  "error_list": [{
     "message": "The 'foo' field is required",
     "code": "missing-field"
  }, {
     "message": "The 'bar' field is required",
     "code": "missing-field"
  }, {
     "message": "The 'baz' field must not be empty",
     "code": "invalid-field"
  }]
}

Reference

Request a macaroon

This endpoint is normally used without authentication, and returns a macaroon with a third-party caveat requiring a discharge from https://login.ubuntu.com.

POST /dev/api/acl/
Request JSON Object
  • permissions – a list of permissions to include in the macaroon

  • channels – a list of channels to restrict the macaroon to, allows wildcards in the fnmatch format

  • packages – a list of packages to restrict the macaroon to

  • expires – a timestamp (string formatted per ISO8601) after which` the macaroon should not be valid (UTC only values are allowed)

Response JSON Object
  • macaroon (string) – the serialized macaroon

Status Codes

The current values are valid permissions

  • edit_account allows updating account information

  • modify_account_key allows modifying, registering new or revoking existing account keys.

  • package_access allows downloads (installs) of any snap revisions as well as reading current details accessible by the authenticated principal.

  • package_register allows registering new snap names.

  • package_push allows pushing new snap revisions, restricted to the packages values, if specified.

  • package_release allows releasing existing snap revisions, restricted to the `channels and packages values, if specified.

  • package_update allows updating details on existing snaps, restricted to the packages values, if specified.

  • package_metrics allows access to snap metrics, restricted to the packages values, if specified.

  • package_manage allows managing snap developers (collaborators), restricted to packages values, if specified.

  • package_upload is equivalent to package_register, package_push, package_release, package_update and package_metrics set of permissions altogether.

  • package_upload_request: allows new name registrations and requesting a discharged package_upload macaroon, see Note below.

When specifying a list of packages to restrict the macaroon, those can be indicated in two ways:

  1. by name/series: each item in the list should be a json dict like {"name": "the-name", "series": "16"}

  2. by snap_id: each item in the list should be a json dict like {"snap_id": "some-snap-id-1234"}

Note

When requesting a package_upload macaroon, a discharged package_upload_request macaroon may be supplied as authorisation for the request; in that case, that is taken as authorisation to issue package_upload macaroons with more specific constraints that do not require a discharge from https://login.ubuntu.com.

Expiry

Using the following permissions will, by default, cause the macaroon to have an expiry date of one year from the date of the request. Including any of these in the request will create this expiry date, no matter which other permissions are also requested. Shorter durations can be created using the expires parameter.

  • edit_account

  • modify_account_key

  • package_access

  • store_admin

  • store_review

For any other mix of permissions, the expires timestamp can be omitted, creating a macaroon which will never expire. Passing a timestamp of more than one year is also valid in this situation.

Usage

Read-only access

Request a generic macaroon for retrieving details from all snaps accessible by the context principal.

Request:

POST /dev/api/acl/ HTTP/1.1
Host: dashboard.snapcraft.io
Content-Type: application/json

{
  "permissions": ["package_access"],
}

Response:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
  "macaroon": "the-serialized-macaroon-data"
}
Restricted upload rights

Request a macaroon for uploading new revisions of a specific snap and releasing only to the “edge” channel.

Request:

POST /dev/api/acl/ HTTP/1.1
Host: dashboard.snapcraft.io
Content-Type: application/json

{
  "permissions": ["package_upload"],
  "channels": ["edge"],
  "packages": [{"name": "foo", "series": "16"}]
}

Response:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
  "macaroon": "the-serialized-macaroon-data"
}
Managing snap collaborators

Request a macaroon for managing collaborators of a specific snap.

Request:

POST /dev/api/acl/ HTTP/1.1
Host: dashboard.snapcraft.io
Content-Type: application/json

{
  "permissions": ["package_manage"],
  "packages": [{"snap_id": "foo-id-1234"}]
}

Response:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
  "macaroon": "the-serialized-macaroon-data"
}
Editing account information

Request a macaroon for editing an account information.

Request:

POST /dev/api/acl/ HTTP/1.1
Host: dashboard.snapcraft.io
Content-Type: application/json

{
  "permissions": ["edit_account"],
}

Response:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
  "macaroon": "the-serialized-macaroon-data"
}
Register/Revoke keys

Request a macaroon for modifying account keys.

Request:

POST /dev/api/acl/ HTTP/1.1
Host: dashboard.snapcraft.io
Content-Type: application/json

{
  "permissions": ["modify_account_key"],
}

Response:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
  "macaroon": "the-serialized-macaroon-data"
}
Error messages

If the request was made for an invalid permission to be included, the server will respond with something like:

Request:

POST /dev/api/acl/ HTTP/1.1
Host: dashboard.snapcraft.io
Content-Type: application/json

{
  "permissions": ["package_delete"],
}

Response:

HTTP/1.1 400 BAD REQUEST
Content-Type: application/json; charset=utf-8

{
  "error_list": [
    {
      "message": "Permission is not valid: package_delete",
      "code": "invalid-request",
      "extra": {"permission": "package_delete"}
    }
  ]
}

If the request is invalid in some other way, the server will respond accordingly. For example:

Request:

POST /dev/api/acl/ HTTP/1.1
Host: dashboard.snapcraft.io
Content-Type: application/json

{
  "permissions": "package_access",
}

Response:

HTTP/1.1 400 BAD REQUEST
Content-Type: application/json; charset=utf-8

{
  "error_list": [
    {
      "message": "Expected permissions to be a list. Got: package_access",
      "code": "invalid-request"
    }
  ]
}

If a macaroon was requested for a particular package that doesn’t exist, the server will respond with a generic 404 response.

Response:

HTTP/1.1 404 NOT FOUND

Validate a discharged macaroon

POST /dev/api/acl/verify/
Request JSON Object
  • auth_data – a json object containing authorization

Response JSON Object
  • allowed (boolean) – the macaroon is valid and the request should be allowed

  • device_refresh_required (boolean) – a device macaroon is expired and needs to be refreshed (obsolete: device macaroons are no longer issued by this service)

  • refresh_required (boolean) – the macaroon is expired and needs to be refreshed

  • account – a json object representing details about the account

  • device – a json object representing details about the device (obsolete: device macaroons are no longer issued by this service)

  • last_auth – a timestamp (string formatted per ISO8601) indicating when the user last authenticated

  • permissions – a list of permissions associated with the macaroon

  • snap_ids – a list of snap identifiers to which the macaroon is constrained

  • channels – a list of channel names to which the macaroon is constrained

Status Codes

The auth_data object should contain one of:

  • authorization: the value of the Authorization header of the request being verified

The account data, if not null will be a json object containing the fields:

  • email: the primary email of the account

  • displayname: the name used to identify the account

  • openid: the openid identifier for the account

  • verified: whether the account has been verified

The device data, if not null will be a json object containing the fields:

  • brand: the brand of the device

  • model: the model of the device

  • serial: the serial number of the device

Usage

Submit the request to verify a macaroon used for authorization.

Request

POST /dev/api/acl/verify/ HTTP/1.1
Host: dashboard.snapcraft.io
Content-Type: application/json

{
  "auth_data": {
    "authorization": "Macaroon root=root-macaroon-data, discharge=discharge-macaroon-data"
  }
}

Response:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
  "allowed": true,
  "device_refresh_required": false,
  "refresh_required": false,
  "account": {
    "email": "user@example.org",
    "displayname": "The User",
    "openid": "oid1234",
    "verified": true
  },
  "device": null,
  "last_auth": "2016-05-26T12:53:23Z",
  "permissions": ["package_access"],
  "snap_ids": null,
  "channels": null
}

If the macaroon was expired the following response would be returned instead

Response:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
  "allowed": false,
  "device_refresh_required": false,
  "refresh_required": true,
  "account": null,
  "device": null,
  "last_auth": null,
  "permissions": null,
  "snap_ids": null,
  "channels": null
}

Alternatively, if the macaroon was invalid in any way, the server would reply with

Response:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
  "allowed": false,
  "device_refresh_required": false,
  "refresh_required": false,
  "account": null,
  "device": null,
  "last_auth": null,
  "permissions": null,
  "snap_ids": null,
  "channels": null
}

In case the original request was invalid, for example if the auth_data parameter was missed, the server would return the following response

Response:

HTTP/1.1 400 BAD REQUEST
Content-Type: application/json; charset=utf-8

{
  "error_list": [
    {
      "message": "Missing expected \"auth_data\" parameter.",
      "code": "invalid-request",
    }
  ]
}