def auth(self): # Start with an authentication request # The client ID appears in the request AUTH_REQ = AuthorizationRequest( client_id="client_1", redirect_uri="https://example.com/cb", scope=["openid", "mail", "address", "offline_access"], state="STATE", response_type="code", ) # The authentication returns a user ID user_id = "diana" # User info is stored in the Session DB authn_event = create_authn_event( user_id, authn_info=INTERNETPROTOCOLPASSWORD, authn_time=time_sans_frac(), ) user_info = UserSessionInfo(user_id=user_id) self.session_manager.set([user_id], user_info) # Now for client session information client_id = AUTH_REQ["client_id"] client_info = ClientSessionInfo(client_id=client_id) self.session_manager.set([user_id, client_id], client_info) # The user consent module produces a Grant instance grant = Grant( scope=AUTH_REQ["scope"], resources=[client_id], authorization_request=AUTH_REQ, authentication_event=authn_event, ) # the grant is assigned to a session (user_id, client_id) self.session_manager.set([user_id, client_id, grant.id], grant) session_id = self.session_manager.encrypted_session_id( user_id, client_id, grant.id) # Constructing an authorization code is now done by code = grant.mint_token( session_id=session_id, endpoint_context=self.endpoint_context, token_class="authorization_code", token_handler=self.session_manager. token_handler["authorization_code"], expires_at=time_sans_frac() + 300, # 5 minutes from now ) # get user info user_info = self.session_manager.get_user_info(uid=user_id, ) return grant.id, code
def test_authn_event(): an = AuthnEvent( uid="uid", valid_until=time_sans_frac() + 1, authn_info="authn_class_ref", ) assert an.is_valid() n = time_sans_frac() + 3 assert an.is_valid(n) is False n = an.expires_in() assert n == 1 # could possibly be 0
def __call__(self, session_id: Optional[str] = "", token_class: Optional[str] = "", **payload) -> str: """ Return a token. :param payload: Token information :return: """ if not token_class and self.token_class: token_class = self.token_class else: token_class = "authorization_code" if self.lifetime >= 0: exp = str(time_sans_frac() + self.lifetime) else: exp = "-1" # Live for ever tmp = "" rnd = "" while rnd == tmp: # Don't use the same random value again rnd = rndstr(32) # Ultimate length multiple of 16 return base64.b64encode( self.crypt.encrypt( lv_pack(rnd, token_class, session_id, exp).encode())).decode("utf-8")
def process_request(self, request=None, **kwargs): _sdb = self.endpoint_context.sdb # should be an access token if not _sdb.is_token_valid(request["access_token"]): return self.error_cls(error="invalid_token", error_description="Invalid Token") session = _sdb.read(request["access_token"]) allowed = True # if the authenticate is still active or offline_access is granted. if session["authn_event"]["valid_until"] > time_sans_frac(): pass else: if "offline_access" in session["authn_req"]["scope"]: pass else: allowed = False if allowed: # Scope can translate to userinfo_claims info = collect_user_info(self.endpoint_context, session) else: info = { "error": "invalid_request", "error_description": "Offline access not granted", } return { "response_args": info, "client_id": session["authn_req"]["client_id"] }
def create_authn_event(uid, salt, authn_info=None, **kwargs): """ :param uid: :param salt: :param authn_info: :param kwargs: :return: """ args = {'uid': uid, 'salt':salt, 'authn_info': authn_info} try: args['authn_time'] = int(kwargs['authn_time']) except KeyError: try: args['authn_time'] = int(kwargs['timestamp']) except KeyError: args['authn_time'] = time_sans_frac() try: args['valid_until'] = kwargs['valid_until'] except KeyError: try: args['valid_until'] = args['authn_time'] + kwargs['expires_in'] except KeyError: args['valid_until'] = args['authn_time'] + 3600 return AuthnEvent(**args)
def test_time_stamp(): now = time_sans_frac() iso = to_iso8601_2004_time() d = from_iso8601_2004_time(iso) assert now == d
def __call__(self, sid='', ttype='', **kwargs): """ Return a token. :param ttype: Type of token :param prev: Previous token, if there is one to go from :param sid: Session id :return: """ if not ttype and self.type: ttype = self.type else: ttype = 'A' if self.lifetime >= 0: exp = str(time_sans_frac() + self.lifetime) else: exp = '-1' # Live for ever tmp = '' rnd = '' while rnd == tmp: # Don't use the same random value again rnd = rndstr(32) # Ultimate length multiple of 16 return base64.b64encode( self.crypt.encrypt(lv_pack(rnd, ttype, sid, exp).encode())).decode("utf-8")
def create_authn_event(uid, salt, authn_info=None, **kwargs): """ :param uid: :param salt: :param authn_info: :param kwargs: :return: """ args = {"uid": uid, "salt": salt, "authn_info": authn_info} try: args["authn_time"] = int(kwargs["authn_time"]) except KeyError: try: args["authn_time"] = int(kwargs["timestamp"]) except KeyError: args["authn_time"] = time_sans_frac() try: args["valid_until"] = kwargs["valid_until"] except KeyError: try: args["valid_until"] = args["authn_time"] + kwargs["expires_in"] except KeyError: args["valid_until"] = args["authn_time"] + 3600 return AuthnEvent(**args)
def is_expired(exp, when=0): if exp < 0: return False if not when: when = time_sans_frac() return when > exp
def test_is_expired(self): session_id = self._create_session(AUTH_REQ) grant = self.session_manager[session_id] code = self._mint_token("authorization_code", grant, session_id) access_token = self._mint_token("access_token", grant, session_id, code) assert access_token.is_active() # 4000 seconds in the future. Passed the lifetime. assert access_token.is_active(now=time_sans_frac() + 4000) is False
def _mint_code(self, grant, session_id): # Constructing an authorization code is now done return grant.mint_token( session_id=session_id, endpoint_context=self.endpoint_context, token_class="authorization_code", token_handler=self.session_manager. token_handler["authorization_code"], expires_at=time_sans_frac() + 300, # 5 minutes from now )
def _mint_access_token(self, grant, session_id, token_ref): access_token = grant.mint_token( session_id=session_id, endpoint_context=self.endpoint_context, token_class="access_token", token_handler=self.session_manager.token_handler["access_token"], expires_at=time_sans_frac() + 900, # 15 minutes from now based_on= token_ref, # Means the token (tok) was used to mint this token ) return access_token
def _mint_token(self, token_class, grant, session_id, based_on=None): # Constructing an authorization code is now done return grant.mint_token( session_id=session_id, endpoint_context=self.endpoint_context, token_class=token_class, token_handler=self.session_manager.token_handler. handler[token_class], expires_at=time_sans_frac() + 300, # 5 minutes from now based_on=based_on, )
def _mint_token(self, token_class, grant, session_id, token_ref=None): _session_info = self.session_manager.get_session_info(session_id, grant=True) return grant.mint_token( session_id=session_id, endpoint_context=self.endpoint.server_get("endpoint_context"), token_class=token_class, token_handler=self.session_manager.token_handler[token_class], expires_at=time_sans_frac() + 900, # 15 minutes from now based_on= token_ref, # Means the token (tok) was used to mint this token )
def _mint_token( self, token_class: str, grant: Grant, session_id: str, client_id: str, based_on: Optional[SessionToken] = None, scope: Optional[list] = None, token_args: Optional[dict] = None, token_type: Optional[str] = "" ) -> SessionToken: _context = self.endpoint.server_get("endpoint_context") _mngr = _context.session_manager usage_rules = grant.usage_rules.get(token_class) if usage_rules: _exp_in = usage_rules.get("expires_in") else: _exp_in = 0 token_args = token_args or {} for meth in _context.token_args_methods: token_args = meth(_context, client_id, token_args) if token_args: _args = {"token_args": token_args} else: _args = {} token = grant.mint_token( session_id, endpoint_context=_context, token_class=token_class, token_handler=_mngr.token_handler[token_class], based_on=based_on, usage_rules=usage_rules, scope=scope, token_type=token_type, **_args, ) if _exp_in: if isinstance(_exp_in, str): _exp_in = int(_exp_in) if _exp_in: token.expires_at = time_sans_frac() + _exp_in _context.session_manager.set(_context.session_manager.unpack_session_key(session_id), grant) return token
def _mint_id_token(self, grant, session_id, token_ref=None, code=None, access_token=None): return grant.mint_token( session_id=session_id, endpoint_context=self.endpoint_context, token_class="id_token", token_handler=self.session_manager.token_handler["id_token"], expires_at=time_sans_frac() + 900, # 15 minutes from now based_on= token_ref, # Means the token (tok) was used to mint this token code=code, access_token=access_token, )
def get_valid_access_token(self, state): """ Find me a valid access token :param state: :return: An access token if a valid one exists and when it expires. Otherwise raise exception. """ exp = 0 token = None indefinite = [] now = time_sans_frac() for cls, typ in [(AccessTokenResponse, 'refresh_token_response'), (AccessTokenResponse, 'token_response'), (AuthorizationResponse, 'auth_response')]: try: response = self.session_interface.get_item(cls, typ, state) except KeyError: pass else: try: access_token = response['access_token'] except: continue else: try: _exp = response['__expires_at'] except KeyError: # No expiry date, lives for ever indefinite.append((access_token, 0)) else: if _exp > now: # expires sometime in the future if _exp > exp: exp = _exp token = (access_token, _exp) if indefinite: return indefinite[0] else: if token: return token else: raise OidcServiceError('No valid access token')
def has_active_authentication(self, state): """ Find out if the user has an active authentication :param state: :return: True/False """ # Look for Id Token in all the places where it can be _arg = self.session_interface.multiple_extend_request_args( {}, state, ['__verified_id_token'], ['auth_response', 'token_response', 'refresh_token_response']) if _arg: _now = time_sans_frac() exp = _arg['__verified_id_token']['exp'] return _now < exp else: return False
def update_service_context(self, resp, key='', **kwargs): try: _idt = resp[verified_claim_name('id_token')] except KeyError: pass else: # If there is a verified ID Token then we have to do nonce # verification try: if self.get_state_by_nonce(_idt['nonce']) != key: raise ParameterError('Someone has messed with "nonce"') except KeyError: raise ValueError('Missing nonce value') self.store_sub2state(_idt['sub'], key) if 'expires_in' in resp: resp['__expires_at'] = time_sans_frac() + int(resp['expires_in']) self.store_item(resp.to_json(), 'auth_response', key)
def is_active(self, now=0): if self.max_usage_reached(): return False if self.revoked: return False if now == 0: now = time_sans_frac() if self.not_before: if now < self.not_before: return False if self.expires_at: if now > self.expires_at: return False return True
def update_service_context(self, resp, key='', **kwargs): try: _idt = resp[verified_claim_name('id_token')] except KeyError: pass else: try: if self.get_state_by_nonce(_idt['nonce']) != key: raise ParameterError('Someone has messed with "nonce"') except KeyError: raise ValueError('Invalid nonce value') self.store_sub2state(_idt['sub'], key) if 'expires_in' in resp: resp['__expires_at'] = time_sans_frac() + int( resp['expires_in']) self.store_item(resp, 'token_response', key)
def create_authn_event(uid, authn_info=None, authn_time: int = 0, valid_until: int = 0, expires_in: int = 0, sub: str = "", **kwargs): """ :param uid: User ID. This is the identifier used by the user DB :param authn_time: When the authentication took place :param authn_info: Information about the authentication :param valid_until: Until when the authentication is valid :param expires_in: How long before the authentication expires :param sub: Subject identifier. The identifier for the user used between the AS and the RP. :param kwargs: :return: """ args = {"uid": uid, "authn_info": authn_info} if sub: args["sub"] = sub if authn_time: args["authn_time"] = authn_time else: _ts = kwargs.get("timestamp") if _ts: args["authn_time"] = _ts else: args["authn_time"] = time_sans_frac() if valid_until: args["valid_until"] = valid_until else: if expires_in: args["valid_until"] = args["authn_time"] + expires_in else: args["valid_until"] = args["authn_time"] + DEFAULT_AUTHN_EXPIRES_IN return AuthnEvent(**args)
def test_invalid_token(self): _auth_req = AUTH_REQ.copy() _auth_req["scope"] = ["openid", "research_and_scholarship"] session_id = self._create_session(_auth_req) grant = self.session_manager[session_id] access_token = self._mint_token("access_token", grant, session_id) http_info = { "headers": { "authorization": "Bearer {}".format(access_token.value) } } _req = self.endpoint.parse_request({}, http_info=http_info) access_token.expires_at = time_sans_frac() - 10 args = self.endpoint.process_request(_req) assert isinstance(args, ResponseMessage) assert args["error_description"] == "Invalid Token"
def __init__( self, usage_rules: Optional[dict] = None, issued_at: int = 0, expires_in: int = 0, expires_at: int = 0, not_before: int = 0, revoked: bool = False, used: int = 0, ): ImpExp.__init__(self) self.issued_at = issued_at or time_sans_frac() self.not_before = not_before if expires_at == 0 and expires_in != 0: self.set_expires_at(expires_in) else: self.expires_at = expires_at self.revoked = revoked self.used = used self.usage_rules = usage_rules or {}
def is_valid(self, now=0): if now: return self["valid_until"] > now else: return self["valid_until"] > time_sans_frac()
def expires_in(self): return self["valid_until"] - time_sans_frac()
def test_code_flow(self): # code is a Token instance code = self.auth() # next step is access token request TOKEN_REQ = AccessTokenRequest( client_id="client_1", redirect_uri="https://example.com/cb", state="STATE", grant_type="authorization_code", client_secret="hemligt", code=code.value, ) # parse the token session_id = self.session_manager.token_handler.sid(TOKEN_REQ["code"]) user_id, client_id, grant_id = self.session_manager.decrypt_session_id( session_id) # Now given I have the client_id from the request and the user_id from the # token I can easily find the grant # client_info = self.session_manager.get([user_id, TOKEN_REQ['client_id']]) tok = self.session_manager.find_token(session_id, TOKEN_REQ["code"]) # Verify that it's of the correct type and can be used assert tok.token_class == "authorization_code" assert tok.is_active() # Mint an access token and a refresh token and mark the code as used assert tok.supports_minting("access_token") client_info = self.session_manager.get( [user_id, TOKEN_REQ["client_id"]]) assert tok.supports_minting("access_token") grant = self.session_manager[session_id] grant.mint_token( session_id=session_id, endpoint_context=self.endpoint_context, token_class="access_token", token_handler=self.session_manager.token_handler["access_token"], expires_at=time_sans_frac() + 900, # 15 minutes from now based_on=tok, # Means the token (tok) was used to mint this token ) # this test is include in the mint_token methods # assert tok.supports_minting("refresh_token") refresh_token = grant.mint_token( session_id=session_id, endpoint_context=self.endpoint_context, token_class="refresh_token", token_handler=self.session_manager.token_handler["refresh_token"], based_on=tok, ) tok.register_usage() assert tok.max_usage_reached() is True # A bit later a refresh token is used to mint a new access token REFRESH_TOKEN_REQ = RefreshAccessTokenRequest( grant_type="refresh_token", client_id="client_1", client_secret="hemligt", refresh_token=refresh_token.value, scope=["openid", "mail", "offline_access"], ) session_id = self.session_manager.encrypted_session_id( user_id, REFRESH_TOKEN_REQ["client_id"], grant_id) reftok = self.session_manager.find_token( session_id, REFRESH_TOKEN_REQ["refresh_token"]) # Can I use this token to mint another token ? assert grant.is_active() user_claims = self.endpoint_context.userinfo( user_id, client_id=TOKEN_REQ["client_id"], user_info_claims=grant.claims) access_token_2 = grant.mint_token( session_id=session_id, endpoint_context=self.endpoint_context, token_class="access_token", token_handler=self.session_manager.token_handler["access_token"], expires_at=time_sans_frac() + 900, # 15 minutes from now based_on= reftok, # Means the refresh token (reftok) was used to mint this token ) assert access_token_2.is_active() token_info = self.session_manager.token_handler.info( access_token_2.value) assert token_info
def create_session_manager(self): conf = { "issuer": "https://example.com/", "password": "******", "token_expires_in": 600, "grant_expires_in": 300, "refresh_token_expires_in": 86400, "verify_ssl": False, "keys": { "key_defs": KEYDEFS, "uri_path": "static/jwks.json" }, "jwks_uri": "https://example.com/jwks.json", "token_handler_args": { "jwks_def": { "private_path": "private/token_jwks.json", "read_only": False, "key_defs": [{ "type": "oct", "bytes": "24", "use": ["enc"], "kid": "code" }], }, "code": { "lifetime": 600 }, "token": { "class": "oidcop.token.jwt_token.JWTToken", "kwargs": { "lifetime": 3600, "add_claims": True, "add_claim_by_scope": True, "aud": ["https://example.org/appl"], }, }, "refresh": { "class": "oidcop.token.jwt_token.JWTToken", "kwargs": { "lifetime": 3600, "aud": ["https://example.org/appl"], }, }, }, "endpoint": { "authorization_endpoint": { "path": "{}/authorization", "class": Authorization, "kwargs": {}, }, "token_endpoint": { "path": "{}/token", "class": Token, "kwargs": {} }, }, "template_dir": "template", "claims_interface": { "class": "oidcop.session.claims.ClaimsInterface", "kwargs": {} }, } server = Server(conf) self.server = server self.endpoint_context = server.endpoint_context self.session_manager = server.endpoint_context.session_manager self.authn_event = AuthnEvent(uid="uid", valid_until=time_sans_frac() + 1, authn_info="authn_class_ref") self.dummy_session_id = self.session_manager.encrypted_session_id( "user_id", "client_id", "grant.id")
def set_expires_at(self, expires_in): self.expires_at = time_sans_frac() + expires_in
def update_service_context(self, resp, key='cc', **kwargs): if 'expires_in' in resp: resp['__expires_at'] = time_sans_frac() + int(resp['expires_in']) self.store_item(resp, 'token_response', key)