def test_scope_who_am_i(provider): registration_params = { "application_type": "web", "response_types": ["code", "token"], "redirect_uris": "http://example.org" } reg_req = RegistrationRequest(**registration_params) resp = provider.registration_endpoint(reg_req.to_urlencoded()) reg_resp = RegistrationResponse().from_json(resp.message) auth_req = AuthorizationRequest( **{ "client_id": reg_resp["client_id"], "scope": "openid who_am_i", "response_type": "code token", "redirect_uri": "http://example.org", "state": "state0", "nonce": "nonce0" }) resp = provider.authorization_endpoint(auth_req.to_urlencoded()) auth_resp = AuthorizationResponse().from_urlencoded(resp.message) userinfo_req = UserInfoRequest( **{"access_token": auth_resp["access_token"]}) resp = provider.userinfo_endpoint(userinfo_req.to_urlencoded()) userinfo_resp = AuthorizationResponse().from_json(resp.message) assert userinfo_resp["given_name"] == "Bruce" assert userinfo_resp["family_name"] == "Lee"
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_userinfo_request(self): aresp = AuthorizationResponse(code="code", state="state000") tresp = AccessTokenResponse( access_token="access_token", token_type="Bearer", expires_in=600, refresh_token="refresh", scope=["openid"], ) self.client.parse_response( AuthorizationResponse, aresp.to_urlencoded(), sformat="urlencoded", state="state0", ) self.client.parse_response(AccessTokenResponse, tresp.to_json(), state="state0") path, body, method, h_args = self.client.user_info_request( state="state0") assert path == "http://example.com/userinfo" assert method == "GET" assert body is None assert h_args == {"headers": {"Authorization": "Bearer access_token"}}
def test_userinfo_request_post(self): aresp = AuthorizationResponse(code="code", state="state000") tresp = AccessTokenResponse( access_token="access_token", token_type="bearer", expires_in=600, refresh_token="refresh", scope=["openid"], ) self.client.parse_response( AuthorizationResponse, aresp.to_urlencoded(), sformat="urlencoded", state="state0", ) self.client.parse_response(AccessTokenResponse, tresp.to_json(), state="state0") path, body, method, h_args = self.client.user_info_request( method="POST", state="state0") assert path == "http://example.com/userinfo" assert method == "POST" assert body == "access_token=access_token" assert h_args == { "headers": { "Content-Type": "application/x-www-form-urlencoded" } }
def parse_authentication_response(self, redirect_uri, fragment_encoded=False): parsed_url = urlparse(redirect_uri) if fragment_encoded: response = parsed_url.fragment else: response = parsed_url.query authn_response = AuthorizationResponse().from_urlencoded(response) assert authn_response.verify(key=[self.app.provider.signing_key]) return authn_response
def id_token(self, released_claims, idp_entity_id, transaction_id, transaction_session): """Make a JWT encoded id token and pass it to the redirect URI. :param released_claims: dictionary containing the following user_id: identifier for the user (as delivered by the IdP, dependent on whether transient or persistent id was requested) auth_time: time of the authentication reported from the IdP idp_entity_id: entity id of the selected IdP :param transaction_id: :return: raises cherrypy.HTTPRedirect. """ identifier = released_claims["Identifier"] auth_time = released_claims["Authentication time"] # have to convert text representation into seconds since epoch _time = time.mktime(str_to_time(auth_time)) # construct the OIDC response transaction_session["sub"] = identifier extra_claims = {k.lower(): released_claims[k] for k in ["Country", "Domain"] if k in released_claims} _jwt = self.OP.id_token_as_signed_jwt(transaction_session, loa="", auth_time=_time, exp={"minutes": 30}, extra_claims=extra_claims) _elapsed_transaction_time = get_timestamp() - transaction_session["start_time"] log_transaction_complete(logger, cherrypy.request, transaction_id, transaction_session["client_id"], idp_entity_id, _time, _elapsed_transaction_time, extra_claims, _jwt) try: _state = transaction_session["state"] except KeyError: _state = None authzresp = AuthorizationResponse(state=_state, id_token=_jwt) if "redirect_uri" in transaction_session: _ruri = transaction_session["redirect_uri"] else: _error_msg = _("We could not complete your validation because an error occurred while " "handling your request. Please return to the service which initiated the " "validation request and try again.") try: cinfo = self.OP.cdb[transaction_session["client_id"]] _ruri = cinfo["redirect_uris"][0] except KeyError as e: abort_with_enduser_error(transaction_id, transaction_session["client_id"], cherrypy.request, logger, _error_msg, "Unknown RP client id '{}': '{}'.".format(transaction_session["client_id"], str(e))) location = authzresp.request(_ruri, True) logger.debug("Redirected to: '{}' ({})".format(location, type(location))) raise cherrypy.HTTPRedirect(location)
def authorization_endpoint(self, query): req = self.parse_authorization_request(query=query) aevent = AuthnEvent("user", "salt", authn_info="acr") sid = self.sdb.create_authz_session(aevent, areq=req) self.sdb.do_sub(sid, 'client_salt') _info = self.sdb[sid] if "code" in req["response_type"]: if "token" in req["response_type"]: grant = _info["code"] _dict = self.sdb.upgrade_to_token(grant) _dict["oauth_state"] = "authz", _dict = by_schema(AuthorizationResponse(), **_dict) resp = AuthorizationResponse(**_dict) else: _state = req["state"] resp = AuthorizationResponse(state=_state, code=_info["code"]) else: # "implicit" in req.response_type: grant = _info["code"] params = AccessTokenResponse.c_param.keys() if "token" in req["response_type"]: _dict = dict([(k, v) for k, v in self.sdb.upgrade_to_token(grant).items() if k in params]) try: del _dict["refresh_token"] except KeyError: pass else: _dict = {"state": req["state"]} if "id_token" in req["response_type"]: _idt = self.make_id_token(_info, issuer=self.name) alg = "RS256" ckey = self.keyjar.get_signing_key(alg2keytype(alg), _info["client_id"]) _signed_jwt = _idt.to_jwt(key=ckey, algorithm=alg) p = _signed_jwt.split(".") p[2] = "aaa" _dict["id_token"] = ".".join(p) resp = AuthorizationResponse(**_dict) location = resp.request(req["redirect_uri"]) response = Response() response.headers = {"location": location} response.status_code = 302 response.text = "" return response
def handle_authn_response(self, context, internal_resp): """ See super class method satosa.frontends.base.FrontendModule#handle_authn_response :type context: satosa.context.Context :type internal_response: satosa.internal_data.InternalResponse :rtype oic.utils.http_util.Response """ auth_req = self._get_authn_request_from_state(context.state) # filter attributes to return in ID Token as claims attributes = self.converter.from_internal( "openid", internal_resp.get_attributes()) satosa_logging( LOGGER, logging.DEBUG, "Attributes delivered by backend to OIDC frontend: {}".format( json.dumps(attributes)), context.state) flattened_attributes = {k: v[0] for k, v in attributes.items()} requested_id_token_claims = auth_req.get("claims", {}).get("id_token") user_claims = self._get_user_info(flattened_attributes, requested_id_token_claims, auth_req["scope"]) satosa_logging( LOGGER, logging.DEBUG, "Attributes filtered by requested claims/scope: {}".format( json.dumps(user_claims)), context.state) # construct epoch timestamp of reported authentication time auth_time = datetime.datetime.strptime( internal_resp.auth_info.timestamp, "%Y-%m-%dT%H:%M:%SZ") epoch_timestamp = (auth_time - datetime.datetime(1970, 1, 1)).total_seconds() base_claims = { "client_id": auth_req["client_id"], "sub": internal_resp.get_user_id(), "nonce": auth_req["nonce"] } id_token = self.provider.id_token_as_signed_jwt( base_claims, user_info=user_claims, auth_time=epoch_timestamp, loa="", alg=self.sign_alg) oidc_client_state = auth_req.get("state") kwargs = {} if oidc_client_state: # inlcude any optional 'state' sent by the client in the authn req kwargs["state"] = oidc_client_state auth_resp = AuthorizationResponse(id_token=id_token, **kwargs) http_response = auth_resp.request( auth_req["redirect_uri"], self._should_fragment_encode(auth_req)) return SeeOther(http_response)
def test_do_user_info_request(self): resp = AuthorizationResponse(code="code", state="state") grant = Grant(10) # expired grant grant.add_code(resp) resp = AccessTokenResponse(refresh_token="refresh_with_me", access_token="access") token = Token(resp) grant.tokens.append(token) self.client.grant["state0"] = grant resp = self.client.do_user_info_request(state="state0") assert isinstance(resp, OpenIDSchema) assert _eq(resp.keys(), ['name', 'email', 'verified', 'nickname', 'sub']) assert resp["name"] == "Melody Gardot"
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 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_userinfo_endpoint(self): self.cons.client_secret = "drickyoughurt" self.cons.config["response_type"] = ["token"] self.cons.config["request_method"] = "parameter" state, location = self.cons.begin("openid", "token", path="http://localhost:8087") resp = self.server.authorization_endpoint( request=location.split("?")[1]) line = resp.message path, query = line.split("#") # redirect atr = AuthorizationResponse().deserialize(query, "urlencoded") uir = UserInfoRequest(access_token=atr["access_token"], schema="openid") resp3 = self.server.userinfo_endpoint(request=uir.to_urlencoded()) ident = OpenIDSchema().deserialize(resp3.message, "json") print ident.keys() assert _eq(ident.keys(), ['nickname', 'sub', 'name', 'email'])
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_userinfo_endpoint(): server = provider_init _session_db = {} cons = Consumer(_session_db, CONSUMER_CONFIG, CLIENT_CONFIG, server_info=SERVER_INFO) cons.debug = True cons.client_secret = "drickyoughurt" cons.config["response_type"] = ["token"] cons.config["request_method"] = "parameter" cons.keyjar[""] = KC_RSA state, location = cons.begin("openid", "token", path="http://localhost:8087") resp = server.authorization_endpoint(request=location.split("?")[1]) line = resp.message path, query = line.split("#") # redirect atr = AuthorizationResponse().deserialize(query, "urlencoded") uir = UserInfoRequest(access_token=atr["access_token"], schema="openid") resp3 = server.userinfo_endpoint(request=uir.to_urlencoded()) ident = OpenIDSchema().deserialize(resp3.message, "json") print ident.keys() assert _eq(ident.keys(), ['nickname', 'sub', 'name', 'email'])
def test_do_user_info_request(self): resp = AuthorizationResponse(code="code", state="state") grant = Grant(10) # expired grant grant.add_code(resp) resp = AccessTokenResponse(refresh_token="refresh_with_me", access_token="access", token_type="Bearer") token = Token(resp) grant.tokens.append(token) self.client.grant["state0"] = grant resp = self.client.do_user_info_request(state="state0") assert isinstance(resp, OpenIDSchema) assert _eq(resp.keys(), ['name', 'email', 'verified', 'nickname', 'sub']) assert resp["name"] == "Melody Gardot"
def test_do_user_info_request(self): resp = AuthorizationResponse(code="code", state="state") grant = Grant(10) # expired grant grant.add_code(resp) resp2 = AccessTokenResponse( refresh_token="refresh_with_me", access_token="access", token_type="Bearer" ) token = Token(resp2) grant.tokens.append(token) self.client.grant["state0"] = grant with responses.RequestsMock() as rsps: rsps.add( responses.POST, "https://example.com/userinfo", content_type="application/json", json={ "name": "Melody Gardot", "email": "*****@*****.**", "verified": False, "nickname": "Melody", "sub": "some sub", }, ) resp3 = self.client.do_user_info_request(state="state0") assert isinstance(resp3, OpenIDSchema) assert _eq(resp3.keys(), ["name", "email", "verified", "nickname", "sub"]) assert resp3["name"] == "Melody Gardot"
def test_client_id(self): resp = AuthorizationResponse(code="code", state="stateX").to_urlencoded() self.client.parse_response(AuthorizationResponse, resp, sformat="urlencoded") args = { "code": "code", "redirect_uri": self.client.redirect_uris[0], "client_id": self.client.client_id, } url, query, ht_args, cis = self.client.request_info( AccessTokenRequest, method="POST", request_args=args, state="stateX", authn_method="client_secret_basic", grant_type="authorization_code", ) assert "client_id" not in cis args = {"code": "code", "redirect_uri": self.client.redirect_uris[0]} url, query, ht_args, cis = self.client.request_info( AccessTokenRequest, method="POST", request_args=args, state="stateX", authn_method="client_secret_basic", grant_type="authorization_code", ) assert "client_id" not in cis
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_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_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_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_token_type(self): args = {"access_token": "foobar", "token_type": "bearer"} ar = AuthorizationResponse(**args) ar.verify() args = {"access_token": "foobar"} ar = AuthorizationResponse(**args) with pytest.raises(MissingRequiredValue): ar.verify()
def authorize(self, authentication_request, # type: oic.oic.message.AuthorizationRequest user_id, # type: str extra_id_token_claims=None # type: Optional[Union[Mapping[str, Union[str, List[str]]], Callable[[str, str], Mapping[str, Union[str, List[str]]]]] ): # type: (...) -> oic.oic.message.AuthorizationResponse """ Creates an Authentication Response for the specified authentication request and local identifier of the authenticated user. """ custom_sub = self.userinfo[user_id].get('sub') if custom_sub: self.authz_state.subject_identifiers[user_id] = {'public': custom_sub} sub = custom_sub else: sub = self._create_subject_identifier(user_id, authentication_request['client_id'], authentication_request['redirect_uri']) self._check_subject_identifier_matches_requested(authentication_request, sub) response = AuthorizationResponse() authz_code = None if 'code' in authentication_request['response_type']: authz_code = self.authz_state.create_authorization_code(authentication_request, sub) response['code'] = authz_code access_token_value = None if 'token' in authentication_request['response_type']: access_token = self.authz_state.create_access_token(authentication_request, sub) access_token_value = access_token.value self._add_access_token_to_response(response, access_token) if 'id_token' in authentication_request['response_type']: if extra_id_token_claims is None: extra_id_token_claims = {} elif callable(extra_id_token_claims): extra_id_token_claims = extra_id_token_claims(user_id, authentication_request['client_id']) requested_claims = self._get_requested_claims_in(authentication_request, 'id_token') if len(authentication_request['response_type']) == 1: # only id token is issued -> no way of doing userinfo request, so include all claims in ID Token, # even those requested by the scope parameter requested_claims.update( scope2claims( authentication_request['scope'], extra_scope_dict=self.extra_scopes ) ) user_claims = self.userinfo.get_claims_for(user_id, requested_claims) response['id_token'] = self._create_signed_id_token(authentication_request['client_id'], sub, user_claims, authentication_request.get('nonce'), authz_code, access_token_value, extra_id_token_claims) logger.debug('issued id_token=%s from requested_claims=%s userinfo=%s extra_claims=%s', response['id_token'], requested_claims, user_claims, extra_id_token_claims) if 'state' in authentication_request: response['state'] = authentication_request['state'] return response
def test_full_flow(self, context, frontend_with_extra_scopes): redirect_uri = "https://client.example.com/redirect" response_type = "code id_token token" mock_callback = Mock() frontend_with_extra_scopes.auth_req_callback_func = mock_callback # discovery http_response = frontend_with_extra_scopes.provider_config(context) provider_config = ProviderConfigurationResponse().deserialize(http_response.message, "json") # client registration registration_request = RegistrationRequest(redirect_uris=[redirect_uri], response_types=[response_type]) context.request = registration_request.to_dict() http_response = frontend_with_extra_scopes.client_registration(context) registration_response = RegistrationResponse().deserialize(http_response.message, "json") # authentication request authn_req = AuthorizationRequest( redirect_uri=redirect_uri, client_id=registration_response["client_id"], response_type=response_type, scope="openid email eduperson", state="state", nonce="nonce", ) context.request = dict(parse_qsl(authn_req.to_urlencoded())) frontend_with_extra_scopes.handle_authn_request(context) assert mock_callback.call_count == 1 # fake authentication response from backend internal_response = self.setup_for_authn_response( context, frontend_with_extra_scopes, authn_req ) http_response = frontend_with_extra_scopes.handle_authn_response( context, internal_response ) authn_resp = AuthorizationResponse().deserialize(urlparse(http_response.message).fragment, "urlencoded") assert "code" in authn_resp assert "access_token" in authn_resp assert "id_token" in authn_resp # token request context.request = AccessTokenRequest(redirect_uri=authn_req["redirect_uri"], code=authn_resp["code"]).to_dict() credentials = "{}:{}".format(registration_response["client_id"], registration_response["client_secret"]) basic_auth = urlsafe_b64encode(credentials.encode("utf-8")).decode("utf-8") context.request_authorization = "Basic {}".format(basic_auth) http_response = frontend_with_extra_scopes.token_endpoint(context) parsed = AccessTokenResponse().deserialize(http_response.message, "json") assert "access_token" in parsed assert "id_token" in parsed # userinfo request context.request = {} context.request_authorization = "Bearer {}".format(parsed["access_token"]) http_response = frontend_with_extra_scopes.userinfo_endpoint(context) parsed = OpenIDSchema().deserialize(http_response.message, "json") assert "email" in parsed assert "eduperson_principal_name" in parsed assert "eduperson_scoped_affiliation" in parsed
def handle_authn_response(self, context, internal_resp): """ See super class method satosa.frontends.base.FrontendModule#handle_authn_response :type context: satosa.context.Context :type internal_response: satosa.internal_data.InternalResponse :rtype oic.utils.http_util.Response """ auth_req = self._get_authn_request_from_state(context.state) # filter attributes to return in ID Token as claims attributes = self.converter.from_internal("openid", internal_resp.get_attributes()) satosa_logging(LOGGER, logging.DEBUG, "Attributes delivered by backend to OIDC frontend: {}".format( json.dumps(attributes)), context.state) flattened_attributes = {k: v[0] for k, v in attributes.items()} requested_id_token_claims = auth_req.get("claims", {}).get("id_token") user_claims = self._get_user_info(flattened_attributes, requested_id_token_claims, auth_req["scope"]) satosa_logging(LOGGER, logging.DEBUG, "Attributes filtered by requested claims/scope: {}".format( json.dumps(user_claims)), context.state) # construct epoch timestamp of reported authentication time auth_time = datetime.datetime.strptime(internal_resp.auth_info.timestamp, "%Y-%m-%dT%H:%M:%SZ") epoch_timestamp = (auth_time - datetime.datetime(1970, 1, 1)).total_seconds() base_claims = {"client_id": auth_req["client_id"], "sub": internal_resp.get_user_id(), "nonce": auth_req["nonce"]} id_token = self.provider.id_token_as_signed_jwt(base_claims, user_info=user_claims, auth_time=epoch_timestamp, loa="", alg=self.sign_alg) oidc_client_state = auth_req.get("state") kwargs = {} if oidc_client_state: # inlcude any optional 'state' sent by the client in the authn req kwargs["state"] = oidc_client_state auth_resp = AuthorizationResponse(id_token=id_token, **kwargs) http_response = auth_resp.request(auth_req["redirect_uri"], self._should_fragment_encode(auth_req)) return SeeOther(http_response)
def authorization_endpoint(self, query): req = self.parse_authorization_request(query=query) aevent = AuthnEvent("user", "salt", authn_info="acr") sid = self.sdb.create_authz_session(aevent, areq=req) self.sdb.do_sub(sid, 'client_salt') _info = self.sdb[sid] if "code" in req["response_type"]: if "token" in req["response_type"]: grant = _info["code"] _dict = self.sdb.upgrade_to_token(grant) _dict["oauth_state"] = "authz", _dict = by_schema(AuthorizationResponse(), **_dict) resp = AuthorizationResponse(**_dict) # resp.code = grant else: _state = req["state"] resp = AuthorizationResponse(state=_state, code=_info["code"]) else: # "implicit" in req.response_type: grant = _info["code"] params = AccessTokenResponse.c_param.keys() if "token" in req["response_type"]: _dict = dict([ (k, v) for k, v in self.sdb.upgrade_to_token(grant).items() if k in params ]) try: del _dict["refresh_token"] except KeyError: pass else: _dict = {"state": req["state"]} if "id_token" in req["response_type"]: _idt = self.make_id_token(_info, issuer=self.name) alg = "RS256" ckey = self.keyjar.get_signing_key(alg2keytype(alg), _info["client_id"]) _signed_jwt = _idt.to_jwt(key=ckey, algorithm=alg) p = _signed_jwt.split(".") p[2] = "aaa" _dict["id_token"] = ".".join(p) resp = AuthorizationResponse(**_dict) location = resp.request(req["redirect_uri"]) response = Response() response.headers = {"location": location} response.status_code = 302 response.text = "" return response
def test_full_flow(self): self.do_auth_flow() # incoming accepted consent, verify response is OIDC authn response to client resp = self.app.get('/consent/handle_consent/allow') assert resp.status_code == 303 authn_resp = AuthorizationResponse().from_urlencoded( urlparse(dict(resp.headers)['Location']).fragment) assert 'id_token' in authn_resp
def test_userinfo_request(self): aresp = AuthorizationResponse(code="code", state="state000") tresp = AccessTokenResponse(access_token="access_token", token_type="Bearer", expires_in=600, refresh_token="refresh", scope=["openid"]) self.client.parse_response(AuthorizationResponse, aresp.to_urlencoded(), sformat="urlencoded", state="state0") self.client.parse_response(AccessTokenResponse, tresp.to_json(), state="state0") path, body, method, h_args = self.client.user_info_request( state="state0") assert path == "http://example.com/userinfo" assert method == "GET" assert body is None assert h_args == {'headers': {'Authorization': 'Bearer access_token'}}
def test_full_flow_with_denied_user_consent(self): self.do_auth_flow() # incoming denied consent, verify response is OIDC authn error response 'access_denied' resp = self.app.get('/consent/handle_consent/deny') assert resp.status_code == 303 authn_resp = AuthorizationResponse().from_urlencoded( urlparse(dict(resp.headers)['Location']).fragment) assert authn_resp['error'] == 'access_denied'
def test_get_access_token_request(): resp = AuthorizationResponse(code="code", state="state") grant = Grant(1) grant.add_code(resp) client = Client() client.grant["openid"] = grant time.sleep(2) raises(GrantExpired, 'client.construct_AccessTokenRequest(state="openid")')
def test_client_get_grant(): cli = Client() resp = AuthorizationResponse(code="code", state="state") grant = Grant() grant.add_code(resp) cli.grant["state"] = grant gr1 = cli.grant_from_state("state") assert gr1.code == "code"
def test_userinfo_request_post(self): aresp = AuthorizationResponse(code="code", state="state000") tresp = AccessTokenResponse(access_token="access_token", token_type="bearer", expires_in=600, refresh_token="refresh", scope=["openid"]) self.client.parse_response(AuthorizationResponse, aresp.to_urlencoded(), sformat="urlencoded", state="state0") self.client.parse_response(AccessTokenResponse, tresp.to_json(), state="state0") path, body, method, h_args = self.client.user_info_request( method="POST", state="state0") assert path == "http://example.com/userinfo" assert method == "POST" assert body == "access_token=access_token" assert h_args == {'headers': { 'Content-Type': 'application/x-www-form-urlencoded'}}
def __call__(self, request): query = urlparse(request.url).query req = self.provider.parse_authorization_request(query=query) resp = AuthorizationResponse() if 'code' in req['response_type']: authz_code = rndstr(10) authz_info = { 'used': False, 'exp': time.time() + self.provider.authorization_code_lifetime, 'sub': 'test-sub', 'granted_scope': ' '.join(req['scope']), 'auth_req': req.to_dict() } self.provider.authz_codes[authz_code] = authz_info resp['code'] = authz_code if 'state' in req: resp['state'] = req['state'] return (302, {'Location': resp.request(req['redirect_uri'])}, '')
def test_handle_authn_response(self, context, frontend, authn_req): self.insert_client_in_client_db(frontend, authn_req["redirect_uri"]) internal_response = self.setup_for_authn_response(context, frontend, authn_req) http_resp = frontend.handle_authn_response(context, internal_response) assert http_resp.message.startswith(authn_req["redirect_uri"]) resp = AuthorizationResponse().deserialize(urlparse(http_resp.message).fragment) assert resp["state"] == authn_req["state"] id_token = IdToken().from_jwt(resp["id_token"], key=[frontend.signing_key]) assert id_token["iss"] == BASE_URL assert id_token["nonce"] == authn_req["nonce"] assert id_token["aud"] == [authn_req["client_id"]] assert "sub" in id_token assert id_token["email"] == USERS["testuser1"]["email"][0] assert frontend.name not in context.state
def test_verify_token_type(self): args = { "access_token": "foobar", "token_type": "bearer" } ar = AuthorizationResponse(**args) ar.verify() args = { "access_token": "foobar", } ar = AuthorizationResponse(**args) with pytest.raises(MissingRequiredValue): ar.verify()
def test_dynamic_client(provider_info, browser): redirect_uri = "http://localhost" # Dynamic registration reg_req = RegistrationRequest(**{"redirect_uris": [redirect_uri], "response_types": ["id_token"]}) resp = requests.post(reg_req.request(provider_info["registration_endpoint"])) reg_resp = RegistrationResponse().from_json(resp.text) # Authentication auth_req = AuthorizationRequest( **{"client_id": reg_resp["client_id"], "scope": "openid", "response_type": "id_token", "redirect_uri": redirect_uri, "state": "state0", "nonce": "nonce0"}) browser.get(auth_req.request(provider_info["authorization_endpoint"])) fill_login_details(browser) # Authentication response urlencoded_resp = urlparse(browser.current_url).fragment auth_resp = AuthorizationResponse().from_urlencoded(urlencoded_resp) idt = IdToken().from_jwt(auth_resp["id_token"], verify=False) assert browser.current_url.startswith(redirect_uri) assert auth_resp["state"] == "state0" assert idt["nonce"] == "nonce0"
def test_userinfo_endpoint_authn(self): self.cons.client_secret = "drickyoughurt" self.cons.config["response_type"] = ["token"] self.cons.config["request_method"] = "parameter" state, location = self.cons.begin("openid", "token", path="http://localhost:8087") resp = self.provider.authorization_endpoint( request=urlparse(location).query) # redirect atr = AuthorizationResponse().deserialize( urlparse(resp.message).fragment, "urlencoded") uir = UserInfoRequest(schema="openid") resp = self.provider.userinfo_endpoint(request=uir.to_urlencoded(), authn='Bearer ' + atr[ 'access_token']) ident = OpenIDSchema().deserialize(resp.message, "json") assert _eq(ident.keys(), ['nickname', 'sub', 'name', 'email'])
def test_do_user_info_request_http_errors(self): resp = AuthorizationResponse(code="code", state="state") grant = Grant(10) # expired grant grant.add_code(resp) resp2 = AccessTokenResponse( refresh_token="refresh_with_me", access_token="access", token_type="Bearer" ) token = Token(resp2) grant.tokens.append(token) self.client.grant["state0"] = grant with responses.RequestsMock() as rsps: rsps.add( responses.POST, "https://example.com/userinfo", status=405, headers={"Allow": "GET"}, ) with pytest.raises(CommunicationError) as excp: self.client.do_user_info_request(state="state0") assert excp.value.args[0] == "Server responded with HTTP Error Code 405" assert excp.value.args[1] == ["GET"]
def authz_part2(self, user, areq, sid, **kwargs): """ After the authentication this is where you should end up """ _log_debug = logger.debug _log_debug("- in authenticated() -") # Do the authorization try: info = OpenIDSchema(**self._collect_user_info(self.sdb[sid])) permission = self.authz(user) self.sdb.update(sid, "permission", permission) except Exception: raise _log_debug("response type: %s" % areq["response_type"]) # create the response aresp = AuthorizationResponse() try: aresp["state"] = areq["state"] except KeyError: pass if "response_type" in areq and \ len(areq["response_type"]) == 1 and \ "none" in areq["response_type"]: pass else: if self.sdb.is_revoked(sid): return self._error(error="access_denied", descr="Token is revoked") _sinfo = self.sdb[sid] try: aresp["scope"] = areq["scope"] except KeyError: pass _log_debug("_dic: %s" % _sinfo) rtype = set(areq["response_type"][:]) if "code" in areq["response_type"]: #if issue_new_code: # scode = self.sdb.duplicate(_sinfo) # _sinfo = self.sdb[scode] _code = aresp["code"] = _sinfo["code"] rtype.remove("code") else: self.sdb[sid]["code"] = None _code = None if "token" in rtype: _dic = self.sdb.update_to_token(issue_refresh=False, key=sid) _log_debug("_dic: %s" % _dic) for key, val in _dic.items(): if key in aresp.parameters() and val is not None: aresp[key] = val rtype.remove("token") try: _access_token = aresp["access_token"] except KeyError: _access_token = None if "id_token" in areq["response_type"]: user_info = self.userinfo_in_id_token_claims(_sinfo) client_info = self.cdb[areq["client_id"]] id_token = self.sign_encrypt_id_token( _sinfo, client_info, areq, code=_code, access_token=_access_token, user_info=user_info) aresp["id_token"] = id_token _sinfo["id_token"] = id_token rtype.remove("id_token") if len(rtype): return BadRequest("Unknown response type") try: redirect_uri = self.get_redirect_uri(areq) except (RedirectURIError, ParameterError), err: return BadRequest("%s" % err)
"json") _log_debug("areq: %s" % areq) # Do the authorization try: permission = self.function["authorize"](user) self.sdb.update(scode, "permission", permission) except Exception: raise _log_debug("response type: %s" % areq["response_type"]) # create the response aresp = AuthorizationResponse() try: aresp["state"] = areq["state"] except KeyError: pass if "response_type" in areq and \ len(areq["response_type"]) == 1 and \ "none" in areq["response_type"]: pass else: if self.sdb.is_revoked(scode): return self._error(environ, start_response, error="access_denied", descr="Token is revoked")