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

Configurable Authorization #204

Merged
merged 24 commits into from
Oct 20, 2021
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
41dd53d
flake8
datamel Sep 21, 2021
c738f1c
Merge branch 'master' of github.com:cylc/cylc-uiserver
datamel Oct 7, 2021
15b2444
Merge branch 'master' of github.com:cylc/cylc-uiserver
datamel Oct 11, 2021
076552f
Merge branch 'master' of github.com:cylc/cylc-uiserver
datamel Oct 15, 2021
e3e00ed
Initial Configurable Auth - Add authorise.py, Change log and Contribu…
datamel Aug 31, 2021
de5ac35
Add configuration for Auth, including docs and add add authorization …
datamel Aug 31, 2021
6506082
Fix await bug in resolvers
datamel Aug 31, 2021
be7d04d
Add authorisation logic
datamel Aug 31, 2021
5ae5ed0
add tests and replace isinstance with has attr
datamel Sep 2, 2021
3a0876d
convert user from string to dict
datamel Sep 7, 2021
1cb0f47
pass permissions to the ui
datamel Sep 7, 2021
91baadb
auth: test AuthorizationMiddleware lifecycle
oliver-sanders Sep 7, 2021
85453ec
- Generate operation list from schema
datamel Sep 24, 2021
af8e0ce
current user to context
datamel Oct 8, 2021
d7aef8f
format test_authorise.py
datamel Oct 11, 2021
94254b5
Refactor _authorise/can_read
datamel Oct 15, 2021
de5dd1f
Address Review Feedback from OS
datamel Oct 18, 2021
5be838e
Update cylc/uiserver/authorise.py
datamel Oct 19, 2021
4cd6133
Update cylc/uiserver/authorise.py
datamel Oct 19, 2021
b86a182
Update cylc/uiserver/authorise.py
datamel Oct 19, 2021
bd5acac
Update cylc/uiserver/authorise.py
hjoliver Oct 19, 2021
fddd8a5
Respond to feedback. Fix camelcase/snake case for ui.
datamel Oct 19, 2021
57fbd06
Merge branch 'authorisation-initial' of github.com:datamel/cylc-uiser…
datamel Oct 19, 2021
d579666
Update cylc/uiserver/authorise.py
hjoliver Oct 20, 2021
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
1 change: 1 addition & 0 deletions .mailmap
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ Bruno Kinoshita <kinow@users.noreply.github.com>
David Matthews <david.matthews@metoffice.gov.uk>
Hilary Oliver <hilary.j.oliver@gmail.com>
Sadie Bartholomew <30274190+sadielbartholomew@users.noreply.github.com>
Mel Hall <37735232+datamel@users.noreply.github.com> Melanie Hall
3 changes: 3 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ Multi-user functionality implemented.

### Enhancements

