Authorization

nextstrain.org implements a role-based access control (RBAC) system for authorizing who can view and manage resources likes datasets and narratives. The same system is used for browser-based visitors and API clients. In this system:

  • Users are members of roles.

  • Objects (e.g. datasets or narratives) have tags.

  • Policies contain rules which define an allowed set of actions for a given (role, tag) pair.

  • Enforcement is performed by calling authz.assertAuthorized(user, action, object).

Currently roles, tags, and policies are entirely system-defined and hardcoded. In the future, all three could be, in part, defined by users and stored/retrieved as needed.

The design of this system is influenced by “RBAC like it was meant to be”.

Policies

There is no single policy for all of nextstrain.org but different policies for different parts of the site. Currently, policies are defined for and attached to each Source and Group.

The design of the system allows for policies to be easily stacked or combined (e.g. concatenate all the rules), so if necessary we could introduce a global policy or other policy layers in the future.

Policies are arrays of objects, e.g.:

[
  { tag: authz.tags.Visibility.Public,
    role: "*",
    allow: [authz.actions.Read],
  },
]

All three keys are required:

tag:

authz.tags symbol, or "*" to impose no restriction on object tag.

role:

Name of role as a string, or "*" to impose no restriction on user role.

allow:

List of authz.actions symbols.

The example above allows any user (anonymous or logged in) to see objects marked public.

Roles and tags both ensure that policy rules are always many-to-many from the start, even if a role only contains a single user (e.g. a single Group owner) or a tag is only used for a single object. This property generally makes management simpler and more consistent.

User roles

A user’s roles are the names of the AWS Cognito groups of which they are a member. Anonymous users have no roles.

Roles for a Nextstrain group are based on the name of the group and name of the generic role within the group (viewers, editors, owners), e.g. blab/editors.

Object tags

Tags are defined in the authz.tags object. Objects which are passed to authz.assertAuthorized() must have an authzTags property that is a Set of tags. Objects may choose to explicitly inherit tags propagated from their container (e.g. a Resource object inheriting tags from its Source object).

By using tags on objects, access policies like:

The SciComm team should be able to read and write narratives but not datasets.

and:

The SARS-CoV-2 researchers should be able to read and write “ncov” datasets and narratives in our Group but not other datasets and narratives.

are expressible without requiring any changes to the policy syntax or authz system design. (The first is implementable with the current tags; the second would require a new system- or user-defined tag.)

If we implement user-defined tags, a good design to follow is the tag owner system described in “RBAC like it was meant to be”.

Actions

Actions are defined in the authz.actions object. There are two available actions: Read and Write. In comparison to the common CRUD model, Write encompasses create, update, and delete.

These two actions provide the only distinctions we need right now. If we need finer control in the future, we can split up Write and/or add new actions.

Enforcement

The main enforcement function used to guard access-controlled code is:

authz.assertAuthorized(user, action, object)

It throws an AuthzDenied exception if the user is not allowed to perform the action on the object as determined by the policy covering the object (e.g. from the object’s Source). Otherwise, it returns nothing.

It is the responsibility of the enforcement function to determine the policy in force for the given object.