From 20574b9fe19fa8a69ab6dd0c6434e1f8f11d3718 Mon Sep 17 00:00:00 2001 From: rorour Date: Fri, 25 Feb 2022 20:42:39 +0000 Subject: [PATCH] fix #3654: jupyterhub moderation requests --- etc/run-flash.sh | 9 +++ etc/run-jupyterhub.sh | 4 +- sirepo/auth/__init__.py | 17 +++- sirepo/auth/github.py | 75 +++++------------- sirepo/auth_db.py | 35 ++++++-- sirepo/auth_role.py | 8 +- sirepo/auth_role_moderation.py | 2 +- sirepo/db_upgrade.py | 2 +- sirepo/events.py | 2 +- sirepo/feature_config.py | 9 ++- sirepo/github_srunit.py | 6 +- sirepo/oauth.py | 70 ++++++++++++++++ .../static/json/schema-common.json | 1 + sirepo/pkcli/jupyterhublogin.py | 2 +- sirepo/sim_api/jupyterhublogin.py | 16 ++-- sirepo/sim_oauth/__init__.py | 18 +++++ sirepo/sim_oauth/flash.py | 43 ++++++++++ sirepo/uri_router.py | 19 ++++- tests/auth/email2_data/db/auth.db | Bin 57344 -> 57344 bytes 19 files changed, 250 insertions(+), 88 deletions(-) create mode 100644 etc/run-flash.sh create mode 100644 sirepo/oauth.py create mode 100644 sirepo/sim_oauth/__init__.py create mode 100644 sirepo/sim_oauth/flash.py diff --git a/etc/run-flash.sh b/etc/run-flash.sh new file mode 100644 index 0000000000..28ede33ac7 --- /dev/null +++ b/etc/run-flash.sh @@ -0,0 +1,9 @@ +#!/bin/bash +set -eou pipefail + +export SIREPO_FEATURE_CONFIG_PROPRIETARY_OAUTH_SIM_TYPES=flash +if [[ ! ${SIREPO_SIM_OAUTH_FLASH_KEY:-} || ! ${SIREPO_SIM_OAUTH_FLASH_SECRET:-} ]]; then + echo 'You must set $SIREPO_SIM_OAUTH_FLASH_KEY and $SIREPO_SIM_OAUTH_FLASH_SECRET' 1>&2 + exit 1 +fi +sirepo service http diff --git a/etc/run-jupyterhub.sh b/etc/run-jupyterhub.sh index 646f2ca34f..4e4f989477 100644 --- a/etc/run-jupyterhub.sh +++ b/etc/run-jupyterhub.sh @@ -39,9 +39,7 @@ elif [[ ! $SIREPO_AUTH_METHODS =~ 'email' ]]; then export SIREPO_AUTH_METHODS=$SIREPO_AUTH_METHODS:email fi -if [[ ${SIREPO_AUTH_GITHUB_KEY:-} || ${SIREPO_AUTH_GITHUB_SECRET:-} ]]; then - export SIREPO_AUTH_GITHUB_METHOD_VISIBLE=0 - export SIREPO_AUTH_METHODS="$SIREPO_AUTH_METHODS:github" +if [[ ${SIREPO_SIM_OAUTH_JUPYTERHUBLOGIN_KEY:-} || ${SIREPO_SIM_OAUTH_JUPYTERHUBLOGIN_SECRET:-} ]]; then export SIREPO_SIM_API_JUPYTERHUBLOGIN_RS_JUPYTER_MIGRATE=1 fi diff --git a/sirepo/auth/__init__.py b/sirepo/auth/__init__.py index d2d3d92af7..1667352895 100644 --- a/sirepo/auth/__init__.py +++ b/sirepo/auth/__init__.py @@ -360,6 +360,14 @@ def _moderate(uid, role): require_email_user() raise sirepo.util.SRException('moderationRequest', None) + def _oauth_redirect(role): + import sirepo.oauth + raise util.Redirect( + sirepo.oauth.create_authorize_redirect( + sirepo.auth_role.sim_type(role), + ) + ) + if sim_type not in sirepo.feature_config.auth_controlled_sim_types(): return u = _assert_login() @@ -368,9 +376,11 @@ def _moderate(uid, role): r = sirepo.auth_role.for_sim_type(sim_type) if auth_db.UserRole.has_role(u, r): return - if r not in sirepo.auth_role.for_moderated_sim_types(): - sirepo.util.raise_forbidden(f'uid={u} does not have access to sim_type={sim_type}') - _moderate(u, r) + elif r in sirepo.auth_role.for_proprietary_oauth_sim_types(): + _oauth_redirect(r) + if r in sirepo.auth_role.for_moderated_sim_types(): + _moderate(u, r) + sirepo.util.raise_forbidden(f'uid={u} does not have access to sim_type={sim_type}') def require_email_user(): @@ -661,6 +671,7 @@ def get_slack_uri(): return v + def _create_roles_for_new_user(uid, method): r = sirepo.auth_role.for_new_user(method == METHOD_GUEST) if r: diff --git a/sirepo/auth/github.py b/sirepo/auth/github.py index c61ff2320c..d6d1e5caa2 100644 --- a/sirepo/auth/github.py +++ b/sirepo/auth/github.py @@ -13,16 +13,10 @@ from sirepo import api_perm from sirepo import auth from sirepo import auth_db -from sirepo import cookie -from sirepo import feature_config -from sirepo import http_reply from sirepo import http_request -from sirepo import uri_router from sirepo import util -import authlib.integrations.requests_client -import authlib.oauth2.rfc6749.errors -import flask import sirepo.events +import sirepo.oauth import sqlalchemy @@ -34,32 +28,13 @@ #: Well known alias for auth UserModel = None -#: module handle -this_module = pkinspect.this_module() - -#: cookie keys for github (prefix is "srag") -_COOKIE_NONCE = 'sragn' -_COOKIE_SIM_TYPE = 'srags' - - @api_perm.allow_cookieless_set_user def api_authGithubAuthorized(): """Handle a callback from a successful OAUTH request. Tracks oauth users in a database. """ - # clear temporary cookie values first - s = cookie.unchecked_remove(_COOKIE_NONCE) - t = cookie.unchecked_remove(_COOKIE_SIM_TYPE) - oc = _client() - try: - oc.fetch_token( - authorization_response=flask.request.url, - state=s, - ) - except authlib.oauth2.rfc6749.errors.MismatchingStateException: - auth.login_fail_redirect(t, this_module, 'oauth-state', reload_js=True) - raise AssertionError('auth.login_fail_redirect returned unexpectedly') + oc, t = sirepo.oauth.check_authorized_callback(github_auth=True) d = oc.get('https://api.github.com/user').json() sirepo.events.emit('github_authorized', PKDict(user_name=d['login'])) with util.THREAD_LOCK: @@ -70,27 +45,25 @@ def api_authGithubAuthorized(): else: u = AuthGithubUser(oauth_id=d['id'], user_name=d['login']) u.save() - auth.login(this_module, model=u, sim_type=t, want_redirect=True) + auth.login( + pkinspect.this_module(), + model=u, + sim_type=t, + want_redirect=True, + ) raise AssertionError('auth.login returned unexpectedly') @api_perm.require_cookie_sentinel def api_authGithubLogin(simulation_type): """Redirects to Github""" - req = http_request.parse_params(type=simulation_type) - s = util.random_base62() - cookie.set_value(_COOKIE_NONCE, s) - cookie.set_value(_COOKIE_SIM_TYPE, req.type) - if not cfg.callback_uri: - # must be executed in an app and request context so can't - # initialize earlier. - cfg.callback_uri = uri_router.uri_for_api('authGithubAuthorized') - u, _ = _client().create_authorization_url( - 'https://github.com/login/oauth/authorize', - redirect_uri=cfg.callback_uri, - state=s, - ) - return http_reply.gen_redirect(u) + import sirepo.oauth + raise sirepo.util.Redirect(sirepo.oauth.create_authorize_redirect( + http_request.parse_params( + type=simulation_type, + ).type, + github_auth=True, + )) @api_perm.allow_cookieless_set_user @@ -106,19 +79,6 @@ def avatar_uri(model, size): ) -def _client(token=None): - """Makes it easier to mock, see github_srunit.py""" - # OAuth2Session doesn't inherit from OAuth2Mixin for some reason. - # So, supplying api_base_url has no effect. - return authlib.integrations.requests_client.OAuth2Session( - cfg.key, - cfg.secret, - scope='user:email', - token=token, - token_endpoint='https://github.com/login/oauth/access_token', - ) - - def _init(): def _init_model(base): """Creates User class bound to dynamic `db` variable""" @@ -134,6 +94,7 @@ class AuthGithubUser(base): global cfg, AUTH_METHOD_VISIBLE cfg = pkconfig.init( + authorize_url=('https://github.com/login/oauth/authorize', str, 'url to redirect to for authorization'), callback_uri=(None, str, 'Github callback URI (defaults to api_authGithubAuthorized)'), key=pkconfig.Required(str, 'Github key'), method_visible=( @@ -141,8 +102,12 @@ class AuthGithubUser(base): bool, 'github auth method is visible to users when it is an enabled method', ), + scope=('user:email', str, 'scope of data to request about user'), secret=pkconfig.Required(str, 'Github secret'), + token_endpoint=('https://github.com/login/oauth/access_token', str, 'url for obtaining access token') ) + cfg.callback_api = 'authGithubAuthorized' + AUTH_METHOD_VISIBLE = cfg.method_visible auth_db.init_model(_init_model) diff --git a/sirepo/auth_db.py b/sirepo/auth_db.py index d099be06c9..5daa01d684 100644 --- a/sirepo/auth_db.py +++ b/sirepo/auth_db.py @@ -4,8 +4,6 @@ :copyright: Copyright (c) 2018-2019 RadiaSoft LLC. All Rights Reserved. :license: http://www.apache.org/licenses/LICENSE-2.0.html """ -import sqlite3 - from pykern.pkcollections import PKDict from pykern.pkdebug import pkdc, pkdexc, pkdlog, pkdp import contextlib @@ -16,6 +14,7 @@ import sirepo.auth_role import sirepo.srcontext import sirepo.srdb +import sirepo.srtime import sirepo.util @@ -258,6 +257,7 @@ class UserRole(UserDbBase): __tablename__ = 'user_role_t' uid = sqlalchemy.Column(UserDbBase.STRING_ID, primary_key=True) role = sqlalchemy.Column(UserDbBase.STRING_NAME, primary_key=True) + expiration = sqlalchemy.Column(sqlalchemy.DateTime()) @classmethod def all_roles(cls): @@ -267,15 +267,28 @@ def all_roles(cls): ] @classmethod - def add_roles(cls, uid, roles): + def add_roles(cls, uid, role_or_roles, expiration=None): + if isinstance(role_or_roles, str): + role_or_roles = [role_or_roles] with sirepo.util.THREAD_LOCK: - for r in roles: + for r in role_or_roles: try: - UserRole(uid=uid, role=r).save() + UserRole(uid=uid, role=r, expiration=expiration).save() except sqlalchemy.exc.IntegrityError: pass audit_proprietary_lib_files(uid) + @classmethod + def add_role_or_update_expiration(cls, uid, role, expiration): + assert isinstance(role, str) + with sirepo.util.THREAD_LOCK: + if not cls.has_role(uid, role): + cls.add_roles(uid, role, expiration=expiration) + return + r = cls.search_by(uid=uid, role=role) + r.expiration = expiration + r.save() + @classmethod def delete_roles(cls, uid, roles): with sirepo.util.THREAD_LOCK: @@ -300,6 +313,18 @@ def has_role(cls, uid, role): with sirepo.util.THREAD_LOCK: return bool(cls.search_by(uid=uid, role=role)) + + @classmethod + def is_expired(cls, uid, role): + with sirepo.util.THREAD_LOCK: + assert cls.has_role(uid, role), \ + f'No role for uid={uid} and role={role}' + r = cls.search_by(uid=uid, role=role) + if not r.expiration: + # Roles with no expiration can't expire + return False + return r.expiration < sirepo.srtime.utc_now() + @classmethod def uids_of_paid_users(cls): return [ diff --git a/sirepo/auth_role.py b/sirepo/auth_role.py index 6b55c2e56b..c441e39216 100644 --- a/sirepo/auth_role.py +++ b/sirepo/auth_role.py @@ -4,8 +4,8 @@ :copyright: Copyright (c) 2021 RadiaSoft LLC. All Rights Reserved. :license: http://www.apache.org/licenses/LICENSE-2.0.html """ -from __future__ import absolute_import, division, print_function from pykern import pkconfig +from pykern.pkdebug import pkdp import sirepo.feature_config ROLE_ADM = 'adm' @@ -25,6 +25,10 @@ def for_new_user(is_guest): return [] +def for_proprietary_oauth_sim_types(): + return [for_sim_type(s) for s in sirepo.feature_config.cfg().proprietary_oauth_sim_types] + + def for_sim_type(sim_type): return _SIM_TYPE_ROLE_PREFIX + sim_type @@ -40,4 +44,4 @@ def get_all(): def sim_type(role): - return role[len(_SIM_TYPE_ROLE_PREFIX):] + return role[len(_SIM_TYPE_ROLE_PREFIX):] \ No newline at end of file diff --git a/sirepo/auth_role_moderation.py b/sirepo/auth_role_moderation.py index 0b6b4076d2..efdf5aa8a4 100644 --- a/sirepo/auth_role_moderation.py +++ b/sirepo/auth_role_moderation.py @@ -145,4 +145,4 @@ def init_apis(): global _cfg _cfg = pkconfig.init( moderator_email=pkconfig.Required(str, 'The email address to send moderation emails to'), - ) + ) \ No newline at end of file diff --git a/sirepo/db_upgrade.py b/sirepo/db_upgrade.py index 7ed645530d..b744d18e0f 100644 --- a/sirepo/db_upgrade.py +++ b/sirepo/db_upgrade.py @@ -171,7 +171,7 @@ def _20210301_migrate_role_jupyterhub(): r in sirepo.auth_db.UserRole.all_roles(): return for u in sirepo.auth_db.all_uids(): - sirepo.auth_db.UserRole.add_roles(u, [r]) + sirepo.auth_db.UserRole.add_roles(u, r) @contextlib.contextmanager diff --git a/sirepo/events.py b/sirepo/events.py index 6f9c38e0eb..d2a1de8a16 100644 --- a/sirepo/events.py +++ b/sirepo/events.py @@ -25,7 +25,7 @@ """ # Limit imports from pykern.pkcollections import PKDict -import aenum +from pykern.pkdebug import pkdp #: Map of events to handlers. Note: this is the list of all possible events. _MAP = PKDict( diff --git a/sirepo/feature_config.py b/sirepo/feature_config.py index d5b743e6e6..36f0de22bb 100644 --- a/sirepo/feature_config.py +++ b/sirepo/feature_config.py @@ -55,7 +55,7 @@ def auth_controlled_sim_types(): frozenset: enabled sim types that require role """ return frozenset( - cfg().proprietary_sim_types.union(cfg().moderated_sim_types), + cfg().proprietary_sim_types.union(cfg().moderated_sim_types, cfg().proprietary_oauth_sim_types), ) @@ -110,6 +110,7 @@ def b(msg, dev=False): hide_guest_warning=b('Hide the guest warning in the UI', dev=True), ), moderated_sim_types=(set(), set, 'codes where all users must be authorized via moderation'), + proprietary_oauth_sim_types=(set(), set, 'codes authorized through oauth'), jspec=dict( derbenevskrinsky_force_formula=b('Include Derbenev-Skrinsky force formula'), ), @@ -140,7 +141,11 @@ def b(msg, dev=False): PROD_FOSS_CODES if pkconfig.channel_in('prod') else FOSS_CODES ) ) - s.update(_cfg.proprietary_sim_types, _cfg.moderated_sim_types) + s.update( + _cfg.proprietary_sim_types, + _cfg.moderated_sim_types, + _cfg.proprietary_oauth_sim_types, + ) for v in _DEPENDENT_CODES: if v[0] in s: s.add(v[1]) diff --git a/sirepo/github_srunit.py b/sirepo/github_srunit.py index edbfbd1b7a..c0dd95534b 100644 --- a/sirepo/github_srunit.py +++ b/sirepo/github_srunit.py @@ -12,7 +12,7 @@ class MockOAuthClient(object): def __init__(self, monkeypatch, user_name='joeblow'): from pykern import pkcollections - from sirepo.auth import github + import sirepo.oauth self.values = PKDict({ 'access_token': 'xyzzy', @@ -22,7 +22,7 @@ def __init__(self, monkeypatch, user_name='joeblow'): login=user_name, ), }) - monkeypatch.setattr(github, '_client', self) + monkeypatch.setattr(sirepo.oauth, '_client', self) def __call__(self, *args, **kwargs): return self @@ -36,7 +36,7 @@ def create_authorization_url(self, *args, **kwargs): import sirepo.http_reply self.values.redirect_uri = kwargs['redirect_uri'] - self.values.state = kwargs['state'] + self.values.state = 'xxyyzz' return f'https://github.com/login/oauth/oauthorize?response_type=code&client_id={github.cfg.key}&redirect_uri={github.cfg.callback_uri}&state={self.values.state}', self.values.state def fetch_token(self, *args, **kwargs): diff --git a/sirepo/oauth.py b/sirepo/oauth.py new file mode 100644 index 0000000000..e265d97d81 --- /dev/null +++ b/sirepo/oauth.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- +u"""oauth for authentication and role moderation + +:copyright: Copyright (c) 2022 RadiaSoft LLC. All Rights Reserved. +:license: http://www.apache.org/licenses/LICENSE-2.0.html +""" +from pykern import pkinspect +from pykern.pkcollections import PKDict +from pykern.pkdebug import pkdc, pkdexc, pkdlog, pkdp +from sirepo import auth +from sirepo import cookie +import authlib.integrations.base_client +import authlib.integrations.requests_client +import flask +import sirepo.events +import sirepo.feature_config +import sirepo.sim_oauth +import sirepo.uri_router + + +#: cookie keys for oauth (prefix is "sroa") +_COOKIE_NONCE = 'sroan' +_COOKIE_SIM_TYPE = 'sroas' + + +def check_authorized_callback(github_auth=False): + # clear temporary cookie values first + s = cookie.unchecked_remove(_COOKIE_NONCE) + t = cookie.unchecked_remove(_COOKIE_SIM_TYPE) + assert t + c = _client(t, github_auth) + try: + c.fetch_token( + authorization_response=flask.request.url, + state=s, + ) + except authlib.oauth2.rfc6749.errors.MismatchingStateException: + auth.login_fail_redirect(t, pkinspect.caller_module(), 'oauth-state', reload_js=True) + raise AssertionError('auth.login_fail_redirect returned unexpectedly') + return c, t + + +def create_authorize_redirect(sim_type, github_auth=False): + cookie.set_value(_COOKIE_SIM_TYPE, sim_type) + c = _cfg(sim_type, github_auth) + u, s = _client(sim_type, github_auth).create_authorization_url( + c.authorize_url, + redirect_uri=c.callback_uri or sirepo.uri_router.uri_for_api(c.callback_api) + ) + cookie.set_value(_COOKIE_NONCE, s) + return u + + +def _cfg(sim_type, github_auth): + if github_auth: + # We are authenticating to sirepo using github oauth + from sirepo.auth import github + return github.cfg + # We are doing oauth for a sim type + return sirepo.sim_oauth.import_module(sim_type).cfg + + +def _client(sim_type, github_auth): + """Makes it easier to mock, see github_srunit.py""" + c = _cfg(sim_type, github_auth) + return authlib.integrations.requests_client.OAuth2Session( + c.key, + c.secret, + **c + ) diff --git a/sirepo/package_data/static/json/schema-common.json b/sirepo/package_data/static/json/schema-common.json index 0e6bbbb93b..946cdd2897 100644 --- a/sirepo/package_data/static/json/schema-common.json +++ b/sirepo/package_data/static/json/schema-common.json @@ -160,6 +160,7 @@ "saveSimulationData": "/save-simulation", "sbatchLogin": "/sbatch-login", "serverStatus": "/server-status", + "simOauthFlashAuthorized": "/sim-oauth-flash-authorized", "simulationData": "/simulation////?
", "simulationFrame": "/simulation-frame/", "simulationSchema": "/simulation-schema", diff --git a/sirepo/pkcli/jupyterhublogin.py b/sirepo/pkcli/jupyterhublogin.py index 6f3f3c03fa..6271428d27 100644 --- a/sirepo/pkcli/jupyterhublogin.py +++ b/sirepo/pkcli/jupyterhublogin.py @@ -74,5 +74,5 @@ def maybe_create_sirepo_user(module, email, display_name): ) with sirepo.auth.set_user_outside_of_http_request(u): n = sirepo.sim_api.jupyterhublogin.create_user(check_dir=True) - sirepo.auth_db.UserRole.add_roles(u, [sirepo.auth_role.for_sim_type('jupyterhublogin')]) + sirepo.auth_db.UserRole.add_roles(u, sirepo.auth_role.for_sim_type('jupyterhublogin')) return PKDict(email=email, jupyterhub_user_name=n) diff --git a/sirepo/sim_api/jupyterhublogin.py b/sirepo/sim_api/jupyterhublogin.py index 976f161cc7..4e516d089a 100644 --- a/sirepo/sim_api/jupyterhublogin.py +++ b/sirepo/sim_api/jupyterhublogin.py @@ -4,7 +4,6 @@ :copyright: Copyright (c) 2020 RadiaSoft LLC. All Rights Reserved. :license: http://www.apache.org/licenses/LICENSE-2.0.html """ -from __future__ import absolute_import, division, print_function from pykern import pkconfig, pkio from pykern.pkcollections import PKDict from pykern.pkdebug import pkdp, pkdlog, pkdexc @@ -15,6 +14,7 @@ import sirepo.events import sirepo.http_reply import sirepo.http_request +import sirepo.oauth import sirepo.srdb import sirepo.uri_router import sirepo.uri @@ -71,11 +71,11 @@ def api_migrateJupyterhub(): if not d.doMigration: create_user() return sirepo.http_reply.gen_redirect('jupyterHub') - return sirepo.uri_router.call_api( - 'authGithubLogin', - kwargs=PKDict(simulation_type='jupyterhublogin'), + raise sirepo.util.Redirect( + sirepo.oauth.create_authorize_redirect('jupyterhublogin', github_auth=True) ) + @sirepo.api_perm.require_user def api_redirectJupyterHub(): sirepo.auth.require_sim_type('jupyterhublogin') @@ -185,14 +185,14 @@ def init_apis(*args, **kwargs): ) pkio.mkdir_parent(cfg.user_db_root_d) sirepo.auth_db.init_model(_init_model) - sirepo.events.register(PKDict( - auth_logout=_event_auth_logout, - end_api_call=_event_end_api_call, - )) if cfg.rs_jupyter_migrate: sirepo.events.register(PKDict( github_authorized=_event_github_authorized, )) + sirepo.events.register(PKDict( + auth_logout=_event_auth_logout, + end_api_call=_event_end_api_call, + )) def _event_auth_logout(kwargs): diff --git a/sirepo/sim_oauth/__init__.py b/sirepo/sim_oauth/__init__.py new file mode 100644 index 0000000000..ffb605aa87 --- /dev/null +++ b/sirepo/sim_oauth/__init__.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +u"""Type agnostic OAuth operations + +:copyright: Copyright (c) 2016 RadiaSoft LLC. All Rights Reserved. +:license: http://www.apache.org/licenses/LICENSE-2.0.html +""" +import sirepo.util + +def import_module(type_or_data): + + """Load the simulation_type module + + Args: + type_or_data (str or dict): simulation type or description + Returns: + module: simulation type module instance + """ + return sirepo.util.import_submodule('sim_oauth', type_or_data) \ No newline at end of file diff --git a/sirepo/sim_oauth/flash.py b/sirepo/sim_oauth/flash.py new file mode 100644 index 0000000000..b408c703d3 --- /dev/null +++ b/sirepo/sim_oauth/flash.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +u"""API's for flash sim + +:copyright: Copyright (c) 2020 RadiaSoft LLC. All Rights Reserved. +:license: http://www.apache.org/licenses/LICENSE-2.0.html +""" +from pykern import pkconfig +from pykern.pkcollections import PKDict +from pykern.pkdebug import pkdp +from sirepo import api_perm +import datetime +import sirepo.auth +import sirepo.auth_db +import sirepo.auth_role +import sirepo.oauth +import sirepo.srtime +import sirepo.util + +cfg = None + +@api_perm.require_user +def api_simOauthFlashAuthorized(): + sirepo.oauth.check_authorized_callback() + sirepo.auth_db.UserRole.add_role_or_update_expiration( + sirepo.auth.logged_in_user(), + sirepo.auth_role.for_sim_type('flash'), + # TODO get expiration from returned response + expiration=None, + ) + raise sirepo.util.Redirect('flash') + + +def init_apis(): + global cfg + cfg = pkconfig.init( + authorize_url=('https://github.com/login/oauth/authorize', str, 'url to redirect to for authorization'), + callback_uri=(None, str, 'Flash callback URI (defaults to api_simOauthFlashAuthorized)'), + key=pkconfig.Required(str, 'OAuth key'), + scope=('user:email', str, 'scope of data to request about user'), + secret=pkconfig.Required(str, 'OAuth secret'), + token_endpoint=('https://github.com/login/oauth/access_token', str, 'url for obtaining access token') + ) + cfg.callback_api = 'simOauthFlashAuthorized' diff --git a/sirepo/uri_router.py b/sirepo/uri_router.py index 8138b317a8..35c9b4cdc3 100644 --- a/sirepo/uri_router.py +++ b/sirepo/uri_router.py @@ -135,6 +135,7 @@ def _api_modules(): for n in _api_modules(): register_api_module(importlib.import_module('sirepo.' + n)) _register_sim_api_modules() + _register_sim_oauth_modules(feature_config.cfg().proprietary_oauth_sim_types) _init_uris(app, simulation_db, feature_config.cfg().sim_types) sirepo.http_request.init( @@ -292,15 +293,27 @@ def _init_uris(app, simulation_db, sim_types): def _register_sim_api_modules(): + _register_sim_modules_from_package('sim_api') + + +def _register_sim_modules_from_package(package, valid_sim_types=None): + r = False for _, n, ispkg in pkgutil.iter_modules( - [os.path.dirname(sirepo.sim_api.__file__)], + [os.path.dirname(importlib.import_module(f'sirepo.{package}').__file__)], ): if ispkg: continue - if not sirepo.template.is_sim_type(n): + if not sirepo.template.is_sim_type(n) or \ + (valid_sim_types is not None and n not in valid_sim_types): pkdc(f'not adding apis for unknown sim_type={n}') continue - register_api_module(importlib.import_module(f'sirepo.sim_api.{n}')) + register_api_module(importlib.import_module(f'sirepo.{package}.{n}')) + r = True + return r + +def _register_sim_oauth_modules(oauth_sim_types): + if _register_sim_modules_from_package('sim_oauth', oauth_sim_types): + register_api_module(importlib.import_module('sirepo.oauth')) def _split_uri(uri): diff --git a/tests/auth/email2_data/db/auth.db b/tests/auth/email2_data/db/auth.db index 9f2aa7d3241b1b820a6657c848545e8e4538bc59..a50b46c93bfc635408a0c8a6872f0abc6ca74bff 100644 GIT binary patch delta 114 zcmZoTz}#?vd4jYc2Ll5G9}vR;>qH%6bq)qS?+Lv8KNxtqxET1}^KazY!MBDllTVHJ zG1p(7Vjey2mt0(2$2LxE;pA$x1SL{^)vP&MH delta 98 zcmZoTz}#?vd4jYcI|Bm)9}vR;%S0Vxb#?|lod90`9}K))@(g_M`8V?H;9J9&$*0Eq vnCmZ3F^?YiOD=h?W19s9W^!^hs_?LjD=RZL@lO`vs-B$9#kqMcSMXH;+bS04