Access Control Lists

Access Control Lists are rooted in the /v1/acls collection.

An ACL defines the applications’ data access restriction using the following three parameters:

  • permission: the value used to limit a client (user, group) access to resources.
  • identity: a client identity reference, e.g. a certain user, a group, an anonymous user or someone who is authenticated to a certain realm.
  • path: the location where to apply the restrictions. Examples of paths are: /, /myorg or /myorg/myproject
Authorization notes

When modifying ACLs, the caller must have acls/write permissions on the path where the ACLs are being modified or its ancestors.

When reading ACLs, the caller must have acls/read permissions on the path where the ACLs are being modified or its ancestors.

Please visit Authentication & authorization section to learn more about it.

Default permissions

When the service starts for the first time, it applies the default permissions to /. This gives all permissions to the anonymous user to enable setting up realms. It is recommended to replace these permissions once user has setup an authorization realm.

ACLs Hierarchy

It is important to know that ACLs are represented in a tree-like structure depending on their path. Imagine the following scenario: Resources tree

Each block is identified by a path that contains a list of permissions for a certain identity (identities are color code divided).

There is a special set of permissions which restrict the use of the ACLs API:

  • acls/read - an auth. token containing an identity with this permission is allowed to fetch a collection of ACL from any other identity.
  • acls/write - an auth. token containing an identity with this permission is allowed to perform the call to the following endpoints: create ACLs, replace ACLs, subtract ACLs, append ACLs and delete ACLs.

Those permissions need to be present in the current {path} where the API interaction occurs or in any parent path. In other words, they are inherited.

Let’s clarify this concept with an example from the previous diagram. identity 1 could call the create ACLs endpoint on any {path} while identity 2 could only call the same endpoint for any path child of /myorg (like /myorg/myproj). At the same time, identity 3 could not perform any of the write operations.

Create

This operation creates a collection of ACL on the provided path.

PUT /v1/acls/{path}
  {...}

…where {path} is the target location for the ACL collection.

The json payload contains the collection of ACL to set.

Example

Request
sourcecurl -XPUT \
  -H "Content-Type: application/json" \
  "http://localhost:8080/v1/acls/org1" -d \
  '{
  "acl": [
    {
      "permissions": [
        "projects/read"
      ],
      "identity": {
        "realm": "myrealm",
        "group": "a-group"
      }
    },
    {
      "permissions": [
        "projects/read",
        "projects/write"
      ],
      "identity": {
        "realm": "realm",
        "group": "some-group"
      }
    },
    {
      "permissions": [
        "acls/read",
        "acls/write"
      ],
      "identity": {
        "realm": "realm",
        "subject": "alice"
      }
    }
  ]
}'
Payload
source{
  "acl": [
    {
      "permissions": [
        "projects/read"
      ],
      "identity": {
        "realm": "myrealm",
        "group": "a-group"
      }
    },
    {
      "permissions": [
        "projects/read",
        "projects/write"
      ],
      "identity": {
        "realm": "myrealm",
        "group": "some-group"
      }
    },
    {
      "permissions": [
        "acls/read",
        "acls/write"
      ],
      "identity": {
        "realm": "myrealm",
        "subject": "alice"
      }
    }
  ]
}
Response
source{
  "@context": [
    "https://bluebrain.github.io/nexus/contexts/acls-metadata.json",
    "https://bluebrain.github.io/nexus/contexts/metadata.json"
  ],
  "@id": "http://localhost:8080/v1/acls/org1",
  "@type": "AccessControlList",
  "_constrainedBy": "https://bluebrain.github.io/nexus/schemas/acls.json",
  "_createdAt": "2021-05-11T11:03:06.071Z",
  "_createdBy": "http://localhost:8080/v1/anonymous",
  "_deprecated": false,
  "_path": "/org1",
  "_rev": 1,
  "_self": "http://localhost:8080/v1/acls/org1",
  "_updatedAt": "2021-05-11T11:03:06.071Z",
  "_updatedBy": "http://localhost:8080/v1/anonymous"
}

Replace

This operation overrides the collection of ACL on the provided path.

PUT /v1/acls/{path}?rev={previous_rev}
  {...}

