Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RESTful API: upload/delete #452

Merged
merged 24 commits into from
Feb 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
a1b1be5
Add two Groups for development/testing: test and test-private
tsibley Jan 24, 2022
3d60a08
Improve internal error message when upstream GET/HEAD fails
tsibley Jan 24, 2022
de6f2b8
Rename variable to make it clear it's a class not an instance
tsibley Jan 5, 2022
dd5847a
negotiate: Add contentTypesConsumed() for dispatch based on request C…
tsibley Nov 16, 2021
41018ed
Add API endpoints for uploading
tsibley Dec 1, 2021
ef13cd6
sources: Refactor subresource construction into Resource base class
tsibley Dec 8, 2021
930f5a5
Add API endpoints for deletion
tsibley Dec 8, 2021
fe2e5bd
Handle authz failures in the app-wide error handler
tsibley Jan 5, 2022
8baccbb
Only redirect for login on GET and HEAD requests
tsibley Jan 20, 2022
b980d09
Hone heuristic for "is this request browser-like?" in error handling
tsibley Jan 25, 2022
8514e55
Configure Express JSON serialization with basic support for Set and M…
tsibley Jan 6, 2022
3d85d16
Add RBAC authorization framework
tsibley Jan 5, 2022
e473621
authn: Assume the "viewers" role for legacy Group members
tsibley Jan 6, 2022
55660c1
Generate cacheable S3 signed URLs for internal requests
tsibley Jan 24, 2022
b4efdb9
docs: Remove speculation about the future that has not come to pass
tsibley Jan 25, 2022
ddc9296
docs: Setup Sphinx project to build docs
tsibley Jan 31, 2022
bac8f6a
docs: Retitle Charon API doc for better fit in the TOC
tsibley Jan 31, 2022
e9e4923
docs: Move RESTful API footnote on motivation into a proper section
tsibley Jan 31, 2022
7608c35
docs: Set default Sphinx domain and highlight language to JS
tsibley Feb 1, 2022
df8f24a
docs: Update glossary
tsibley Jan 31, 2022
4c266b4
docs: Describe the authz system
tsibley Jan 31, 2022
d2e3351
authz: Split policy evaluation from authorized()
tsibley Feb 2, 2022
cbc96fb
tests: Add authz tests
tsibley Feb 2, 2022
28edaee
Add some clarifying comments based on review feedback
tsibley Feb 10, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,6 @@ scratch

# test logs
/test/server.log

# docs build
/docs/build/
9 changes: 9 additions & 0 deletions .readthedocs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
version: 2

sphinx:
configuration: docs/conf.py

python:
install:
- requirements: docs/requirements.txt
20 changes: 20 additions & 0 deletions docs/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Minimal makefile for Sphinx documentation
#

# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = .
BUILDDIR = build

# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

.PHONY: help Makefile

# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
8 changes: 3 additions & 5 deletions docs/api-charon.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Data API between clients and nextstrain.org
# Charon API

The main data communications between clients (such as Auspice) and the nextstrain.org server occur at URLs beginning with `https://nextstrain.org/charon`.
Currently the only client accessing these is Auspice (more specifically, a nextstrain.org customised version of Auspice).
Expand All @@ -14,11 +14,9 @@ Each handler is defined in an file of the same name within `src`.

### Authorization

Each handler is responsible for checking authorization by calling a `Source` class method like so:
Each handler is responsible for checking authorization:
```js
if (!source.visibleToUser(req.user)) {
return helpers.unauthorized(req, res);
}
authz.assertAuthorized(req.user, authz.actions.Read, source);
```

## Tests
Expand Down
46 changes: 27 additions & 19 deletions docs/api-restful.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,30 +17,15 @@ The API's design tries to:
making it possible to maintain backwards compatibility for existing clients.

