diff --git a/README.md b/README.md index b6c775c7..2b5e3615 100644 --- a/README.md +++ b/README.md @@ -183,30 +183,7 @@ This is intended to reduce the amount of polling required by clients to keep up However the specification is deliberately left open-ended; only the message bodies are specified, but not the protocol by which they are carried nor the method by which clients subscribe. It is assumed that implementations will provide a suitable mechanism, such as a call to allow clients to subscribe to webhooks, or details of an event bus to connect to and receive the messages. -### Security - -The TAMS specification stipulates authentication methods that a client should support in order to identify themselves and provide credentials to the server, using standard HTTP approaches. -The authorisation model (the rules by which authenticated requests are allowed or denied) is not part of the TAMS specification, and is up to individual implementers and organisations depending on their exact rules, needs and threat model. - -It is assumed that implementations will apply other IT and cloud infrastructure security best practices, notably including the use of TLS (e.g. HTTPS connections) within and between their systems. - -## Mock TAMS Service - -This repo contains some automation to run a mock version of the API using [Stoplight Prism](https://stoplight.io/open-source/prism). -To run the mock server using Docker, try something like the command below (or run `make mock-server-up`): - -```shell -docker run --rm --init --name mock-tams -v "$(pwd)":/data:ro -p 4010:4010 stoplight/prism mock /data/TimeAddressableMediaStore.yaml -h 0.0.0.0 -``` - -A mock API server will start at - -## Proposals, Decisions and Architecture Changes - -This repository uses [(M)ADR documents](https://adr.github.io/madr/) to propose significant changes, facilitate discussions and decision making, and to store a record of options that were considered. -These documents may be found in the [docs/adr](./docs/adr/) directory, and are managed as described by the [ADR Readme](./docs/adr/README.md). - -## API Versioning +### API Versioning The API is versioned using a major and minor version number. A breaking change - such as removal of a feature, or renaming of properties in such a way that would break compatibility (including fixing a typo) - results in a major version increment and the minor version is reset to 0. @@ -226,7 +203,20 @@ Otherwise, the version will not change. It is possible to see what the version would be if a release was made at the current commit by running `make next-version` in the top directory of this repository. -### Making a release +### Security + +The TAMS specification stipulates authentication methods that a client should support in order to identify themselves and provide credentials to the server, using standard HTTP approaches. +The authorisation model (the rules by which authenticated requests are allowed or denied) is not part of the TAMS specification, and is up to individual implementers and organisations depending on their exact rules, needs and threat model. +However some principles and suggestions are discussed in [AppNote0016: Authorisation in TAMS workflows](./docs/appnotes/0016-authorisation-in-tams-workflows.md). + +It is assumed that implementations will apply other IT and cloud infrastructure security best practices, notably including the use of TLS (e.g. HTTPS connections) within and between their systems. + +## Proposals, Decisions and Architecture Changes + +This repository uses [(M)ADR documents](https://adr.github.io/madr/) to propose significant changes, facilitate discussions and decision making, and to store a record of options that were considered. +These documents may be found in the [docs/adr](./docs/adr/) directory, and are managed as described by the [ADR Readme](./docs/adr/README.md). + +## Making a release Run the `release` workflow under the `Actions` tab on this repository on GitHub against the `main` branch. This workflow requires approval. diff --git a/api/TimeAddressableMediaStore.yaml b/api/TimeAddressableMediaStore.yaml index c6bca505..f71c972f 100644 --- a/api/TimeAddressableMediaStore.yaml +++ b/api/TimeAddressableMediaStore.yaml @@ -2239,7 +2239,7 @@ paths: "400": $ref: '#/components/responses/trait_resource_info_head_400' "404": - description: The requested Media Object does not exist. + description: The requested Media Object does not exist. 404 MUST be returned if the ID has been assigned via the [`/flows/{flowId}/storage`](#/operations/POST_flows-flowId-storage), but not yet registered against a Flow Segment. get: summary: Media Object Information description: | @@ -2338,7 +2338,7 @@ paths: "400": description: Bad request. Invalid query options. "404": - description: The requested Media Object does not exist. + description: The requested Media Object does not exist. 404 MUST be returned if the ID has been assigned via the [`/flows/{flowId}/storage`](#/operations/POST_flows-flowId-storage), but not yet registered against a Flow Segment. /objects/{objectId}/instances: post: summary: Register a Media Object instance @@ -2389,7 +2389,7 @@ paths: "403": description: Forbidden. You do not have permission to modify this Media Object. "404": - description: The Media Object does not exist. + description: The Media Object does not exist. 404 MUST be returned if the ID has been assigned via the [`/flows/{flowId}/storage`](#/operations/POST_flows-flowId-storage), but not yet registered against a Flow Segment. delete: summary: Delete a Media Object instance description: | @@ -2428,7 +2428,8 @@ paths: "403": description: Forbidden. You do not have permission to modify this Media Object. "404": - description: The requested Object ID in the path is invalid. + description: The requested Object ID in the path is invalid. 404 MUST be returned if the ID has been assigned via the [`/flows/{flowId}/storage`](#/operations/POST_flows-flowId-storage), but not yet registered against a Flow Segment. + /flow-delete-requests: head: summary: List Flow Delete Requests diff --git a/docs/README.md b/docs/README.md index 38706d3f..bb3c89a8 100644 --- a/docs/README.md +++ b/docs/README.md @@ -22,6 +22,7 @@ For more information on how we use application notes, see [here](./appnotes/READ | [0013](./appnotes/0013-setting-flow-bit-rate-properties.md) | Setting Flow bit rate properties | | [0014](./appnotes/0014-referencing-tams-content-in-other-systems.md) | Referencing TAMS content in other systems | | [0015](./appnotes/0015-using-tams-in-opentimelineio.md) | Using TAMS in OpenTimelineIO | +| [0016](./appnotes/0016-authorisation-in-tams-workflows.md) | Authorisation in TAMS workflows | | [0017](./appnotes/0017-reuse-of-ids.md) | When to re-use IDs in TAMS and compatible systems | | [0018](./appnotes/0018-managing-multiple-object-instances.md) | Managing Multiple Object Instances | @@ -68,6 +69,7 @@ For more information on how we use ADRs, see [here](./adr/README.md). | [0031](./adr/0031-flow-image-support.md) | Add new flow type to support still images | | [0032](./adr/0032-specifying-storage-backend.md) | Specifying storage backend when requesting storage allocation | | [0034](./adr/0034-storage-allow-object_ids.md) | Add object_ids option to Flow Storage request | +| [0035](./adr/0035-fine-grained-auth.md) | Fine-grained Authorisation in TAMS Workflows | | [0036](./adr/0036-specifying-partial-segment-usage.md) | Specifying partial segment usage | | [0037](./adr/0037-improve-webhooks.md) | Proposal for improvements to the Webhooks endpoints | | [0038](./adr/0038-improved-storage-management.md) | Improved Storage Management | diff --git a/docs/adr/0035-fine-grained-auth.md b/docs/adr/0035-fine-grained-auth.md new file mode 100644 index 00000000..a1b54f10 --- /dev/null +++ b/docs/adr/0035-fine-grained-auth.md @@ -0,0 +1,178 @@ +--- +status: "accepted" +--- +# Fine-grained Authorisation in TAMS Workflows + +## Context and Problem Statement + +TAMS provides multiple methods for authentication, as described in [ADR0028](./0028-authentication-methods.md). +The most commonly used method to implement authentication is Bearer tokens acquired using OAuth2 flows. +OAuth2 allows for auth tokens to claim [scopes](https://oauth.net/2/scope/) as a means to restrict the permissions of clients and requests. +This has been used in TAMS implementations to provide coarse grained authorisation. + +Emerging TAMS use cases are making use of TAMS' media re-use capabilities over increasingly large numbers of users, teams, and organisations. +As the number of clients accessing content in a TAMS store grows, the need for finer-grained control of that content becomes more acute. + +This ADR presents the decisions and considerations that informed the initial approach to fine-grained auth in TAMS. + +## Decision Drivers + +* Be prescriptive enough to enable interoperability of service and client implementations +* Be permissive enough to facilitate integration with existing workflows and systems +* Acknowledge that organisations will have different threat models, and interoperability should be equally possible in more open and more restrictive environments +* As far as practical, maintain sensible parallels between coarse and fine-grained approaches +* Enable authorization at a sufficiently fine-grained level for practical use cases +* Must be possible to implement the design efficiently + +## Considered Options + +* Option 1a: Granularity of auth - Source +* Option 1b: Granularity of auth - Flow +* Option 1c: Granularity of auth - Segment +* Option 1d: Granularity of auth - Object +* Option 2a: Level of prescriptiveness - General principles +* Option 2b: Level of prescriptiveness - Auth logic +* Option 2c: Level of prescriptiveness - Specific API requests/pseudocode/scopes +* Option 2d: Level of prescriptiveness - Mandate the use of the proposed approach +* Option 3a: Supported architectures - Deep integration +* Option 3b: Supported architectures - Auth proxy +* Option 4a: Auth attributes - Single Tag with a list value +* Option 4b: Auth attributes - Specific parameters + +## Decision Outcome + +Chosen options: + +* Option 1b: Granularity of auth - Flow +* Option 2b: Level of prescriptiveness - Auth logic +* Option 3b: Supported architectures - Auth proxy +* Option 4a: Auth attributes - Single Tag with a list value + +This combination of options provides a good balance of well defined behaviour and flexibility. +It facilitates both interoperability and integration with existing auth systems and workflows. +The choice of Option 3b does not preclude the solution developed being implemented as described in Option 3a. +The choice of Option 4a allows for us to experiment with, and refine our approach to fine-grained auth. +Once our approach is mature, we may wish to consider implementing Option 1c in [ADR0040](./0040-tag-usability-enhancements.md) to provide a more efficient solution. + +### Implementation + +Implemented by . + +## Pros and Cons of the Options + +### Option 1a: Granularity of auth - Source + +Define permissions at the Source level. +Permissions are then propagated down to Flows, Segments, and Objects. + +* Good, because it can be implemented with minimal changes to the API specification +* Good, because it requires the least additional data to be stored in/alongside the API +* Bad, because it provides limited control over different representations of media + * Prevents cost control (e.g. allow access to proxies, but not hi-res) + * Prevents management of access to specific storage backends + +### Option 1b: Granularity of auth - Flow + +Define permissions at the Source and Flow levels. +Permissions are then propagated down to Segments, and Objects. +However the default approach should be to define permissions on Sources (since permissions are likely to apply to all renditions of a piece of content), with the option to use Flows where restricting specific variants is required. + +* Good, because it can be implemented with minimal changes to the API specification +* Good, because it requires a manageable amount of additional data to be stored in/alongside the API +* Good, because it provides control over different representations of media +* Neutral, because access can only be provided to entire flows + * Time-scoped access requires new flows to be created, potentially via zero-copy mechanism + +### Option 1c: Granularity of auth - Segment + +Define permissions at the Source, Flow, and Segment levels. +Permissions are then propagated down to Objects. + +* Good, because it can be implemented with minimal changes to the API specification +* Good, because it provides control over different representations of media +* Good, because it enables direct control of access to segments of flow timelines + * Note that segments may contain multiple video frames/audio samples etc + * Allowing direct sub-segment access control would require significant further modification +* Bad, because it requires significant amounts of additional data to be stored in/alongside the API + +### Option 1d: Granularity of auth - Object + +Define permissions at the Source, Flow, Segment, and Object levels. + +* Good, because it can be implemented with minimal changes to the API specification +* Good, because it provides control over different representations of media +* Good, because it maps the right to access content onto the content itself, regardless of how it is re-used +* Neutral, because it enables direct control of access to segments of flow timelines + * Allowing direct sub-segment access control would require significant modification +* Bad, because it requires significant amounts of additional data to be stored in/alongside the API +* Bad, because it may make re-use of media very complicated + +### Option 2a: Level of prescriptiveness - General principles + +State some general principles implementations may follow, and technologies they may use. +Leave the definition of auth logic etc up to individual implementations. + +* Good, because it provides high levels of flexibility in implementations +* Bad, because it makes interoperability around fine-grained auth difficult +* Bad, because it requires implementers to derive all auth logic from scratch, including a few known non-trivial aspects + +### Option 2b: Level of prescriptiveness - Auth logic + +Define principles, and high-level auth logic. +Leave specific algorithms, and the requests that permissions evaluation systems may make to the API up to the individual implementations. + +* Good, because it facilitates interoperability around fine-grained auth +* Good, because implementers don't have to derive auth logic from scratch +* Neutral, because it provides medium levels of flexibility to implementations + +### Option 2c: Level of prescriptiveness - Specific API requests/pseudocode/scopes + +Define all auth logic, algorithms that evaluate that auth logic, and API requests permission evaluation systems will make. + +* Good, because it facilitates interoperability around fine-grained auth +* Good, because implementers don't have to derive auth logic, or algorithms from scratch +* Bad, because it provides low levels of flexibility to implementations + * This may conflict with existing auth systems, and workflows in deployments and organisational structures and models + +### Option 2d: Level of prescriptiveness - Mandate the use of the proposed approach + +Make the proposed approach to fine-grained auth mandatory. + +* Good, because it ensures full interoperability around fine-grained auth +* Bad, because the approach may conflict with existing auth systems, and workflows in deployments and organisational structures and models +* Bad, because such conflicts being a mandatory part of the specification may prevent/impede use of TAMS by some organisations + +### Option 3a: Supported architectures - Deep integration + +Assume all TAMS services that support fine-grained auth will implement it with deep integration into the API implementation where policy decisions are made by the store implementation as part of processing a request, with full access to any backing databases etc. + +* Good, because its the most efficient option to implement +* Bad, because it provides low levels of flexibility when integrating with existing auth systems, and workflows in deployments + +### Option 3b: Supported architectures - Auth proxy + +Assume fine-grained auth may be implemented using the Auth Proxy pattern where an HTTP reverse proxy can receive incoming requests, amend them as needed and forward them onto a store (which may have no fine-grained authorisation model). +The proxy can use the contents of the original request, the amended request(s) and the response(s) to make a decision on what to return to the user without modifying the underlying store. + +* Good, because it provides high levels of flexibility when integrating with existing auth systems, and workflows in deployments +* Neutral, because its an acceptably efficient option to implement + +### Option 4a: Auth attributes - Single Tag with a list value + +Use the tag list support (added with [ADR0040](./0040-tag-usability-enhancements.md)) to provide/query attributes as part of an Attribute-Based Access Control (ABAC) system. + +* Good, because it requires little/no changes to the API specification beyond ADR0040 +* Good, because it makes use of tags for this purpose intuitive +* Good, because it requires very few API calls when carrying out evaluation of permissions on endpoints + +### Option 4b: Auth attributes - Specific parameters + +Define new parameters throughout the API specification for the purpose of storing auth-specific attributes. + +* Good, because it allows for an optimal number API calls when carrying out evaluation of permissions on endpoints +* Neutral, because it results in increased integration of auth mechanisms into the core specification +* Bad, because it requires significant changes to the API specification for a feature that has not been widely tested + +## More Information + +Information on TAMS' approach to authentication may be found in [ADR0028](./0028-authentication-methods.md). diff --git a/docs/appnotes/0003-tag-names.md b/docs/appnotes/0003-tag-names.md index 69918f1e..26ab5e10 100644 --- a/docs/appnotes/0003-tag-names.md +++ b/docs/appnotes/0003-tag-names.md @@ -195,6 +195,13 @@ The type is a `boolean`. It is used to indictate the Flow should be excluded from HLS manifest generation. Defaults to `false` if the tag is not set. +### auth_classes + +Status: **Experimental** + +Suggested as a way to build lightweight Attribute-based Access Control in [AppNote0016: Authorisation in TAMS workflows](./0016-authorisation-in-tams-workflows.md). +A comma seperated list of auth classes used to derive permissions on the Flow. + ## Known Source Tags ### hls_exclude @@ -206,3 +213,10 @@ Used in the TAMS demonstration at NAB 2025. The type is a `boolean`. It is used to indictate the Source should be excluded from HLS manifest generation. Defaults to `false` if the tag is not set. + +### auth_classes + +Status: **Experimental** + +Suggested as a way to build lightweight Attribute-based Access Control in [AppNote0016: Authorisation in TAMS workflows](./0016-authorisation-in-tams-workflows.md). +A comma seperated list of auth classes used to derive permissions on the Source. diff --git a/docs/appnotes/0016-authorisation-in-tams-workflows.md b/docs/appnotes/0016-authorisation-in-tams-workflows.md new file mode 100644 index 00000000..1fc8e860 --- /dev/null +++ b/docs/appnotes/0016-authorisation-in-tams-workflows.md @@ -0,0 +1,313 @@ +# 0016: Authorisation in TAMS Workflows + +## Abstract + +Media workflows often contain sensitive or high-value content, and media organisations need to effectively manage access to that content across their estate. +That requires suitable approaches across both authentication (identifying the user) as discussed in [ADR0028](../adr/0028-authentication-methods.md), and also authorisation (deciding what the user can do), which is discussed in [ADR035](../adr/0035-fine-grained-auth.md) and implemented here. +In general, service implementations, and the organisations that deploy them, are free to define how the authorisation model works based on their needs, however this Application Note provides some guidelines and a starting point. +It is recommended to follow these guidelines where possible to aid in interoperability between TAMS components. + +## Overall Principles + +Implementers should consider the material they need to protect, the nature of their business and their threat model when deciding how to build authorisation into TAMS-based media workflows. + +For some organisations a coarse-grained approach is sufficient: for example allowing groups of users to have read- or write-access to a service instance or large blocks of content. +This might be appropriate for example in a newsroom, where staff are deliberately enabled to work together and access each other's material. + +Conversely a finer-grained approach may be required, where specific rules and policies are applied to each piece of content, and groups of users are carefully managed. +This may be appropriate for example when working with a large number of third parties and freelancers in drama production, or when managing a large media archive of high-value content. +It may also be necessary to consider the context of a request: for example whether it originated from a managed device (such as an ingester or edit suite in a facility), from a particular network or in a particular setting: in some cases it may be appropriate to create "machine users" with broader permssions, and in others to have the user work interactively, using their own credentials. + +> [!NOTE] +> Throughout this document the term "user" is used as a shorthand for all security principals, including human users, machine accounts, third-party SaaS integrations, etc. + +## Coarse Grained Authorisation + +A simple approach is to define permissions that apply to an entire TAMS instance at a very coarse level, and use Role-based Access Control (RBAC) to grant access through those permissions. +In RBAC, each action is restricted to users holding a certain role, and users are assigned the relevant roles they need. + +These are the recommended permissions (or "scopes" in OAuth 2.0). +This is a deliberately minimal set: for example implementations may wish to add a specific permission for managing webhooks or object instances. + +| Endpoint | Method | `tams-api/admin` | `tams-api/read` | `tams-api/write` | `tams-api/delete` | +| ------------------------------------ | --------------- | ---------------- | --------------- | ---------------- | ----------------- | +| `/` | `HEAD`/`GET` ⚠️ | ✅ | ✅ | ✅ | ✅ | +| `/service` | `HEAD`/`GET` ⚠️ | ✅ | ✅ | ✅ | ✅ | +| | `POST` ⚠️ | ✅ | | | | +| `/service/storage-backends` | `HEAD`/`GET` ⚠️ | ✅ | ✅ | ✅ | ✅ | +| `/service/webhooks` | `HEAD`/`GET` | ✅ | ✅ | | | +| | `POST` | ✅ | | ✅ | | +| `/service/webhooks/{webhookId}` | `HEAD`/`GET` | ✅ | ✅ | | | +| | `PUT` ⚠️ | ✅ | ✅ | | | +| | `DELETE` ⚠️ | ✅ | ✅ | | | +| `/sources` | `HEAD`/`GET` | ✅ | ✅ | | | +| `/sources/{sourceId}` | `HEAD`/`GET` | ✅ | ✅ | | | +| `/sources/{sourceId}/tags` | `HEAD`/`GET` | ✅ | ✅ | | | +| `/sources/{sourceId}/tags/{name}` | `HEAD`/`GET` | ✅ | ✅ | | | +| | `PUT` | ✅ | | ✅ | | +| | `DELETE` ⚠️ | ✅ | | ✅ | | +| `/sources/{sourceId}/description` | `HEAD`/`GET` | ✅ | ✅ | | | +| | `PUT` | ✅ | | ✅ | | +| | `DELETE` ⚠️ | ✅ | | ✅ | | +| `/sources/{sourceId}/label` | `HEAD`/`GET` | ✅ | ✅ | | | +| | `PUT` | ✅ | | ✅ | | +| | `DELETE` ⚠️ | ✅ | | ✅ | | +| `/flows` | `HEAD`/`GET` | ✅ | ✅ | | | +| `/flows/{flowId}` | `HEAD`/`GET` | ✅ | ✅ | | | +| | `PUT` | ✅ | | ✅ | | +| | `DELETE` | ✅ | | | ✅ | +| `/flows/{flowId}/tags` | `HEAD`/`GET` | ✅ | ✅ | | | +| `/flows/{flowId}/tags/{name}` | `HEAD`/`GET` | ✅ | ✅ | | | +| | `PUT` | ✅ | | ✅ | | +| | `DELETE` ⚠️ | ✅ | | ✅ | | +| `/flows/{flowId}/description` | `HEAD`/`GET` | ✅ | ✅ | | | +| | `PUT` | ✅ | | ✅ | | +| | `DELETE` ⚠️ | ✅ | | ✅ | | +| `/flows/{flowId}/label` | `HEAD`/`GET` | ✅ | ✅ | | | +| | `PUT` | ✅ | | ✅ | | +| | `DELETE` ⚠️ | ✅ | | ✅ | | +| `/flows/{flowId}/read_only` | `HEAD`/`GET` | ✅ | ✅ | | | +| | `PUT` | ✅ | | ✅ | | +| `/flows/{flowId}/flow_collection` | `HEAD`/`GET` | ✅ | ✅ | | | +| | `PUT` | ✅ | | ✅ | | +| | `DELETE` ⚠️ | ✅ | | ✅ | | +| `/flows/{flowId}/max_bit_rate` | `HEAD`/`GET` | ✅ | ✅ | | | +| | `PUT` | ✅ | | ✅ | | +| | `DELETE` ⚠️ | ✅ | | ✅ | | +| `/flows/{flowId}/avg_bit_rate` | `HEAD`/`GET` | ✅ | ✅ | | | +| | `PUT` | ✅ | | ✅ | | +| | `DELETE` ⚠️ | ✅ | | ✅ | | +| `/flows/{flowId}/segments` | `HEAD`/`GET` | ✅ | ✅ | | | +| | `POST` | ✅ | | ✅ | | +| | `DELETE` | ✅ | | | ✅ | +| `/flows/{flowId}/storage` | `POST` | ✅ | | ✅ | | +| `/objects/{objectId}` | `HEAD`/`GET` | ✅ | ✅ | | | +| `/objects/{objectId}/instances` | `POST` | ✅ | | ✅ | | +| | `DELETE` | ✅ | | ✅ | | +| `/flow-delete-requests` | `HEAD`/`GET` ⚠️ | ✅ | | | | +| `/flow-delete-requests/{request-id}` | `HEAD`/`GET` ⚠️ | ✅ | | | ✅ | + +Key for the listing: + +- ✅: Allow method with this OAuth scope +- ⚠️: Method does not follow the basic mapping of `tams-api/read` to `HEAD`/`GET`, `tams-api/write` to `POST`/`PUT`, and `tams-api/delete` to `DELETE` + +Users may be assigned combinations of these roles for different purposes, for example: + +- `administrator`: Has all four scopes +- `viewer`: Has `tams-api/read` +- `editor`: Has `tams-api/read` and `tams-api/write` +- `store-writer`: Has `tams-api/write` +- `store-cleanup-system`: Has `tams-api/delete` + +To implement the authorisation, the authorisation server checks the requested scopes against the user's access when issuing a token. +The TAMS server, or its auth proxy, rejects requests without appropriate scopes. + +## Finer Grained Authorisation + +A further build on the very coarse role-based approach above is to expand the set of permissions to apply to specific Sources and Flows. +Broadly those permissions can be aligned along the lines of the scopes in the coarse-grained model: granting read, write and delete access separately. +The policy that assigns read/write/delete permissions to users depends on the needs of the organisation, and may be based on their roles or group memberships, or use an Attribute-Based Access Control (ABAC) approach where other factors such as position in the organisation, time-of-day or location are considered. + +This section will describe the logic that needs to be applied to each endpoint depending on the user's resource permissions, along with some considerations for implementing fine-grained authorisation. +Later in the document the [Implementation using Tags](#implementation-using-tags) section describes a simple approach that could be taken using an `auth_classes` tag, and how a reverse proxy may be implemented to enforce user access accordingly. + +To implement this, solutions such as [Amazon Verified Permissions](https://aws.amazon.com/verified-permissions/) and [Permify](https://github.com/Permify/permify) could be used as permissions management tools. +They allow authorisation decisions to be made by taking a set of policies defined in some domain-specific language, along with the attributes of the user (group membership) and resource (Source/Flow/webhook), and computing whether to allow the request. +They may also return the permitted resource attributes for a given user of a given endpoint to aid filtering results in Flow listings, for example. +It may be necessary to add additional attributes specific to authorisation (such as the owner of a piece of content), which could be stored as tags (e.g. `auth_classes`) or another media library database. +This decision process is intended to be run inline for each request, for example at an authenticating proxy placed in front of the API server. + +### Auth logic + +In order that implementations may have consistent expectations about which methods they may access, this section provides recommended auth logic for methods. + +It is assumed that along with read/write and delete, an "administrator" permission exists that can execute all methods on all endpoints. +It is only explicitly called out in the listing below where admins are the only users granted permissions. + +The listing below refers to requests having permissions, rather than users. +This is to account for cases where users only "claim" a subset of their permissions for a given request. + +Implementations may choose to additionally filter data based on the permissions of a request. +For example, a Source's collections may be filtered to only include Sources the request has read/write/delete permissions on. +Implementers should consider the implications of hiding data. +For example - hiding collection relationships may result in clients deciding to delete a resource which, unknowingly, is still referenced by another. + +| Endpoint | Method | Auth logic | +| ------------------------------------ | ------------ | --------------------------------------------------------------------------------------- | +| `/` | `HEAD`/`GET` | Available to all | +| `/service` | `HEAD`/`GET` | Available to all | +| | `POST` | Request must have admin permissions. | +| `/service/storage-backends` | `HEAD`/`GET` | Available to all | +| `/service/webhooks` | `HEAD`/`GET` | Restrict returned data to only the webhooks that the request has read permission on. | +| | `POST` | If the request includes Source or Flow filters, the request must have read permissions on all Source or Flow IDs requested. Otherwise, reject. Note that this endpoint only allows creation, not modification, of webhooks. | +| `/service/webhooks/{webhookId}` | `HEAD`/`GET` | Request must have read permissions on {webhookId}. | +| | `PUT` | Request must have write permissions on {webhookId}. If the request includes Source or Flow filters, the request must have read permissions on all Source or Flow IDs requested. | +| | `DELETE` | Request must have delete permissions on {webhookId}. | +| `/sources` | `HEAD`/`GET` | Restrict the returned data to only the Sources that the request has read permission on. | +| `/sources/{sourceId}` | `HEAD`/`GET` | Request must have read permissions on {sourceId}. | +| `/sources/{sourceId}/tags` | `HEAD`/`GET` | Request must have read permissions on {sourceId}. | +| `/sources/{sourceId}/tags/{name}` | `HEAD`/`GET` | Request must have read permissions on {sourceId}. | +| | `PUT` | Request must have write permissions on {sourceId}. | +| | `DELETE` | Request must have write permissions on {sourceId}. | +| `/sources/{sourceId}/description` | `HEAD`/`GET` | Request must have read permissions on {sourceId}. | +| | `PUT` | Request must have write permissions on {sourceId}. | +| | `DELETE` | Request must have write permissions on {sourceId}. | +| `/sources/{sourceId}/label` | `HEAD`/`GET` | Request must have read permissions on {sourceId}. | +| | `PUT` | Request must have write permissions on {sourceId}. | +| | `DELETE` | Request must have write permissions on {sourceId}. | +| `/flows` | `HEAD`/`GET` | Restrict returned data to only the Flows that the request has read permission on. | +| `/flows/{flowId}` | `HEAD`/`GET` | Request must have read permissions on {flowID}. | +| | `PUT` | If {flowId} does not currently exist, request must have write permissions on the Flow's Source ID if it already exists in this TAMS instance. If neither {flowId} nor the Source ID exist, allow if the request has create permission (see [Creating new Flows and Sources](#creating-new-flows-and-sources)). If {flowId} already exists, request must have write permissions on {flowId}. | +| | `DELETE` | Request must have delete permissions on {flowId}. | +| `/flows/{flowId}/tags` | `HEAD`/`GET` | Request must have read permissions on {flowId}. | +| `/flows/{flowId}/tags/{name}` | `HEAD`/`GET` | Request must have read permissions on {flowId}. | +| | `PUT` | Request must have write permissions on {flowId}. | +| | `DELETE` | Request must have write permissions on {flowId}. | +| `/flows/{flowId}/description` | `HEAD`/`GET` | Request must have read permissions on {flowId}. | +| | `PUT` | Request must have write permissions on {flowId}. | +| | `DELETE` | Request must have write permissions on {flowId}. | +| `/flows/{flowId}/label` | `HEAD`/`GET` | Request must have read permissions on {flowId}. | +| | `PUT` | Request must have write permissions on {flowId}. | +| | `DELETE` | Request must have write permissions on {flowId}. | +| `/flows/{flowId}/read_only` | `HEAD`/`GET` | Request must have read permissions on {flowId}. | +| | `PUT` | Request must have write permissions on {flowId}. | +| `/flows/{flowId}/flow_collection` | `HEAD`/`GET` | Request must have read permissions on {flowId}. | +| | `PUT` | Request must have write permissions on {flowId}. | +| | `DELETE` | Request must have write permissions on {flowId}. | +| `/flows/{flowId}/max_bit_rate` | `HEAD`/`GET` | Request must have read permissions on {flowId}. | +| | `PUT` | Request must have write permissions on {flowId}. | +| | `DELETE` | Request must have write permissions on {flowId}. | +| `/flows/{flowId}/avg_bit_rate` | `HEAD`/`GET` | Request must have read permissions on {flowId}. | +| | `PUT` | Request must have write permissions on {flowId}. | +| | `DELETE` | Request must have write permissions on {flowId}. | +| `/flows/{flowId}/segments` | `HEAD`/`GET` | Request must have read permissions on {flowId}. | +| | `POST` | Request must have write permissions on {flowId}, and either this must be the first registration of the Media Object(s) (i.e. `/objects/{objectId}` returns 404) or the request must have read access to the Media Object(s) being written. Otherwise reject. | +| | `DELETE` | Request must have delete permissions on {flowId}. | +| `/flows/{flowId}/storage` | `POST` | Request must have write permissions on {flowId}. | +| `/objects/{objectId}` | `HEAD`/`GET` | Restrict returned data in `referenced_by_flows` property to only the Flows that the request has read access to. If the request has read access to no Flows of this object, return 404, however if the request has access but all of the Flows have been filtered out, return the response with an empty `referenced_by_flows` list. | +| `/objects/{objectId}/instances` | `POST` | Request must have write permissions on {objectId}. | +| | `DELETE` | Request must have write permissions on {objectId}. | +| `/flow-delete-requests` | `HEAD`/`GET` | Request must have admin permissions. | +| `/flow-delete-requests/{request-id}` | `HEAD`/`GET` | Request must have delete permissions on the Delete Request's Flow ID. | + +#### Handling rejected requests + +Where requests are rejected, they should return as follows: + +- `404` if the request has no permissions on the endpoint +- `403` if the request has any permission on the endpoint, but not sufficient to complete the request + +#### Creating new Flows and Sources + +Along with granting access to existing Flows and Sources, some method needs to exist to decide whether a user can _create_ an new Flow or Source. +Depending on the model in use, it may be a requirement to specify the permissions of that resource at creation time: for example setting an `auth_classes` tag to a subset of the user's own groups, and rejecting requests that do not include one. +Alternatively an implementation may provide some signalling of a default set of permissions that should be applied when none is given: for example a user could have a default group or project, or an approach where users can own content themselves, and then share it as needed. + +For systems that create resources on behalf of a user, the same approach could be taken, or placeholder resources could be created by the user with permissions assigned to that system: for example creating an empty Flow and then specifying the Flow ID to an ingester. + +#### Adding Flows to existing Sources + +New Sources inherit permissions from the first Flow which references them. +In order to prevent malicious actors adding maliciously crafted Flows to an existing Source, Flows using an existing Source ID SHOULD have write permissions on the Source. +This may be an impediment to some workflows, such as where dual-redundant ingesters capture the same Source. +Or where different teams within a business re-ingest the same Source in a different format. +Some deployments may choose to accept this risk and allow broader re-use of Sources. +Implementations may either apply default auth classes to Sources which will grant all users write permissions, or they may use more permissive auth logic. + +## Use cases and additional optional functionality + +### Providing access to a subset of a Flow's timerange + +The fine-grained authorisation model described above considers access control at the Source/Flow level. +Some use cases may require finer grained control. +This may be achieved by creating a new Flow with the relevant permissions that refers to the Objects of interest. +Caution should be taken where the boundary timestamps land partway through an Object. +Where the material around the boundaries is sensitive, new trimmed Objects should be created at the boundaries that remove content outside the permitted range. + +### Global read access + +Some organisations/implementations may choose to provide read access to all Sources and Flows to promote content re-use, and reduce the writing of duplicate content to the service instance. +Implementations may provide this feature by either adding default groups to Sources and Flows that provide appropriate read access to users, or by using more permissive auth logic. + +### Fine-grained authorisation and webhook events + +Implementations must evaluate permissions against webhook events themselves as well as the API's HTTP endpoints. +A basic implementation may enumerate Flows and Sources a user has access to when creating/updating the webhook and use this to filter events. +This approach is strongly discouraged as permissions may change over time. +It is recommended that implementations assess permissions on a per-event basis, and cache this information (with an appropriately short cache expiry time) in their internal implementation. + +### Permissions propagation + +A basic implementation could set permissions on a new Source with those in the Flow that results in its creation. +This is consistent with TAMS' general behaviour of populating Source metadata from Flows on creation. + +Some implementations may also find it useful to propagate changes of Source permissions to their Flows, and Source/Flow permissions down to Sources and Flows they collect. +For example, where Multi Source A collects Video Source B and Audio Source C, changes to permissions on Source A would be reflected on Sources B and C as well as the Flows of A, B, and C. +The propagation of these permissions may need to happen on write to avoid the need for potentially extensive tree traversal on read. +When changes are propagated, they must only be applied to resources where the request has the permission to edit those permissions. +Where propagation reaches a resource that the request doesn't have sufficient permissions to edit, the process will stop following that branch of the resource tree. +Propagation of permissions should only be performed after the successful modification of a parent resource. + +Propagation should also be triggered when a new Source/Flow is added to a Source/Flow collection, or when a Flow is added to an existing Source. + +### Deny permissions + +Implementations may wish to support auth classes and related auth logic that explicitly denies permissions against resources. +In these cases, a matching "deny" class takes precedent over an "allow" class. + +## Where to Enforce Authorisation + +Some consideration should be given for where to apply the authorisation step, depending on how TAMS is deployed and integrated. +For example a TAMS instance could be deployed with fine-grained authorisation support, and used directly by systems across an organisation. +In this case it would make sense to treat all clients of that TAMS instance as identical from an authentication/authorisation perspective: for example a user operating an NLE would be expected to provide suitable authorised credentials, but so too would the organisations MAM when it wants access to the service instance. + +Another deployment approach might see a MAM or other tool expose a TAMS API interface itself, which is proxied through to some simpler backing store. +In this case the MAM might manage and enforce other policies and rules around access to content, so it would make more sense to do the same in the TAMS API interface, and then use the MAM's own credentials to access the backing TAMS instance. + +## Implementation using Tags + +In practical TAMS solutions, implementing the [Finer Grained Authorisation](#finer-grained-authorisation) described above could look like defining an `auth_classes` tag, and assigning a "class" as a resource attribute for ABAC. + +An authenticating proxy then makes decisions based on those `auth_classes` and a request's OAuth claims (e.g. groups), along with the policies contained within a permissions system (or implemented by the proxy itself). +The authenticating proxy would need to take steps to prevent unauthorised modification of this special tag. +This approach should be considered experimental at this point. + +Consider a service instance shared by multiple teams from the News and Sport production teams of an organisation. +Each team have the ability to read and write their own content, and no access to the other team's content. +However in some cases it is necessary to share a particular Source (e.g. to work on a shared story) to the other team. + +| Resource | Auth classes | Comments | +| -------------- | ------------------ | --------------------------------------------------- | +| Source Sport A | `sport` | Sport have full access. News have no access. | +| Source Sport B | `sport` | Sport have full access. News have no access. | +| Source News X | `news`, `sport_ro` | News have full access. Sport have read access only. | +| Source News Y | `news` | News have full access. Sport have no access. | + +As a result, the process of authorising a request to a resource is: + +1. Authenticate the user's token using standard OAuth2 techniques (e.g. online, or using JWKS) +2. Read the user's claimed groups from their provided token +3. Read the list of auth classes assigned to the resource +4. Request a decision from the permissions system based on those data +5. (Write requests only): Check whether the request would modify the special `auth_classes` tag, and confirm the user has permission to make that modification. +6. (Flow Segment write requests only): Check if the Media Object is already referenced by a Flow Segment in the service instance using the `/objects` endpoint, and if it does, confirm the user would have access to read it +7. (Write requests only): Propagate any changes to the `auth_classes` tag to Flows and Sources collected by this one + +For listing endpoints (e.g. `GET /flows`) it may be possible to add a `tag.auth_classes` query parameter to the request, and have the upstream API filter Flows returned accordingly. +In cases where the incoming request already filters on the same tag, it will need to be rewritten to prevent returning content the user should not have access to. + +> [!CAUTION] +> Write request handling should be carefully designed and analysed to ensure that user's cannot modify the `auth_classes` tag in such a way to execute a permission elevation attack. + +Note that in some circumstances, requests may have to claim more permissions than may initially be assumed. +For example - when editing the `auth_classes` tag on a Source/Flow/webhook, requests must claim both write permissions and the permission they are changing. + +## Future Work + +The model described above allows for more use cases than coarse-grained RBAC: especially use cases where multiple tenants share a single service instance. +However it would be useful to allow more attributes to be used in rules: for example allowing write access to the tags of Sources/Flows but not other properties. + +One of the areas noted in [ADR0028: Authentication Methods](../adr/0028-authentication-methods.md) is being able to issue credentials restricted to a limited subset of Sources or Flows, which must also be supported by the authorisation system. +This could be achieved by issuing JWT bearer tokens using the [RFC9396 authorization details](https://www.rfc-editor.org/rfc/rfc9396.html#name-authorization-request) field to embed permissions granted directly into the token. +It would also allow for a suitable authenticating proxy to validate access without making a query to the authorisation server or permissions system, instead relying on the access claimed in the token, along with the cryptographic properties of the token itself.