def test_faulty_id_token(self): _faulty_signed_jwt = self._faulty_id_token() with pytest.raises(BadSignature): IdToken().from_jwt(_faulty_signed_jwt, key=[SYMKey(key="TestPassword")]) # What if no verification key is given ? # Should also result in an exception with pytest.raises(MissingSigningKey): IdToken().from_jwt(_faulty_signed_jwt)
def test_verify_id_token_missing_c_hash(): code = "AccessCode1" idt = IdToken( **{ "sub": "553df2bcf909104751cfd8b2", "aud": ["5542958437706128204e0000", "554295ce3770612820620000"], "auth_time": 1441364872, "azp": "554295ce3770612820620000", }) kj = KeyJar() kj.add_symmetric("", "dYMmrcQksKaPkhdgRNYk3zzh5l7ewdDJ", ["sig"]) kj.add_symmetric( "https://sso.qa.7pass.ctf.prosiebensat1.com", "dYMmrcQksKaPkhdgRNYk3zzh5l7ewdDJ", ["sig"], ) packer = JWT( kj, sign_alg="HS256", iss="https://sso.qa.7pass.ctf.prosiebensat1.com", lifetime=3600, ) _jws = packer.pack(**idt.to_dict()) msg = AuthorizationResponse(code=code, id_token=_jws) with pytest.raises(MissingRequiredAttribute): verify_id_token( msg, check_hash=True, keyjar=kj, iss="https://sso.qa.7pass.ctf.prosiebensat1.com", client_id="554295ce3770612820620000", )
def test_token_endpoint_with_extra_claims( self, context, frontend_config_with_extra_id_token_claims, authn_req): frontend = self.create_frontend( frontend_config_with_extra_id_token_claims) user_id = "test_user" self.insert_client_in_client_db(frontend, authn_req["redirect_uri"]) self.insert_user_in_user_db(frontend, user_id) authn_req["response_type"] = "code" authn_resp = frontend.provider.authorize(authn_req, user_id) context.request = AccessTokenRequest( redirect_uri=authn_req["redirect_uri"], code=authn_resp["code"]).to_dict() credentials = "{}:{}".format(CLIENT_ID, CLIENT_SECRET) basic_auth = urlsafe_b64encode( credentials.encode("utf-8")).decode("utf-8") context.request_authorization = "Basic {}".format(basic_auth) response = frontend.token_endpoint(context) parsed = AccessTokenResponse().deserialize(response.message, "json") assert parsed["access_token"] id_token = IdToken().from_jwt(parsed["id_token"], key=[frontend.signing_key]) assert id_token["email"] == "*****@*****.**"
def test_hybrid_flow(self): self.authn_request_args['response_type'] = 'code id_token token' auth_req = AuthorizationRequest().from_dict(self.authn_request_args) resp = self.provider.authorize(auth_req, TEST_USER_ID, extra_id_token_claims={'foo': 'bar'}) assert resp['state'] == self.authn_request_args['state'] assert resp['code'] in self.provider.authz_state.authorization_codes assert resp['access_token'] in self.provider.authz_state.access_tokens assert resp[ 'expires_in'] == self.provider.authz_state.access_token_lifetime assert resp['token_type'] == 'Bearer' id_token = IdToken().from_jwt(resp['id_token'], key=[self.provider.signing_key]) assert_id_token_base_claims(resp['id_token'], self.provider.signing_key, self.provider, self.authn_request_args) assert id_token["c_hash"] == jws.left_hash( resp['code'].encode('utf-8'), 'HS256') assert id_token["at_hash"] == jws.left_hash( resp['access_token'].encode('utf-8'), 'HS256') assert id_token['foo'] == 'bar'
def test_make_id_token(): srv = Server() srv.keyjar = KEYJ srv.keyjar["http://oic.example/rp"] = KC_RSA session = {"sub": "user0", "client_id": "http://oic.example/rp"} issuer = "http://oic.example/idp" code = "abcdefghijklmnop" _idt = srv.make_id_token(session, loa="2", issuer=issuer, code=code, access_token="access_token") algo = "RS256" ckey = srv.keyjar.get_signing_key(alg2keytype(algo), session["client_id"]) _signed_jwt = _idt.to_jwt(key=ckey, algorithm="RS256") idt = IdToken().from_jwt(_signed_jwt, keyjar=srv.keyjar) print idt header = unpack(_signed_jwt) lha = left_hash(code, func="HS" + header[0]["alg"][-3:]) assert lha == idt["c_hash"] atr = AccessTokenResponse(id_token=_signed_jwt, access_token="access_token", token_type="Bearer") atr["code"] = code assert atr.verify(keyjar=srv.keyjar)
def do_post_logout_redirect(self, end_session_request): # type: (oic.oic.message.EndSessionRequest) -> oic.oic.message.EndSessionResponse if 'post_logout_redirect_uri' not in end_session_request: return None client_id = None if 'id_token_hint' in end_session_request: id_token = IdToken().from_jwt(end_session_request['id_token_hint'], key=[self.signing_key]) client_id = id_token['aud'][0] if 'post_logout_redirect_uri' in end_session_request: if not client_id: return None if not end_session_request[ 'post_logout_redirect_uri'] in self.clients[client_id].get( 'post_logout_redirect_uris', []): return None end_session_response = EndSessionResponse() if 'state' in end_session_request: end_session_response['state'] = end_session_request['state'] return end_session_response.request( end_session_request['post_logout_redirect_uri'])
def test_verify_token_encrypted_no_key(): idt = IdToken( sub="553df2bcf909104751cfd8b2", aud=["5542958437706128204e0000", "554295ce3770612820620000"], auth_time=1441364872, azp="554295ce3770612820620000", ) kj = KeyJar() kb = KeyBundle() kb.do_local_der( os.path.join(os.path.dirname(__file__), "data", "keys", "cert.key"), "some", ["enc", "sig"], ) kj.add_kb("", kb) kj.add_kb("https://sso.qa.7pass.ctf.prosiebensat1.com", kb) packer = JWT( kj, lifetime=3600, iss="https://sso.qa.7pass.ctf.prosiebensat1.com", encrypt=True, ) _jws = packer.pack(**idt.to_dict()) msg = AuthorizationResponse(id_token=_jws) # Do not pass they keyjar with keys with pytest.raises(VerificationError): verify_id_token( msg, keyjar=KeyJar(), iss="https://sso.qa.7pass.ctf.prosiebensat1.com", client_id="554295ce3770612820620000", )
def test_verify_id_token_mismatch_aud_azp(): idt = IdToken( **{ "sub": "553df2bcf909104751cfd8b2", "aud": ["5542958437706128204e0000", "554295ce3770612820620000"], "auth_time": 1441364872, "azp": "aaaaaaaaaaaaaaaaaaaa", }) kj = KeyJar() kj.add_symmetric("", "dYMmrcQksKaPkhdgRNYk3zzh5l7ewdDJ", ["sig"]) kj.add_symmetric( "https://sso.qa.7pass.ctf.prosiebensat1.com", "dYMmrcQksKaPkhdgRNYk3zzh5l7ewdDJ", ["sig"], ) packer = JWT(kj, sign_alg="HS256", iss="https://example.com/as", lifetime=3600) _jws = packer.pack(**idt.to_dict()) msg = AuthorizationResponse(id_token=_jws) with pytest.raises(ValueError): verify_id_token( msg, keyjar=kj, iss="https://sso.qa.7pass.ctf.prosiebensat1.com", client_id="aaaaaaaaaaaaaaaaaaaa", )
def test_handle_authn_response_returns_id_token_for_verified_affiliation( self, signing_key_path, context, scope_value, affiliation): authn_req = AuthorizationRequest( scope='openid ' + scope_value, client_id='client1', redirect_uri='https://client.example.com', response_type='id_token') context.state[self.frontend.name] = { 'oidc_request': authn_req.to_urlencoded() } internal_response = InternalResponse( AuthenticationInformation(None, str(datetime.now()), 'https://idp.example.com')) internal_response.attributes['affiliation'] = [affiliation] internal_response.user_id = 'user1' resp = self.frontend.handle_authn_response(context, internal_response) auth_resp = AuthorizationResponse().from_urlencoded( urlparse(resp.message).fragment) id_token = IdToken().from_jwt( auth_resp['id_token'], key=[RSAKey(key=rsa_load(signing_key_path))]) assert id_token['iss'] == self.frontend.base_url assert id_token['aud'] == ['client1'] assert id_token['auth_time'] == internal_response.auth_info.timestamp
def test_configurable_userinfo_endpoint_method_is_used(self, method): state = 'state' nonce = 'nonce' sub = 'foobar' authn = OIDCAuthentication( self.app, provider_configuration_info={ 'issuer': ISSUER, 'token_endpoint': '/token' }, client_registration_info={'client_id': 'foo'}, userinfo_endpoint_method=method) authn.client.do_access_token_request = MagicMock( return_value=AccessTokenResponse( **{ 'id_token': IdToken(**{ 'sub': sub, 'nonce': nonce }), 'access_token': 'access_token' })) userinfo_request_mock = MagicMock(return_value=OpenIDSchema( **{'sub': sub})) authn.client.do_user_info_request = userinfo_request_mock with self.app.test_request_context('/redirect_uri?code=foo&state=' + state): flask.session['state'] = state flask.session['nonce'] = nonce flask.session['destination'] = '/' authn._handle_authentication_response() userinfo_request_mock.assert_called_with(method=method, state=state)
def test_verify_token_encrypted(): idt = IdToken( sub="553df2bcf909104751cfd8b2", aud=["5542958437706128204e0000", "554295ce3770612820620000"], auth_time=1441364872, azp="554295ce3770612820620000", ) kj = KeyJar() kb = KeyBundle() kb.do_local_der( os.path.join(os.path.dirname(__file__), "data", "keys", "cert.key"), "some", ["enc", "sig"], ) kj.add_kb("", kb) kj.add_kb("https://sso.qa.7pass.ctf.prosiebensat1.com", kb) packer = JWT( kj, lifetime=3600, iss="https://sso.qa.7pass.ctf.prosiebensat1.com", encrypt=True, ) _jws = packer.pack(**idt.to_dict()) msg = AuthorizationResponse(id_token=_jws) vidt = verify_id_token( msg, keyjar=kj, iss="https://sso.qa.7pass.ctf.prosiebensat1.com", client_id="554295ce3770612820620000", ) assert vidt assert vidt.jwe_header == {"enc": "A128CBC-HS256", "alg": "RSA1_5", "cty": "JWT"}
def test_does_not_need_refresh(self): authn = OIDCAuthentication( self.app, provider_configuration_info={'issuer': ISSUER}, client_registration_info={ 'client_id': 'foo', 'session_refresh_interval_seconds': 1 }, ) client_mock = MagicMock() callback_mock = MagicMock() now = time.time() callback_mock.__name__ = 'test_callback' # required for Python 2 authn.client = client_mock id_token = IdToken(**{'sub': 'sub1', 'nonce': 'nonce', 'exp': 0}) with self.app.test_request_context('/'): flask.session['destination'] = '/' flask.session['access_token'] = 'test token' flask.session['id_token'] = id_token.to_dict() flask.session['id_token_jwt'] = id_token.to_jwt() flask.session['last_authenticated'] = now + 100 authn.oidc_auth(callback_mock)() session = Session( flask_session=flask.session, client_registration_info=authn.client_registration_info) assert session.needs_refresh() is False
def test_faulty_idtoken(self): _now = time_util.utc_time_sans_frac() idval = { 'nonce': 'KUEYfRM2VzKDaaKD', 'sub': 'EndUserSubject', 'iss': 'https://alpha.cloud.nds.rub.de', 'exp': _now + 3600, 'iat': _now, 'aud': 'TestClient' } idts = IdToken(**idval) key = SYMKey(key="TestPassword") _signed_jwt = idts.to_jwt(key=[key], algorithm="HS256") # Mess with the signed id_token p = _signed_jwt.split(".") p[2] = "aaa" _faulty_signed_jwt = ".".join(p) _info = { "access_token": "accessTok", "id_token": _faulty_signed_jwt, "token_type": "Bearer", "expires_in": 3600 } at = AccessTokenResponse(**_info) with pytest.raises(BadSignature): at.verify(key=[key])
def test_wrong_alg(): idval = { 'nonce': 'KUEYfRM2VzKDaaKD', 'sub': 'EndUserSubject', 'iss': 'https://alpha.cloud.nds.rub.de', 'exp': 1420823073, 'iat': 1420822473, 'aud': 'TestClient' } idts = IdToken(**idval) key = SYMKey(key="TestPassword") _signed_jwt = idts.to_jwt(key=[key], algorithm="HS256") _info = { "access_token": "accessTok", "id_token": _signed_jwt, "token_type": "Bearer", "expires_in": 3600 } at = AccessTokenResponse(**_info) try: at.verify(key=[key], algs={"sign": "HS512"}) except WrongSigningAlgorithm: pass
def test_faulty_idtoken(): idval = { 'nonce': 'KUEYfRM2VzKDaaKD', 'sub': 'EndUserSubject', 'iss': 'https://alpha.cloud.nds.rub.de', 'exp': 1420823073, 'iat': 1420822473, 'aud': 'TestClient' } idts = IdToken(**idval) key = SYMKey(key="TestPassword") _signed_jwt = idts.to_jwt(key=[key], algorithm="HS256") #Mess with the signed id_token p = _signed_jwt.split(".") p[2] = "aaa" _faulty_signed_jwt = ".".join(p) _info = { "access_token": "accessTok", "id_token": _faulty_signed_jwt, "token_type": "Bearer", "expires_in": 3600 } # Should fail at = AccessTokenResponse(**_info) try: at.verify(key=[key]) except BadSignature: pass else: raise
def test_get_session_management_id(self): now = utc_time_sans_frac() smid = "session_management_id" idval = { "nonce": "KUEYfRM2VzKDaaKD", "sub": "EndUserSubject", "iss": "https://example.com", "exp": now + 3600, "iat": now, "aud": self.consumer.client_id, "sid": smid, } idts = IdToken(**idval) _signed_jwt = idts.to_jwt(key=KC_RSA.keys(), algorithm="RS256") _state = "state" self.consumer.sdb[_state] = { "redirect_uris": ["https://example.org/cb"] } resp = AuthorizationResponse(id_token=_signed_jwt, state=_state) self.consumer.consumer_config["response_type"] = ["id_token"] self.consumer.parse_authz(resp.to_urlencoded()) assert self.consumer.sso_db["state"]["smid"] == smid assert session_get(self.consumer.sso_db, "smid", smid) == [_state]
def test_verify_id_token_at_hash(): token = "AccessTokenWhichCouldBeASignedJWT" lhsh = left_hash(token) idt = IdToken( **{ "sub": "553df2bcf909104751cfd8b2", "aud": ["5542958437706128204e0000", "554295ce3770612820620000"], "auth_time": 1441364872, "azp": "554295ce3770612820620000", "at_hash": lhsh, }) kj = KeyJar() kj.add_symmetric("", "dYMmrcQksKaPkhdgRNYk3zzh5l7ewdDJ", ["sig"]) kj.add_symmetric( "https://sso.qa.7pass.ctf.prosiebensat1.com", "dYMmrcQksKaPkhdgRNYk3zzh5l7ewdDJ", ["sig"], ) packer = JWT( kj, sign_alg="HS256", iss="https://sso.qa.7pass.ctf.prosiebensat1.com", lifetime=3600, ) _jws = packer.pack(**idt.to_dict()) msg = AuthorizationResponse(access_token=token, id_token=_jws) verify_id_token( msg, check_hash=True, keyjar=kj, iss="https://sso.qa.7pass.ctf.prosiebensat1.com", client_id="554295ce3770612820620000", )
def test_verify_id_token_reject_wrong_aud(self, monkeypatch): issuer = "https://provider.example.com" monkeypatch.setattr(self.client, "provider_info", {"issuer": issuer}) id_token = IdToken(**dict(iss=issuer, aud=["nobody"])) with pytest.raises(OtherError) as exc: self.client._verify_id_token(id_token) assert "me" in str(exc.value)
def test_faulty_id_token(): _faulty_signed_jwt = _faulty_id_token() try: _ = IdToken().from_jwt(_faulty_signed_jwt, key=[SYMKEY]) except BadSignature: pass else: assert False # What if no verification key is given ? # Should also result in an exception try: _ = IdToken().from_jwt(_faulty_signed_jwt) except MissingSigningKey: pass else: assert False
def test_authorize_id_token_includes_typ_header(self): self.authn_request_args['response_type'] = 'id_token' auth_req = AuthorizationRequest().from_dict(self.authn_request_args) resp = self.provider.authorize(auth_req, TEST_USER_ID) id_token = IdToken().from_jwt(resp['id_token'], key=[self.provider.signing_key]) assert id_token.jws_header['typ'] == 'JWT'
def test_complete_auth_token_idtoken_no_alg_config(self): _state = "state0" self.consumer.consumer_config["response_type"] = ["id_token", "token"] self.consumer.provider_info = ProviderConfigurationResponse( issuer="https://example.com") # abs min self.consumer.authz_req = {} # Store AuthzReq with state as key args = { "client_id": self.consumer.client_id, "response_type": self.consumer.consumer_config["response_type"], "scope": ["openid"], "nonce": "nonce", } token = IdToken( iss="https://example.com", aud="client_1", sub="some_sub", exp=1565348600, iat=1565348300, nonce="nonce", ) location = ( "https://example.com/cb?state=state0&access_token=token&token_type=bearer&" "scope=openid&id_token={}".format( token.to_jwt(key=[SYMKey(key="hemlig")], algorithm="HS256"))) with responses.RequestsMock() as rsps: rsps.add( responses.GET, "https://example.com/authorization", status=302, headers={"location": location}, ) result = self.consumer.do_authorization_request(state=_state, request_args=args) query = parse_qs(urlparse(result.request.url).query) assert query["client_id"] == ["client_1"] assert query["scope"] == ["openid"] assert query["response_type"] == ["id_token token"] assert query["state"] == ["state0"] assert query["nonce"] == ["nonce"] assert query["redirect_uri"] == ["https://example.com/cb"] parsed = urlparse(result.headers["location"]) with freeze_time("2019-08-09 11:00:00"): part = self.consumer.parse_authz(query=parsed.query, algs={"sign": "HS256"}) assert isinstance(part, tuple) auth = part[0] atr = part[1] idt = part[2] assert auth is None assert isinstance(atr, AccessTokenResponse) assert _eq( atr.keys(), ["access_token", "id_token", "token_type", "state", "scope"]) assert isinstance(idt, IdToken)
def test_server_authorization_endpoint_id_token(): provider = provider_init bib = { "scope": ["openid"], "state": "id-6da9ca0cc23959f5f33e8becd9b08cae", "redirect_uri": "http://localhost:8087/authz", "response_type": ["code", "id_token"], "client_id": "a1b2c3", "nonce": "Nonce", "prompt": ["none"] } req = AuthorizationRequest(**bib) areq = AuthorizationRequest(response_type="code", client_id="client_1", redirect_uri="http://example.com/authz", scope=["openid"], state="state000") sdb = provider.sdb ae = AuthnEvent("userX") sid = sdb.create_authz_session(ae, areq) sdb.do_sub(sid) _info = sdb[sid] # All this is jut removed when the id_token is constructed # The proper information comes from the session information _user_info = IdToken(iss="https://foo.example.om", sub="foo", aud=bib["client_id"], exp=epoch_in_a_while(minutes=10), acr="2", nonce=bib["nonce"]) print provider.keyjar.issuer_keys print _user_info.to_dict() idt = provider.id_token_as_signed_jwt(_info, access_token="access_token", user_info=_user_info) req["id_token"] = idt query_string = req.to_urlencoded() # client_id not in id_token["aud"] so login required resp = provider.authorization_endpoint(request=query_string, cookie="FAIL") print resp assert "error=login_required" in resp.message req["client_id"] = "client_1" query_string = req.to_urlencoded() # client_id is in id_token["aud"] so no login required resp = provider.authorization_endpoint(request=query_string, cookie="FAIL") print resp.message assert resp.message.startswith("http://localhost:8087/authz")
def test_check_session_endpoint(self): session = {"sub": "UserID", "client_id": "number5"} idtoken = self.provider.id_token_as_signed_jwt(session) csr = CheckSessionRequest(id_token=idtoken) info = self.provider.check_session_endpoint(request=csr.to_urlencoded()) idt = IdToken().deserialize(info.message, "json") assert _eq(idt.keys(), ['sub', 'aud', 'iss', 'acr', 'exp', 'iat']) assert idt["iss"] == self.provider.name + "/"
def logout_user(self, subject_identifier=None, end_session_request=None): # type: (Optional[str], Optional[oic.oic.message.EndSessionRequest]) -> None if not end_session_request: end_session_request = EndSessionRequest() if 'id_token_hint' in end_session_request: id_token = IdToken().from_jwt(end_session_request['id_token_hint'], key=[self.signing_key]) subject_identifier = id_token['sub'] self.authz_state.delete_state_for_subject_identifier(subject_identifier)
def test_logout_redirects_to_provider_if_end_session_endpoint_is_configured( self, post_logout_redirect_uri): end_session_endpoint = 'https://provider.example.com/end_session' client_metadata = {} if post_logout_redirect_uri: client_metadata['post_logout_redirect_uris'] = [ post_logout_redirect_uri ] authn = self.init_app(provider_metadata_extras={ 'end_session_endpoint': end_session_endpoint }, client_metadata_extras=client_metadata) logout_view_mock = self.get_view_mock() id_token = IdToken(**{'sub': 'sub1', 'nonce': 'nonce'}) # register logout view view_func = authn.oidc_logout(logout_view_mock) self.app.add_url_rule('/logout', view_func=view_func) with self.app.test_request_context('/logout'): UserSession(flask.session, self.PROVIDER_NAME).update( access_token='test_access_token', id_token=id_token.to_dict(), id_token_jwt=id_token.to_jwt(), userinfo={'sub': 'user1'}) end_session_redirect = view_func() # ensure user session has been cleared assert all(k not in flask.session for k in UserSession.KEYS) parsed_request = dict( parse_qsl( urlparse(end_session_redirect.headers['Location']).query)) assert parsed_request['state'] == flask.session[ 'end_session_state'] assert end_session_redirect.status_code == 303 assert end_session_redirect.location.startswith(end_session_endpoint) assert IdToken().from_jwt(parsed_request['id_token_hint']) == id_token expected_post_logout_redirect_uri = post_logout_redirect_uri if post_logout_redirect_uri else 'http://{}/logout'.format( self.CLIENT_DOMAIN) assert parsed_request[ 'post_logout_redirect_uri'] == expected_post_logout_redirect_uri assert not logout_view_mock.called
def _create_signed_id_token( self, client_id, # type: str sub, # type: str user_claims=None, # type: Optional[Mapping[str, Union[str, List[str]]]] nonce=None, # type: Optional[str] authorization_code=None, # type: Optional[str] access_token_value=None, # type: Optional[str] extra_id_token_claims=None ): # type: Optional[Mappings[str, Union[str, List[str]]]] # type: (...) -> str """ Creates a signed ID Token. :param client_id: who the ID Token is intended for :param sub: who the ID Token is regarding :param user_claims: any claims about the user to be included :param nonce: nonce from the authentication request :param authorization_code: the authorization code issued together with this ID Token :param access_token_value: the access token issued together with this ID Token :param extra_id_token_claims: any extra claims that should be included in the ID Token :return: a JWS, containing the ID Token as payload """ alg = self.clients[client_id].get( 'id_token_signed_response_alg', self.configuration_information[ 'id_token_signing_alg_values_supported'][0]) args = {} hash_alg = 'HS{}'.format(alg[-3:]) if authorization_code: args['c_hash'] = jws.left_hash(authorization_code.encode('utf-8'), hash_alg) if access_token_value: args['at_hash'] = jws.left_hash(access_token_value.encode('utf-8'), hash_alg) if user_claims: args.update(user_claims) if extra_id_token_claims: args.update(extra_id_token_claims) id_token = IdToken(iss=self.configuration_information['issuer'], sub=sub, aud=client_id, iat=int(time.time()), exp=int(time.time()) + self.id_token_lifetime, **args) if nonce: id_token['nonce'] = nonce logger.debug('signed id_token with kid=%s using alg=%s', self.signing_key, alg) return id_token.to_jwt([self.signing_key], alg)
def assert_id_token_base_claims(jws, verification_key, provider, auth_req): id_token = IdToken().from_jwt(jws, key=[verification_key]) assert id_token['nonce'] == auth_req['nonce'] assert id_token['iss'] == ISSUER assert provider.authz_state.get_user_id_for_subject_identifier( id_token['sub']) == TEST_USER_ID assert id_token['iat'] == MOCK_TIME.return_value assert id_token['exp'] == id_token['iat'] + provider.id_token_lifetime assert TEST_CLIENT_ID in id_token['aud'] return id_token
def logout(request, next_page=None): if not "op" in request.session.keys(): return auth_logout_view(request, next_page) client = CLIENTS[request.session["op"]] # User is by default NOT redirected to the app - it stays on an OP page after logout. # Here we determine if a redirection to the app was asked for and is possible. if next_page is None and "next" in request.GET.keys(): next_page = request.GET['next'] if next_page is None and "next" in request.session.keys(): next_page = request.session['next'] extra_args = {} if "post_logout_redirect_uris" in client.registration_response.keys() and len( client.registration_response["post_logout_redirect_uris"]) > 0: if next_page is not None: # First attempt a direct redirection from OP to next_page next_page_url = resolve_url(next_page) urls = [url for url in client.registration_response["post_logout_redirect_uris"] if next_page_url in url] if len(urls) > 0: extra_args["post_logout_redirect_uri"] = urls[0] else: # It is not possible to directly redirect from the OP to the page that was asked for. # We will try to use the redirection point - if the redirection point URL is registered that is. next_page_url = resolve_url('openid_logout_cb') urls = [url for url in client.registration_response["post_logout_redirect_uris"] if next_page_url in url] if len(urls) > 0: extra_args["post_logout_redirect_uri"] = urls[0] else: # Just take the first registered URL as a desperate attempt to come back to the application extra_args["post_logout_redirect_uri"] = client.registration_response["post_logout_redirect_uris"][ 0] else: # No post_logout_redirect_uris registered at the OP - no redirection to the application is possible anyway pass # Redirect client to the OP logout page try: request_args = None if 'id_token' in request.session.keys(): request_args = {'id_token': IdToken(**request.session['id_token'])} res = client.do_end_session_request(state=request.session["state"], extra_args=extra_args, request_args=request_args) resp = HttpResponse(content_type=res.headers["content-type"], status=res.status_code, content=res._content) for key, val in res.headers.items(): resp[key] = val return resp finally: # Always remove Django session stuff - even if not logged out from OP. Don't wait for the callback as it may never come. auth_logout(request) if next_page: request.session['next'] = next_page
def test_session_expiration_set_to_id_token_exp(self): token_endpoint = ISSUER + '/token' userinfo_endpoint = ISSUER + '/userinfo' exp_time = 10 epoch_int = int(time.mktime(datetime(2017, 1, 1).timetuple())) id_token = IdToken( **{ 'sub': 'sub1', 'iat': epoch_int, 'iss': ISSUER, 'aud': 'foo', 'nonce': 'test', 'exp': epoch_int + exp_time }) token_response = { 'access_token': 'test', 'token_type': 'Bearer', 'id_token': id_token.to_jwt() } userinfo_response = {'sub': 'sub1'} responses.add(responses.POST, token_endpoint, body=json.dumps(token_response), content_type='application/json') responses.add(responses.POST, userinfo_endpoint, body=json.dumps(userinfo_response), content_type='application/json') authn = OIDCAuthentication( self.app, provider_configuration_info={ 'issuer': ISSUER, 'token_endpoint': token_endpoint, 'userinfo_endpoint': userinfo_endpoint }, client_registration_info={ 'client_id': 'foo', 'client_secret': 'foo' }, ) self.app.config.update({'SESSION_PERMANENT': True}) with self.app.test_request_context( '/redirect_uri?state=test&code=test'): flask.session['destination'] = '/' flask.session['state'] = 'test' flask.session['nonce'] = 'test' flask.session['id_token'] = id_token.to_dict() flask.session['id_token_jwt'] = id_token.to_jwt() authn._handle_authentication_response() assert flask.session.permanent is True assert int(flask.session.permanent_session_lifetime) == exp_time
def test_complete_auth_token_idtoken_none_cipher_token(self): _state = "state0" self.consumer.consumer_config["response_type"] = ["token"] self.consumer.registration_response = RegistrationResponse( id_token_signed_response_alg="none") self.consumer.provider_info = ProviderConfigurationResponse( issuer="https://example.com") # abs min self.consumer.authz_req = {} # Store AuthzReq with state as key self.consumer.sdb[_state] = {"redirect_uris": []} args = { "client_id": self.consumer.client_id, "response_type": self.consumer.consumer_config["response_type"], "scope": ["openid"], "nonce": "nonce", } token = IdToken( iss="https://example.com", aud="client_1", sub="some_sub", exp=1565348600, iat=1565348300, nonce="nonce", ) # Downgrade the algorithm to `none` location = ( "https://example.com/cb?state=state0&access_token=token&token_type=bearer&" "scope=openid&id_token={}".format( token.to_jwt(key=KC_RSA.keys(), algorithm="none"))) with responses.RequestsMock() as rsps: rsps.add( responses.GET, "https://example.com/authorization", status=302, headers={"location": location}, ) result = self.consumer.do_authorization_request(state=_state, request_args=args) query = parse_qs(urlparse(result.request.url).query) assert query["client_id"] == ["client_1"] assert query["scope"] == ["openid"] assert query["response_type"] == ["token"] assert query["state"] == ["state0"] assert query["nonce"] == ["nonce"] assert query["redirect_uri"] == ["https://example.com/cb"] parsed = urlparse(result.headers["location"]) with freeze_time("2019-08-09 11:00:00"): with pytest.raises(WrongSigningAlgorithm): self.consumer.parse_authz(query=parsed.query)