…where:

  • {previous_rev}: Number - the last known revision for the ACL collection. Not required for replacing empty ACLs.
  • {path}: String - is the target location for the ACL collection.

The json payload contains the collection of ACL to set.

Example

Request
sourcecurl -XPUT \
-H "Content-Type: application/json" "http://localhost:8080/v1/acls/org1?rev=1" -d \
'{
  "acl": [
    {
      "permissions": [
        "projects/read"
      ],
      "identity": {
        "realm": "myrealm",
        "group": "a-group"
      }
    },
    {
      "permissions": [
        "projects/read",
        "projects/write"
      ],
      "identity": {
        "realm": "realm",
        "group": "some-group"
      }
    },
    {
      "permissions": [
        "acls/read",
        "acls/write"
      ],
      "identity": {
        "realm": "realm",
        "subject": "alice"
      }
    }
  ]
}'
Payload
source{
  "acl": [
    {
      "permissions": [
        "projects/read"
      ],
      "identity": {
        "realm": "myrealm",
        "group": "a-group"
      }
    },
    {
      "permissions": [
        "projects/read",
        "projects/write"
      ],
      "identity": {
        "realm": "myrealm",
        "group": "some-group"
      }
    },
    {
      "permissions": [
        "acls/read",
        "acls/write"
      ],
      "identity": {
        "realm": "myrealm",
        "subject": "alice"
      }
    }
  ]
}
Response
source{
  "@context": [
    "https://bluebrain.github.io/nexus/contexts/acls-metadata.json",
    "https://bluebrain.github.io/nexus/contexts/metadata.json"
  ],
  "@id": "http://localhost:8080/v1/acls/org1",
  "@type": "AccessControlList",
  "_constrainedBy": "https://bluebrain.github.io/nexus/schemas/acls.json",
  "_createdAt": "2021-05-11T11:03:06.071Z",
  "_createdBy": "http://localhost:8080/v1/anonymous",
  "_deprecated": false,
  "_path": "/org1",
  "_rev": 2,
  "_self": "http://localhost:8080/v1/acls/org1",
  "_updatedAt": "2021-05-11T11:03:32.596Z",
  "_updatedBy": "http://localhost:8080/v1/anonymous"
}

Subtract

This operation removes the provided ACL collection from the existing collection of ACL on the provided path.

PATCH /v1/acls/{path}?rev={previous_rev}
  {...}

…where:

  • {previous_rev}: Number - the last known revision for the ACL collection.
  • {path}: String - is the target location for the ACL collection.

The json payload contains the collection of ACL to remove.

Example

Request
sourcecurl -XPATCH \
  -H "Content-Type: application/json" \
  "http://localhost:8080/v1/acls/org1?rev=2" -d \
  '{
  "@type": "Subtract",
  "acl": [
    {
      "permissions": [
        "projects/read"
      ],
      "identity": {
        "group": "a-group",
        "realm": "myrealm"
      }
    }
  ]
}'
Payload
source{
  "@type": "Subtract",
  "acl": [
    {
      "permissions": [
        "projects/read"
      ],
      "identity": {
        "realm": "myrealm",
        "group": "a-group"
      }
    }
  ]
}
Response
source{
  "@context": [
    "https://bluebrain.github.io/nexus/contexts/acls-metadata.json",
    "https://bluebrain.github.io/nexus/contexts/metadata.json"
  ],
  "@id": "http://localhost:8080/v1/acls/org1",
  "@type": "AccessControlList",
  "_constrainedBy": "https://bluebrain.github.io/nexus/schemas/acls.json",
  "_createdAt": "2021-05-11T11:03:06.071Z",
  "_createdBy": "http://localhost:8080/v1/anonymous",
  "_deprecated": false,
  "_path": "/org1",
  "_rev": 3,
  "_self": "http://localhost:8080/v1/acls/org1",
  "_updatedAt": "2021-05-11T11:03:52.875Z",
  "_updatedBy": "http://localhost:8080/v1/anonymous"
}

Append

This operation appends the provided ACL collection to the existing collection of ACL on the provided path.

PATCH /v1/acls/{path}?rev={previous_rev}
  {...}

…where:

  • {previous_rev}: Number - the last known revision for the ACL collection. Not required for appending to empty ACLs.
  • {path}: String - is the target location for the ACL collection.