The initial functionality is focused on dataset and narrative management
endpoints to support the `nextstrain remote`_ family of commands.[#]_ In the
future, we intend to expand the functionality and make this API a foundation
for serving other software clients and user audiences.

.. [#] Development was motivated by the goal for `Nextstrain CLI`_ to make
requests to nextstrain.org using normal user login credentials instead
instead of making requests directly to S3 using separate, per-user AWS IAM
credentials. An alternative solution of using temporary AWS credentials
provisioned by an AWS Cognito Identity Pool seemed like a clear choice
given we're using Cognito User Pools for authentication, but it wasn't
feasible to appropriately scope the credentials for each group of users due
to limitations of resource tags and IAM policy tag matching.

Proxying through nextstrain.org also gives us a lot more power to make the
API easier for clients to work with (e.g. auto-compressing for them,
setting resource metadata, validating schemas to prevent bad uploads, etc)
and makes backend changes easier to coordinate since clients won't be
directly accessing the storage backend.
endpoints to support the `nextstrain remote`_ family of commands (see
motivation_). In the future, we intend to expand the functionality and make this
API a foundation for serving other software clients and user audiences.

.. _RESTful: https://restfulapi.net
.. _content negotiation: https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation
.. _nextstrain remote: https://docs.nextstrain.org/projects/cli/en/stable/commands/remote/
.. _Nextstrain CLI: https://docs.nextstrain.org/projects/cli/en/stable/

.. highlight:: none

Synopsis
========
Expand Down Expand Up @@ -277,3 +262,26 @@ The following narrative endpoints exist::
{GET, HEAD} /community/narratives/{user}/{repo}/*

{GET, HEAD} /fetch/narratives/*


.. _motivation:

Motivation
==========

Development was motivated by the goal for `Nextstrain CLI`_ to make requests to
nextstrain.org using normal user login credentials instead instead of making
requests directly to S3 using separate, per-user AWS IAM credentials. An
alternative solution of using temporary AWS credentials provisioned by an AWS
Cognito Identity Pool seemed like a clear choice given we're using Cognito User
Pools for authentication, but it wasn't feasible to appropriately scope the
credentials for each group of users due to limitations of resource tags and IAM
policy tag matching.

Proxying through nextstrain.org also gives us a lot more power to make the API
easier for clients to work with (e.g. auto-compressing for them, setting
resource metadata, validating schemas to prevent bad uploads, etc) and makes
backend changes easier to coordinate since clients won't be directly accessing
the storage backend.

.. _Nextstrain CLI: https://docs.nextstrain.org/projects/cli/en/stable/
133 changes: 133 additions & 0 deletions docs/authz.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
=============
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:

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 :term:`Source`.

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.


.. _roles:

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 :term:`Nextstrain group <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``.


.. _tags:

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).

.. _Set: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set

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
<https://tailscale.com/blog/rbac-like-it-was-meant-to-be/>`_".


.. _actions:

Actions
=======

Actions are defined in the ``authz.actions`` object. There are two available
actions: ``Read`` and ``Write``. In comparsion 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:

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 (i.e. from the object's ``Source`` currently). Otherwise, it returns
nothing.

It is the responsibility of the enforcement function to determine the policy in
force for the given object.
28 changes: 28 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""
Configuration file for the Sphinx documentation builder.

See <https://www.sphinx-doc.org/en/master/usage/configuration.html>.
"""

project = 'nextstrain.org'
copyright = '2022, Trevor Bedford and Richard Neher'
author = 'The Nextstrain Team'

primary_domain = 'js'
highlight_language = 'js'

default_role = 'literal'

html_theme = 'nextstrain-sphinx-theme'

extensions = [
'recommonmark',
'sphinx_markdown_tables',
'sphinx.ext.intersphinx',
]

intersphinx_mapping = {
'docs': ('https://docs.nextstrain.org/en/latest/', None),
'augur': ('https://docs.nextstrain.org/projects/augur/en/stable', None),
'auspice': ('https://docs.nextstrain.org/projects/auspice/en/stable', None),
}
102 changes: 0 additions & 102 deletions docs/glossary.md

This file was deleted.

Loading