From 2c9e54dfec3dba66a7d07b1f109d307fd59a35c4 Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Fri, 16 Feb 2024 15:42:27 +0000 Subject: [PATCH 1/4] authorisation: allow jupyter lab to work in standalone mode --- cylc/uiserver/authorise.py | 6 ++++++ cylc/uiserver/handlers.py | 27 +++++---------------------- cylc/uiserver/tests/conftest.py | 2 +- cylc/uiserver/utils.py | 29 +++++++++++++++++++++++++++++ 4 files changed, 41 insertions(+), 23 deletions(-) diff --git a/cylc/uiserver/authorise.py b/cylc/uiserver/authorise.py index 415913b7..57aa81b4 100644 --- a/cylc/uiserver/authorise.py +++ b/cylc/uiserver/authorise.py @@ -28,6 +28,7 @@ from traitlets.config.loader import LazyConfigValue from cylc.uiserver.schema import UISMutations +from cylc.uiserver.utils import is_bearer_token_authenticated class CylcAuthorizer(Authorizer): @@ -82,6 +83,11 @@ def is_authorized(self, handler, user, action, resource) -> bool: Note that Cylc uses its own authorization system (which is locked-down by default) and is not affected by this policy. """ + if is_bearer_token_authenticated(handler): + # this session is authenticated by a token or password NOT by + # Jupyter Hub -> the bearer of the token has full permissions + return True + # the username of the user running this server # (used for authorzation purposes) me = getuser() diff --git a/cylc/uiserver/handlers.py b/cylc/uiserver/handlers.py index 1ec87421..9b5c4b00 100644 --- a/cylc/uiserver/handlers.py +++ b/cylc/uiserver/handlers.py @@ -24,11 +24,6 @@ from graphql import get_default_backend from graphql_ws.constants import GRAPHQL_WS from jupyter_server.base.handlers import JupyterHandler -from jupyter_server.auth.identity import ( - User as JPSUser, - IdentityProvider as JPSIdentityProvider, - PasswordIdentityProvider, -) from tornado import web, websocket from tornado.ioloop import IOLoop @@ -38,12 +33,14 @@ ) from cylc.uiserver.authorise import Authorization, AuthorizationMiddleware +from cylc.uiserver.utils import is_bearer_token_authenticated from cylc.uiserver.websockets import authenticated as websockets_authenticated if TYPE_CHECKING: from cylc.uiserver.resolvers import Resolvers from cylc.uiserver.websockets.tornado import TornadoSubscriptionServer from graphql.execution import ExecutionResult + from jupyter_server.auth.identity import User as JPSUser ME = getpass.getuser() @@ -66,7 +63,7 @@ def _inner( **kwargs, ): nonlocal fun - user: JPSUser = handler.current_user + user: 'JPSUser' = handler.current_user if not user or not user.username: # the user is only truthy if they have authenticated successfully @@ -76,7 +73,7 @@ def _inner( # if authentication is turned off we don't want to work with this raise web.HTTPError(403, reason='authorization insufficient') - if is_token_authenticated(handler): + if is_bearer_token_authenticated(handler): # token or password authenticated, the bearer of the token or # password has full control pass @@ -90,20 +87,6 @@ def _inner( return _inner -def is_token_authenticated(handler: 'CylcAppHandler') -> bool: - """Returns True if this request is bearer token authenticated. - - E.G. The default single-user token-based authenticated. - - In these cases the bearer of the token is awarded full privileges. - """ - identity_provider: JPSIdentityProvider = ( - handler.serverapp.identity_provider # type: ignore[union-attr] - ) - return identity_provider.__class__ == PasswordIdentityProvider - # NOTE: not using isinstance to narrow this down to just the one class - - def _authorise( handler: 'CylcAppHandler', username: str @@ -139,7 +122,7 @@ def get_user_info(handler: 'CylcAppHandler'): If the handler is token authenticated, then we return the username of the account that this server instance is running under. """ - if is_token_authenticated(handler): + if is_bearer_token_authenticated(handler): # the bearer of the token has full privileges return {'name': ME, 'initials': get_initials(ME), 'username': ME} else: diff --git a/cylc/uiserver/tests/conftest.py b/cylc/uiserver/tests/conftest.py index bb1d512a..ee3451d2 100644 --- a/cylc/uiserver/tests/conftest.py +++ b/cylc/uiserver/tests/conftest.py @@ -211,7 +211,7 @@ def mock_authentication_yossarian(monkeypatch): user, ) monkeypatch.setattr( - 'cylc.uiserver.handlers.is_token_authenticated', + 'cylc.uiserver.handlers.is_bearer_token_authenticated', lambda x: True, ) diff --git a/cylc/uiserver/utils.py b/cylc/uiserver/utils.py index 41a90521..98f9cb66 100644 --- a/cylc/uiserver/utils.py +++ b/cylc/uiserver/utils.py @@ -14,6 +14,35 @@ # along with this program. If not, see . +from typing import TYPE_CHECKING + +from jupyter_server.auth.identity import PasswordIdentityProvider + +if TYPE_CHECKING: + from cylc.uiserver.handlers import CylcAppHandler + from jupyter_server.auth.identity import ( + IdentityProvider as JPSIdentityProvider, + ) + + +def is_bearer_token_authenticated(handler: 'CylcAppHandler') -> bool: + """Returns True if this request is bearer token authenticated. + + Bearer tokens, e.g. tokens (?token=1234) and passwords, are short pieces of + text that are used for authentication. These can be used in single-user + mode (i.e. "cylc gui"). In these cases the bearer of the token is awarded + full privileges. + + In multi-user mode, we have more advanced authentication based on an + external service which allows us to implement fine-grained authorisation. + """ + identity_provider: 'JPSIdentityProvider' = ( + handler.serverapp.identity_provider # type: ignore[union-attr] + ) + return identity_provider.__class__ == PasswordIdentityProvider + # NOTE: not using isinstance to narrow this down to just the one class + + def _repr(value): if isinstance(value, dict): return '' From d5343ee5511ab3926851ca745eaacf0b8870a3a3 Mon Sep 17 00:00:00 2001 From: Oliver Sanders Date: Thu, 4 Apr 2024 16:59:01 +0100 Subject: [PATCH 2/4] changelogs --- changes.d/558.fix.md | 1 + changes.d/570.fix.md | 1 + 2 files changed, 2 insertions(+) create mode 100644 changes.d/558.fix.md create mode 100644 changes.d/570.fix.md diff --git a/changes.d/558.fix.md b/changes.d/558.fix.md new file mode 100644 index 00000000..699f1676 --- /dev/null +++ b/changes.d/558.fix.md @@ -0,0 +1 @@ +Permit Jupyter Lab to be run in the same Jupyter Server instance as the Cylc UI Server in standalone mode (i.e. via `cylc gui`), note it was already possible to do this in multi-user mode (i.e. via `cylc hub`). diff --git a/changes.d/570.fix.md b/changes.d/570.fix.md new file mode 100644 index 00000000..ab57970a --- /dev/null +++ b/changes.d/570.fix.md @@ -0,0 +1 @@ +Fix an issue that could impose a low limit on the number of active workflows the server is able to track. From 9f2a0e6cc5efd9100a19b5b9d2d1c67196defac3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 5 Apr 2024 02:55:28 +0000 Subject: [PATCH 3/4] Prepare release 1.4.4 Workflow: Release stage 1 - create release PR, run: 27 --- CHANGES.md | 10 ++++++++++ changes.d/+4e284398.ui-version.md | 1 - changes.d/558.fix.md | 1 - changes.d/570.fix.md | 1 - cylc/uiserver/__init__.py | 2 +- 5 files changed, 11 insertions(+), 4 deletions(-) delete mode 100644 changes.d/+4e284398.ui-version.md delete mode 100644 changes.d/558.fix.md delete mode 100644 changes.d/570.fix.md diff --git a/CHANGES.md b/CHANGES.md index b4962abe..ec0203a4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,6 +12,16 @@ $ towncrier create ..md --content "Short description" +## cylc-uiserver-1.4.4 (Released 2024-04-05) + +[Updated cylc-ui to 2.4.0](https://github.com/cylc/cylc-ui/blob/master/CHANGES.md) + +### 🔧 Fixes + +[#558](https://github.com/cylc/cylc-uiserver/pull/558) - Permit Jupyter Lab to be run in the same Jupyter Server instance as the Cylc UI Server in standalone mode (i.e. via `cylc gui`), note it was already possible to do this in multi-user mode (i.e. via `cylc hub`). + +[#570](https://github.com/cylc/cylc-uiserver/pull/570) - Fix an issue that could impose a low limit on the number of active workflows the server is able to track. + ## cylc-uiserver-1.4.3 (Released 2023-12-05) ### 🔧 Fixes diff --git a/changes.d/+4e284398.ui-version.md b/changes.d/+4e284398.ui-version.md deleted file mode 100644 index 2f1b685e..00000000 --- a/changes.d/+4e284398.ui-version.md +++ /dev/null @@ -1 +0,0 @@ -Updated cylc-ui to 2.4.0 \ No newline at end of file diff --git a/changes.d/558.fix.md b/changes.d/558.fix.md deleted file mode 100644 index 699f1676..00000000 --- a/changes.d/558.fix.md +++ /dev/null @@ -1 +0,0 @@ -Permit Jupyter Lab to be run in the same Jupyter Server instance as the Cylc UI Server in standalone mode (i.e. via `cylc gui`), note it was already possible to do this in multi-user mode (i.e. via `cylc hub`). diff --git a/changes.d/570.fix.md b/changes.d/570.fix.md deleted file mode 100644 index ab57970a..00000000 --- a/changes.d/570.fix.md +++ /dev/null @@ -1 +0,0 @@ -Fix an issue that could impose a low limit on the number of active workflows the server is able to track. diff --git a/cylc/uiserver/__init__.py b/cylc/uiserver/__init__.py index e673287d..453bc9e7 100644 --- a/cylc/uiserver/__init__.py +++ b/cylc/uiserver/__init__.py @@ -13,7 +13,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -__version__ = "1.4.4.dev" +__version__ = "1.4.4" import os from typing import Dict From d5b28ab7288640a4949e1fe8be5c5915eb6c88b6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 5 Apr 2024 10:12:27 +0000 Subject: [PATCH 4/4] Bump dev version Workflow: Release stage 2 - auto publish, run: 51 --- cylc/uiserver/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cylc/uiserver/__init__.py b/cylc/uiserver/__init__.py index 453bc9e7..4314b2bd 100644 --- a/cylc/uiserver/__init__.py +++ b/cylc/uiserver/__init__.py @@ -13,7 +13,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -__version__ = "1.4.4" +__version__ = "1.4.5.dev" import os from typing import Dict