The json payload contains the collection of ACL to add.

Example

Request
sourcecurl -XPATCH \
  -H "Content-Type: application/json" \
  "http://localhost:8080/v1/acls/org1?rev=3" -d \
    '{
      "@type": "Append",
      "acl": [
        {
          "permissions": [
            "own",
            "other"
          ],
          "identity": {
            "realm": "myrealm",
            "group": "a-group"
          }
        }
      ]
    }'
Payload
source{
  "@type": "Append",
  "acl": [
    {
      "permissions": [
        "own",
        "other"
      ],
      "identity": {
        "realm": "myrealm",
        "group": "a-group"
      }
    }
  ]
}
Response
source{
  "@context": [
    "https://bluebrain.github.io/nexus/contexts/acls-metadata.json",
    "https://bluebrain.github.io/nexus/contexts/metadata.json"
  ],
  "@id": "http://localhost:8080/v1/acls/org1",
  "@type": "AccessControlList",
  "_constrainedBy": "https://bluebrain.github.io/nexus/schemas/acls.json",
  "_createdAt": "2021-05-11T11:03:06.071Z",
  "_createdBy": "http://localhost:8080/v1/anonymous",
  "_deprecated": false,
  "_path": "/org1",
  "_rev": 4,
  "_self": "http://localhost:8080/v1/acls/org1",
  "_updatedAt": "2021-05-11T11:04:54.614Z",
  "_updatedBy": "http://localhost:8080/v1/anonymous"
}

Delete

This operation deletes the entire collection of ACL on the provided path.

DELETE /v1/acls/{path}?rev={previous_rev}

…where:

  • {previous_rev}: Number - the last known revision for the ACL collection.
  • {path}: String - is the target location for the ACL collection.
Request
sourcecurl -XDELETE "http://localhost:8080/v1/acls/org1?rev=4"
Response
source{
  "@context": [
    "https://bluebrain.github.io/nexus/contexts/acls-metadata.json",
    "https://bluebrain.github.io/nexus/contexts/metadata.json"
  ],
  "@id": "http://localhost:8080/v1/acls/org1",
  "@type": "AccessControlList",
  "_constrainedBy": "https://bluebrain.github.io/nexus/schemas/acls.json",
  "_createdAt": "2021-05-11T11:03:06.071Z",
  "_createdBy": "http://localhost:8080/v1/anonymous",
  "_deprecated": false,
  "_path": "/org1",
  "_rev": 5,
  "_self": "http://localhost:8080/v1/acls/org1",
  "_updatedAt": "2021-05-11T11:05:15.919Z",
  "_updatedBy": "http://localhost:8080/v1/anonymous"
}

Fetch

GET /v1/acls/{path}?rev={rev}&self={self}

…where

  • {path}: String - is the target location for the ACL collection.
  • {rev}: Number - the revision of the ACL to be retrieved. This parameter is optional and it defaults to the current revision.
  • {self}: Boolean - if true, only the ACLs containing the identities found on the auth. token are included in the response. If false all the ACLs on the current {path} are included. This parameter is optional and it defaults to true.

The ability to use the query parameter self=false depends on whether or not any of the identities found on the auth. token contains the acls:read permission on the provided {path} or its ancestors. For further details, check ACLs hierarchy.

Request
sourcecurl "http://localhost:8080/v1/acls/org1?rev=1&self=false"
Response
source{
  "@context": [
    "https://bluebrain.github.io/nexus/contexts/metadata.json",
    "https://bluebrain.github.io/nexus/contexts/search.json",
    "https://bluebrain.github.io/nexus/contexts/acls.json"
  ],
  "_total": 1,
  "_results": [
    {
      "@id": "http://localhost:8080/v1/acls/org1",
      "@type": "AccessControlList",
      "acl": [
        {
          "identity": {
            "@id": "http://localhost:8080/v1/realms/myrealm/groups/a-group",
            "@type": "Group",
            "group": "a-group",
            "realm": "myrealm"
          },
          "permissions": [
            "projects/read"
          ]
        },
        {
          "identity": {
            "@id": "http://localhost:8080/v1/realms/realm/groups/some-group",
            "@type": "Group",
            "group": "some-group",
            "realm": "realm"
          },
          "permissions": [
            "projects/read",
            "projects/write"
          ]
        },
        {
          "identity": {
            "@id": "http://localhost:8080/v1/realms/realm/users/alice",
            "@type": "User",
            "realm": "realm",
            "subject": "alice"
          },
          "permissions": [
            "acls/read",
            "acls/write"
          ]
        }
      ],
      "_constrainedBy": "https://bluebrain.github.io/nexus/schemas/acls.json",
      "_createdAt": "2021-05-11T11:03:06.071Z",
      "_createdBy": "http://localhost:8080/v1/anonymous",
      "_deprecated": false,
      "_path": "/org1",
      "_rev": 1,
      "_self": "http://localhost:8080/v1/acls/org1",
      "_updatedAt": "2021-05-11T11:03:06.071Z",
      "_updatedBy": "http://localhost:8080/v1/anonymous"
    }
  ]
}

