def test_set_get(): from pykern import pkunit from pykern.pkunit import pkeq from pykern.pkdebug import pkdp from pykern import pkcollections from sirepo import cookie class _Response(pkcollections.Dict): def set_cookie(self, *args, **kwargs): self.args = args self.kwargs = kwargs cookie.process_header('x') with pkunit.pkexcept('KeyError'): cookie.get_value('hi') with pkunit.pkexcept('AssertionError'): cookie.set_value('hi', 'hello') pkeq(None, cookie.unchecked_get_value('hi')) cookie.init_mock() cookie.set_value('hi', 'hello') r = _Response(status_code=200) cookie.save_to_cookie(r) pkeq('sirepo_dev', r.args[0]) pkeq(False, r.kwargs['secure']) pkeq('hello', cookie.get_value('hi')) cookie.unchecked_remove('hi') pkeq(None, cookie.unchecked_get_value('hi')) cookie.process_header('sirepo_dev={}'.format(r.args[1])) pkeq('hello', cookie.get_value('hi'))
def logged_in_user(check_path=True): """Get the logged in user Args: check_path (bool): call `simulation_db.user_path` [True] Returns: str: uid of authenticated user """ u = _get_user() if not _is_logged_in(): raise util.SRException( 'login', None, 'user not logged in uid={}', u, ) assert u, \ 'no user in cookie: state={} method={}'.format( cookie.unchecked_get_value(_COOKIE_STATE), cookie.unchecked_get_value(_COOKIE_METHOD), ) if check_path: import sirepo.simulation_db sirepo.simulation_db.user_path(u, check=True) return u
def api_authState(): s = cookie.unchecked_get_value(_COOKIE_STATE) v = pkcollections.Dict( avatarUrl=None, displayName=None, needCompleteRegistration=s == _STATE_COMPLETE_REGISTRATION, isLoggedIn=_is_logged_in(s), method=cookie.unchecked_get_value(_COOKIE_METHOD), userName=None, visibleMethods=visible_methods, ) u = cookie.unchecked_get_value(_COOKIE_USER) if v.isLoggedIn: r = auth_db.UserRegistration.search_by(uid=u) if r: v.displayName = r.display_name _method_auth_state(v, u) if pkconfig.channel_in('dev'): # useful for testing/debugging v.uid = u return http_reply.render_static( 'auth-state', 'js', pkcollections.Dict(auth_state=v), )
def require_user(): s = cookie.unchecked_get_value(_COOKIE_STATE) e = None r = 'login' m = cookie.unchecked_get_value(_COOKIE_METHOD) p = None if s is None: e = 'no user in cookie' elif s == _STATE_LOGGED_IN: if m in cfg.methods: # Success return None u = _get_user() if m in cfg.deprecated_methods: e = 'deprecated' else: e = 'invalid' reset_state() e = 'auth_method={} is {}, forcing login: uid='.format(m, e, u) elif s == _STATE_LOGGED_OUT: e = 'logged out uid={}'.format(_get_user()) if m in cfg.deprecated_methods: # Force login to this specific method so we can migrate to valid method r = 'loginWith' p = {'method': m} elif s == _STATE_COMPLETE_REGISTRATION: r = 'completeRegistration' e = 'uid={} needs to complete registration'.format(_get_user()) else: cookie.reset_state('state={} invalid, cannot continue'.format(s)) e = 'invalid cookie' pkdlog('user not logged in: {}', e) return http_reply.gen_sr_exception(r, p)
def login(module, uid=None, model=None, sim_type=None, display_name=None, is_mock=False, want_redirect=False): """Login the user Raises an exception if successful, except in the case of methods Args: module (module): method module uid (str): user to login model (auth_db.UserDbBase): user to login (overrides uid) sim_type (str): app to redirect to """ _validate_method(module, sim_type=sim_type) guest_uid = None if model: uid = model.uid # if previously cookied as a guest, move the non-example simulations into uid below m = cookie.unchecked_get_value(_COOKIE_METHOD) if m == METHOD_GUEST and module.AUTH_METHOD != METHOD_GUEST: guest_uid = _get_user() if _is_logged_in() else None if uid: _login_user(module, uid) if module.AUTH_METHOD in cfg.deprecated_methods: pkdlog('deprecated auth method={} uid={}'.format(module.AUTH_METHOD, uid)) if not uid: # No user so clear cookie so this method is removed reset_state() # We are logged in with a deprecated method, and now the user # needs to login with an allowed method. login_fail_redirect(sim_type, module, 'deprecated', reload_js=not uid) if not uid: # No user in the cookie and method didn't provide one so # the user might be switching methods (e.g. github to email or guest to email). # Not allowed to go to guest from other methods, because there's # no authentication for guest. # Or, this is just a new user, and we'll create one. uid = _get_user() if _is_logged_in() else None m = cookie.unchecked_get_value(_COOKIE_METHOD) if uid and module.AUTH_METHOD not in (m, METHOD_GUEST): # switch this method to this uid (even for methods) # except if the same method, then assuming logging in as different user. # This handles the case where logging in as guest, creates a user every time _login_user(module, uid) else: uid = create_new_user(lambda u: _login_user(module, u), module) if model: model.uid = uid model.save() if display_name: complete_registration(_parse_display_name(display_name)) if is_mock: return if sim_type: if guest_uid and guest_uid != uid: import sirepo.simulation_db sirepo.simulation_db.move_user_simulations(guest_uid, uid) login_success_response(sim_type, want_redirect) assert not module.AUTH_METHOD_VISIBLE
def logged_in_user(): """Get the logged in user Returns: str: uid of authenticated user """ res = _get_user() if not _is_logged_in(): util.raise_unauthorized('user not logged in uid={}', res) assert res, 'no user in cookie: state={} method={}'.format( cookie.unchecked_get_value(_COOKIE_STATE), cookie.unchecked_get_value(_COOKIE_METHOD), ) return res
def is_login_expired(res=None): """If expiry is configured, check timestamp Args: res (hash): If a hash and return is True, will contain (uid, expiry, and now). Returns: bool: true if login is expired """ if not cfg.expiry_days: return False n = int(srtime.utc_now_as_float()) t = int(cookie.unchecked_get_value(_COOKIE_EXPIRY_TIMESTAMP, 0)) if n <= t: # cached timestamp less than expiry return False # db expiry at most one day from now so we can change expiry_days # and (in any event) ensure expiry is checked once a day. This # would also allow us to extend the expired period in the db. u = auth.logged_in_user() r = auth.user_registration(u) t = r.created + cfg.expiry_days n = srtime.utc_now() if n > t: if res is not None: res.update(uid=u, expiry=t, now=n) return True # set expiry in cookie t2 = n + _ONE_DAY if t2 < t: t = t2 t -= datetime.datetime.utcfromtimestamp(0) cookie.set_value(_COOKIE_EXPIRY_TIMESTAMP, int(t.total_seconds())) return False
def user_name(): u = getattr( _METHOD_MODULES[cookie.unchecked_get_value(_COOKIE_METHOD, )], 'UserModel', ) if u: with auth_db.thread_lock: return u.search_by(uid=logged_in_user()).user_name
def require_user(): e = None m = cookie.unchecked_get_value(_COOKIE_METHOD) p = None r = 'login' s = cookie.unchecked_get_value(_COOKIE_STATE) u = _get_user() if s is None: e = 'no user in cookie' elif s == _STATE_LOGGED_IN: if m in cfg.methods: f = getattr(_METHOD_MODULES[m], 'validate_login', None) if f: pkdc('validate_login method={}', m) f() return if m in cfg.deprecated_methods: e = 'deprecated' else: e = 'invalid' reset_state() p = PKDict(reload_js=True) e = 'auth_method={} is {}, forcing login: uid='.format(m, e, u) elif s == _STATE_LOGGED_OUT: e = 'logged out uid={}'.format(u) if m in cfg.deprecated_methods: # Force login to this specific method so we can migrate to valid method r = 'loginWith' p = PKDict({':method': m}) e = 'forced {}={} uid={}'.format(m, r, p) elif s == _STATE_COMPLETE_REGISTRATION: if m == METHOD_GUEST: pkdc('guest completeRegistration={}', u) complete_registration() return r = 'completeRegistration' e = 'uid={} needs to complete registration'.format(u) else: cookie.reset_state('uid={} state={} invalid, cannot continue'.format( s, u)) p = PKDict(reload_js=True) e = 'invalid cookie state={} uid={}'.format(s, u) pkdc('SRException uid={} route={} params={} method={} error={}', u, r, p, m, e) raise util.SRException(r, p, 'user not logged in: {}', e)
def _is_logged_in(state=None): """Logged in is either needing to complete registration or done Args: state (str): logged in state [None: from cookie] Returns: bool: is in one of the logged in states """ s = state or cookie.unchecked_get_value(_COOKIE_STATE) return s in (_STATE_COMPLETE_REGISTRATION, _STATE_LOGGED_IN)
def logged_in_user(): """Get the logged in user Returns: str: uid of authenticated user """ res = _get_user() if not _is_logged_in(): raise util.SRException( 'login', None, 'user not logged in uid={}', res, ) assert res, 'no user in cookie: state={} method={}'.format( cookie.unchecked_get_value(_COOKIE_STATE), cookie.unchecked_get_value(_COOKIE_METHOD), ) return res
def _op(): from sirepo import cookie from sirepo import auth pkeq(uid, auth._get_user()) for expect, key in cases: if expect is None: pkeq(False, cookie.has_key(key)) else: pkeq(expect, cookie.unchecked_get_value(key))
def user_if_logged_in(method): """Verify user is logged in and method matches Args: method (str): method must be logged in as """ if not _is_logged_in(): return None m = cookie.unchecked_get_value(_COOKIE_METHOD) if m != method: return None return _get_user()
def test_set_get(): from pykern import pkunit, pkcompat from pykern.pkunit import pkeq from pykern.pkdebug import pkdp from sirepo import cookie with cookie.process_header('x'): with pkunit.pkexcept('KeyError'): cookie.get_value('hi1') with pkunit.pkexcept('AssertionError'): cookie.set_value('hi2', 'hello') pkeq(None, cookie.unchecked_get_value('hi3'))
def user_name(): m = cookie.unchecked_get_value(_COOKIE_METHOD) u = getattr( _METHOD_MODULES[m], 'UserModel', ) if u: with util.THREAD_LOCK: return u.search_by(uid=logged_in_user()).user_name raise AssertionError( f'user_name not found for uid={logged_in_user()} with method={m}', )
def _auth_state(): import sirepo.simulation_db s = cookie.unchecked_get_value(_COOKIE_STATE) v = pkcollections.Dict( avatarUrl=None, displayName=None, guestIsOnlyMethod=not non_guest_methods, isGuestUser=False, isLoggedIn=_is_logged_in(s), isLoginExpired=False, jobRunModeMap=sirepo.simulation_db.JOB_RUN_MODE_MAP, method=cookie.unchecked_get_value(_COOKIE_METHOD), needCompleteRegistration=s == _STATE_COMPLETE_REGISTRATION, roles=[], userName=None, visibleMethods=visible_methods, ) if 'sbatch' in v.jobRunModeMap: v.sbatchQueueMaxes=job.NERSC_QUEUE_MAX u = cookie.unchecked_get_value(_COOKIE_USER) if v.isLoggedIn: if v.method == METHOD_GUEST: # currently only method to expire login v.displayName = _GUEST_USER_DISPLAY_NAME v.isGuestUser = True v.isLoginExpired = _METHOD_MODULES[METHOD_GUEST].is_login_expired() v.needCompleteRegistration = False v.visibleMethods = non_guest_methods else: r = auth_db.UserRegistration.search_by(uid=u) if r: v.displayName = r.display_name v.roles = auth_db.UserRole.get_roles(u) _plan(v) _method_auth_state(v, u) if pkconfig.channel_in_internal_test(): # useful for testing/debugging v.uid = u pkdc('state={}', v) return v
def complete_registration(name=None): """Update the database with the user's display_name and sets state to logged-in. Guests will have no name. """ u = _get_user() with auth_db.thread_lock: r = user_registration(u) if cookie.unchecked_get_value(_COOKIE_METHOD) is METHOD_GUEST: assert name is None, \ 'Cookie method is {} and name is {}. Expected name to be None'.format(METHOD_GUEST, name) r.display_name = name r.save() cookie.set_value(_COOKIE_STATE, _STATE_LOGGED_IN)
def _set_log_user(): if not _uwsgi: # Only works for uWSGI (service.uwsgi). sirepo.service.http uses # the limited http server for development only. This uses # werkzeug.serving.WSGIRequestHandler.log which hardwires the # common log format to: '%s - - [%s] %s\n'. Could monkeypatch # but we only use the limited http server for development. return u = _get_user() if u: u = cookie.unchecked_get_value(_COOKIE_STATE) + '-' + u else: u = '-' _app.uwsgi.set_logvar(_UWSGI_LOG_KEY_USER, u)
def api_authState(): s = cookie.unchecked_get_value(_COOKIE_STATE) v = pkcollections.Dict( avatarUrl=None, displayName=None, guestIsOnlyMethod=not non_guest_methods, isGuestUser=False, isLoggedIn=_is_logged_in(s), isLoginExpired=False, method=cookie.unchecked_get_value(_COOKIE_METHOD), needCompleteRegistration=s == _STATE_COMPLETE_REGISTRATION, userName=None, visibleMethods=visible_methods, ) u = cookie.unchecked_get_value(_COOKIE_USER) if v.isLoggedIn: if v.method == METHOD_GUEST: # currently only method to expire login v.displayName = _GUEST_USER_DISPLAY_NAME v.isGuestUser = True v.isLoginExpired = _METHOD_MODULES[METHOD_GUEST].is_login_expired() v.needCompleteRegistration = False v.visibleMethods = non_guest_methods else: r = auth_db.UserRegistration.search_by(uid=u) if r: v.displayName = r.display_name _method_auth_state(v, u) if pkconfig.channel_in('dev'): # useful for testing/debugging v.uid = u pkdc('state={}', v) return http_reply.render_static( 'auth-state', 'js', pkcollections.Dict(auth_state=v), )
def login(module, uid=None, model=None, sim_type=None, **kwargs): """Login the user Args: module (module): method module uid (str): user to login model (auth_db.UserDbBase): user to login (overrides uid) sim_type (str): app to redirect to Returns: flask.Response: reply object or None (if no sim_type) """ r = _validate_method(module, sim_type=sim_type) if r: return r if model: uid = model.uid if uid: _login_user(module, uid) if module.AUTH_METHOD in cfg.deprecated_methods: pkdlog('deprecated auth method={} uid={}'.format( module.AUTH_METHOD, uid)) if not uid: # No user so clear cookie so this method is removed reset_state() # We are logged in with a deprecated method, and now the user # needs to login with an allowed method. return login_fail_redirect(sim_type, module, 'deprecated') if not uid: # No user in the cookie and method didn't provide one so # the user might be switching methods (e.g. github to email or guest to email). # Not allowed to go to guest from other methods, because there's # no authentication for guest. # Or, this is just a new user, and we'll create one. uid = _get_user() if _is_logged_in() else None m = cookie.unchecked_get_value(_COOKIE_METHOD) if uid and module.AUTH_METHOD not in (m, _METHOD_GUEST): # switch this method to this uid (even for methods) # except if the same method, then assuming logging in as different user. # This handles the case where logging in as guest, creates a user every time _login_user(module, uid) else: uid = simulation_db.user_create(lambda u: _login_user(module, u)) if model: model.uid = uid model.save() if sim_type: return login_success_redirect(sim_type) # bluesky or basic return None
def test_cookie_outside_of_flask_request(): from pykern import pkcompat from pykern.pkunit import pkeq from sirepo import cookie from sirepo import srunit with srunit.auth_db_session(), \ cookie.set_cookie_outside_of_flask_request(): cookie.set_value('hi4', 'hello') r = _Response(status_code=200) cookie.save_to_cookie(r) pkeq('sirepo_dev', r.args[0]) pkeq(False, r.kwargs['secure']) pkeq('hello', cookie.get_value('hi4')) cookie.unchecked_remove('hi4') pkeq(None, cookie.unchecked_get_value('hi4')) # Nest cookie contexts with cookie.process_header( 'sirepo_dev={}'.format(pkcompat.from_bytes(r.args[1])), ): pkeq('hello', cookie.get_value('hi4'))
def _get_user(): return cookie.unchecked_get_value(_COOKIE_USER)