[#204](https://github.com/cylc/cylc-uiserver/pull/**204**) -
Implementation of configurable multi user authorisation.

[#230](https://github.com/cylc/cylc-uiserver/pull/230) -
Convert the UI Server to a jupyter_server extension.

Expand Down
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ below.
- Stephen McVeigh
- Ronnie Dutta
- David Matthews
- Mel Hall
hjoliver marked this conversation as resolved.
Show resolved Hide resolved
<!-- end-shortlog -->

(All contributors are identifiable with email addresses in the git version
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,8 @@ Currently the UI Server accepts these configurations:
* `c.CylcUIServer.ui_build_dir`
* `c.CylcUIServer.ui_version`
* `c.CylcUIServer.scan_iterval`
* `c.CylcUIServer.site_authorization`
* `c.CylcUIServer.user_authorization`

See the `cylc.uiserver.app.UIServer` file for details.

Expand Down
161 changes: 151 additions & 10 deletions cylc/uiserver/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

import getpass
import os
from pathlib import Path, PurePath
import sys
Expand All @@ -23,6 +24,7 @@
from tornado import ioloop
from tornado.web import RedirectHandler
from traitlets import (
Dict,
Float,
TraitError,
TraitType,
Expand All @@ -38,10 +40,13 @@
from cylc.flow.network.graphql import (
CylcGraphQLBackend, IgnoreFieldMiddleware
)

from cylc.uiserver import (
__file__ as uis_pkg
)
from cylc.uiserver.authorise import (
Authorization,
AuthorizationMiddleware
)
from cylc.uiserver.data_store_mgr import DataStoreMgr
from cylc.uiserver.handlers import (
CylcStaticHandler,
Expand Down Expand Up @@ -102,6 +107,103 @@ class CylcUIServer(ExtensionApp):
]
)
)
# TODO: Add a link to the access group table mappings in cylc documentation
AUTH_DESCRIPTION = '''
Authorization can be granted at operation (mutation) level, i.e.
specifically grant user access to execute Cylc commands, e.g.
``play``, ``pause``, ``edit``, ``trigger`` etc. For your
convenience, these operations have been mapped to access groups
``READ``, ``CONTROL`` and ``ALL``.

To remove permissions, prepend the access group or operation with
``!``.

Permissions are additive but negated permissions take precedence
above additions e.g. ``CONTROL, !stop`` will permit all operations
in the ``CONTROL`` group except for ``stop``.

.. note::

Any authorization permissions granted to a user will be
applied to all workflows.
'''

site_authorization = Dict(
config=True,
help='''
Dictionary containing site limits and defaults for authorization.
datamel marked this conversation as resolved.
Show resolved Hide resolved

This configuration should be placed only in the site set
configuration file and not the user configuration file (use
``c.CylcUIServer.user_authorization`` for user defined
authorization).
datamel marked this conversation as resolved.
Show resolved Hide resolved

If this configuration is empty, site authorization defaults to no
configurable authorization and users will be unable to set any
authorization.

''' + AUTH_DESCRIPTION + '''

.. rubric:: Example Configuration:

.. code-block:: python

c.CylcUIServer.site_authorization = {
"*": { # For all ui-server owners,
"*": { # Any authenticated user
"default": "READ", # Will have default read-only access
},
"user1": { # user1
"default": ["!ALL"], # No privileges for all ui-server
# owners.
}, # No limit set, so all ui-server owners
}, # limit is also "!ALL" for user1
"server_owner_1": { # For specific UI Server owner,
"group:group_a": { # Any user who is a member of group_a
"default": "READ", # Will have default read-only access
"limit": ["ALL", "!play"], # server_owner_1 is able to
}, # grant All privileges, except play.
},
"group:grp_of_svr_owners": { # Group of owners of UI Servers
"group:group_b": {
"limit": [ # can grant groupB users up to READ and
"READ", # CONTROL privileges, without stop and
"CONTROL", # kill
"!stop",
"!kill", # No default, so default is no access
],
},
},
}

''')

user_authorization = Dict(
config=True,
help='''
datamel marked this conversation as resolved.
Show resolved Hide resolved
Dictionary containing authorized users and permission levels for
authorization.

Use this setting to share control of your workflows
with other users.
Note that you are only permitted to give away permissions up to
your limit for each user, as defined in the site_authorization
configuration.
''' + AUTH_DESCRIPTION + '''
Example configuration, residing in
``~/.cylc/hub/jupyter_config.py``:

.. code-block:: python
datamel marked this conversation as resolved.
Show resolved Hide resolved

c.CylcUIServer.user_authorization = {
"*": ["READ"], # any authenticated user has READ access
"group:group2": ["ALL"], # Any user in system group2 has access
# to all operations
"userA": ["ALL", "!stop"], # userA has ALL operations, not stop
}

'''
)

ui_path = PathType(
config=False,
Expand Down Expand Up @@ -189,6 +291,14 @@ def _check_ui_build_dir_exists(self, proposed):
return proposed['value']
raise TraitError(f'ui_build_dir does not exist: {proposed["value"]}')

@validate('site_authorization')
def _check_site_auth_dict_correct_format(self, proposed):
# TODO: More advanced auth dict validating
hjoliver marked this conversation as resolved.
Show resolved Hide resolved
if isinstance(proposed['value'], dict):
return proposed['value']
raise TraitError(
f'Error in site authorization config: {proposed["value"]}')

@staticmethod
def _list_ui_versions(path: Path) -> List[str]:
"""Return a list of UI build versions detected in self.ui_path."""
Expand Down Expand Up @@ -243,12 +353,6 @@ def __init__(self, *args, **kwargs):
log=self.log,
workflows_mgr=self.workflows_mgr,
)
self.subscription_server = TornadoSubscriptionServer(
schema,
backend=CylcGraphQLBackend(),
middleware=[IgnoreFieldMiddleware],
)

ioloop.IOLoop.current().add_callback(
self.workflows_mgr.update
)
Expand Down Expand Up @@ -277,16 +381,27 @@ def initialize_settings(self):
).start()

def initialize_handlers(self):
self.authobj = self.set_auth()
self.set_sub_server()

self.handlers.extend([
('cylc/version', CylcVersionHandler),
(
'cylc/version',
CylcVersionHandler,
{'auth': self.authobj}
),
(
'cylc/graphql',
UIServerGraphQLHandler,
{
'schema': schema,
'resolvers': self.resolvers,
'backend': CylcGraphQLBackend(),
'middleware': [IgnoreFieldMiddleware],
'middleware': [
AuthorizationMiddleware,
IgnoreFieldMiddleware
],
'auth': self.authobj,
}
),
(
Expand All @@ -296,8 +411,12 @@ def initialize_handlers(self):
'schema': schema,
'resolvers': self.resolvers,
'backend': CylcGraphQLBackend(),
'middleware': [IgnoreFieldMiddleware],
'middleware': [
AuthorizationMiddleware,
IgnoreFieldMiddleware
],
'batch': True,
'auth': self.authobj,
}
),
(
Expand All @@ -311,6 +430,7 @@ def initialize_handlers(self):
(
'cylc/userprofile',
UserProfileHandler,
{'auth': self.authobj}
),
(
'cylc/(.*)?',
Expand All @@ -330,6 +450,27 @@ def initialize_handlers(self):
)
])

def set_sub_server(self):
self.subscription_server = TornadoSubscriptionServer(
schema,
backend=CylcGraphQLBackend(),
middleware=[
IgnoreFieldMiddleware,
AuthorizationMiddleware,
],
auth=self.authobj
)

def set_auth(self):
"""Create authorization object.
One for the lifetime of the UIServer.
"""
return Authorization(
getpass.getuser(),
self.config.CylcUIServer.user_authorization,
self.config.CylcUIServer.site_authorization
)

def initialize_templates(self):
"""Change the jinja templating environment."""

Expand Down
Loading