[CEP] Improve granularity of API key access controls
See original GitHub issueAbstract
CommCare does not have any granularity in our user / role permission model to properly restrict API access. This CEP is to provide a mechanism to allow more fine-grained control over API access at the token level instead of relying on user-level controls only.
Motivation
Today CommCare has the notion of permissions at the user/project level. Users are provided access to projects (domains) and project administrators control the level of access granted to those users via the Roles and Permissions framework.
Additionally, users have API keys, which can be associated with a whitelisted set of IP addresses. API keys can also be deleted (revoked). API keys do not provide any granularity on top of the user’s permissions. All API keys grant access to whatever the user can access.
For a third-party integration ecosystem it’s important that we can properly scope API access to the most narrow set of rights as possible. External applications should be able to access the minimum set of data needed for those applications to function, and not all data available to the user.
Specification
This CEP seeks to add more fine-grained control over APIs.
The rest of this section documents permissions in the context of an API key, but the same should apply to an Oauth application, and any future externally integratable authentication mechanisms.
Types of access
It’s unclear which set of these controls we will need. At a minimum we likely need Domain-level access. The specifics of which types will be added will depend on emerging requirements and feasibility.
Identity Access
Identity access would simply let users confirm they are who they say they are, and potentially provide a few details, for example email address and name. It would not provide access to any underlying data.
Identity access would primarily be used for external authentication. For example, using something like OpenID Connect.
Domain-Level Access
API keys should be able to be scoped to particular projects. For example, a user should be able to say for a particular API key that it can access their data from one project, but not from others. This will allow users to safely scope the data permissions provided to the project spaces they seek to integrate with.
Permission-Level Access
In this model you could hook into the Roles and Permissions framework to associate the API key with a particular set of permissions. For example, you could create an API key that only provided access to application data but not report data or individual cases/forms. Or you could create roles that can read data but not modify it.
API-Level Access (likely ruled out for the first version)
This would be the most fine-grained option. In it, you could grant access to specific APIs, as well as choose the type of access (e.g. read/write). Any APIs not explicitly included would not be allowed.
Modeling
Since at a minimum we would like to share functionality between API keys and Oauth grants, these models should extend from some shared interface. We’ll call it a PermissionCollection
.
In YAML, this model would look something like this (this uses the “permission-level access” paradigm mentioned above, just as an example):
name: role1
domain_permissions:
domain1:
- view_apps
- view_reports
- edit_data
domain2:
- view_apps
In English, this would represent: the “role1” role has the “view_apps”, “view_reports”, and “edit_data” permissions on domain1, and the “view_apps” permission on “domain2”.
Then both API Keys (via HQApiKey) and Oauth Grants (likely via a new model) would extend this interface.
Permission Checks
Permission checks would apply everywhere that API-based authentication is supported (which is basically just APIs). We would start by building this functionality for tastypie endpoints only
Our current tastypie implementation conflates authentication and authorization, e.g. RequirePermissionAuthentication actually includes authorization checks built into the authentication workflow.
For this work it is recommended to decouple authentication from authorization. So the authentication class would simply verify the user account and the (new) authorization class would check permissions. In order to maintain authorization at the API Key level, some state will have to be maintained about how the user authenticated. The details of this need to be ironed out, but it should be possible to save the data e.g. on the request object.
Impact on users End users who make use of APIs will be able to have finer-grained control over how their API keys grant access than they currently do.
Impact on hosting No anticipated impact.
Backwards compatibility To ensure backwards compatibility all existing API keys must be granted “all” access either as a default behavior or via a data migration.
Release Timeline No concrete timeline as of yet.
Open questions and issues
- What’s the right type of access to provide?
- How do we handle API permission migrations if we make permissions more restrictive?
Issue Analytics
- State:
- Created 3 years ago
- Comments:16 (16 by maintainers)
Top GitHub Comments
I’m working on the UI for this. Basically adding (optional) “domain” and “role” selection boxes to the API key UI. Does anyone want to make the case that those options shouldn’t be generally available? Thinking I won’t bother with a feature flag.
cc @dimagi/product
Backwards compatibility does seem tough. This approach seems right:
I also agree with @esoergel that we’d benefit from using the existing roles and permissions framework. I’d imagine API permissions would eventually get more granular than our existing permissions, possibly to the point of granting access to specific endpoints, but I think ideally that granularity would be an additional layer that uses most of the same code but isn’t visible in the UI, rather than a totally separate framework. That’d hopefully let us share code, and it also seems better for users to only have one permissions model to comprehend.