def test_session_state_in_auth_req_for_session_support(self): provider = Provider( "foo", SessionDB(SERVER_INFO["issuer"]), CDB, AUTHN_BROKER, USERINFO, AUTHZ, verify_client, SYMKEY, urlmap=URLMAP, keyjar=KEYJAR, capabilities={"check_session_iframe": "https://op.example.com/check_session"}, ) req_args = { "scope": ["openid"], "redirect_uri": "http://localhost:8087/authz", "response_type": ["code"], "client_id": "a1b2c3", } areq = AuthorizationRequest(**req_args) resp = provider.authorization_endpoint(request=areq.to_urlencoded()) aresp = self.cons.parse_response(AuthorizationResponse, resp.message, sformat="urlencoded") assert "session_state" in aresp
class TestOICProvider(object): def setup_class(self): self.server = Provider("pyoicserv", SessionDB(SERVER_INFO["issuer"]), CDB, AUTHN_BROKER, USERINFO, AUTHZ, verify_client, SYMKEY, urlmap=URLMAP, keyjar=KEYJAR) self.cons = Consumer({}, CONSUMER_CONFIG, CLIENT_CONFIG, server_info=SERVER_INFO, ) self.cons.behaviour = {"request_object_signing_alg": DEF_SIGN_ALG["openid_request_object"]} self.cons.debug = True self.cons.keyjar[""] = KC_RSA def test_server_init(self): assert self.server assert self.server.authn_broker == AUTHN_BROKER print self.server.urlmap assert self.server.urlmap["client_1"] == ["https://example.com/authz"] def test_server_authorization_endpoint(self): bib = {"scope": ["openid"], "state": "id-6da9ca0cc23959f5f33e8becd9b08cae", "redirect_uri": "http://*****:*****@example.com"] req["response_types"] = ["code"] print req.to_dict() resp = self.server.registration_endpoint(request=req.to_json()) print resp.message regresp = RegistrationResponse().deserialize(resp.message, "json") print regresp.keys() assert _eq(regresp.keys(), ['redirect_uris', 'contacts', 'application_type', 'client_name', 'registration_client_uri', 'client_secret_expires_at', 'registration_access_token', 'client_id', 'client_secret', 'client_id_issued_at', 'response_types']) def test_provider_key_setup(self): provider = Provider("pyoicserv", SessionDB(SERVER_INFO["issuer"]), None, None, None, None, None, "") provider.baseurl = "http://www.example.com/" provider.key_setup("static", sig={"format": "jwk", "alg": "RSA"}) keys = provider.keyjar.get_signing_key("RSA") assert len(keys) == 1 assert provider.jwks_uri == "http://www.example.com/static/jwks" def _client_id(self, cdb): cid = None for k, item in cdb.items(): if item in cdb.keys(): cid = item break return cid def test_registered_redirect_uri_without_query_component(self): provider = Provider("FOO", {}, {}, None, None, None, None, "") rr = RegistrationRequest(operation="register", redirect_uris=["http://example.org/cb"], response_types=["code"]) registration_req = rr.to_json() provider.registration_endpoint(request=registration_req) correct = [ "http://example.org/cb", "http://example.org/cb/foo", ] faulty = [ "http://example.org/foo", "http://example.com/cb", "http://example.org/cb?got=you", "http://example.org/cb/foo?got=you" ] cid = self._client_id(provider.cdb) for ruri in faulty: areq = AuthorizationRequest(redirect_uri=ruri, client_id=cid, response_type="code", scope="openid") print areq try: provider._verify_redirect_uri(areq) assert False except RedirectURIError: pass for ruri in correct: areq = AuthorizationRequest(redirect_uri=ruri, client_id=cid, response_type="code", scope="openid") print areq try: provider._verify_redirect_uri(areq) except RedirectURIError, err: print err assert False
class TestProvider(object): @pytest.fixture(autouse=True) def create_provider(self): self.provider = Provider("pyoicserv", SessionDB(SERVER_INFO["issuer"]), CDB, AUTHN_BROKER, USERINFO, AUTHZ, verify_client, SYMKEY, urlmap=URLMAP, keyjar=KEYJAR) self.provider.baseurl = self.provider.name self.cons = Consumer({}, CONSUMER_CONFIG, CLIENT_CONFIG, server_info=SERVER_INFO, ) self.cons.behaviour = { "request_object_signing_alg": DEF_SIGN_ALG["openid_request_object"]} self.cons.keyjar[""] = KC_RSA def test_authorization_endpoint(self): bib = {"scope": ["openid"], "state": "id-6da9ca0cc23959f5f33e8becd9b08cae", "redirect_uri": "http://*****:*****@example.com"] req["response_types"] = ["code"] resp = self.provider.registration_endpoint(request=req.to_json()) regresp = RegistrationResponse().deserialize(resp.message, "json") assert _eq(regresp.keys(), ['redirect_uris', 'contacts', 'application_type', 'client_name', 'registration_client_uri', 'client_secret_expires_at', 'registration_access_token', 'client_id', 'client_secret', 'client_id_issued_at', 'response_types']) def test_registration_endpoint_with_non_https_redirect_uri_implicit_flow( self): params = {"application_type": "web", "redirect_uris": ["http://example.com/authz"], "response_types": ["id_token", "token"]} req = RegistrationRequest(**params) resp = self.provider.registration_endpoint(request=req.to_json()) assert resp.status == "400 Bad Request" error = json.loads(resp.message) assert error["error"] == "invalid_redirect_uri" def test_verify_redirect_uris_with_https_code_flow(self): params = {"application_type": "web", "redirect_uris": ["http://example.com/authz"], "response_types": ["code"]} request = RegistrationRequest(**params) verified_uris = self.provider._verify_redirect_uris(request) assert verified_uris == [("http://example.com/authz", None)] def test_verify_redirect_uris_with_non_https_redirect_uri_implicit_flow(self): params = {"application_type": "web", "redirect_uris": ["http://example.com/authz"], "response_types": ["id_token", "token"]} request = RegistrationRequest(**params) with pytest.raises(InvalidRedirectURIError) as exc_info: self.provider._verify_redirect_uris(request) assert str(exc_info.value) == "None https redirect_uri not allowed" @pytest.mark.network def test_registration_endpoint_openid4us(self): req = RegistrationRequest( **{'token_endpoint_auth_method': u'client_secret_post', 'redirect_uris': [ u'https://connect.openid4.us:5443/phpRp/index.php/callback', u'https://connect.openid4.us:5443/phpRp/authcheck.php/authcheckcb'], 'jwks_uri': u'https://connect.openid4.us:5443/phpRp/rp/rp.jwk', 'userinfo_encrypted_response_alg': u'RSA1_5', 'contacts': [u'*****@*****.**'], 'userinfo_encrypted_response_enc': u'A128CBC-HS256', 'application_type': u'web', 'client_name': u'ABRP-17', 'grant_types': [u'authorization_code', u'implicit'], 'post_logout_redirect_uris': [ u'https://connect.openid4.us:5443/phpRp/index.php/logoutcb'], 'subject_type': u'public', 'response_types': [u'code', u'token', u'id_token', u'code token', u'code id_token', u'id_token token', u'code id_token token'], 'policy_uri': u'https://connect.openid4.us:5443/phpRp/index.php/policy', 'logo_uri': u'https://connect.openid4.us:5443/phpRp/media/logo.png'}) resp = self.provider.registration_endpoint(request=req.to_json()) regresp = RegistrationResponse().deserialize(resp.message, "json") assert _eq(regresp.keys(), list(req.keys()) + ['registration_client_uri', 'client_secret_expires_at', 'registration_access_token', 'client_id', 'client_secret', 'client_id_issued_at']) def test_provider_key_setup(self, tmpdir): path = tmpdir.strpath provider = Provider("pyoicserv", SessionDB(SERVER_INFO["issuer"]), None, None, None, None, None, "") provider.baseurl = "http://www.example.com" provider.key_setup(path, path, sig={"format": "jwk", "alg": "RSA"}) keys = provider.keyjar.get_signing_key("RSA") assert len(keys) == 1 assert provider.jwks_uri == "http://www.example.com/{}/jwks".format( path) @pytest.mark.parametrize("uri", [ "http://example.org/foo", "http://example.com/cb", "http://example.org/cb?got=you", "http://example.org/cb/foo?got=you" ]) def test_verify_redirect_uri_faulty_without_query(self, uri): rr = RegistrationRequest(operation="register", redirect_uris=["http://example.org/cb"], response_types=["code"]) registration_req = rr.to_json() resp = self.provider.registration_endpoint(request=registration_req) regresp = RegistrationResponse().from_json(resp.message) cid = regresp["client_id"] areq = AuthorizationRequest(redirect_uri=uri, client_id=cid, response_type="code", scope="openid") with pytest.raises(RedirectURIError): self.provider._verify_redirect_uri(areq) @pytest.mark.parametrize("uri", [ "http://example.org/cb", "http://example.org/cb/foo" ]) def test_verify_redirect_uri_correct_without_query(self, uri): rr = RegistrationRequest(operation="register", redirect_uris=["http://example.org/cb"], response_types=["code"]) registration_req = rr.to_json() resp = self.provider.registration_endpoint(request=registration_req) regresp = RegistrationResponse().from_json(resp.message) cid = regresp["client_id"] areq = AuthorizationRequest(redirect_uri=uri, client_id=cid, response_type="code", scope="openid") self.provider._verify_redirect_uri(areq) @pytest.mark.parametrize("uri", [ "http://example.org/cb", "http://example.org/cb/foo", "http://example.org/cb?got=you", "http://example.org/cb?foo=you" "http://example.org/cb?foo=bar&got=you", "http://example.org/cb?foo=you&foo=bar" ]) def test_registered_redirect_uri_faulty_with_query_component(self, uri): rr = RegistrationRequest(operation="register", redirect_uris=[ "http://example.org/cb?foo=bar"], response_types=["code"]) registration_req = rr.to_json() resp = self.provider.registration_endpoint(request=registration_req) regresp = RegistrationResponse().from_json(resp.message) cid = regresp["client_id"] areq = AuthorizationRequest(redirect_uri=uri, client_id=cid, scope="openid", response_type="code") with pytest.raises(RedirectURIError): self.provider._verify_redirect_uri(areq) def test_registered_redirect_uri_correct_with_query_component(self): rr = RegistrationRequest(operation="register", redirect_uris=[ "http://example.org/cb?foo=bar"], response_types=["code"]) registration_req = rr.to_json() resp = self.provider.registration_endpoint(request=registration_req) regresp = RegistrationResponse().from_json(resp.message) cid = regresp["client_id"] areq = AuthorizationRequest( redirect_uri="http://example.org/cb?foo=bar", client_id=cid, scope="openid", response_type="code") self.provider._verify_redirect_uri(areq) def test_key_rollover(self): provider2 = Provider("FOOP", {}, {}, None, None, None, None, "") provider2.keyjar = KEYJAR # Number of KeyBundles assert len(provider2.keyjar.issuer_keys[""]) == 1 kb = ec_init({"type": "EC", "crv": "P-256", "use": ["sig"]}) provider2.do_key_rollover(json.loads(kb.jwks()), "b%d") assert len(provider2.keyjar.issuer_keys[""]) == 2 kb = ec_init({"type": "EC", "crv": "P-256", "use": ["sig"]}) provider2.do_key_rollover(json.loads(kb.jwks()), "b%d") assert len(provider2.keyjar.issuer_keys[""]) == 3 provider2.remove_inactive_keys(-1) assert len(provider2.keyjar.issuer_keys[""]) == 2 def test_endsession_endpoint(self): resp = self.provider.endsession_endpoint("") self._assert_cookies_expired(resp.headers) # End session not allowed if no cookie is sent (can't determine session) resp = self.provider.endsession_endpoint("", cookie="FAIL") assert resp.status == "400 Bad Request" def test_endsession_endpoint_with_id_token_hint(self): id_token = self._auth_with_id_token() assert self.provider.sdb.get_sids_by_sub( id_token["sub"]) # verify we got valid session id_token_hint = id_token.to_jwt(algorithm="none") resp = self.provider.endsession_endpoint( urlencode({"id_token_hint": id_token_hint})) assert not self.provider.sdb.get_sids_by_sub( id_token["sub"]) # verify session has been removed self._assert_cookies_expired(resp.headers) def test_endsession_endpoint_with_post_logout_redirect_uri(self): id_token = self._auth_with_id_token() assert self.provider.sdb.get_sids_by_sub( id_token["sub"]) # verify we got valid session post_logout_redirect_uri = \ CDB[CLIENT_CONFIG["client_id"]]["post_logout_redirect_uris"][0][0] resp = self.provider.endsession_endpoint(urlencode( {"post_logout_redirect_uri": post_logout_redirect_uri})) assert isinstance(resp, Redirect) assert not self.provider.sdb.get_sids_by_sub( id_token["sub"]) # verify session has been removed self._assert_cookies_expired(resp.headers) def test_session_state_in_auth_req_for_session_support(self): provider = Provider("foo", SessionDB(SERVER_INFO["issuer"]), CDB, AUTHN_BROKER, USERINFO, AUTHZ, verify_client, SYMKEY, urlmap=URLMAP, keyjar=KEYJAR, capabilities={ "check_session_iframe": "https://op.example.com/check_session"}) req_args = {"scope": ["openid"], "redirect_uri": "http://localhost:8087/authz", "response_type": ["code"], "client_id": "a1b2c3" } areq = AuthorizationRequest(**req_args) resp = provider.authorization_endpoint( request=areq.to_urlencoded()) aresp = self.cons.parse_response(AuthorizationResponse, resp.message, sformat="urlencoded") assert "session_state" in aresp def _assert_cookies_expired(self, http_headers): cookies_string = ";".join( [c[1] for c in http_headers if c[0] == "Set-Cookie"]) all_cookies = SimpleCookie() all_cookies.load(cookies_string) now = datetime.datetime.now() for c in [self.provider.cookie_name, self.provider.session_cookie_name]: dt = datetime.datetime.strptime(all_cookies[c]["expires"], "%a, %d-%b-%Y %H:%M:%S GMT") assert dt < now # make sure the cookies have expired to be cleared def _auth_with_id_token(self): state, location = self.cons.begin("openid", "id_token", path="http://localhost:8087") resp = self.provider.authorization_endpoint( request=location.split("?")[1]) aresp = self.cons.parse_response(AuthorizationResponse, resp.message, sformat="urlencoded") return aresp["id_token"]
class TestOICConsumerLogout: @pytest.fixture(autouse=True) def setup_consumer(self, session_db_factory): client_config = { "client_id": CLIENT_ID, "client_authn_method": CLIENT_AUTHN_METHOD, } self.consumer = Consumer(DictSessionBackend(), CONFIG, client_config, SERVER_INFO) self.consumer.keyjar = CLIKEYS self.consumer.redirect_uris = ["https://example.com/authz"] self.consumer.client_secret = "hemlig" self.consumer.secret_type = "basic" self.consumer.issuer = ISSUER_ID self.provider = Provider( ISSUER_ID, session_db_factory(ISSUER_ID), CDB, AUTHN_BROKER, USERINFO, AUTHZ, verify_client, SYMKEY, urlmap=URLMAP, keyjar=SRVKEYS, ) self.provider.baseurl = self.provider.name def test_logout_with_sub(self): # Simulate an authorization sid, request_location = self.consumer.begin("openid", "code", path="https://example.com") resp = self.provider.authorization_endpoint(request=request_location) part = self.consumer.parse_authz(resp.message) assert isinstance(part, tuple) aresp = part[0] assert aresp assert self.consumer.sdb[sid]["issuer"] == self.provider.baseurl # Simulate an accesstoken request areq = AccessTokenRequest( code=aresp["code"], client_id=CLIENT_ID, redirect_uri="http://example.com/authz", client_secret=self.consumer.client_secret, grant_type="authorization_code", ) token_resp = self.provider.code_grant_type(areq) tresp = self.consumer.parse_response(AccessTokenResponse, token_resp.message, sformat="json") # Now, for the backchannel logout. This happens on the OP logout_info = { "sub": tresp["id_token"]["sub"], "events": { BACK_CHANNEL_LOGOUT_EVENT: {} }, } alg = "RS256" _jws = JWT( self.provider.keyjar, iss=self.provider.baseurl, lifetime=86400, sign_alg=alg, ) logout_token = _jws.pack(aud=CLIENT_ID, **logout_info) # The logout request that gets sent to the RP request = BackChannelLogoutRequest(logout_token=logout_token) # The RP evaluates the request. If everything is OK a session ID (== original state # value) is returned. _sid = self.consumer.backchannel_logout(request_args=request.to_dict()) assert _sid == sid # Test other coding _sid = self.consumer.backchannel_logout( request=request.to_urlencoded()) assert _sid == sid def test_not_for_me(self): _sub = "sub" logout_info = {"sub": _sub, "events": {BACK_CHANNEL_LOGOUT_EVENT: {}}} alg = "RS256" _jws = JWT( self.provider.keyjar, iss=self.provider.baseurl, lifetime=86400, sign_alg=alg, ) logout_token = _jws.pack(aud="someone", **logout_info) # The logout request that gets sent to the RP request = BackChannelLogoutRequest(logout_token=logout_token) with pytest.raises(MessageException): self.consumer.backchannel_logout(request_args=request.to_dict()) def test_logout_without_sub(self): # Simulate an authorization sid, request_location = self.consumer.begin("openid", "code", path="https://example.com") resp = self.provider.authorization_endpoint(request=request_location) part = self.consumer.parse_authz(resp.message) assert isinstance(part, tuple) aresp = part[0] assert aresp assert self.consumer.sdb[sid]["issuer"] == self.provider.baseurl # Simulate an accesstoken request areq = AccessTokenRequest( code=aresp["code"], client_id=CLIENT_ID, redirect_uri="http://example.com/authz", client_secret=self.consumer.client_secret, grant_type="authorization_code", ) token_resp = self.provider.code_grant_type(areq) self.consumer.parse_response(AccessTokenResponse, token_resp.message, sformat="json") # Have to fake this until the provider changes are in place _smid = "session_management_id" self.consumer.sso_db.update(sid, "smid", _smid) # Now, for the backchannel logout. This happens on the OP logout_info = {"sid": _smid, "events": {BACK_CHANNEL_LOGOUT_EVENT: {}}} alg = "RS256" _jws = JWT( self.provider.keyjar, iss=self.provider.baseurl, lifetime=86400, sign_alg=alg, ) logout_token = _jws.pack(aud=CLIENT_ID, **logout_info) # The logout request that gets sent to the RP request = BackChannelLogoutRequest(logout_token=logout_token) # The RP evaluates the request. If everything is OK a session ID (== original state # value) is returned. _sid = self.consumer.backchannel_logout(request_args=request.to_dict()) assert _sid == [sid] def test_logout_with_none(self): # Now for the backchannel logout. This happens on the OP logout_info = LogoutToken(events={BACK_CHANNEL_LOGOUT_EVENT: {}}) alg = "RS256" _jws = JWT( self.provider.keyjar, iss=self.provider.baseurl, lifetime=86400, sign_alg=alg, ) logout_token = _jws.pack(aud=CLIENT_ID, **logout_info) # The logout request that gets sent to the RP request = BackChannelLogoutRequest(logout_token=logout_token) # The RP evaluates the request. If everything is OK a session ID (== original state # value) is returned. with pytest.raises(MessageException): self.consumer.backchannel_logout(request_args=request.to_dict()) def test_sso_db_dict(self): client_config = { "client_id": CLIENT_ID, "client_authn_method": CLIENT_AUTHN_METHOD, } _consumer = Consumer({}, CONFIG, client_config, SERVER_INFO, sso_db={}) _consumer.keyjar = CLIKEYS _consumer.redirect_uris = ["https://example.com/authz"] _consumer.client_secret = "hemlig" _consumer.secret_type = "basic" _consumer.issuer = ISSUER_ID # Simulate an authorization sid, request_location = _consumer.begin("openid", "code", path="https://example.com") resp = self.provider.authorization_endpoint(request=request_location) part = _consumer.parse_authz(resp.message) assert isinstance(part, tuple) aresp = part[0] assert aresp assert _consumer.sdb[sid]["issuer"] == self.provider.baseurl # Simulate an accesstoken request areq = AccessTokenRequest( code=aresp["code"], client_id=CLIENT_ID, redirect_uri="http://example.com/authz", client_secret=_consumer.client_secret, grant_type="authorization_code", ) token_resp = self.provider.code_grant_type(areq) tresp = _consumer.parse_response(AccessTokenResponse, token_resp.message, sformat="json") # Now, for the backchannel logout. This happens on the OP logout_info = { "sub": tresp["id_token"]["sub"], "events": { BACK_CHANNEL_LOGOUT_EVENT: {} }, } alg = "RS256" _jws = JWT( self.provider.keyjar, iss=self.provider.baseurl, lifetime=86400, sign_alg=alg, ) logout_token = _jws.pack(aud=CLIENT_ID, **logout_info) # The logout request that gets sent to the RP request = BackChannelLogoutRequest(logout_token=logout_token) # The RP evaluates the request. If everything is OK a session ID (== original state # value) is returned. _sid = _consumer.backchannel_logout(request_args=request.to_dict()) assert _sid == sid def test_attribute_error(self): self.consumer.sdb.update("sid", "foo", "bar") self.consumer.update("sid") with pytest.raises(AttributeError): getattr(self.consumer, "foo")
class FakeOP: STATE = "12345678" def __init__(self): op_base_url = TestConfiguration.get_instance().rp_config.OP_URL self.provider = Provider( "https://op.tester.se/", SessionDB(op_base_url), CDB, AUTHN_BROKER, USERINFO, AUTHZ, verify_client, SYMKEY, urlmap=None, keyjar=KEYJAR ) self.provider.baseurl = TestConfiguration.get_instance().rp_config.OP_URL self.op_base = TestConfiguration.get_instance().rp_config.OP_URL self.redirect_urls = TestConfiguration.get_instance().rp_config.CLIENTS[PROVIDER]["client_info"][ "redirect_uris"] def setup_userinfo_endpoint(self): cons = Consumer({}, CONSUMER_CONFIG, {"client_id": CLIENT_ID}, server_info=SERVER_INFO, ) cons.behaviour = { "request_object_signing_alg": DEF_SIGN_ALG["openid_request_object"]} cons.keyjar[""] = KC_RSA cons.client_secret = "drickyoughurt" state, location = cons.begin("openid", "token", path=TestConfiguration.get_instance().rp_base) resp = self.provider.authorization_endpoint( request=urlparse(location).query) # redirect atr = AuthorizationResponse().deserialize( urlparse(resp.message).fragment, "urlencoded") uir = UserInfoRequest(access_token=atr["access_token"], schema="openid") resp = self.provider.userinfo_endpoint(request=uir.to_urlencoded()) responses.add( responses.POST, self.op_base + "userinfo", body=resp.message, status=200, content_type='application/json') def setup_token_endpoint(self): authreq = AuthorizationRequest(state="state", redirect_uri=self.redirect_urls[0], client_id=CLIENT_ID, response_type="code", scope=["openid"]) _sdb = self.provider.sdb sid = _sdb.token.key(user="******", areq=authreq) access_grant = _sdb.token(sid=sid) ae = AuthnEvent("user", "salt") _sdb[sid] = { "oauth_state": "authz", "authn_event": ae, "authzreq": authreq.to_json(), "client_id": CLIENT_ID, "code": access_grant, "code_used": False, "scope": ["openid"], "redirect_uri": self.redirect_urls[0], } _sdb.do_sub(sid, "client_salt") # Construct Access token request areq = AccessTokenRequest(code=access_grant, client_id=CLIENT_ID, redirect_uri=self.redirect_urls[0], client_secret="client_secret_1") txt = areq.to_urlencoded() resp = self.provider.token_endpoint(request=txt) responses.add( responses.POST, self.op_base + "token", body=resp.message, status=200, content_type='application/json') def setup_authentication_response(self, state=None): context = Context() context.path = 'openid/authz_cb' op_base = TestConfiguration.get_instance().rp_config.OP_URL if not state: state = rndstr() context.request = { 'code': 'F+R4uWbN46U+Bq9moQPC4lEvRd2De4o=', 'scope': 'openid profile email address phone', 'state': state} context.state = self.generate_state(op_base) return context def generate_state(self, op_base): state = State() state_id = TestConfiguration.get_instance().rp_config.STATE_ID state_data = { StateKeys.OP: PROVIDER, StateKeys.NONCE: "9YraWpJAmVp4L3NJ", StateKeys.TOKEN_ENDPOINT: TestConfiguration.get_instance().rp_config.OP_URL + "token", StateKeys.CLIENT_ID: "client_1", StateKeys.CLIENT_SECRET: "2222222222", StateKeys.JWKS_URI: TestConfiguration.get_instance().rp_config.OP_URL + "static/jwks.json", StateKeys.USERINFO_ENDPOINT: TestConfiguration.get_instance().rp_config.OP_URL + "userinfo", StateKeys.STATE: FakeOP.STATE } state.add(state_id, state_data) return state def setup_client_registration_endpoint(self): client_info = TestConfiguration.get_instance().rp_config.CLIENTS[PROVIDER]["client_info"] request = RegistrationRequest().deserialize(json.dumps(client_info), "json") _cinfo = self.provider.do_client_registration(request, CLIENT_ID) args = dict([(k, v) for k, v in _cinfo.items() if k in RegistrationResponse.c_param]) args['client_id'] = CLIENT_ID self.provider.comb_uri(args) registration_response = RegistrationResponse(**args) responses.add( responses.POST, self.op_base + "registration", body=registration_response.to_json(), status=200, content_type='application/json') def setup_opienid_config_endpoint(self): self.provider.baseurl = self.op_base self.provider.jwks_uri = self.publish_jwks() provider_info = self.provider.create_providerinfo() responses.add( responses.GET, self.op_base + ".well-known/openid-configuration", body=provider_info.to_json(), status=200, content_type='application/json' ) def setup_webfinger_endpoint(self): wf = WebFinger() resp = Response(wf.response(subject=self.op_base, base=self.op_base)) responses.add(responses.GET, self.op_base + ".well-known/webfinger", body=resp.message, status=200, content_type='application/json') def publish_jwks(self): jwks_uri = self.op_base + "static/jwks.json" responses.add( responses.GET, jwks_uri, body=json.dumps(JWKS), status=200, content_type='application/json') return jwks_uri
class TestProvider(object): @pytest.fixture(autouse=True) def create_provider(self): self.provider = Provider(SERVER_INFO["issuer"], SessionDB(SERVER_INFO["issuer"]), CDB, AUTHN_BROKER, USERINFO, AUTHZ, verify_client, SYMKEY, urlmap=URLMAP, keyjar=KEYJAR) self.provider.baseurl = self.provider.name self.cons = Consumer( {}, CONSUMER_CONFIG, CLIENT_CONFIG, server_info=SERVER_INFO, ) self.cons.behaviour = { "request_object_signing_alg": DEF_SIGN_ALG["openid_request_object"] } self.cons.keyjar[""] = KC_RSA def test_authorization_endpoint(self): bib = { "scope": ["openid"], "state": "id-6da9ca0cc23959f5f33e8becd9b08cae", "redirect_uri": "http://*****:*****@example.com"] req["response_types"] = ["code"] resp = self.provider.registration_endpoint(request=req.to_json()) regresp = RegistrationResponse().deserialize(resp.message, "json") assert _eq(regresp.keys(), [ 'redirect_uris', 'contacts', 'application_type', 'client_name', 'registration_client_uri', 'client_secret_expires_at', 'registration_access_token', 'client_id', 'client_secret', 'client_id_issued_at', 'response_types' ]) def test_registration_endpoint_with_non_https_redirect_uri_implicit_flow( self): params = { "application_type": "web", "redirect_uris": ["http://example.com/authz"], "response_types": ["id_token", "token"] } req = RegistrationRequest(**params) resp = self.provider.registration_endpoint(request=req.to_json()) assert resp.status == "400 Bad Request" error = json.loads(resp.message) assert error["error"] == "invalid_redirect_uri" def test_verify_redirect_uris_with_https_code_flow(self): params = { "application_type": "web", "redirect_uris": ["http://example.com/authz"], "response_types": ["code"] } request = RegistrationRequest(**params) verified_uris = self.provider._verify_redirect_uris(request) assert verified_uris == [("http://example.com/authz", None)] def test_verify_redirect_uris_with_non_https_redirect_uri_implicit_flow( self): params = { "application_type": "web", "redirect_uris": ["http://example.com/authz"], "response_types": ["id_token", "token"] } request = RegistrationRequest(**params) with pytest.raises(InvalidRedirectURIError) as exc_info: self.provider._verify_redirect_uris(request) assert str(exc_info.value) == "None https redirect_uri not allowed" # @pytest.mark.network # def test_registration_endpoint_openid4us(self): # req = RegistrationRequest( # **{'token_endpoint_auth_method': u'client_secret_post', # 'redirect_uris': [ # u'https://connect.openid4.us:5443/phpRp/index.php' # u'/callback', # u'https://connect.openid4.us:5443/phpRp/authcheck.php' # u'/authcheckcb'], # 'jwks_uri': # u'https://connect.openid4.us:5443/phpRp/rp/rp.jwk', # 'userinfo_encrypted_response_alg': u'RSA1_5', # 'contacts': [u'*****@*****.**'], # 'userinfo_encrypted_response_enc': u'A128CBC-HS256', # 'application_type': u'web', # 'client_name': u'ABRP-17', # 'grant_types': [u'authorization_code', u'implicit'], # 'post_logout_redirect_uris': [ # u'https://connect.openid4.us:5443/phpRp/index.php' # u'/logoutcb'], # 'subject_type': u'public', # 'response_types': [u'code', u'token', u'id_token', # u'code token', # u'code id_token', u'id_token token', # u'code id_token token'], # 'policy_uri': # u'https://connect.openid4.us:5443/phpRp/index.php' # u'/policy', # 'logo_uri': # u'https://connect.openid4.us:5443/phpRp/media/logo.png'}) # # resp = self.provider.registration_endpoint(request=req.to_json()) # # regresp = RegistrationResponse().deserialize(resp.message, "json") # assert _eq(regresp.keys(), list(req.keys()) + # ['registration_client_uri', # 'client_secret_expires_at', # 'registration_access_token', # 'client_id', 'client_secret', # 'client_id_issued_at']) def test_provider_key_setup(self, tmpdir): path = tmpdir.strpath provider = Provider("pyoicserv", SessionDB(SERVER_INFO["issuer"]), None, None, None, None, None, "") provider.baseurl = "http://www.example.com" provider.key_setup(path, path, sig={"format": "jwk", "alg": "RSA"}) keys = provider.keyjar.get_signing_key("RSA") assert len(keys) == 1 assert provider.jwks_uri == "http://www.example.com/{}/jwks".format( path) @pytest.mark.parametrize("uri", [ "http://example.org/foo", "http://example.com/cb", "http://example.org/cb?got=you", "http://example.org/cb/foo?got=you" ]) def test_verify_redirect_uri_faulty_without_query(self, uri): rr = RegistrationRequest(operation="register", redirect_uris=["http://example.org/cb"], response_types=["code"]) registration_req = rr.to_json() resp = self.provider.registration_endpoint(request=registration_req) regresp = RegistrationResponse().from_json(resp.message) cid = regresp["client_id"] areq = AuthorizationRequest(redirect_uri=uri, client_id=cid, response_type="code", scope="openid") with pytest.raises(RedirectURIError): self.provider._verify_redirect_uri(areq) @pytest.mark.parametrize("uri", [ "http://example.org/cb", ]) def test_verify_redirect_uri_correct_without_query(self, uri): rr = RegistrationRequest(operation="register", redirect_uris=["http://example.org/cb"], response_types=["code"]) registration_req = rr.to_json() resp = self.provider.registration_endpoint(request=registration_req) regresp = RegistrationResponse().from_json(resp.message) cid = regresp["client_id"] areq = AuthorizationRequest(redirect_uri=uri, client_id=cid, response_type="code", scope="openid") self.provider._verify_redirect_uri(areq) @pytest.mark.parametrize("uri", [ "http://example.org/cb", "http://example.org/cb?got=you", "http://example.org/cb?foo=you" "http://example.org/cb?foo=bar&got=you", "http://example.org/cb?foo=you&foo=bar" ]) def test_registered_redirect_uri_faulty_with_query_component(self, uri): rr = RegistrationRequest( operation="register", redirect_uris=["http://example.org/cb?foo=bar"], response_types=["code"]) registration_req = rr.to_json() resp = self.provider.registration_endpoint(request=registration_req) regresp = RegistrationResponse().from_json(resp.message) cid = regresp["client_id"] areq = AuthorizationRequest(redirect_uri=uri, client_id=cid, scope="openid", response_type="code") with pytest.raises(RedirectURIError): self.provider._verify_redirect_uri(areq) def test_registered_redirect_uri_correct_with_query_component(self): rr = RegistrationRequest( operation="register", redirect_uris=["http://example.org/cb?foo=bar"], response_types=["code"]) registration_req = rr.to_json() resp = self.provider.registration_endpoint(request=registration_req) regresp = RegistrationResponse().from_json(resp.message) cid = regresp["client_id"] areq = AuthorizationRequest( redirect_uri="http://example.org/cb?foo=bar", client_id=cid, scope="openid", response_type="code") self.provider._verify_redirect_uri(areq) def test_read_registration(self): rr = RegistrationRequest(operation="register", redirect_uris=["http://example.org/new"], response_types=["code"]) registration_req = rr.to_json() resp = self.provider.registration_endpoint(request=registration_req) regresp = RegistrationResponse().from_json(resp.message) authn = ' '.join(['Bearer', regresp['registration_access_token']]) query = '='.join(['client_id', regresp['client_id']]) resp = self.provider.read_registration(authn, query) assert json.loads(resp.message) == regresp.to_dict() def test_read_registration_wrong_authn(self): resp = self.provider.read_registration('wrong string', 'request') assert resp.status == '400 Bad Request' assert json.loads(resp.message) == { 'error': 'invalid_request', 'error_description': None } def test_key_rollover(self): provider2 = Provider("FOOP", {}, {}, None, None, None, None, "") provider2.keyjar = KEYJAR # Number of KeyBundles assert len(provider2.keyjar.issuer_keys[""]) == 1 kb = ec_init({"type": "EC", "crv": "P-256", "use": ["sig"]}) provider2.do_key_rollover(json.loads(kb.jwks()), "b%d") assert len(provider2.keyjar.issuer_keys[""]) == 2 kb = ec_init({"type": "EC", "crv": "P-256", "use": ["sig"]}) provider2.do_key_rollover(json.loads(kb.jwks()), "b%d") assert len(provider2.keyjar.issuer_keys[""]) == 3 provider2.remove_inactive_keys(-1) assert len(provider2.keyjar.issuer_keys[""]) == 2 def test_endsession_endpoint(self): resp = self.provider.endsession_endpoint("") self._assert_cookies_expired(resp.headers) # End session not allowed if no cookie is sent (can't determine session) resp = self.provider.endsession_endpoint("", cookie="FAIL") assert resp.status == "400 Bad Request" def test_endsession_endpoint_with_id_token_hint(self): id_token = self._auth_with_id_token() assert self.provider.sdb.get_sids_by_sub( id_token["sub"]) # verify we got valid session id_token_hint = id_token.to_jwt(algorithm="none") resp = self.provider.endsession_endpoint( urlencode({"id_token_hint": id_token_hint})) assert not self.provider.sdb.get_sids_by_sub( id_token["sub"]) # verify session has been removed self._assert_cookies_expired(resp.headers) def test_endsession_endpoint_with_post_logout_redirect_uri(self): id_token = self._auth_with_id_token() assert self.provider.sdb.get_sids_by_sub( id_token["sub"]) # verify we got valid session post_logout_redirect_uri = \ CDB[CLIENT_CONFIG["client_id"]]["post_logout_redirect_uris"][0][0] resp = self.provider.endsession_endpoint( urlencode({"post_logout_redirect_uri": post_logout_redirect_uri})) assert isinstance(resp, SeeOther) assert not self.provider.sdb.get_sids_by_sub( id_token["sub"]) # verify session has been removed self._assert_cookies_expired(resp.headers) def test_session_state_in_auth_req_for_session_support(self): provider = Provider(SERVER_INFO["issuer"], SessionDB(SERVER_INFO["issuer"]), CDB, AUTHN_BROKER, USERINFO, AUTHZ, verify_client, SYMKEY, urlmap=URLMAP, keyjar=KEYJAR) provider.capabilities.update( {"check_session_iframe": "https://op.example.com/check_session"}) req_args = { "scope": ["openid"], "redirect_uri": "http://localhost:8087/authz", "response_type": ["code"], "client_id": "number5" } areq = AuthorizationRequest(**req_args) resp = provider.authorization_endpoint(request=areq.to_urlencoded()) aresp = self.cons.parse_response(AuthorizationResponse, resp.message, sformat="urlencoded") assert "session_state" in aresp def _assert_cookies_expired(self, http_headers): cookies_string = ";".join( [c[1] for c in http_headers if c[0] == "Set-Cookie"]) all_cookies = SimpleCookie() try: cookies_string = cookies_string.decode() except (AttributeError, UnicodeDecodeError): pass all_cookies.load(cookies_string) now = datetime.datetime.utcnow() # for c in [ self.provider.cookie_name, self.provider.session_cookie_name ]: dt = datetime.datetime.strptime(all_cookies[c]["expires"], "%a, %d-%b-%Y %H:%M:%S GMT") assert dt < now # make sure the cookies have expired to be cleared def _auth_with_id_token(self): state, location = self.cons.begin("openid", "id_token", path="http://localhost:8087") resp = self.provider.authorization_endpoint( request=location.split("?")[1]) aresp = self.cons.parse_response(AuthorizationResponse, resp.message, sformat="urlencoded") return aresp["id_token"] def test_id_token_RS512_sign(self): self.provider.capabilities['id_token_signing_alg_values_supported'] = [ 'RS512' ] self.provider.build_jwx_def() id_token = self._auth_with_id_token() assert id_token.jws_header['alg'] == "RS512"
class TestProvider(object): CDB = { "number5": { "password": "******", "client_secret": "drickyoughurt", "redirect_uris": [("http://localhost:8087/authz", None)], "post_logout_redirect_uris": [("https://example.com/post_logout", None)], "client_salt": "salted", "response_types": [ "code", "token", "code id_token", "none", "code token", "id_token", ], }, "a1b2c3": { "redirect_uris": [("http://localhost:8088/authz", None)], "client_salt": "salted", "client_secret": "very_secret", "response_types": ["code", "token", "code id_token"], }, "client0": { "redirect_uris": [("http://www.example.org/authz", None)], "client_secret": "very_secret", "post_logout_redirect_uris": [("https://www.example.org/post_logout", None)], "client_salt": "salted", "response_types": ["code", "token", "code id_token"], }, CLIENT_ID: { "client_secret": CLIENT_SECRET, "redirect_uris": [("http://localhost:8087/authz", None)], "client_salt": "salted", "token_endpoint_auth_method": "client_secret_post", "response_types": ["code", "token", "code id_token"], }, } # type: Dict[str, Dict[str, Any]] @pytest.fixture(autouse=True) def create_provider(self, session_db_factory): self.provider = Provider( SERVER_INFO["issuer"], session_db_factory(SERVER_INFO["issuer"]), self.CDB, AUTHN_BROKER, USERINFO, AUTHZ, verify_client, SYMKEY, urlmap=URLMAP, keyjar=KEYJAR, ) self.provider.baseurl = self.provider.name self.provider.logout_verify_url = "https://127.0.0.1/logout_verify.html" self.cons = Consumer( DictSessionBackend(), CONSUMER_CONFIG.copy(), CLIENT_CONFIG, server_info=SERVER_INFO, ) self.cons.behaviour = { "request_object_signing_alg": DEF_SIGN_ALG["openid_request_object"] } self.cons.keyjar[""] = KC_RSA self.cons.keyjar.import_jwks(self.provider.keyjar.export_jwks(), self.cons.issuer) self.cons2 = Consumer({}, CONSUMER_CONFIG.copy(), CLIENT_CONFIG_2, server_info=SERVER_INFO) self.cons2.behaviour = { "request_object_signing_alg": DEF_SIGN_ALG["openid_request_object"] } self.cons2.keyjar[""] = KC_RSA def _code_auth(self): state, location = self.cons.begin("openid", "code", path="http://localhost:8087") return self.provider.authorization_endpoint( request=location.split("?")[1]) def _code_auth2(self): state, location = self.cons2.begin("openid", "code", path="http://www.example.org") return self.provider.authorization_endpoint( request=location.split("?")[1]) def _auth_with_id_token(self): state, location = self.cons.begin("openid", "id_token", path="http://localhost:8087") resp = self.provider.authorization_endpoint( request=location.split("?")[1]) aresp = self.cons.parse_response(AuthorizationResponse, resp.message, sformat="urlencoded") return aresp["id_token"] def _create_cookie(self, user, client_id, c_type="sso"): cd = CookieDealer(self.provider) set_cookie = cd.create_cookie("{}][{}".format(user, client_id), c_type, self.provider.sso_cookie_name) cookies_string = set_cookie[1] all_cookies = SimpleCookie() # type: SimpleCookie try: cookies_string = cookies_string.decode() except (AttributeError, UnicodeDecodeError): pass all_cookies.load(cookies_string) return all_cookies def test_missing_post_logout_redirect_uri(self): esr = EndSessionRequest(state="foo") assert self.provider.verify_post_logout_redirect_uri(esr, CLIENT_ID) is None def test_wrong_post_logout_redirect_uri(self): self.provider.cdb[CLIENT_ID]["post_logout_redirect_uris"] = [ "https://example.com/plru" ] esr = EndSessionRequest( state="foo", post_logout_redirect_uri="https://localhost:8087/plru") assert self.provider.verify_post_logout_redirect_uri(esr, CLIENT_ID) is None def test_no_post_logout_redirect_uri(self): self.provider.cdb[CLIENT_ID]["post_logout_redirect_uris"] = [ "https://example.com/plru", "https://example.com/plru2", ] esr = EndSessionRequest(state="foo") assert self.provider.verify_post_logout_redirect_uri(esr, CLIENT_ID) is None def test_let_user_verify_logout(self): self.provider.cdb[CLIENT_ID]["post_logout_redirect_uris"] = [ "https://localhost:8087/plru" ] esr = EndSessionRequest( state="foo", post_logout_redirect_uri="https://localhost:8087/plru") res = self.provider.let_user_verify_logout("user", esr, None, None) assert isinstance(res, Response) assert res.headers == [("Content-type", "text/html")] assert res.status_code == 200 def test_let_user_verify_logout_with_cookie(self): self.provider.cdb[CLIENT_ID]["post_logout_redirect_uris"] = [ "https://localhost:8087/plru" ] esr = EndSessionRequest( state="foo", post_logout_redirect_uri="https://localhost:8087/plru") res = self.provider.let_user_verify_logout("user", esr, [("Set-Cookie", "kaka")], None) assert isinstance(res, Response) assert set(res.headers) == { ("Content-type", "text/html"), ("Set-Cookie", "kaka"), } assert res.status_code == 200 def test_let_user_verify_logout_with_redirect(self): self.provider.cdb[CLIENT_ID]["post_logout_redirect_uris"] = [ "https://localhost:8087/plru" ] esr = EndSessionRequest( state="foo", post_logout_redirect_uri="https://localhost:8087/plru") res = self.provider.let_user_verify_logout( "user", esr, None, "https://example.com/redirect") assert isinstance(res, Response) assert set(res.headers) == {("Content-type", "text/html")} assert res.status_code == 200 # make sure the redirect was propagated txt = '<input type="hidden" name="{}" value="{}"/>'.format( "post_logout_redirect_uri", "https://localhost:8087/plru") assert txt in res.message def test_let_user_verify_logout_with_id_token_hint(self): self.provider.cdb[CLIENT_ID]["post_logout_redirect_uris"] = [ "https://localhost:8087/plru" ] esr = EndSessionRequest( state="foo", post_logout_redirect_uri="https://localhost:8087/plru", id_token_hint="J.W.S", ) res = self.provider.let_user_verify_logout("user", esr, None, None) assert isinstance(res, Response) assert set(res.headers) == {("Content-type", "text/html")} assert res.status_code == 200 # make sure the id_token_hint was propagated txt = '<input type="hidden" name="{}" value="{}"/>'.format( "id_token_hint", "J.W.S") assert txt in res.message def test_end_session_endpoint_with_cookie(self): self.provider.events = DummyEventStore() # type: ignore self._code_auth() cookie = self._create_cookie("username", "number5") resp = self.provider.end_session_endpoint(urlencode({"state": "abcde"}), cookie=cookie) # returns a SeeOther instance p = urlparse(resp.message) qs = parse_qs(p.query) jwt_info = self.provider.unpack_signed_jwt(qs["sjwt"][0]) assert jwt_info["state"] == "abcde" assert jwt_info["uid"] == "username" assert jwt_info["client_id"] == "number5" assert jwt_info["redirect_uri"] == "https://example.com/post_logout" def test_end_session_endpoint_with_wrong_cookie(self): # Need cookie and ID Token to figure this out id_token = self._auth_with_id_token() assert session_get(self.provider.sdb, "sub", id_token["sub"]) # verify we got valid session id_token_hint = id_token.to_jwt(algorithm="none") cookie = self._create_cookie("diggins", "number5") resp = self.provider.end_session_endpoint(urlencode( {"id_token_hint": id_token_hint}), cookie=cookie) assert isinstance(resp, Response) _err = ErrorResponse().from_json(resp.message) assert _err["error"] == "invalid_request" assert _err["error_description"] == "Wrong user" def test_end_session_endpoint_with_cookie_wrong_user(self): # Need cookie and ID Token to figure this out id_token = self._auth_with_id_token() assert session_get(self.provider.sdb, "sub", id_token["sub"]) id_token_hint = id_token.to_jwt(algorithm="none") cookie = self._create_cookie("diggins", "number5") resp = self.provider.end_session_endpoint(urlencode( {"id_token_hint": id_token_hint}), cookie=cookie) assert isinstance(resp, Response) _err = ErrorResponse().from_json(resp.message) assert _err["error"] == "invalid_request" assert _err["error_description"] == "Wrong user" def test_end_session_endpoint_with_cookie_wrong_client(self): # Need cookie and ID Token to figure this out id_token = self._auth_with_id_token() assert session_get(self.provider.sdb, "sub", id_token["sub"]) id_token_hint = id_token.to_jwt(algorithm="none") # Wrong client_id cookie = self._create_cookie("username", "a1b2c3") resp = self.provider.end_session_endpoint(urlencode( {"id_token_hint": id_token_hint}), cookie=cookie) assert isinstance(resp, Response) _err = ErrorResponse().from_json(resp.message) assert _err["error"] == "invalid_request" def test_end_session_endpoint_with_cookie_dual_login(self): self._code_auth() self._code_auth2() cookie = self._create_cookie("username", "client0") resp = self.provider.end_session_endpoint(urlencode({"state": "abcde"}), cookie=cookie) # returns a SeeOther instance p = urlparse(resp.message) qs = parse_qs(p.query) jwt_info = self.provider.unpack_signed_jwt(qs["sjwt"][0]) assert jwt_info["state"] == "abcde" assert jwt_info["uid"] == "username" assert jwt_info["client_id"] == "client0" assert jwt_info[ "redirect_uri"] == "https://www.example.org/post_logout" def test_end_session_endpoint_with_cookie_dual_login_wrong_client(self): self._code_auth() self._code_auth2() # The cookie states that a user has a session at a client and this # statement is false. cookie = self._create_cookie("username", "a1b2c3") resp = self.provider.end_session_endpoint(urlencode({"state": "abcde"}), cookie=cookie) assert isinstance(resp, Response) _err = ErrorResponse().from_json(resp.message) assert _err["error"] == "invalid_request" def test_end_session_endpoint_with_id_token_hint_only(self): id_token = self._auth_with_id_token() assert session_get(self.provider.sdb, "sub", id_token["sub"]) id_token_hint = id_token.to_jwt(algorithm="none") resp = self.provider.end_session_endpoint( urlencode({"id_token_hint": id_token_hint})) # returns a SeeOther instance p = urlparse(resp.message) qs = parse_qs(p.query) jwt_info = self.provider.unpack_signed_jwt(qs["sjwt"][0]) assert jwt_info["uid"] == "username" assert jwt_info["client_id"] == "number5" assert jwt_info["redirect_uri"] == "https://example.com/post_logout" def test_end_session_endpoint_with_id_token_hint_and_cookie(self): id_token = self._auth_with_id_token() assert session_get(self.provider.sdb, "sub", id_token["sub"]) id_token_hint = id_token.to_jwt(algorithm="none") cookie = self._create_cookie("username", "number5") resp = self.provider.end_session_endpoint(urlencode( {"id_token_hint": id_token_hint}), cookie=cookie) # returns a SeeOther instance p = urlparse(resp.message) qs = parse_qs(p.query) jwt_info = self.provider.unpack_signed_jwt(qs["sjwt"][0]) assert jwt_info["uid"] == "username" assert jwt_info["client_id"] == "number5" assert jwt_info["redirect_uri"] == "https://example.com/post_logout" def test_end_session_endpoint_with_post_logout_redirect_uri(self): self._code_auth() cookie = self._create_cookie("username", "number5") post_logout_redirect_uri = self.CDB[str( CLIENT_CONFIG["client_id"])]["post_logout_redirect_uris"][0][0] resp = self.provider.end_session_endpoint( urlencode({ "post_logout_redirect_uri": post_logout_redirect_uri, "state": "abcde" }), cookie=cookie, ) # returns a SeeOther instance p = urlparse(resp.message) qs = parse_qs(p.query) jwt_info = self.provider.unpack_signed_jwt(qs["sjwt"][0]) assert jwt_info["state"] == "abcde" assert jwt_info["uid"] == "username" assert jwt_info["client_id"] == "number5" assert jwt_info["redirect_uri"] == "https://example.com/post_logout" def test_end_session_endpoint_without_post_logout_redirect_uri(self): # default post logout page registered self.provider.post_logout_page = "https://foo.example.com/def_post" # No post_logout_redirect_uris registered _plru = self.provider.cdb["number5"]["post_logout_redirect_uris"] del self.provider.cdb["number5"]["post_logout_redirect_uris"] self._code_auth() cookie = self._create_cookie("username", "number5") resp = self.provider.end_session_endpoint(urlencode({"state": "abcde"}), cookie=cookie) # returns a SeeOther instance p = urlparse(resp.message) qs = parse_qs(p.query) jwt_info = self.provider.unpack_signed_jwt(qs["sjwt"][0]) assert jwt_info["state"] == "abcde" assert jwt_info["uid"] == "username" assert jwt_info["client_id"] == "number5" assert jwt_info["redirect_uri"] == "https://foo.example.com/def_post" # restore self.provider.cdb["number5"]["post_logout_redirect_uris"] = _plru def test_end_session_endpoint_without_post_logout_redirect_uri_no_default( self): # No post_logout_redirect_uris registered _plru = self.provider.cdb["number5"]["post_logout_redirect_uris"] del self.provider.cdb["number5"]["post_logout_redirect_uris"] self._code_auth() cookie = self._create_cookie("username", "number5") resp = self.provider.end_session_endpoint(urlencode({"state": "abcde"}), cookie=cookie) assert isinstance(resp, Response) _err = ErrorResponse().from_json(resp.message) assert _err["error"] == "server_error" # restore self.provider.cdb["number5"]["post_logout_redirect_uris"] = _plru def test_end_session_endpoint_bogus_sjwt(self): self._code_auth() cookie = self._create_cookie("username", "number5") post_logout_redirect_uri = self.CDB[str( CLIENT_CONFIG["client_id"])]["post_logout_redirect_uris"][0][0] resp = self.provider.end_session_endpoint( urlencode({ "post_logout_redirect_uri": post_logout_redirect_uri, "state": "abcde" }), cookie=cookie, ) # returns a SeeOther instance p = urlparse(resp.message) qs = parse_qs(p.query) _sjwt = qs["sjwt"][0] _sjwt = ".".join(_sjwt.split(".")[:2]) + "." # Not signed with pytest.raises(ValueError): self.provider.unpack_signed_jwt(_sjwt) def test_end_session_endpoint_with_wrong_post_logout_redirect_uri(self): self._code_auth() cookie = self._create_cookie("username", "number5") post_logout_redirect_uri = "https://www.example.com/logout" resp = self.provider.end_session_endpoint( urlencode({ "post_logout_redirect_uri": post_logout_redirect_uri, "state": "abcde" }), cookie=cookie, ) assert isinstance(resp, Response) _err = ErrorResponse().from_json(resp.message) assert _err["error"] == "invalid_request" def test_end_session_endpoint_with_registered_post_logout_redirect_uri_with_query_part( self, ): self._code_auth() cookie = self._create_cookie("username", "number5") self.provider.cdb["number5"]["post_logout_redirect_uris"] = [ ("https://www.example.com/logout", { "foo": ["bar"] }) ] # No post_logout_redirect_uri in request resp = self.provider.end_session_endpoint(urlencode({"state": "abcde"}), cookie=cookie) assert isinstance(resp, Response) _qp = parse_qs(resp.message.split("?")[1]) _jwt = self.provider.unpack_signed_jwt(_qp["sjwt"][0]) assert _jwt["redirect_uri"] == "https://www.example.com/logout?foo=bar" def test_back_channel_logout_no_uri(self): self._code_auth() res = self.provider.do_back_channel_logout( self.provider.cdb[CLIENT_ID], "username", "sid") assert res is None def test_back_channel_logout(self): self._code_auth() _cdb = copy.copy(self.provider.cdb[CLIENT_ID]) _cdb["backchannel_logout_uri"] = "https://example.com/bc_logout" _cdb["client_id"] = CLIENT_ID res = self.provider.do_back_channel_logout(_cdb, "username", "_sid_") assert isinstance(res, tuple) assert res[0] == "https://example.com/bc_logout" _jwt = self.provider.unpack_signed_jwt(res[1]) assert _jwt assert _jwt["iss"] == SERVER_INFO["issuer"] assert _jwt["aud"] == [CLIENT_ID] assert _jwt["sub"] == "username" assert _jwt["sid"] == "_sid_" def test_front_channel_logout(self): self._code_auth() _cdb = copy.copy(self.provider.cdb[CLIENT_ID]) _cdb["frontchannel_logout_uri"] = "https://example.com/fc_logout" _cdb["client_id"] = CLIENT_ID res = self.provider.do_front_channel_logout_iframe( _cdb, str(SERVER_INFO["issuer"]), "_sid_") assert res == '<iframe src="https://example.com/fc_logout">' def test_front_channel_logout_session_required(self): self._code_auth() _cdb = copy.copy(self.provider.cdb[CLIENT_ID]) _cdb["frontchannel_logout_uri"] = "https://example.com/fc_logout" _cdb["frontchannel_logout_session_required"] = True _cdb["client_id"] = CLIENT_ID res = self.provider.do_front_channel_logout_iframe( _cdb, str(SERVER_INFO["issuer"]), "_sid_") m = re.match(r'<iframe src="([^"]+)">', str(res)) assert m _q = parse_qs(str(m.group(1)).split("?")[1]) assert set(_q.keys()) == {"iss", "sid"} def test_front_channel_logout_session_required_uri_query(self): self._code_auth() _cdb = copy.copy(self.provider.cdb[CLIENT_ID]) _cdb[ "frontchannel_logout_uri"] = "https://example.com/fc_logout?foo=bar" _cdb["frontchannel_logout_session_required"] = True _cdb["client_id"] = CLIENT_ID res = self.provider.do_front_channel_logout_iframe( _cdb, str(SERVER_INFO["issuer"]), "_sid_") m = re.match(r'<iframe src="([^"]+)">', str(res)) assert m _q = parse_qs(str(m.group(1)).split("?")[1]) assert set(_q.keys()) == {"foo", "iss", "sid"} def test_front_channel_logout_missing_url(self): self._code_auth() _cdb = copy.copy(self.provider.cdb[CLIENT_ID]) _cdb["client_id"] = CLIENT_ID res = self.provider.do_front_channel_logout_iframe( _cdb, str(SERVER_INFO["issuer"]), "_sid_") assert res is None def test_logout_from_client_bc(self): self._code_auth() self.provider.cdb[CLIENT_ID][ "backchannel_logout_uri"] = "https://example.com/bc_logout" self.provider.cdb[CLIENT_ID]["client_id"] = CLIENT_ID # Get a session ID, anyone will do. # I know the session backend DB is a DictSessionBackend so I can use that _sid = list(self.provider.sdb._db.storage.keys())[0] res = self.provider.logout_info_for_one_client(_sid, CLIENT_ID) assert set(res.keys()) == {"back_channel", "front_channel"} assert res["back_channel"] != {} assert res["front_channel"] == {} assert set(res["back_channel"].keys()) == {CLIENT_ID} _spec = res["back_channel"][CLIENT_ID] assert _spec[0] == "https://example.com/bc_logout" _jwt = self.provider.unpack_signed_jwt(_spec[1]) assert _jwt assert _jwt["iss"] == SERVER_INFO["issuer"] assert _jwt["aud"] == [CLIENT_ID] assert _jwt["sid"] == _sid def test_logout_from_client_fc(self): self._code_auth() del self.provider.cdb[CLIENT_ID]["backchannel_logout_uri"] self.provider.cdb[CLIENT_ID][ "frontchannel_logout_uri"] = "https://example.com/fc_logout" self.provider.cdb[CLIENT_ID]["client_id"] = CLIENT_ID # Get a session ID, anyone will do. # I know the session backend DB is a DictSessionBackend so I can use that _sid = list(self.provider.sdb._db.storage.keys())[0] res = self.provider.logout_info_for_one_client(_sid, CLIENT_ID) assert set(res.keys()) == {"front_channel", "back_channel"} assert res["back_channel"] == {} assert set(res["front_channel"].keys()) == {CLIENT_ID} _spec = res["front_channel"][CLIENT_ID] assert _spec == '<iframe src="https://example.com/fc_logout">' def test_logout_from_client(self): self._code_auth() self._code_auth2() # client0 self.provider.cdb["client0"][ "backchannel_logout_uri"] = "https://example.com/bc_logout" self.provider.cdb["client0"]["client_id"] = "client0" self.provider.cdb["number5"][ "frontchannel_logout_uri"] = "https://example.com/fc_logout" self.provider.cdb["number5"]["client_id"] = CLIENT_ID # Get a session ID, anyone will do. # I know the session backend DB is a DictSessionBackend so I can use that _sid = list(self.provider.sdb._db.storage.keys())[0] res = self.provider.logout_info_for_all_clients(sid=_sid) assert res assert set(res.keys()) == {"back_channel", "front_channel"} assert set(res["front_channel"].keys()) == {"number5"} _spec = res["front_channel"]["number5"] assert _spec == '<iframe src="https://example.com/fc_logout">' assert set(res["back_channel"].keys()) == {"client0"} _spec = res["back_channel"]["client0"] assert _spec[0] == "https://example.com/bc_logout" _jwt = self.provider.unpack_signed_jwt(_spec[1]) assert _jwt assert _jwt["iss"] == SERVER_INFO["issuer"] assert _jwt["aud"] == ["client0"] def test_logout_spec_all(self): self._code_auth() self._code_auth2() # client0 self.provider.cdb["client0"][ "backchannel_logout_uri"] = "https://example.com/bc_logout" self.provider.cdb["client0"]["client_id"] = "client0" self.provider.cdb["number5"][ "frontchannel_logout_uri"] = "https://example.com/fc_logout" self.provider.cdb["number5"]["client_id"] = CLIENT_ID # Get a session ID, anyone will do. # I know the session backend DB is a DictSessionBackend so I can use that _sid = list(self.provider.sdb._db.storage.keys())[0] logout_spec_all = self.provider.logout_info_for_all_clients(sid=_sid) assert set(logout_spec_all.keys()) == {"back_channel", "front_channel"} assert set(logout_spec_all["back_channel"].keys()) == {"client0"} assert set(logout_spec_all["front_channel"].keys()) == {"number5"} def test_do_verified_logout_all(self): self._code_auth() self._code_auth2() # client0 self.provider.cdb["client0"][ "backchannel_logout_uri"] = "https://example.com/bc_logout" self.provider.cdb["client0"]["client_id"] = "client0" self.provider.cdb["number5"][ "frontchannel_logout_uri"] = "https://example.com/fc_logout" self.provider.cdb["number5"]["client_id"] = CLIENT_ID # Get a session ID, anyone will do. # I know the session backend DB is a DictSessionBackend so I can use that _sid = list(self.provider.sdb._db.storage.keys())[0] with responses.RequestsMock() as rsps: rsps.add(rsps.POST, "https://example.com/bc_logout", status=200) res = self.provider.do_verified_logout(_sid, CLIENT_ID, alla=True) assert set(res.keys()) == {"iframe", "cookie"} def test_do_verified_logout_just_the_one(self): self.provider.events = DummyEventStore() # type: ignore self._code_auth() self._code_auth2() # client0 self.provider.cdb["client0"][ "backchannel_logout_uri"] = "https://example.com/bc_logout" self.provider.cdb["client0"]["client_id"] = "client0" self.provider.cdb["number5"][ "frontchannel_logout_uri"] = "https://example.com/fc_logout" self.provider.cdb["number5"]["client_id"] = CLIENT_ID # Get a session ID, anyone will do. # I know the session backend DB is a DictSessionBackend so I can use that _sid = list(self.provider.sdb._db.storage.keys())[0] # There is no back channel logout, hence there should be no HTTP POST exception = requests.ConnectionError() with responses.RequestsMock( assert_all_requests_are_fired=False) as rsps: rsps.add(responses.POST, "https://example.com/bc_logout", body=exception) res = self.provider.do_verified_logout(_sid, CLIENT_ID, alla=False) assert set(res.keys()) == {"iframe", "cookie"} def test_do_verified_logout_the_other(self): self._code_auth() self._code_auth2() # client0 self.provider.cdb["client0"][ "backchannel_logout_uri"] = "https://example.com/bc_logout" self.provider.cdb["client0"]["client_id"] = "client0" self.provider.cdb["number5"][ "frontchannel_logout_uri"] = "https://example.com/fc_logout" self.provider.cdb["number5"]["client_id"] = CLIENT_ID # Get a session ID, anyone will do. # I know the session backend DB is a DictSessionBackend so I can use that _sid = list(self.provider.sdb._db.storage.keys())[0] # This only does back channel logout with responses.RequestsMock() as rsps: rsps.add(rsps.POST, "https://example.com/bc_logout", status=200) res = self.provider.do_verified_logout(_sid, "client0", alla=False) assert set(res.keys()) == {"cookie"} def test_do_verified_logout_the_other_back_channel_failed(self): self._code_auth() self._code_auth2() # client0 self.provider.cdb["client0"][ "backchannel_logout_uri"] = "https://example.com/bc_logout" self.provider.cdb["client0"]["client_id"] = "client0" self.provider.cdb["number5"][ "frontchannel_logout_uri"] = "https://example.com/fc_logout" self.provider.cdb["number5"]["client_id"] = CLIENT_ID # Get a session ID, anyone will do. # I know the session backend DB is a DictSessionBackend so I can use that _sid = list(self.provider.sdb._db.storage.keys())[0] # Does back channel logout and it will fail with responses.RequestsMock() as rsps: rsps.add(rsps.POST, "https://example.com/bc_logout", status=400) res = self.provider.do_verified_logout(_sid, "client0", alla=False) assert list(res.keys()) == [] def test_end_session_endpoint_no_post_logout_redirect_uri(self): self._code_auth() cookie = self._create_cookie("username", "number5") self.provider.cdb["number5"]["post_logout_redirect_uris"] = [ ("https://example.com/plru", ""), ("https://example.com/plru2", ""), ] res = self.provider.end_session_endpoint(urlencode({"state": "abcde"}), cookie=cookie) assert isinstance(res, Response) assert res.status_code == 400 def test_logout_info_for_all_clients_no_params(self): with pytest.raises(ParameterError): self.provider.logout_info_for_all_clients() def test_do_back_channel_logout_no_backchannel(self): self._code_auth() # Get a session ID, anyone will do. # I know the session backend DB is a DictSessionBackend so I can use that _sid = list(self.provider.sdb._db.storage.keys())[0] _sub = self.provider.sdb[_sid]["sub"] # if "backchannel_logout_uri" in self.provider.cdb["number5"]: del self.provider.cdb["number5"]["backchannel_logout_uri"] res = self.provider.do_back_channel_logout( self.provider.cdb["number5"], _sub, _sid) assert res is None def test_id_token_hint_multiple_aud(self): id_token = self._auth_with_id_token() assert session_get(self.provider.sdb, "sub", id_token["sub"]) # verify we got valid session self.provider.cdb["number5"]["post_logout_redirect_uris"] = [ ("https://example.com/plru", "") ] # add another aud and an azp. id_token["azp"] = id_token["aud"][0] id_token["aud"].append("foobar") id_token_hint = id_token.to_jwt(algorithm="none") resp = self.provider.end_session_endpoint( urlencode({"id_token_hint": id_token_hint})) assert isinstance(resp, SeeOther) def test_id_token_hint_aud_does_not_match_client_id(self): id_token = self._auth_with_id_token() assert session_get(self.provider.sdb, "sub", id_token["sub"]) # verify we got valid session # add another aud and an azp. id_token_hint = id_token.to_jwt(algorithm="none") # Mess with the session DB _sid = list(self.provider.sdb._db.storage.keys())[0] self.provider.sdb[_sid]["client_id"] = "something else" resp = self.provider.end_session_endpoint( urlencode({"id_token_hint": id_token_hint})) assert isinstance(resp, Response) assert resp.status_code == 400 def test_no_back_or_front_channel_logout(self): self._code_auth() # Mess with client DB for c in ["backchannel_logout_uri", "frontchannel_logout_uri"]: if c in self.provider.cdb["number5"]: del self.provider.cdb["number5"][c] resp = self.provider.do_verified_logout(sid=list( self.provider.sdb._db.storage.keys())[0], client_id="number5") # only cookies assert set(resp.keys()) == {"cookie"} def test_back_channel_logout_fails(self): self._code_auth() # client0 self.provider.cdb["client0"][ "backchannel_logout_uri"] = "https://example.com/bc_logout" self.provider.cdb["client0"]["client_id"] = "client0" # Get a session ID, anyone will do. # I know the session backend DB is a DictSessionBackend so I can use that _sid = list(self.provider.sdb._db.storage.keys())[0] # There is no back channel logout, hence there should be no HTTP POST with responses.RequestsMock(): res = self.provider.do_verified_logout(_sid, "client0", alla=False) assert res == {} def test_logout_info_for_one_client_no_logout_info(self): self._code_auth() # Mess with client DB for c in ["backchannel_logout_uri", "frontchannel_logout_uri"]: if c in self.provider.cdb["number5"]: del self.provider.cdb["number5"][c] # Get a session ID, anyone will do. # I know the session backend DB is a DictSessionBackend so I can use that _sid = list(self.provider.sdb._db.storage.keys())[0] resp = self.provider.logout_info_for_one_client(_sid, "number5") assert resp == {"back_channel": {}, "front_channel": {}} def test_unknown_client(self): self._code_auth() cookie = self._create_cookie("username", "unknown") resp = self.provider.end_session_endpoint(urlencode({"state": "abcde"}), cookie=cookie) assert isinstance(resp, Response) assert resp.status_code == 400 def test_no_cookie_no_id_token_hint(self): self._code_auth() resp = self.provider.end_session_endpoint(urlencode({"state": "abcde"})) assert isinstance(resp, Response) assert resp.status_code == 400 def test_back_channel_logout_failed_front_channel_logout_exists(self): self._code_auth() # client0 self.provider.cdb["number5"][ "backchannel_logout_uri"] = "https://example.com/bc_logout" self.provider.cdb["number5"][ "frontchannel_logout_uri"] = "https://example.com/fc_logout" self.provider.cdb["number5"]["client_id"] = "number5" # Get a session ID, anyone will do. # I know the session backend DB is a DictSessionBackend so I can use that _sid = list(self.provider.sdb._db.storage.keys())[0] # Does back channel logout and it will fail with responses.RequestsMock() as rsps: rsps.add(rsps.POST, "https://example.com/bc_logout", status=400) res = self.provider.do_verified_logout(_sid, "client0", alla=True) assert set(res.keys()) == {"cookie", "iframe"} def test_logout_from_clients_one_without_logout_info(self): self._code_auth() self._code_auth2() # Mess with client DB # neither back channel nor front channel for c in ["backchannel_logout_uri", "frontchannel_logout_uri"]: if c in self.provider.cdb["client0"]: del self.provider.cdb["client0"][c] self.provider.cdb["client0"]["client_id"] = "client0" # both back channel and front channel self.provider.cdb["number5"][ "frontchannel_logout_uri"] = "https://example.com/fc_logout" self.provider.cdb["number5"]["client_id"] = "number5" # Get a session ID, anyone will do. # I know the session backend DB is a DictSessionBackend so I can use that _sid = list(self.provider.sdb._db.storage.keys())[0] res = self.provider.logout_info_for_all_clients(sid=_sid) assert set(res.keys()) == {"back_channel", "front_channel"} assert set(res["back_channel"].keys()) == {"number5"} assert set(res["front_channel"].keys()) == {"number5"}
class FakeOP: STATE = "12345678" def __init__(self): op_base_url = TestConfiguration.get_instance().rp_config.OP_URL self.provider = Provider("https://op.tester.se/", SessionDB(op_base_url), CDB, AUTHN_BROKER, USERINFO, AUTHZ, verify_client, SYMKEY, urlmap=None, keyjar=KEYJAR) self.provider.baseurl = TestConfiguration.get_instance( ).rp_config.OP_URL self.op_base = TestConfiguration.get_instance().rp_config.OP_URL self.redirect_urls = TestConfiguration.get_instance( ).rp_config.CLIENTS[PROVIDER]["client_info"]["redirect_uris"] def setup_userinfo_endpoint(self): cons = Consumer( {}, CONSUMER_CONFIG, {"client_id": CLIENT_ID}, server_info=SERVER_INFO, ) cons.behaviour = { "request_object_signing_alg": DEF_SIGN_ALG["openid_request_object"] } cons.keyjar[""] = KC_RSA cons.client_secret = "drickyoughurt" state, location = cons.begin( "openid", "token", path=TestConfiguration.get_instance().rp_base) resp = self.provider.authorization_endpoint( request=urlparse(location).query) # redirect atr = AuthorizationResponse().deserialize( urlparse(resp.message).fragment, "urlencoded") uir = UserInfoRequest(access_token=atr["access_token"], schema="openid") resp = self.provider.userinfo_endpoint(request=uir.to_urlencoded()) responses.add(responses.POST, self.op_base + "userinfo", body=resp.message, status=200, content_type='application/json') def setup_token_endpoint(self): authreq = AuthorizationRequest(state="state", redirect_uri=self.redirect_urls[0], client_id=CLIENT_ID, response_type="code", scope=["openid"]) _sdb = self.provider.sdb sid = _sdb.token.key(user="******", areq=authreq) access_grant = _sdb.token(sid=sid) ae = AuthnEvent("user", "salt") _sdb[sid] = { "oauth_state": "authz", "authn_event": ae, "authzreq": authreq.to_json(), "client_id": CLIENT_ID, "code": access_grant, "code_used": False, "scope": ["openid"], "redirect_uri": self.redirect_urls[0], } _sdb.do_sub(sid, "client_salt") # Construct Access token request areq = AccessTokenRequest(code=access_grant, client_id=CLIENT_ID, redirect_uri=self.redirect_urls[0], client_secret="client_secret_1") txt = areq.to_urlencoded() resp = self.provider.token_endpoint(request=txt) responses.add(responses.POST, self.op_base + "token", body=resp.message, status=200, content_type='application/json') def setup_authentication_response(self, state=None): context = Context() context.path = 'openid/authz_cb' op_base = TestConfiguration.get_instance().rp_config.OP_URL if not state: state = rndstr() context.request = { 'code': 'F+R4uWbN46U+Bq9moQPC4lEvRd2De4o=', 'scope': 'openid profile email address phone', 'state': state } context.state = self.generate_state(op_base) return context def generate_state(self, op_base): state = State() state_id = TestConfiguration.get_instance().rp_config.STATE_ID state_data = { StateKeys.OP: PROVIDER, StateKeys.NONCE: "9YraWpJAmVp4L3NJ", StateKeys.TOKEN_ENDPOINT: TestConfiguration.get_instance().rp_config.OP_URL + "token", StateKeys.CLIENT_ID: "client_1", StateKeys.CLIENT_SECRET: "2222222222", StateKeys.JWKS_URI: TestConfiguration.get_instance().rp_config.OP_URL + "static/jwks.json", StateKeys.USERINFO_ENDPOINT: TestConfiguration.get_instance().rp_config.OP_URL + "userinfo", StateKeys.STATE: FakeOP.STATE } state.add(state_id, state_data) return state def setup_client_registration_endpoint(self): client_info = TestConfiguration.get_instance( ).rp_config.CLIENTS[PROVIDER]["client_info"] request = RegistrationRequest().deserialize(json.dumps(client_info), "json") _cinfo = self.provider.do_client_registration(request, CLIENT_ID) args = dict([(k, v) for k, v in _cinfo.items() if k in RegistrationResponse.c_param]) args['client_id'] = CLIENT_ID self.provider.comb_uri(args) registration_response = RegistrationResponse(**args) responses.add(responses.POST, self.op_base + "registration", body=registration_response.to_json(), status=200, content_type='application/json') def setup_opienid_config_endpoint(self): self.provider.baseurl = self.op_base self.provider.jwks_uri = self.publish_jwks() provider_info = self.provider.create_providerinfo() responses.add(responses.GET, self.op_base + ".well-known/openid-configuration", body=provider_info.to_json(), status=200, content_type='application/json') def setup_webfinger_endpoint(self): wf = WebFinger() resp = Response(wf.response(subject=self.op_base, base=self.op_base)) responses.add(responses.GET, self.op_base + ".well-known/webfinger", body=resp.message, status=200, content_type='application/json') def publish_jwks(self): jwks_uri = self.op_base + "static/jwks.json" responses.add(responses.GET, jwks_uri, body=json.dumps(JWKS), status=200, content_type='application/json') return jwks_uri