List

GET /v1/acls/{path}?ancestors={ancestors}&self={self}

…where

  • {path}: String - is the target location for the ACL collection.
  • {ancestors}: Boolean - if true, the ACLs of the parent {path} are included in the response. If false only the ACLs on the current {path} are included. This parameter is optional and it defaults to false.
  • {self}: Boolean - if true, only the ACLs containing the identities found on the auth. token are included in the response. If false all the ACLs on the current {path} are included. This parameter is optional and it defaults to true.

The ability to use the query parameter self=false and ancestors=true depends on whether or not any of the identities found on the auth. token contains the acls:read permission on the provided {path} or its parents. For further details, check ACLs hierarchy.

The {path} can contain the special character * which can be read as any.

Let’s imagine we have the ACLs from the following diagram in place. If we query this endpoint with the path /myorg/*, we are selecting the ACLs defined in /myorg/myproj and myorg/myproj2. Likewise If we use the path /*, we are selecting the ACLs defined in /myorg and myorg2.

The following examples illustrate listings from the diagram on the section ACLs hierarchy with the following considerations:

  • identity 1: Is a group called one
  • identity 2: Is a group called two
  • identity 3: Is a user called me
  • The auth. token is linked to the identity 1.
Request
sourcecurl "http://localhost:8080/v1/acls/*?ancestors=true&self=true"
Response
source{
  "@context": [
    "https://bluebrain.github.io/nexus/contexts/metadata.json",
    "https://bluebrain.github.io/nexus/contexts/search.json",
    "https://bluebrain.github.io/nexus/contexts/acls.json"
  ],
  "_total": 2,
  "_results": [
    {
      "@id": "http://localhost:8080/v1/acls/myorg/myproj",
      "@type": "AccessControlList",
      "acl": [
        {
          "permissions": [
            "read",
            "write"
          ],
          "identity": {
            "@id": "http://localhost:8080/v1/realm/groups/two",
            "@type": "Group",
            "realm": "myrealm",
            "group": "two"
          }
        }
      ],
      "_constrainedBy": "https://bluebrain.github.io/nexus/schemas/acls.json",
      "_createdAt": "2018-09-17T14:55:42.939Z",
      "_createdBy": "http://localhost:8080/v1/realms/myrealm/users/john",
      "_deprecated": false,
      "_path": "/myorg/myproj",
      "_rev": 1,
      "_self": "http://localhost:8080/v1/acls/myorg/myproj",
      "_updatedAt": "2018-09-17T15:05:42.939Z",
      "_updatedBy": "http://localhost:8080/v1/realms/myrealm/users/john"
    },
    {
      "@id": "http://localhost:8080/v1/acls/myorg/myproj2",
      "@type": "AccessControlList",
      "acl": [
        {
          "permissions": [
            "read"
          ],
          "identity": {
            "@id": "http://localhost:8080/v1/realms/myrealm/users/me",
            "@type": "User",
            "realm": "myrealm",
            "subject": "me"
          }
        }
      ],
      "_constrainedBy": "https://bluebrain.github.io/nexus/schemas/acls.json",
      "_createdAt": "2018-09-17T14:00:42.939Z",
      "_createdBy": "http://localhost:8080/v1/realms/myrealm/users/alice",
      "_deprecated": false,
      "_path": "/myorg/myproj2",
      "_rev": 2,
      "_self": "http://localhost:8080/v1/acls/myorg/myproj2",
      "_updatedAt": "2018-09-17T14:05:42.939Z",
      "_updatedBy": "http://localhost:8080/v1/realms/myrealm/users/alice"
    }
  ]
}
Request (with ancestors)
sourcecurl "http://localhost:8080/v1/acls/myorg/*?ancestors=false&self=false"
Response (with ancestors)
source{
  "@context": [
    "https://bluebrain.github.io/nexus/contexts/resource.json",
    "https://bluebrain.github.io/nexus/contexts/iam.json",
    "https://bluebrain.github.io/nexus/contexts/search.json"
  ],
  "_total": 3,
  "_results": [
    {
      "@id": "http://localhost:8080/v1/acls/",
      "@type": "AccessControlList",
      "acl": [
        {
          "permissions": [
            "acls/write"
          ],
          "identity": {
            "@id": "http://localhost:8080/v1/realms/myrealm/groups/one",
            "@type": "Group",
            "realm": "myrealm",
            "group": "one"
          }
        }
      ],
      "_createdAt": "2018-09-17T14:55:42.939Z",
      "_constrainedBy": "https://bluebrain.github.io/nexus/schemas/acls.json",
      "_createdBy": "http://localhost:8080/v1/realms/myrealm/users/john",
      "_deprecated": false,
      "_path": "/",
      "_rev": 1,
      "_self": "http://localhost:8080/v1/acls",
      "_updatedAt": "2018-09-17T15:05:42.939Z",
      "_updatedBy": "http://localhost:8080/v1/realms/myrealm/users/john"
    },
    {
      "@id": "http://localhost:8080/v1/acls/myorg",
      "@type": "AccessControlList",
      "acl": [
        {
          "permissions": [
            "acls/write"
          ],
          "identity": {
            "@id": "http://localhost:8080/v1/realms/myrealm/groups/two",
            "@type": "Group",
            "realm": "myrealm",
            "group": "two"
          }
        }
      ],
      "_constrainedBy": "https://bluebrain.github.io/nexus/schemas/acls.json",
      "_createdAt": "2018-09-17T14:00:42.939Z",
      "_createdBy": "http://localhost:8080/v1/realms/myrealm/users/alice",
      "_deprecated": false,
      "_path": "/myorg",
      "_rev": 2,
      "_self": "http://localhost:8080/v1/acls/myorg",
      "_updatedAt": "2018-09-17T14:05:42.939Z",
      "_updatedBy": "http://localhost:8080/v1/realms/myrealm/users/alice"
    },
    {
      "@id": "http://localhost:8080/v1/acls/myorg2",
      "@type": "nxv:AccessControlList",
      "acl": [
        {
          "permissions": [
            "other"
          ],
          "identity": {
            "@id": "http://localhost:8080/v1/realms/myrealm/groups/one",
            "@type": "Group",
            "realm": "myrealm",
            "group": "one"
          }
        }
      ],
      "_constrainedBy": "https://bluebrain.github.io/nexus/schemas/acls.json",
      "_createdAt": "2018-09-17T14:00:42.939Z",
      "_deprecated": false,
      "_createdBy": "http://localhost:8080/v1/realms/myrealm/users/alice",
      "_path": "/myorg2",
      "_rev": 1,
      "_self": "http://localhost:8080/v1/acls/myorg2",
      "_updatedAt": "2018-09-17T14:05:42.939Z",
      "_updatedBy": "http://localhost:8080/v1/realms/myrealm/users/alice"
    }
  ]
}

ACL Server Sent Events

This endpoint allows clients to receive automatic updates from the ACLs in a streaming fashion.

GET /v1/acls/events

where Last-Event-Id is an optional HTTP Header that identifies the last consumed ACL event. It can be used for cases when a client does not want to retrieve the whole event stream, but to start after a specific event.

The response contains a series of ACL events, represented in the following way

data:{payload}
event:{type}
id:{id}

where…

  • {payload}: Json - is the actual payload of the current ACL
  • {type}: String - is a type identifier for the current ACL. Possible types are: AclAppended, AclSubtracted, AclReplaced, AclDeleted
  • {id}: String - is the identifier of the ACL event. It can be used in the Last-Event-Id HTTP Header

Example

Request
sourcecurl "http://localhost:8080/v1/acls/events"
Response
sourcedata:{"@context":["https://bluebrain.github.io/nexus/contexts/metadata.json","https://bluebrain.github.io/nexus/contexts/acls.json"],"@type":"AclReplaced","acl":[{"identity":{"@id":"http://localhost:8080/v1/realms/myrealm/groups/a-group","@type":"Group","group":"a-group","realm":"myrealm"},"permissions":["projects/read"]},{"identity":{"@id":"http://localhost:8080/v1/realms/realm/groups/some-group","@type":"Group","group":"some-group","realm":"realm"},"permissions":["projects/read","projects/write"]},{"identity":{"@id":"http://localhost:8080/v1/realms/realm/users/alice","@type":"User","realm":"realm","subject":"alice"},"permissions":["acls/read","acls/write"]}],"_aclId":"http://localhost:8080/v1/acls/org1","_instant":"2021-05-11T11:03:06.071Z","_path":"/org1","_rev":1,"_subject":"http://localhost:8080/v1/anonymous"}
event:AclReplaced
id:76848d80-b248-11eb-a0d9-6dedbaa155f8

data:{"@context":["https://bluebrain.github.io/nexus/contexts/metadata.json","https://bluebrain.github.io/nexus/contexts/acls.json"],"@type":"AclReplaced","acl":[{"identity":{"@id":"http://localhost:8080/v1/realms/myrealm/groups/a-group","@type":"Group","group":"a-group","realm":"myrealm"},"permissions":["projects/read"]},{"identity":{"@id":"http://localhost:8080/v1/realms/realm/groups/some-group","@type":"Group","group":"some-group","realm":"realm"},"permissions":["projects/read","projects/write"]},{"identity":{"@id":"http://localhost:8080/v1/realms/realm/users/alice","@type":"User","realm":"realm","subject":"alice"},"permissions":["acls/read","acls/write"]}],"_aclId":"http://localhost:8080/v1/acls/org1","_instant":"2021-05-11T11:03:32.596Z","_path":"/org1","_rev":2,"_subject":"http://localhost:8080/v1/anonymous"}
event:AclReplaced
id:8653f250-b248-11eb-a0d9-6dedbaa155f8

data:{"@context":["https://bluebrain.github.io/nexus/contexts/metadata.json","https://bluebrain.github.io/nexus/contexts/acls.json"],"@type":"AclSubtracted","acl":[{"identity":{"@id":"http://localhost:8080/v1/realms/myrealm/groups/a-group","@type":"Group","group":"a-group","realm":"myrealm"},"permissions":["projects/read"]}],"_aclId":"http://localhost:8080/v1/acls/org1","_instant":"2021-05-11T11:03:52.875Z","_path":"/org1","_rev":3,"_subject":"http://localhost:8080/v1/anonymous"}
event:AclSubtracted
id:926a46c0-b248-11eb-a0d9-6dedbaa155f8

data:{"@context":["https://bluebrain.github.io/nexus/contexts/metadata.json","https://bluebrain.github.io/nexus/contexts/acls.json"],"@type":"AclAppended","acl":[{"identity":{"@id":"http://localhost:8080/v1/realms/myrealm/groups/a-group","@type":"Group","group":"a-group","realm":"myrealm"},"permissions":["own","other"]}],"_aclId":"http://localhost:8080/v1/acls/org1","_instant":"2021-05-11T11:04:54.614Z","_path":"/org1","_rev":4,"_subject":"http://localhost:8080/v1/anonymous"}
event:AclAppended
id:b736bf60-b248-11eb-a0d9-6dedbaa155f8

data:{"@context":["https://bluebrain.github.io/nexus/contexts/metadata.json","https://bluebrain.github.io/nexus/contexts/acls.json"],"@type":"AclDeleted","_aclId":"http://localhost:8080/v1/acls/org1","_instant":"2021-05-11T11:05:15.919Z","_path":"/org1","_rev":5,"_subject":"http://localhost:8080/v1/anonymous"}
event:AclDeleted
id:c3e9c900-b248-11eb-a0d9-6dedbaa155f8