Macaroon API

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.

Error responses will be structured according to the Problem Details for HTTP APIs specification.

In summary this means that they will include the following fields in the body:

type:

string

A URI reference [RFC3986] that identifies the problem type.

title:

string

A short, human-readable summary of the problem type.

detail:

string

An human readable explanation specific to this occurrence of the problem.

status:

number

The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.

Example

{
  "type": "devportal:v1:request-invalid",
  "title": "Invalid request.",
  "detail": "Expected permissions to be a list. Got: package_access",
  "status": 400
}

Reference

Request a macaroon

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
  • 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
Response JSON Object:
 
  • macaroon (string) – the serialized macaroon
Status Codes:

The current values are valid permissions

  • edit_account
  • package_access
  • package_manage
  • package_upload
  • package_upload_request
  • package_purchase
  • modify_account_key

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"}

This endpoint is normally used without authentication, and returns a macaroon with a third-party caveat requiring a discharge from SSO. However, when requesting a package_upload macaroon, a discharged package_upload_request macaroon may be supplied as authorization for the request; in that case, that is taken as authorization to issue package_upload macaroons with more specific constraints that do not require a discharge from SSO.

Usage

Request a macaroon for editing an account.

Request:

POST /dev/api/acl/ HTTP/1.1
Host: myapps.developer.ubuntu.com
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"
}

Request a generic macaroon for retrieving package metadata.

Request:

POST /dev/api/acl/ HTTP/1.1
Host: myapps.developer.ubuntu.com
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"
}

Request a macaroon for managing a specific package

Request:

POST /dev/api/acl/ HTTP/1.1
Host: myapps.developer.ubuntu.com
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"
}

Request a macaroon for uploading a specific package

Request:

POST /dev/api/acl/ HTTP/1.1
Host: myapps.developer.ubuntu.com
Content-Type: application/json

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

Response:

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

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

Request a macaroon for purchasing a package identified by snap_id

Request:

POST /dev/api/acl/ HTTP/1.1
Host: myapps.developer.ubuntu.com
Content-Type: application/json

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

Response:

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

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

Request a macaroon for modifying account keys.

Request:

POST /dev/api/acl/ HTTP/1.1
Host: myapps.developer.ubuntu.com
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"
}

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: myapps.developer.ubuntu.com
Content-Type: application/json

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

Response:

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

{
  "type": "devportal:v1:macaroon-permission-invalid",
  "title": "Invalid permission for macaroon.",
  "detail": "Permission is not valid: package_delete",
  "status": 400,
  "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: myapps.developer.ubuntu.com
Content-Type: application/json

{
  "permissions": "package_access",
}

Response:

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

{
  "type": "devportal:v1:request-invalid",
  "title": "Invalid request.",
  "detail": "Expected permissions to be a list. Got: package_access",
  "status": 400,
}

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 http_uri, http_method and authorization
Response JSON Object:
 
  • allowed (boolean) – the macaroon is valid and the request should be allowed
  • refresh_required (boolean) – the macaroon is expired and needs to be refreshed
  • account – a json object representing details about the account
  • last_auth – a timestamp (string formatted per ISO8601) indicating when the user last authenticated
  • permissions – a list of permissions associated with the macaroon
Status Codes:

The auth_data object should contain:

  • http_uri: the full URI of the request being verified
  • http_method: the http method used for the request being verified
  • 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

Usage

Submit the request to verify a macaroon used for authorization.

Request

POST /dev/api/acl/verify/ HTTP/1.1
Host: myapps.developer.ubuntu.com
Content-Type: application/json

{
  "auth_data": {
    "http_uri": "http://example.org",
    "http_method": "POST",
    "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,
  "refresh_required": false,
  "account": {
    "email": "user@example.org",
    "displayname": "The User",
    "openid": "oid1234",
    "verified": true
  },
  "last_auth": "2016-05-26T12:53:23Z",
  "permissions": ["package_access"]
}

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,
  "refresh_required": true,
  "account": null,
  "last_auth": null,
  "permissions": 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,
  "refresh_required": false,
  "account": null,
  "last_auth": null,
  "permissions": 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

{
  "type": "devportal:v1:request-invalid",
  "title": "Invalid request.",
  "detail": "Missing expected \"auth_data\" parameter.",
  "status": 400
}