def test_kspec(): _ckey = pem_cert2rsa(CERT) _jwk = RSAKey(key=_ckey) _jwk.serialize() print _jwk assert _jwk.kty == "RSA" assert _jwk.e == JWK["keys"][0]["e"] assert _jwk.n == JWK["keys"][0]["n"]
def test_extract_rsa_from_cert_2(): _ckey = pem_cert2rsa(CERT) _jwk = RSAKey(key=_ckey) _jwk.serialize() print _jwk _n = base64_to_long(str(_jwk.n)) assert _ckey.n == _n
def generate_jwks(self, mode): if "rotenc" in self.behavior_type: # Rollover encryption keys rsa_key = RSAKey(kid="rotated_rsa_{}".format(time.time()), use="enc").load_key(RSA.generate(2048)) ec_key = ECKey(kid="rotated_ec_{}".format(time.time()), use="enc").load_key(P256) keys = [rsa_key.serialize(private=True), ec_key.serialize(private=True)] new_keys = {"keys": keys} #self.do_key_rollover(new_keys, "%d") signing_keys = [k.to_dict() for k in self.keyjar.get_signing_key()] new_keys["keys"].extend(signing_keys) return json.dumps(new_keys) elif "nokid1jwk" in self.behavior_type: alg = mode["sign_alg"] if not alg: alg = "RS256" keys = [k.to_dict() for kb in self.keyjar[""] for k in list(kb.keys())] for key in keys: if key["use"] == "sig" and key["kty"].startswith(alg[:2]): key.pop("kid", None) jwk = dict(keys=[key]) return json.dumps(jwk) raise Exception( "Did not find sig {} key for nokid1jwk test ".format(alg)) else: # Return all keys keys = [k.to_dict() for kb in self.keyjar[""] for k in list(kb.keys())] jwks = dict(keys=keys) return json.dumps(jwks)
def test_serialize_rsa_priv_key(): rsakey = RSAKey(key=import_rsa_key_from_file(full_path("rsa.key"))) assert rsakey.d d_rsakey = rsakey.serialize(private=True) restored_key = RSAKey(**d_rsakey) assert rsa_eq(restored_key, rsakey)
def test_serialize_rsa_priv_key(): rsakey = RSAKey(key=import_rsa_key_from_file(full_path("rsa.key"))) assert rsakey.d d_rsakey = rsakey.serialize(private=True) restored_key = RSAKey(**d_rsakey) assert rsa_eq(restored_key, rsakey)
def get_public_jwk(self): try: _rsakey = get_key_storage().public except FileNotFoundError: self.generate_keys() _rsakey = get_key_storage().public _rsakey = RSA.import_key(_rsakey) _rsajwk = RSAKey(kid="test", use="sig", alg="RS256", key=_rsakey) return _rsajwk.serialize(private=True)
def test_kspec(): _ckey = pem_cert2rsa(CERT) _key = RSAKey() _key.load_key(_ckey) print(_key) jwk = _key.serialize() assert jwk["kty"] == "RSA" assert jwk["e"] == JWK["keys"][0]["e"].encode("utf-8") assert jwk["n"] == JWK["keys"][0]["n"].encode("utf-8")
def test_kspec(): _ckey = pem_cert2rsa(CERT) _key = RSAKey() _key.load_key(_ckey) print(_key) jwk = _key.serialize() assert jwk["kty"] == "RSA" assert jwk["e"] == JWK["keys"][0]["e"] assert jwk["n"] == JWK["keys"][0]["n"]
def create_and_store_rsa_key_pair(name="pyoidc", path=".", size=1024): key = RSA.generate(size) keyfile = os.path.join(path, name) f = open("%s.key" % keyfile, "w") f.write(key.exportKey("PEM")) f.close() f = open("%s.pub" % keyfile, "w") f.write(key.publickey().exportKey("PEM")) f.close() rsa_key = RSAKey(key=key) rsa_key.serialize() # This will create JWK from the public RSA key jwk_spec = json.dumps(rsa_key.to_dict(), "enc") f = open(keyfile + ".jwk", "w") f.write(str(jwk_spec)) f.close() return key
def create_and_store_rsa_key_pair(name="pyoidc", path=".", size=1024): key = RSA.generate(size) keyfile = os.path.join(path, name) f = open("%s.key" % keyfile, "w") f.write(key.exportKey("PEM")) f.close() f = open("%s.pub" % keyfile, "w") f.write(key.publickey().exportKey("PEM")) f.close() rsa_key = RSAKey(key=key) rsa_key.serialize() # This will create JWK from the public RSA key jwk_spec = json.dumps(rsa_key.to_dict(), "enc") f = open(keyfile + ".jwk", "w") f.write(str(jwk_spec)) f.close() return key
def id_token_as_signed_jwt(self, session, loa="2", alg="", code=None, access_token=None, user_info=None, auth_time=0, exp=None, extra_claims=None, **kwargs): kwargs = {} if "rotsig" in self.behavior_type: # Rollover signing keys if alg == "RS256": key = RSAKey(kid="rotated_rsa_{}".format(time.time()), use="sig").load_key(RSA.generate(2048)) else: # alg == "ES256" key = ECKey(kid="rotated_ec_{}".format(time.time()), use="sig").load_key(P256) new_keys = {"keys": [key.serialize(private=True)]} self.events.store("New signing keys", new_keys) self.do_key_rollover(new_keys, "%d") self.events.store("Rotated signing keys", '') if "nokid1jwks" in self.behavior_type: kwargs['keys'] = self.no_kid_keys() # found_key = None # for kb in self.keyjar.key_summary[""]: # issuer_key = list(kb.keys())[0] # if issuer_key.use == "sig" and \ # issuer_key.kty.startswith( # alg[:2]): # issuer_key.kid = None # found_key = key # break # self.keyjar.key_summary[""] = [found_key] if "nokidmuljwks" in self.behavior_type: kwargs['keys'] = self.no_kid_keys() # for key in self.keyjar.key_summary[""]: # for inner_key in list(key.keys()): # inner_key.kid = None _jws = provider.Provider.id_token_as_signed_jwt( self, session, loa=loa, alg=alg, code=code, access_token=access_token, user_info=user_info, auth_time=auth_time, exp=exp, extra_claims=extra_claims, **kwargs) if "idts" in self.behavior_type: # mess with the signature # p = _jws.split(".") p[2] = sort_string(p[2]) _jws = ".".join(p) return _jws
def id_token_as_signed_jwt(self, session, loa="2", alg="", code=None, access_token=None, user_info=None, auth_time=0, exp=None, extra_claims=None, **kwargs): kwargs = {} if "rotsig" in self.behavior_type: # Rollover signing keys if alg == "RS256": key = RSAKey(kid="rotated_rsa_{}".format(time.time()), use="sig").load_key(RSA.generate(2048)) else: # alg == "ES256" key = ECKey(kid="rotated_ec_{}".format(time.time()), use="sig").load_key(P256) new_keys = {"keys": [key.serialize(private=True)]} self.events.store("New signing keys", new_keys) self.do_key_rollover(new_keys, "%d") self.events.store("Rotated signing keys", '') if "nokid1jwks" in self.behavior_type: kwargs['keys'] = self.no_kid_keys() # found_key = None # for kb in self.keyjar.key_summary[""]: # issuer_key = list(kb.keys())[0] # if issuer_key.use == "sig" and \ # issuer_key.kty.startswith( # alg[:2]): # issuer_key.kid = None # found_key = key # break # self.keyjar.key_summary[""] = [found_key] if "nokidmuljwks" in self.behavior_type: kwargs['keys'] = self.no_kid_keys() # for key in self.keyjar.key_summary[""]: # for inner_key in list(key.keys()): # inner_key.kid = None _jws = provider.Provider.id_token_as_signed_jwt( self, session, loa=loa, alg=alg, code=code, access_token=access_token, user_info=user_info, auth_time=auth_time, exp=exp, extra_claims=extra_claims, **kwargs) if "idts" in self.behavior_type: # mess with the signature # p = _jws.split(".") p[2] = sort_string(p[2]) _jws = ".".join(p) return _jws
def update(self, msg, state, key_size=0): """ Used to 'update' the AccessToken Request :param msg: :param state: Used to map access token response to this request :param key_size: :return: """ if not key_size: key_size = self.key_size key = RSAKey(key=RSA.generate(key_size)) self.state2key[state] = key msg['key'] = json.dumps(key.serialize()) return msg
def update(self, msg, state, key_size=0): """ Use to 'update' the AccessToken Request. :param msg: :param state: Used to map access token response to this request :param key_size: :return: """ if not key_size: key_size = self.key_size key = RSAKey(key=RSA.generate(key_size)) self.state2key[state] = key msg["key"] = json.dumps(key.serialize()) return msg
def generate_jwks(self, mode): if "rotenc" in self.behavior_type: # Rollover encryption keys rsa_key = RSAKey(kid="rotated_rsa_{}".format(time.time()), use="enc").load_key(RSA.generate(2048)) ec_key = ECKey(kid="rotated_ec_{}".format(time.time()), use="enc").load_key(P256) keys = [ rsa_key.serialize(private=True), ec_key.serialize(private=True) ] new_keys = {"keys": keys} #self.do_key_rollover(new_keys, "%d") signing_keys = [k.to_dict() for k in self.keyjar.get_signing_key()] new_keys["keys"].extend(signing_keys) return json.dumps(new_keys) elif "nokid1jwk" in self.behavior_type: alg = mode["sign_alg"] if not alg: alg = "RS256" keys = [ k.to_dict() for kb in self.keyjar[""] for k in list(kb.keys()) ] for key in keys: if key["use"] == "sig" and key["kty"].startswith(alg[:2]): key.pop("kid", None) jwk = dict(keys=[key]) return json.dumps(jwk) raise Exception( "Did not find sig {} key for nokid1jwk test ".format(alg)) else: # Return all keys keys = [ k.to_dict() for kb in self.keyjar[""] for k in list(kb.keys()) ] jwks = dict(keys=keys) return json.dumps(jwks)
def authorization_endpoint(self, request="", cookie=None, **kwargs): if isinstance(request, dict): _req = request else: _req = {} for key, val in parse_qs(request).items(): if len(val) == 1: _req[key] = val[0] else: _req[key] = val # self.events.store(EV_REQUEST, _req) try: _scope = _req["scope"] except KeyError: return error_response( error="incorrect_behavior", descr="No scope parameter" ) else: # verify that openid is among the scopes _scopes = _scope.split(" ") if "openid" not in _scopes: return error_response( error="incorrect_behavior", descr="Scope does not contain 'openid'" ) client_id = _req["client_id"] try: f = response_type_cmp(self.capabilities['response_types_supported'], _req['response_type']) except KeyError: pass else: if f is False: self.events.store( EV_FAULT, 'Wrong response type: {}'.format(_req['response_type'])) return error_response(error="incorrect_behavior", descr="Not supported response_type") _rtypes = _req['response_type'].split(' ') if 'id_token' in _rtypes: try: self._update_client_keys(client_id) except TestError: return error_response(error="incorrect_behavior", descr="No change in client keys") if isinstance(request, dict): request = urlencode(request) if "max_age" in _req and _req["max_age"] == "0" and "prompt" in _req and _req["prompt"] == "none": aresp = { "error": "login_required", } if "state" in _req: aresp['state'] = _req["state"] return self.response_mode(_req, False, aresp=aresp, redirect_uri=_req['redirect_uri'], headers={}) else: _response = provider.Provider.authorization_endpoint(self, request, cookie, **kwargs) if "rotenc" in self.behavior_type: # Rollover encryption keys rsa_key = RSAKey(kid="rotated_rsa_{}".format(time.time()), use="enc").load_key(RSA.generate(2048)) ec_key = ECKey(kid="rotated_ec_{}".format(time.time()), use="enc").load_key(P256) keys = [rsa_key.serialize(private=True), ec_key.serialize(private=True)] new_keys = {"keys": keys} self.events.store("New encryption keys", new_keys) self.do_key_rollover(new_keys, "%d") self.events.store("Rotated encryption keys", '') logger.info( 'Rotated OP enc keys, new set: {}'.format( key_summary(self.keyjar, ''))) # This is just for logging purposes try: _resp = self.server.http_request(_req["request_uri"]) except KeyError: pass except requests.ConnectionError as err: self.events.store(EV_EXCEPTION, err) err = unwrap_exception(err) return error_response(error="server_error", descr=err) else: if _resp.status_code == 200: self.events.store(EV_REQUEST, "Request from request_uri: {}".format( _resp.text)) return _response
class TestBearerTokenAuthentication: @pytest.fixture(autouse=True) def setup(self): httpretty.enable() self.key = RSAKey(kid='testkey').load( os.path.join(FIXTURE_ROOT, 'testkey.pem')) def jwks(_request, _uri, headers): # noqa: E306 ks = KEYS() ks.add(self.key.serialize()) return 200, headers, ks.dump_jwks() httpretty.register_uri(httpretty.GET, oidc_rp_settings.PROVIDER_JWKS_ENDPOINT, status=200, body=jwks) httpretty.register_uri(httpretty.POST, oidc_rp_settings.PROVIDER_TOKEN_ENDPOINT, body=json.dumps({ 'id_token': self.generate_jws(), 'access_token': 'accesstoken', 'refresh_token': 'refreshtoken', }), content_type='text/json') httpretty.register_uri(httpretty.GET, oidc_rp_settings.PROVIDER_USERINFO_ENDPOINT, body=json.dumps({ 'sub': '1234', 'email': '*****@*****.**', }), content_type='text/json') yield httpretty.disable() def generate_jws(self, **kwargs): return JWS(self.generate_jws_dict(**kwargs), jwk=self.key, alg='RS256').sign_compact() def generate_jws_dict(self, **kwargs): client_key = kwargs.get('client_key', oidc_rp_settings.CLIENT_ID) now_dt = dt.datetime.utcnow() expiration_dt = kwargs.get('expiration_dt', (now_dt + dt.timedelta(seconds=30))) issue_dt = kwargs.get('issue_dt', now_dt) nonce = kwargs.get('nonce', 'nonce') return { 'iss': kwargs.get('iss', oidc_rp_settings.PROVIDER_ENDPOINT), 'nonce': nonce, 'aud': kwargs.get('aud', client_key), 'azp': kwargs.get('azp', client_key), 'exp': timegm(expiration_dt.utctimetuple()), 'iat': timegm(issue_dt.utctimetuple()), 'nbf': timegm(kwargs.get('nbf', now_dt).utctimetuple()), 'sub': '1234', } def test_can_authenticate_a_new_user(self): rf = APIRequestFactory() request = rf.get('/', HTTP_AUTHORIZATION='Bearer accesstoken') SessionMiddleware().process_request(request) request.session.save() backend = BearerTokenAuthentication() user, _ = backend.authenticate(request) assert user.email == '*****@*****.**' assert user.oidc_user.sub == '1234' def test_can_authenticate_an_existing_user(self): rf = APIRequestFactory() request = rf.get('/', HTTP_AUTHORIZATION='Bearer accesstoken') SessionMiddleware().process_request(request) request.session.save() backend = BearerTokenAuthentication() user = get_user_model().objects.create_user('test', '*****@*****.**') OIDCUser.objects.create(user=user, sub='1234') user, _ = backend.authenticate(request) assert user.email == '*****@*****.**' assert user.oidc_user.sub == '1234' def test_cannot_authenticate_a_user_if_no_auth_header_is_present(self): rf = APIRequestFactory() request = rf.get('/') SessionMiddleware().process_request(request) request.session.save() backend = BearerTokenAuthentication() assert backend.authenticate(request) is None def test_cannot_authenticate_a_user_if_the_auth_header_is_not_a_bearer_authentication( self): rf = APIRequestFactory() request = rf.get('/', HTTP_AUTHORIZATION='DummyAuth accesstoken') SessionMiddleware().process_request(request) request.session.save() backend = BearerTokenAuthentication() assert backend.authenticate(request) is None def test_cannot_authenticate_a_user_if_the_auth_header_does_not_contain_the_access_token( self): rf = APIRequestFactory() request = rf.get('/', HTTP_AUTHORIZATION='Bearer') SessionMiddleware().process_request(request) request.session.save() backend = BearerTokenAuthentication() with pytest.raises(AuthenticationFailed): backend.authenticate(request) def test_cannot_authenticate_a_user_if_multiple_tokens_are_present_in_the_auth_header( self): rf = APIRequestFactory() request = rf.get('/', HTTP_AUTHORIZATION='Bearer token1 token2') SessionMiddleware().process_request(request) request.session.save() backend = BearerTokenAuthentication() with pytest.raises(AuthenticationFailed): backend.authenticate(request) def test_cannot_authenticate_a_user_if_the_userinfo_endpoint_raises_an_error( self): httpretty.register_uri(httpretty.GET, oidc_rp_settings.PROVIDER_USERINFO_ENDPOINT, body='Nop', status=401) rf = APIRequestFactory() request = rf.get('/', HTTP_AUTHORIZATION='Bearer badtoken') SessionMiddleware().process_request(request) request.session.save() backend = BearerTokenAuthentication() with pytest.raises(AuthenticationFailed): backend.authenticate(request)
def authorization_endpoint(self, request="", cookie=None, **kwargs): if isinstance(request, dict): _req = request else: _req = {} for key, val in parse_qs(request).items(): if len(val) == 1: _req[key] = val[0] else: _req[key] = val # self.events.store(EV_REQUEST, _req) try: _scope = _req["scope"] except KeyError: return error( error="incorrect_behavior", descr="No scope parameter" ) else: # verify that openid is among the scopes _scopes = _scope.split(" ") if "openid" not in _scopes: return error( error="incorrect_behavior", descr="Scope does not contain 'openid'" ) client_id = _req["client_id"] try: f = response_type_cmp(self.capabilities['response_types_supported'], _req['response_type']) except KeyError: pass else: if f is False: self.events.store( EV_FAULT, 'Wrong response type: {}'.format(_req['response_type'])) return error_response(error="incorrect_behavior", descr="Not supported response_type") _rtypes = _req['response_type'].split(' ') if 'id_token' in _rtypes: try: self._update_client_keys(client_id) except TestError: return error(error="incorrect_behavior", descr="No change in client keys") if isinstance(request, dict): request = urlencode(request) _response = provider.Provider.authorization_endpoint(self, request, cookie, **kwargs) if "rotenc" in self.behavior_type: # Rollover encryption keys rsa_key = RSAKey(kid="rotated_rsa_{}".format(time.time()), use="enc").load_key(RSA.generate(2048)) ec_key = ECKey(kid="rotated_ec_{}".format(time.time()), use="enc").load_key(P256) keys = [rsa_key.serialize(private=True), ec_key.serialize(private=True)] new_keys = {"keys": keys} self.events.store("New encryption keys", new_keys) self.do_key_rollover(new_keys, "%d") self.events.store("Rotated encryption keys", '') logger.info( 'Rotated OP enc keys, new set: {}'.format( key_summary(self.keyjar, ''))) # This is just for logging purposes try: _resp = self.server.http_request(_req["request_uri"]) except KeyError: pass except requests.ConnectionError as err: self.events.store(EV_EXCEPTION, err) err = unwrap_exception(err) return error_response(error="server_error", descr=err) else: if _resp.status_code == 200: self.events.store(EV_REQUEST, "Request from request_uri: {}".format( _resp.text)) return _response
parser.add_argument("message", nargs="?", help="The message to encrypt") args = parser.parse_args() keys = {} if args.jwk_url: keys = load_jwks_from_url(args.jwk_url) elif args.jwk_file: keys = load_jwks(open(args.jwk_file).read()) elif args.x509_url: keys = load_x509_cert(args.x509_url, {}) elif args.x509_file: keys = [import_rsa_key_from_file(args.x509_file)] elif args.rsa_file: key = rsa_load(args.rsa_file) rsa_key = RSAKey(key=key) rsa_key.serialize() keys = [rsa_key] else: print("Needs encryption key") exit() if args.file: msg = open(args.file).read() msg = msg.strip("\n\r") else: msg = args.message jwe = JWE() print(jwe.decrypt(msg, keys))
keys = {} if args.jwk_url: keys = load_jwks_from_url(args.jwk_url, {}) elif args.jwk_file: keys = load_jwks(open(args.jwk_file).read()) elif args.x509_url: # load_x509_cert returns list of 2-tuples keys = [ RSAKey(key=x) for x, y in load_x509_cert(lrequest, args.x509_url) ] for key in keys: key.serialize() elif args.x509_file: # import_rsa_key_from_file returns RSA key instance _key = RSAKey(key=import_rsa_key_from_file(args.x509_file)) _key.serialize() keys = [_key] elif args.rsa_file: _key = RSAKey(key=rsa_load(args.rsa_file)) _key.serialize() keys = [_key] else: print >> sys.stderr, "Needs encryption key" exit() if not args.enc or not args.alg: print >> sys.stderr, "There are no default encryption methods" exit() if args.enc not in SUPPORTED["enc"]: print >> sys.stderr, "Encryption method %s not supported" % args.enc
def authorization_endpoint(self, request="", cookie=None, **kwargs): _req = parse_qs(request) #self.events.store(EV_REQUEST, _req) try: _scope = _req["scope"] except KeyError: return self._error( error="incorrect_behavior", descr="No scope parameter" ) else: # verify that openid is among the scopes _scopes = _scope[0].split(" ") if "openid" not in _scopes: return self._error( error="incorrect_behavior", descr="Scope does not contain 'openid'" ) client_id = _req["client_id"][0] try: f = response_type_cmp(kwargs['test_cnf']['response_type'], _req['response_type']) except KeyError: pass else: if f is False: self.events.store( EV_FAULT, 'Wrong response type: {}'.format(_req['response_type'])) return self._error_response(error="incorrect_behavior", descr="Wrong response_type") _rtypes = [] for rt in _req['response_type']: _rtypes.extend(rt.split(' ')) if 'id_token' in _rtypes: try: self._update_client_keys(client_id) except TestError: return self._error(error="incorrect_behavior", descr="No change in client keys") _response = provider.Provider.authorization_endpoint(self, request, cookie, **kwargs) if "rotenc" in self.behavior_type: # Rollover encryption keys rsa_key = RSAKey(kid="rotated_rsa_{}".format(time.time()), use="enc").load_key(RSA.generate(2048)) ec_key = ECKey(kid="rotated_ec_{}".format(time.time()), use="enc").load_key(P256) keys = [rsa_key.serialize(private=True), ec_key.serialize(private=True)] new_keys = {"keys": keys} self.events.store("New encryption keys", new_keys) self.do_key_rollover(new_keys, "%d") self.events.store("Rotated encryption keys", '') logger.info( 'Rotated OP enc keys, new set: {}'.format( key_summary(self.keyjar, ''))) # This is just for logging purposes try: _resp = self.server.http_request(_req["request_uri"][0]) except KeyError: pass else: if _resp.status_code == 200: self.events.store(EV_REQUEST, "Request from request_uri: {}".format(_resp.text)) return _response
def gen_public(private_key_jwk): rsak = RSAKey(**private_key_jwk) return(rsak.serialize())
""" import json from oic.utils.keyio import create_and_store_rsa_key_pair from oic.utils.keyio import build_keyjar from jwkest.jwk import RSAKey from jwkest.jwk import KEYS from jwkest.jwk import keyitems2keyreps # Will create 2 files on disc # 'foo' will contain the private key # 'foo.pub' will contain the public key key = create_and_store_rsa_key_pair("foo", size=2048) rsa = RSAKey().load_key(key) # by default this will be the public part of the key ser_rsa = rsa.serialize() print("--- JWK (public) ----") print(json.dumps(ser_rsa, sort_keys=True, indent=4, separators=(',', ': '))) print() # and this will give you the serialization of the private key ser_rsa = rsa.serialize(private=True) print("--- JWK (private) ----") print(json.dumps(ser_rsa, sort_keys=True, indent=4, separators=(',', ': '))) print() # ============================================================================ # And now for the JWKS
keys = {} if args.jwk_url: keys = load_jwks_from_url(args.jwk_url, {}) elif args.jwk_file: keys = load_jwks(open(args.jwk_file).read()) elif args.x509_url: # load_x509_cert returns list of 2-tuples keys = [RSAKey(key=x) for x, y in load_x509_cert(lrequest, args.x509_url)] for key in keys: key.serialize() elif args.x509_file: # import_rsa_key_from_file returns RSA key instance _key = RSAKey(key=import_rsa_key_from_file(args.x509_file)) _key.serialize() keys = [_key] elif args.rsa_file: _key = RSAKey(key=rsa_load(args.rsa_file)) _key.serialize() keys = [_key] else: print >> sys.stderr, "Needs encryption key" exit() if not args.enc or not args.alg: print >> sys.stderr, "There are no default encryption methods" exit() if args.enc not in SUPPORTED["enc"]: print >> sys.stderr, "Encryption method %s not supported" % args.enc
class OpenIdConnectTestMixin(object): """ Mixin to test OpenID Connect consumers. Inheriting classes should also inherit OAuth2Test. """ client_key = 'a-key' client_secret = 'a-secret-key' issuer = None # id_token issuer openid_config_body = None key = None def setUp(self): super(OpenIdConnectTestMixin, self).setUp() here = os.path.dirname(__file__) self.key = RSAKey(kid='testkey').load( os.path.join(here, '../testkey.pem')) HTTPretty.register_uri(HTTPretty.GET, self.backend.OIDC_ENDPOINT + '/.well-known/openid-configuration', status=200, body=self.openid_config_body) oidc_config = json.loads(self.openid_config_body) def jwks(_request, _uri, headers): ks = KEYS() ks.add(self.key.serialize()) return 200, headers, ks.dump_jwks() HTTPretty.register_uri(HTTPretty.GET, oidc_config.get('jwks_uri'), status=200, body=jwks) def extra_settings(self): settings = super(OpenIdConnectTestMixin, self).extra_settings() settings.update({ 'SOCIAL_AUTH_{0}_KEY'.format(self.name): self.client_key, 'SOCIAL_AUTH_{0}_SECRET'.format(self.name): self.client_secret, 'SOCIAL_AUTH_{0}_ID_TOKEN_DECRYPTION_KEY'.format(self.name): self.client_secret }) return settings def access_token_body(self, request, _url, headers): """ Get the nonce from the request parameters, add it to the id_token, and return the complete response. """ nonce = self.backend.data['nonce'].encode('utf-8') body = self.prepare_access_token_body(nonce=nonce) return 200, headers, body def get_id_token(self, client_key=None, expiration_datetime=None, issue_datetime=None, nonce=None, issuer=None): """ Return the id_token to be added to the access token body. """ return { 'iss': issuer, 'nonce': nonce, 'aud': client_key, 'azp': client_key, 'exp': expiration_datetime, 'iat': issue_datetime, 'sub': '1234' } def prepare_access_token_body(self, client_key=None, tamper_message=False, expiration_datetime=None, issue_datetime=None, nonce=None, issuer=None): """ Prepares a provider access token response. Arguments: client_id -- (str) OAuth ID for the client that requested authentication. expiration_time -- (datetime) Date and time after which the response should be considered invalid. """ body = {'access_token': 'foobar', 'token_type': 'bearer'} client_key = client_key or self.client_key now = datetime.datetime.utcnow() expiration_datetime = expiration_datetime or \ (now + datetime.timedelta(seconds=30)) issue_datetime = issue_datetime or now nonce = nonce or 'a-nonce' issuer = issuer or self.issuer id_token = self.get_id_token( client_key, timegm(expiration_datetime.utctimetuple()), timegm(issue_datetime.utctimetuple()), nonce, issuer) body['id_token'] = JWS(id_token, jwk=self.key, alg='RS256').sign_compact() if tamper_message: header, msg, sig = body['id_token'].split('.') id_token['sub'] = '1235' msg = b64encode_item(id_token).decode('utf-8') body['id_token'] = '.'.join([header, msg, sig]) return json.dumps(body) def authtoken_raised(self, expected_message, **access_token_kwargs): self.access_token_body = self.prepare_access_token_body( **access_token_kwargs) with self.assertRaisesRegexp(AuthTokenError, expected_message): self.do_login() @unittest2.skipIf(NO_JWKEST, 'No Jwkest installed') def test_invalid_signature(self): self.authtoken_raised('Token error: Signature verification failed', tamper_message=True) @unittest2.skipIf(NO_JWKEST, 'No Jwkest installed') def test_expired_signature(self): expiration_datetime = datetime.datetime.utcnow() - \ datetime.timedelta(seconds=30) self.authtoken_raised('Token error: Signature has expired', expiration_datetime=expiration_datetime) @unittest2.skipIf(NO_JWKEST, 'No Jwkest installed') def test_invalid_issuer(self): self.authtoken_raised('Token error: Invalid issuer', issuer='someone-else') @unittest2.skipIf(NO_JWKEST, 'No Jwkest installed') def test_invalid_audience(self): self.authtoken_raised('Token error: Invalid audience', client_key='someone-else') @unittest2.skipIf(NO_JWKEST, 'No Jwkest installed') def test_invalid_issue_time(self): expiration_datetime = datetime.datetime.utcnow() - \ datetime.timedelta(hours=1) self.authtoken_raised('Token error: Incorrect id_token: iat', issue_datetime=expiration_datetime) @unittest2.skipIf(NO_JWKEST, 'No Jwkest installed') def test_invalid_nonce(self): self.authtoken_raised('Token error: Incorrect id_token: nonce', nonce='something-wrong')
def setup_server_env(proxy_conf, conf_mod, key): """ :param proxy_conf: :param conf_mod: :param key: RSA key :return: """ global SERVER_ENV global logger #noinspection PyUnboundLocalVariable SERVER_ENV = dict([(k, v) for k, v in proxy_conf.__dict__.items() if not k.startswith("__")]) SERVER_ENV["sessions"] = {} SERVER_ENV["eptid"] = EptidShelve(proxy_conf.SECRET, proxy_conf.EPTID_DB) _idp = server.Server(conf_mod) if key: rsa_key = RSAKey(key=key) rsa_key.serialize() else: rsa_key = None args = {"metad": _idp.metadata, "dkeys": [rsa_key]} SERVER_ENV["consumer_info"] = utils.ConsumerInfo(proxy_conf.CONSUMER_INFO, **args) SERVER_ENV["service"] = proxy_conf.SERVICE # add the service endpoints part = urlparse.urlparse(_idp.config.entityid) base = "%s://%s/" % (part.scheme, part.netloc) SERVER_ENV["SCHEME"] = part.scheme try: (host, port) = part.netloc.split(":") port = int(port) except ValueError: # no port specification host = part.netloc if part.scheme == "http": port = 80 elif part.scheme == "https": port = 443 else: raise ValueError("Unsupported scheme") SERVER_ENV["HOST"] = host SERVER_ENV["PORT"] = port endpoints = {"single_sign_on_service": [], "single_logout_service": []} for key, _dict in proxy_conf.SERVICE.items(): _sso = _dict["saml_endpoint"] endpoints["single_sign_on_service"].append("%s%s" % (base, _sso)) endpoints["single_logout_service"].append(("%s%s/logout" % (base, _sso), BINDING_HTTP_REDIRECT)) _idp.config.setattr("idp", "endpoints", endpoints) SERVER_ENV["idp"] = _idp SERVER_ENV["template_lookup"] = LOOKUP SERVER_ENV["sid_generator"] = session_nr() SERVER_ENV["base_url"] = base SERVER_ENV["STATIC_DIR"] = proxy_conf.STATIC_DIR SERVER_ENV["METADATA_DIR"] = proxy_conf.METADATA_DIR SERVER_ENV["SIGN"] = proxy_conf.SIGN #print SERVER_ENV if proxy_conf.CACHE == "memory": SERVER_ENV["CACHE"] = cache.Cache(SERVER_ENV["SERVER_NAME"], SERVER_ENV["SECRET"]) elif proxy_conf.CACHE.startswith("file:"): SERVER_ENV["CACHE"] = cache.Cache(SERVER_ENV["SERVER_NAME"], SERVER_ENV["SECRET"], filename=proxy_conf.CACHE[5:]) logger = setup_logger(_idp.config) if proxy_conf.DEBUG: logger.setLevel(logging.DEBUG) logger.debug("SERVER_ENV: %s" % SERVER_ENV) return _idp
def id_token_as_signed_jwt(self, session, loa="2", alg="", code=None, access_token=None, user_info=None, auth_time=0, exp=None, extra_claims=None, **kwargs): kwargs = {} if "rotsig" in self.behavior_type: # Rollover signing keys if alg == "RS256": key = RSAKey(kid="rotated_rsa_{}".format(time.time()), use="sig").load_key(RSA.generate(2048)) else: # alg == "ES256" key = ECKey(kid="rotated_ec_{}".format(time.time()), use="sig").load_key(P256) new_keys = {"keys": [key.serialize(private=True)]} self.events.store("New signing keys", new_keys) self.do_key_rollover(new_keys, "%d") self.events.store("Rotated signing keys", '') if "nokid1jwks" in self.behavior_type: # Remove key ID from keys kwargs['keys'] = self.no_kid_keys() if "nokidmuljwks" in self.behavior_type: # Remove key ID from keys kwargs['keys'] = self.no_kid_keys() if "sid" in session: if extra_claims is None: extra_claims = {"sid": session["sid"]} else: extra_claims.update({"sid": session["sid"]}) _jws = provider.Provider.id_token_as_signed_jwt( self, session, loa=loa, alg=alg, code=code, access_token=access_token, user_info=user_info, auth_time=auth_time, exp=exp, extra_claims=extra_claims, **kwargs) if "idts" in self.behavior_type: # Mess with the signature of the JWS p = _jws.split(".") p[2] = sort_string(p[2]) _jws = ".".join(p) return _jws
def gen_jwk_keypair(kid): # Mint a new RSA key _rsakey = RSA.generate(2048) # Wrap it in a JWK class _rsajwk = RSAKey(kid=kid, use="sig", alg="RS256", key=_rsakey) return json.dumps(_rsajwk.serialize(private=True))
def setup_server_env(proxy_conf, conf_mod, key): """ :param proxy_conf: :param conf_mod: :param key: RSA key :return: """ global SERVER_ENV global logger #noinspection PyUnboundLocalVariable SERVER_ENV = dict([(k, v) for k, v in proxy_conf.__dict__.items() if not k.startswith("__")]) SERVER_ENV["sessions"] = {} SERVER_ENV["eptid"] = EptidShelve(proxy_conf.SECRET, proxy_conf.EPTID_DB) _idp = server.Server(conf_mod) if key: rsa_key = RSAKey(key=key) rsa_key.serialize() else: rsa_key = None args = {"metad": _idp.metadata, "dkeys": [rsa_key]} SERVER_ENV["consumer_info"] = utils.ConsumerInfo(proxy_conf.CONSUMER_INFO, **args) SERVER_ENV["service"] = proxy_conf.SERVICE # add the service endpoints part = urlparse.urlparse(_idp.config.entityid) base = "%s://%s/" % (part.scheme, part.netloc) SERVER_ENV["SCHEME"] = part.scheme try: (host, port) = part.netloc.split(":") port = int(port) except ValueError: # no port specification host = part.netloc if part.scheme == "http": port = 80 elif part.scheme == "https": port = 443 else: raise ValueError("Unsupported scheme") SERVER_ENV["HOST"] = host SERVER_ENV["PORT"] = port endpoints = {"single_sign_on_service": [], "single_logout_service": []} for key, _dict in proxy_conf.SERVICE.items(): _sso = _dict["saml_endpoint"] endpoints["single_sign_on_service"].append("%s%s" % (base, _sso)) endpoints["single_logout_service"].append( ("%s%s/logout" % (base, _sso), BINDING_HTTP_REDIRECT)) _idp.config.setattr("idp", "endpoints", endpoints) SERVER_ENV["idp"] = _idp SERVER_ENV["template_lookup"] = LOOKUP SERVER_ENV["sid_generator"] = session_nr() SERVER_ENV["base_url"] = base SERVER_ENV["STATIC_DIR"] = proxy_conf.STATIC_DIR SERVER_ENV["METADATA_DIR"] = proxy_conf.METADATA_DIR SERVER_ENV["SIGN"] = proxy_conf.SIGN #print SERVER_ENV if proxy_conf.CACHE == "memory": SERVER_ENV["CACHE"] = cache.Cache(SERVER_ENV["SERVER_NAME"], SERVER_ENV["SECRET"]) elif proxy_conf.CACHE.startswith("file:"): SERVER_ENV["CACHE"] = cache.Cache(SERVER_ENV["SERVER_NAME"], SERVER_ENV["SECRET"], filename=proxy_conf.CACHE[5:]) logger = setup_logger(_idp.config) if proxy_conf.DEBUG: logger.setLevel(logging.DEBUG) logger.debug("SERVER_ENV: %s" % SERVER_ENV) return _idp
# - create a RSAKey object, and load the key with the load_key method # # A JWKS can instead be created as follow: # - retrieve the rsa key # - create a KEYS object and add the keys specifying the algorithm used for creation and the usage allowed for the key # (signature or encryption) # # A key jar can also be created with the method build_keyjar specifying a key_conf containing a list of keys to be # created, with their type, name and usage (encryption of signature) key = create_and_store_rsa_key_pair("foo", size=4096) key2 = create_and_store_rsa_key_pair("foo2", size=4096) rsa = RSAKey().load_key(key) print "--- JWK ---" print json.dumps(rsa.serialize(), sort_keys=True, indent=4, separators=(',', ': ')) print ######################################################## keys = KEYS() keys.wrap_add(key, use="sig", kid="rsa1") keys.wrap_add(key2, use="enc", kid="rsa1") print "--- JWKS---" print keys.dump_jwks() print ######################################################## key_conf = [
class TestValidateAndReturnIDTokenUtility: @pytest.fixture(autouse=True) def setup(self): httpretty.enable() self.key = RSAKey(kid='testkey').load( os.path.join(FIXTURE_ROOT, 'testkey.pem')) def jwks(_request, _uri, headers): # noqa: E306 ks = KEYS() ks.add(self.key.serialize()) return 200, headers, ks.dump_jwks() httpretty.register_uri(httpretty.GET, oidc_rp_settings.PROVIDER_JWKS_ENDPOINT, status=200, body=jwks) yield httpretty.disable() def generate_jws(self, **kwargs): return JWS(self.generate_jws_dict(**kwargs), jwk=self.key, alg='RS256').sign_compact() def generate_jws_dict(self, **kwargs): client_key = kwargs.get('client_key', oidc_rp_settings.CLIENT_ID) now_dt = dt.datetime.utcnow() expiration_dt = kwargs.get('expiration_dt', (now_dt + dt.timedelta(seconds=30))) issue_dt = kwargs.get('issue_dt', now_dt) nonce = kwargs.get('nonce', 'nonce') return { 'iss': kwargs.get('iss', oidc_rp_settings.PROVIDER_ENDPOINT), 'nonce': nonce, 'aud': kwargs.get('aud', client_key), 'azp': kwargs.get('azp', client_key), 'exp': timegm(expiration_dt.utctimetuple()), 'iat': timegm(issue_dt.utctimetuple()), 'nbf': timegm(kwargs.get('nbf', now_dt).utctimetuple()), 'sub': '1234', } @unittest.mock.patch('oidc_rp.conf.settings.CLIENT_ID', 'client_id') @unittest.mock.patch('oidc_rp.conf.settings.CLIENT_SECRET', 'client_secret') def test_can_validate_and_decode_an_id_token(self): jws = self.generate_jws() id_token = validate_and_return_id_token(jws, 'nonce') assert id_token['iss'] == 'http://example.com/a/' assert id_token['nonce'] == 'nonce' assert id_token['aud'] == ['client_id'] assert id_token['azp'] == 'client_id' assert id_token['sub'] == '1234' @unittest.mock.patch('oidc_rp.conf.settings.CLIENT_ID', 'client_id') @unittest.mock.patch('oidc_rp.conf.settings.CLIENT_SECRET', 'client_secret') @unittest.mock.patch('oidc_rp.conf.settings.USE_NONCE', False) def test_can_validate_and_decode_an_id_token_when_nonces_are_disabled( self): jws = self.generate_jws() id_token = validate_and_return_id_token(jws, validate_nonce=False) assert id_token['iss'] == 'http://example.com/a/' assert id_token['aud'] == ['client_id'] assert id_token['azp'] == 'client_id' assert id_token['sub'] == '1234' @unittest.mock.patch('oidc_rp.conf.settings.CLIENT_ID', 'client_id') @unittest.mock.patch('oidc_rp.conf.settings.CLIENT_SECRET', 'client_secret') def test_cannot_validate_an_incorrect_id_token(self): id_token = validate_and_return_id_token('dummy') assert id_token is None @unittest.mock.patch('oidc_rp.conf.settings.CLIENT_ID', 'client_id') @unittest.mock.patch('oidc_rp.conf.settings.CLIENT_SECRET', 'client_secret') def test_cannot_validate_an_id_token_with_an_invalid_iss(self): jws = self.generate_jws(iss='http://dummy.com') with pytest.raises(SuspiciousOperation): validate_and_return_id_token(jws) @unittest.mock.patch('oidc_rp.conf.settings.CLIENT_ID', 'client_id') @unittest.mock.patch('oidc_rp.conf.settings.CLIENT_SECRET', 'client_secret') def test_cannot_validate_an_id_token_with_an_invalid_client_id(self): jws = self.generate_jws(client_key='unknown') with pytest.raises(SuspiciousOperation): validate_and_return_id_token(jws) @unittest.mock.patch('oidc_rp.conf.settings.CLIENT_ID', 'client_id') @unittest.mock.patch('oidc_rp.conf.settings.CLIENT_SECRET', 'client_secret') def test_cannot_validate_an_id_token_with_multiple_audiences_but_no_authorized_party( self): jws_dict = self.generate_jws_dict() jws_dict['aud'] = [oidc_rp_settings.CLIENT_ID, '2'] jws_dict.pop('azp') jws = JWS(jws_dict, jwk=self.key, alg='RS256').sign_compact() with pytest.raises(SuspiciousOperation): validate_and_return_id_token(jws) @unittest.mock.patch('oidc_rp.conf.settings.CLIENT_ID', 'client_id') @unittest.mock.patch('oidc_rp.conf.settings.CLIENT_SECRET', 'client_secret') def test_cannot_validate_an_id_token_with_an_authorized_party(self): jws = self.generate_jws(azp='dummy') with pytest.raises(SuspiciousOperation): validate_and_return_id_token(jws) @unittest.mock.patch('oidc_rp.conf.settings.CLIENT_ID', 'client_id') @unittest.mock.patch('oidc_rp.conf.settings.CLIENT_SECRET', 'client_secret') def test_cannot_validate_an_id_token_whose_signature_has_expired(self): jws = self.generate_jws(expiration_dt=dt.datetime.utcnow() - dt.timedelta(minutes=40)) with pytest.raises(SuspiciousOperation): validate_and_return_id_token(jws) @unittest.mock.patch('oidc_rp.conf.settings.CLIENT_ID', 'client_id') @unittest.mock.patch('oidc_rp.conf.settings.CLIENT_SECRET', 'client_secret') def test_cannot_validate_an_id_token_with_an_invalid_nbf_value(self): jws = self.generate_jws(nbf=dt.datetime.utcnow() + dt.timedelta(minutes=100)) with pytest.raises(SuspiciousOperation): validate_and_return_id_token(jws) @unittest.mock.patch('oidc_rp.conf.settings.CLIENT_ID', 'client_id') @unittest.mock.patch('oidc_rp.conf.settings.CLIENT_SECRET', 'client_secret') def test_cannot_validate_an_id_token_which_is_too_aged(self): jws = self.generate_jws(issue_dt=dt.datetime.utcnow() - dt.timedelta(minutes=100)) with pytest.raises(SuspiciousOperation): validate_and_return_id_token(jws) @unittest.mock.patch('oidc_rp.conf.settings.CLIENT_ID', 'client_id') @unittest.mock.patch('oidc_rp.conf.settings.CLIENT_SECRET', 'client_secret') def test_cannot_validate_an_id_token_whose_nonce_is_not_valid(self): jws = self.generate_jws(nonce='invalidnonce') with pytest.raises(SuspiciousOperation): validate_and_return_id_token(jws)
def authorization_endpoint(self, request="", cookie=None, **kwargs): _req = parse_qs(request) #self.events.store(EV_REQUEST, _req) try: _scope = _req["scope"] except KeyError: return self._error(error="incorrect_behavior", descr="No scope parameter") else: # verify that openid is among the scopes _scopes = _scope[0].split(" ") if "openid" not in _scopes: return self._error(error="incorrect_behavior", descr="Scope does not contain 'openid'") client_id = _req["client_id"][0] try: f = response_type_cmp(kwargs['test_cnf']['response_type'], _req['response_type']) except KeyError: pass else: if f is False: self.events.store( EV_FAULT, 'Wrong response type: {}'.format(_req['response_type'])) return self._error_response(error="incorrect_behavior", descr="Wrong response_type") _rtypes = [] for rt in _req['response_type']: _rtypes.extend(rt.split(' ')) if 'id_token' in _rtypes: try: self._update_client_keys(client_id) except TestError: return self._error(error="incorrect_behavior", descr="No change in client keys") _response = provider.Provider.authorization_endpoint( self, request, cookie, **kwargs) if "rotenc" in self.behavior_type: # Rollover encryption keys rsa_key = RSAKey(kid="rotated_rsa_{}".format(time.time()), use="enc").load_key(RSA.generate(2048)) ec_key = ECKey(kid="rotated_ec_{}".format(time.time()), use="enc").load_key(P256) keys = [ rsa_key.serialize(private=True), ec_key.serialize(private=True) ] new_keys = {"keys": keys} self.events.store("New encryption keys", new_keys) self.do_key_rollover(new_keys, "%d") self.events.store("Rotated encryption keys", '') logger.info('Rotated OP enc keys, new set: {}'.format( key_summary(self.keyjar, ''))) # This is just for logging purposes try: _resp = self.server.http_request(_req["request_uri"][0]) except KeyError: pass else: if _resp.status_code == 200: self.events.store( EV_REQUEST, "Request from request_uri: {}".format(_resp.text)) return _response
def serialize_rsa_key(key): kid = hashlib.md5(key.encode('utf-8')).hexdigest() key = RSAKey(kid=kid, key=RSA.importKey(key), use='sig', alg='RS512') return key.serialize(private=False)
class OpenIdConnectTestMixin(object): """ Mixin to test OpenID Connect consumers. Inheriting classes should also inherit OAuth2Test. """ client_key = 'a-key' client_secret = 'a-secret-key' issuer = None # id_token issuer openid_config_body = None key = None def setUp(self): super(OpenIdConnectTestMixin, self).setUp() test_root = os.path.dirname(os.path.dirname(__file__)) self.key = RSAKey(kid='testkey').load(os.path.join(test_root, 'testkey.pem')) HTTPretty.register_uri(HTTPretty.GET, self.backend.OIDC_ENDPOINT + '/.well-known/openid-configuration', status=200, body=self.openid_config_body ) oidc_config = json.loads(self.openid_config_body) def jwks(_request, _uri, headers): ks = KEYS() ks.add(self.key.serialize()) return 200, headers, ks.dump_jwks() HTTPretty.register_uri(HTTPretty.GET, oidc_config.get('jwks_uri'), status=200, body=jwks) def extra_settings(self): settings = super(OpenIdConnectTestMixin, self).extra_settings() settings.update({ 'SOCIAL_AUTH_{0}_KEY'.format(self.name): self.client_key, 'SOCIAL_AUTH_{0}_SECRET'.format(self.name): self.client_secret, 'SOCIAL_AUTH_{0}_ID_TOKEN_DECRYPTION_KEY'.format(self.name): self.client_secret }) return settings def access_token_body(self, request, _url, headers): """ Get the nonce from the request parameters, add it to the id_token, and return the complete response. """ nonce = self.backend.data['nonce'].encode('utf-8') body = self.prepare_access_token_body(nonce=nonce) return 200, headers, body def get_id_token(self, client_key=None, expiration_datetime=None, issue_datetime=None, nonce=None, issuer=None): """ Return the id_token to be added to the access token body. """ return { 'iss': issuer, 'nonce': nonce, 'aud': client_key, 'azp': client_key, 'exp': expiration_datetime, 'iat': issue_datetime, 'sub': '1234' } def prepare_access_token_body(self, client_key=None, tamper_message=False, expiration_datetime=None, issue_datetime=None, nonce=None, issuer=None): """ Prepares a provider access token response. Arguments: client_id -- (str) OAuth ID for the client that requested authentication. expiration_time -- (datetime) Date and time after which the response should be considered invalid. """ body = {'access_token': 'foobar', 'token_type': 'bearer'} client_key = client_key or self.client_key now = datetime.datetime.utcnow() expiration_datetime = expiration_datetime or \ (now + datetime.timedelta(seconds=30)) issue_datetime = issue_datetime or now nonce = nonce or 'a-nonce' issuer = issuer or self.issuer id_token = self.get_id_token( client_key, timegm(expiration_datetime.utctimetuple()), timegm(issue_datetime.utctimetuple()), nonce, issuer) body['id_token'] = JWS(id_token, jwk=self.key, alg='RS256').sign_compact() if tamper_message: header, msg, sig = body['id_token'].split('.') id_token['sub'] = '1235' msg = b64encode_item(id_token).decode('utf-8') body['id_token'] = '.'.join([header, msg, sig]) return json.dumps(body) def authtoken_raised(self, expected_message, **access_token_kwargs): self.access_token_body = self.prepare_access_token_body( **access_token_kwargs ) with self.assertRaisesRegexp(AuthTokenError, expected_message): self.do_login() @unittest2.skipIf(NO_JWKEST, 'No Jwkest installed') def test_invalid_signature(self): self.authtoken_raised( 'Token error: Signature verification failed', tamper_message=True ) @unittest2.skipIf(NO_JWKEST, 'No Jwkest installed') def test_expired_signature(self): expiration_datetime = datetime.datetime.utcnow() - \ datetime.timedelta(seconds=30) self.authtoken_raised('Token error: Signature has expired', expiration_datetime=expiration_datetime) @unittest2.skipIf(NO_JWKEST, 'No Jwkest installed') def test_invalid_issuer(self): self.authtoken_raised('Token error: Invalid issuer', issuer='someone-else') @unittest2.skipIf(NO_JWKEST, 'No Jwkest installed') def test_invalid_audience(self): self.authtoken_raised('Token error: Invalid audience', client_key='someone-else') @unittest2.skipIf(NO_JWKEST, 'No Jwkest installed') def test_invalid_issue_time(self): expiration_datetime = datetime.datetime.utcnow() - \ datetime.timedelta(hours=1) self.authtoken_raised('Token error: Incorrect id_token: iat', issue_datetime=expiration_datetime) @unittest2.skipIf(NO_JWKEST, 'No Jwkest installed') def test_invalid_nonce(self): self.authtoken_raised( 'Token error: Incorrect id_token: nonce', nonce='something-wrong' )
def serialize_rsa_key(key): kid = hashlib.md5(key.encode('utf-8')).hexdigest() key = RSAKey(kid=kid, key=RSA.importKey(key), use='sig', alg='RS512') return key.serialize(private=False)
class TestOIDCAuthBackend: @pytest.fixture(autouse=True) def setup(self): httpretty.enable() self.key = RSAKey(kid='testkey').load( os.path.join(FIXTURE_ROOT, 'testkey.pem')) def jwks(_request, _uri, headers): # noqa: E306 ks = KEYS() ks.add(self.key.serialize()) return 200, headers, ks.dump_jwks() httpretty.register_uri(httpretty.GET, oidc_rp_settings.PROVIDER_JWKS_ENDPOINT, status=200, body=jwks) httpretty.register_uri(httpretty.POST, oidc_rp_settings.PROVIDER_TOKEN_ENDPOINT, body=json.dumps({ 'id_token': self.generate_jws(), 'access_token': 'accesstoken', 'refresh_token': 'refreshtoken', }), content_type='text/json') httpretty.register_uri(httpretty.GET, oidc_rp_settings.PROVIDER_USERINFO_ENDPOINT, body=json.dumps({ 'sub': '1234', 'email': '*****@*****.**', }), content_type='text/json') yield httpretty.disable() def generate_jws(self, **kwargs): return JWS(self.generate_jws_dict(**kwargs), jwk=self.key, alg='RS256').sign_compact() def generate_jws_dict(self, **kwargs): client_key = kwargs.get('client_key', oidc_rp_settings.CLIENT_ID) now_dt = dt.datetime.utcnow() expiration_dt = kwargs.get('expiration_dt', (now_dt + dt.timedelta(seconds=30))) issue_dt = kwargs.get('issue_dt', now_dt) nonce = kwargs.get('nonce', 'nonce') ret = kwargs ret.update({ 'iss': kwargs.get('iss', oidc_rp_settings.PROVIDER_ENDPOINT), 'nonce': nonce, 'aud': kwargs.get('aud', client_key), 'azp': kwargs.get('azp', client_key), 'exp': timegm(expiration_dt.utctimetuple()), 'iat': timegm(issue_dt.utctimetuple()), 'nbf': timegm(kwargs.get('nbf', now_dt).utctimetuple()), 'sub': '1234', }) return ret def test_can_authenticate_a_new_user(self, rf): request = rf.get('/oidc/cb/', { 'state': 'state', 'code': 'authcode', }) SessionMiddleware().process_request(request) request.session.save() backend = OIDCAuthBackend() user = backend.authenticate(request, 'nonce') assert user.email == '*****@*****.**' assert user.oidc_user.sub == '1234' def test_can_authenticate_an_existing_user(self, rf): request = rf.get('/oidc/cb/', { 'state': 'state', 'code': 'authcode', }) SessionMiddleware().process_request(request) request.session.save() backend = OIDCAuthBackend() user = get_user_model().objects.create_user('test', '*****@*****.**') OIDCUser.objects.create(user=user, sub='1234') user = backend.authenticate(request, 'nonce') assert user.email == '*****@*****.**' assert user.oidc_user.sub == '1234' def test_can_authenticate_a_new_user_even_if_no_email_is_in_userinfo_data( self, rf): httpretty.register_uri( httpretty.GET, oidc_rp_settings.PROVIDER_USERINFO_ENDPOINT, body=json.dumps({ 'sub': '1234', }), content_type='text/json', ) request = rf.get('/oidc/cb/', { 'state': 'state', 'code': 'authcode', }) SessionMiddleware().process_request(request) request.session.save() backend = OIDCAuthBackend() user = backend.authenticate(request, 'nonce') assert not user.email assert user.oidc_user.sub == '1234' def test_cannot_authenticate_a_user_if_the_nonce_is_not_provided_and_if_it_is_mandatory( self, rf): request = rf.get('/oidc/cb/', { 'code': 'authcode', }) SessionMiddleware().process_request(request) request.session.save() backend = OIDCAuthBackend() assert backend.authenticate(None, request) is None def test_cannot_authenticate_a_user_if_the_request_object_is_not_provided( self, rf): request = rf.get('/oidc/cb/', { 'code': 'authcode', }) SessionMiddleware().process_request(request) request.session.save() backend = OIDCAuthBackend() assert backend.authenticate('nonce', None) is None def test_cannot_authenticate_a_user_if_the_state_is_not_present_in_the_request_parameters( self, rf): request = rf.get('/oidc/cb/', { 'code': 'authcode', }) SessionMiddleware().process_request(request) request.session.save() backend = OIDCAuthBackend() with pytest.raises(SuspiciousOperation): backend.authenticate(request, 'nonce') def test_cannot_authenticate_a_user_if_the_code_is_not_present_in_the_request_parameters( self, rf): request = rf.get('/oidc/cb/', { 'state': 'state', }) SessionMiddleware().process_request(request) request.session.save() backend = OIDCAuthBackend() with pytest.raises(SuspiciousOperation): backend.authenticate(request, 'nonce') def test_cannot_authenticate_a_user_if_the_id_token_validation_shows_a_suspicious_operation( self, rf): request = rf.get('/oidc/cb/', { 'state': 'state', 'code': 'authcode', }) SessionMiddleware().process_request(request) request.session.save() backend = OIDCAuthBackend() with pytest.raises(SuspiciousOperation): backend.authenticate(request, 'badnonce') def test_cannot_authenticate_a_user_if_the_id_token_validation_fails( self, rf): httpretty.register_uri(httpretty.POST, oidc_rp_settings.PROVIDER_TOKEN_ENDPOINT, body=json.dumps({ 'id_token': 'badidtoken', 'access_token': 'accesstoken', 'refresh_token': 'refreshtoken', }), content_type='text/json') request = rf.get('/oidc/cb/', { 'state': 'state', 'code': 'authcode', }) SessionMiddleware().process_request(request) request.session.save() backend = OIDCAuthBackend() assert backend.authenticate(request, 'nonce') is None @unittest.mock.patch('oidc_rp.conf.settings.USER_DETAILS_HANDLER', 'tests.unit.test_backends.set_users_as_staff_members') def test_can_authenticate_a_new_user_and_update_its_details_with_a_specific_handler( self, rf): request = rf.get('/oidc/cb/', { 'state': 'state', 'code': 'authcode', }) SessionMiddleware().process_request(request) request.session.save() backend = OIDCAuthBackend() user = backend.authenticate(request, 'nonce') assert user.email == '*****@*****.**' assert user.oidc_user.sub == '1234' assert user.is_staff @unittest.mock.patch('oidc_rp.conf.settings.ID_TOKEN_INCLUDE_USERINFO', True) def test_can_process_userinfo_included_in_the_id_token_instead_of_calling_the_userinfo_endpoint( self, rf): httpretty.register_uri( httpretty.POST, oidc_rp_settings.PROVIDER_TOKEN_ENDPOINT, body=json.dumps({ 'id_token': self.generate_jws(email='*****@*****.**'), 'access_token': 'accesstoken', 'refresh_token': 'refreshtoken', }), content_type='text/json') request = rf.get('/oidc/cb/', { 'state': 'state', 'code': 'authcode', }) SessionMiddleware().process_request(request) request.session.save() backend = OIDCAuthBackend() user = backend.authenticate(request, 'nonce') assert user.email == '*****@*****.**' assert user.oidc_user.sub == '1234' def test_oidc_user_created_signal_is_sent_during_new_user_authentication( self, rf): self.signal_was_called = False def handler(sender, request, oidc_user, **kwargs): self.request = request self.oidc_user = oidc_user self.signal_was_called = True oidc_user_created.connect(handler) request = rf.get('/oidc/cb/', { 'state': 'state', 'code': 'authcode', }) SessionMiddleware().process_request(request) request.session.save() backend = OIDCAuthBackend() backend.authenticate(request, 'nonce') assert self.signal_was_called is True assert type(self.request) is WSGIRequest assert self.oidc_user.userinfo['email'] == '*****@*****.**' assert self.oidc_user.userinfo['sub'] == '1234' oidc_user_created.disconnect(handler)
parser.add_argument("message", nargs="?", help="The message to encrypt") args = parser.parse_args() keys = {} if args.jwk_url: keys = assign(load_jwks_from_url(lrequest, args.jwk_url)) elif args.jwk_file: keys = load_jwks(open(args.jwk_file).read()) elif args.x509_url: keys = load_x509_cert(lrequest, args.x509_url) elif args.x509_file: keys = [import_rsa_key_from_file(args.x509_file)] elif args.rsa_file: key = rsa_load(args.rsa_file) rsa_key = RSAKey(key=key) rsa_key.serialize() keys = [rsa_key] else: print("Needs encryption key") exit() if args.file: msg = open(args.file).read() msg = msg.strip("\n\r") else: msg = args.message jwe = JWE() print(jwe.decrypt(msg, keys))
class TestOIDCRefreshIDTokenMiddleware: @pytest.fixture(autouse=True) def setup(self): httpretty.enable() self.key = RSAKey(kid='testkey').load( os.path.join(FIXTURE_ROOT, 'testkey.pem')) def jwks(_request, _uri, headers): # noqa: E306 ks = KEYS() ks.add(self.key.serialize()) return 200, headers, ks.dump_jwks() httpretty.register_uri(httpretty.GET, oidc_rp_settings.PROVIDER_JWKS_ENDPOINT, status=200, body=jwks) httpretty.register_uri(httpretty.POST, oidc_rp_settings.PROVIDER_TOKEN_ENDPOINT, body=json.dumps({ 'id_token': self.generate_jws(), 'access_token': 'accesstoken', 'refresh_token': 'refreshtoken', }), content_type='text/json') httpretty.register_uri(httpretty.GET, oidc_rp_settings.PROVIDER_USERINFO_ENDPOINT, body=json.dumps({ 'sub': '1234', 'email': '*****@*****.**', }), content_type='text/json') yield httpretty.disable() def generate_jws(self, **kwargs): return JWS(self.generate_jws_dict(**kwargs), jwk=self.key, alg='RS256').sign_compact() def generate_jws_dict(self, **kwargs): client_key = kwargs.get('client_key', oidc_rp_settings.CLIENT_ID) now_dt = dt.datetime.utcnow() expiration_dt = kwargs.get('expiration_dt', (now_dt + dt.timedelta(seconds=30))) issue_dt = kwargs.get('issue_dt', now_dt) nonce = kwargs.get('nonce', 'nonce') return { 'iss': kwargs.get('iss', oidc_rp_settings.PROVIDER_ENDPOINT), 'nonce': nonce, 'aud': kwargs.get('aud', client_key), 'azp': kwargs.get('azp', client_key), 'exp': timegm(expiration_dt.utctimetuple()), 'iat': timegm(issue_dt.utctimetuple()), 'nbf': timegm(kwargs.get('nbf', now_dt).utctimetuple()), 'sub': '1234', } def test_can_refresh_the_access_token_of_a_previously_authenticated_user( self, rf): request = rf.get('/oidc/cb/', { 'state': 'state', 'code': 'authcode', }) SessionMiddleware().process_request(request) request.session.save() backend = OIDCAuthBackend() user = backend.authenticate('nonce', request) request.session['oidc_auth_id_token_exp_timestamp'] = \ (tz.now() - dt.timedelta(minutes=1)).timestamp() request.session['oidc_auth_refresh_token'] = 'this_is_a_refresh_token' auth.login(request, user) request.user = user middleware = OIDCRefreshIDTokenMiddleware(lambda r: 'OK') middleware(request) assert request.session['oidc_auth_refresh_token'] == 'refreshtoken' def test_can_properly_handle_the_case_where_a_user_was_authenticated_using_the_model_backend( self, rf): request = rf.get('/') SessionMiddleware().process_request(request) request.session.save() user = get_user_model().objects.create_user('test', '*****@*****.**', 'insecure') request.user = user auth.authenticate(username='******', password='******') auth.login(request, user) middleware = OIDCRefreshIDTokenMiddleware(lambda r: 'OK') middleware(request) assert request.user == user assert request.user.is_authenticated def test_do_nothing_if_the_access_token_is_still_valid(self, rf): request = rf.get('/oidc/cb/', { 'state': 'state', 'code': 'authcode', }) SessionMiddleware().process_request(request) request.session.save() backend = OIDCAuthBackend() user = backend.authenticate('nonce', request) request.session['oidc_auth_id_token_exp_timestamp'] = \ (tz.now() + dt.timedelta(minutes=1)).timestamp() request.session['oidc_auth_refresh_token'] = 'this_is_a_refresh_token' auth.login(request, user) request.user = user middleware = OIDCRefreshIDTokenMiddleware(lambda r: 'OK') middleware(request) assert request.session[ 'oidc_auth_refresh_token'] == 'this_is_a_refresh_token' def test_log_out_the_user_if_the_id_token_is_not_valid(self, rf): request = rf.get('/oidc/cb/', { 'state': 'state', 'code': 'authcode', }) SessionMiddleware().process_request(request) request.session.save() backend = OIDCAuthBackend() user = backend.authenticate('nonce', request) request.session['oidc_auth_id_token_exp_timestamp'] = \ (tz.now() - dt.timedelta(minutes=1)).timestamp() request.session['oidc_auth_refresh_token'] = 'this_is_a_refresh_token' auth.login(request, user) request.user = user httpretty.register_uri(httpretty.POST, oidc_rp_settings.PROVIDER_TOKEN_ENDPOINT, body=json.dumps({ 'id_token': 'badidtoken', 'access_token': 'accesstoken', 'refresh_token': 'refreshtoken', }), content_type='text/json') middleware = OIDCRefreshIDTokenMiddleware(lambda r: 'OK') middleware(request) assert not request.user.is_authenticated def test_log_out_the_user_if_the_refresh_token_is_expired(self, rf): request = rf.get('/oidc/cb/', { 'state': 'state', 'code': 'authcode', }) SessionMiddleware().process_request(request) request.session.save() backend = OIDCAuthBackend() user = backend.authenticate('nonce', request) request.session['oidc_auth_id_token_exp_timestamp'] = \ (tz.now() - dt.timedelta(minutes=1)).timestamp() request.session['oidc_auth_refresh_token'] = 'this_is_a_refresh_token' auth.login(request, user) request.user = user httpretty.register_uri(httpretty.POST, oidc_rp_settings.PROVIDER_TOKEN_ENDPOINT, body=json.dumps({'error': 'yes'}), content_type='text/json', status=400) middleware = OIDCRefreshIDTokenMiddleware(lambda r: 'OK') middleware(request) assert not request.user.is_authenticated
class MetadataGeneration(object): def __init__(self, conf, key, idp_conf, xmlsec_path): """ Constructor. Initiates the class. :param conf: Specific metadata conf :param key: A RSA key to be used for encryption. :param idp_conf: idp_conf see IdpProxy/idp_conf.example.py :param xmlsec_path: :raise: """ if (conf is None) or (key is None): raise ValueError( "A new instance must include a value for logger, conf and key.") #Key to be used for encryption. self.key = RSAKey(key=key) self.key.serialize() self.alg = 'RSA-OAEP' self.enc = 'A128CBC-HS256' #Used for presentation of mako files. self.lookup = TemplateLookup( directories=[CONST_STATIC_MAKO + 'templates', CONST_STATIC_MAKO + 'htdocs'], module_directory='modules', input_encoding='utf-8', output_encoding='utf-8') #A list of all social services used by this IdPproxy. self.social_service_key_list = [] for key in conf: self.social_service_key_list.append(conf[key]["name"]) #A list of all service providers used by this sp. self.sp_key_list = idp_conf.metadata.service_providers() self.xmlsec_path = xmlsec_path @staticmethod def verify_handle_request(path): """ Verifies if the given path should be handled by this class. :param path: A path. :return: True if the path should be handled by this class, otherwise false. """ return re.match(CONST_METADATA + ".*", path) @staticmethod def get_query_dict(environ): """ Retrieves a dictionary with query parameters. :param environ: The wsgi enviroment. :return: A dictionary with query parameters. """ qs = {} query = environ.get("QUERY_STRING", "") if not query: post_env = environ.copy() post_env['QUERY_STRING'] = '' query = cgi.FieldStorage(fp=environ['wsgi.input'], environ=post_env, keep_blank_values=True) if query is not None: try: for item in query: qs[query[item].name] = query[item].value except: qs[CONST_BODY] = query.file.read() else: qs = dict((k, v if len(v) > 1 else v[0]) for k, v in parse_qs(query).iteritems()) return qs def handle_request(self, environ, start_response, path): """ Call this method from the wsgi application. Handles the request if the path matched by verify_handle_request and any static file or CONST_METADATA or CONST_METADATASAVE. :param environ: wsgi enviroment :param start_response: the start response :param path: the requested path :return: a response fitted for wsgi application. """ try: if path == CONST_METADATA: return self.handle_metadata(environ, start_response) elif path == CONST_METADATAVERIFY: return self.handle_metadata_verify(environ, start_response) elif path == CONST_METADATAVERIFYJSON: return self.handle_metadata_verify_json( environ, start_response, self.get_query_dict(environ)) elif path == CONST_METADATASAVE: return self.handle_metadata_save(environ, start_response, self.get_query_dict(environ)) else: filename = CONST_STATIC_FILE + self.get_static_file_name(path) if self.verify_static(filename): return self.handle_static(environ, start_response, filename) else: return self.handle_static(environ, start_response, CONST_UNKNOWFILE) except Exception: _logger.fatal('Unknown error in handle_request.', exc_info=True) return self.handle_static(environ, start_response, CONST_UNKNOWERROR) def get_static_file_name(self, path): """ Parses out the static file name from the path. :param path: The requested path. :return: The static file name. """ if self.verify_handle_request(path): try: return path[len(CONST_METADATA) + 1:] except: pass return "" @staticmethod def verify_static(filename): """ Verifies if a static file exists in the folder IdPproxy/src/idpproxy/metadata/files/static :param filename: The name of the file. :return: True if the file exists, otherwise false. """ try: with open(filename): pass except IOError: return False return True def handle_metadata(self, environ, start_response): """ Creates the response for the first page in the metadata generation. :param environ: wsgi enviroment :param start_response: wsgi start respons :return: wsgi response for the mako file metadata.mako. """ resp = Response(mako_template="metadata.mako", template_lookup=self.lookup, headers=[]) argv = { "action": CONST_METADATASAVE, "sociallist": sorted(self.social_service_key_list), "spKeyList": sorted(self.sp_key_list), "verify": CONST_METADATAVERIFY, } return resp(environ, start_response, **argv) def handle_metadata_save(self, environ, start_response, qs): """ Takes the input for the page metadata.mako. Encrypts entity id and secret information for the social services. Creates the partial xml to be added to the metadata for the service provider. :param environ: wsgi enviroment :param start_response: wsgi start respons :param qs: Query parameters in a dictionary. :return: wsgi response for the mako file metadatasave.mako. """ resp = Response(mako_template="metadatasave.mako", template_lookup=self.lookup, headers=[]) if "entityId" not in qs or "secret" not in qs: xml = ("Xml could not be generated because no entityId or secret" "has been sent to the service.") _logger.warning(xml) else: try: secret_data = json.dumps({"entityId": json.loads(qs["entityId"]), "secret": json.loads(qs["secret"])}) # create a JWE jwe = JWE(secret_data, alg=self.alg, enc=self.enc) secret_data_encrypted = jwe.encrypt([self.key]) val = AttributeValue() val.set_text(secret_data_encrypted) attr = Attribute( name_format=NAME_FORMAT_URI, name="http://social2saml.nordu.net/customer", attribute_value=[val]) eattr = mdattr.EntityAttributes(attribute=[attr]) nspair = { "mdattr": "urn:oasis:names:tc:SAML:metadata:attribute", "samla": "urn:oasis:names:tc:SAML:2.0:assertion", } xml = eattr.to_string(nspair) xml_list = xml.split("\n", 1) if len(xml_list) == 2: xml = xml_list[1] except Exception: _logger.fatal('Unknown error in handle_metadata_save.', exc_info=True) xml = "Xml could not be generated." argv = { "home": CONST_METADATA, "action": CONST_METADATAVERIFY, "xml": xml } return resp(environ, start_response, **argv) def handle_metadata_verify(self, environ, start_response): """ Will show the page for metadata verification (metadataverify.mako). :param environ: wsgi enviroment :param start_response: wsgi start respons :return: wsgi response for the mako file metadatasave.mako. """ resp = Response(mako_template="metadataverify.mako", template_lookup=self.lookup, headers=[]) argv = { "home": CONST_METADATA, "action": CONST_METADATAVERIFYJSON } return resp(environ, start_response, **argv) def handle_metadata_verify_json(self, environ, start_response, qs): """ Handles JSON metadata verifications. The post body must contains a JSON message like { 'xml' : 'a metadata file'} :param environ: wsgi enviroment :param start_response: wsgi start respons :param qs: Query parameters in a dictionary. :return: wsgi response contaning a JSON response. The JSON message will contain the parameter ok and services. ok will contain true if the metadata file can be parsed, otherwise false. services will contain a list of all the service names contained in the metadata file. """ ok = False services = "[]" try: if CONST_BODY in qs: json_message = json.loads(qs[CONST_BODY]) if "xml" in json_message: xml = json_message["xml"] xml = xml.strip() metadata_ok = False ci = None mds = MetadataStore( CONST_ONTS.values(), CONST_ATTRCONV, self.xmlsec_path, disable_ssl_certificate_validation=True) _md = MetaData(CONST_ONTS.values(), CONST_ATTRCONV, metadata=xml) try: _md.load() except: _logger.info( 'Could not parse the metadata file in handleMetadataVerifyJSON.', exc_info=True) else: entity_id = _md.entity.keys()[0] mds.metadata[entity_id] = _md args = {"metad": mds, "dkeys": [self.key]} ci = utils.ConsumerInfo(['metadata'], **args) metadata_ok = True services = "[" first = True if ci is not None: for item in ci.info: if item.ava is not None and entity_id in item.ava: for social in item.ava[entity_id]: if not first: services += "," else: first = False services += '"' + social + '"' services += "]" if metadata_ok: ok = True except: _logger.fatal('Unknown error in handleMetadataVerifyJSON.', exc_info=True) resp = Response('{"ok":"' + str(ok) + '", "services":' + services + '}', headers=[('Content-Type', CONST_TYPEJSON)]) return resp(environ, start_response) @staticmethod def handle_static(environ, start_response, path): """ Creates a response for a static file. :param environ: wsgi enviroment :param start_response: wsgi start response :param path: the static file and path to the file. :return: wsgi response for the static file. """ try: text = open(path).read() if path.endswith(".ico"): resp = Response(text, headers=[('Content-Type', "image/x-icon")]) elif path.endswith(".html"): resp = Response(text, headers=[('Content-Type', 'text/html')]) elif path.endswith(".txt"): resp = Response(text, headers=[('Content-Type', 'text/plain')]) elif path.endswith(".css"): resp = Response(text, headers=[('Content-Type', 'text/css')]) else: resp = Response(text, headers=[('Content-Type', 'text/xml')]) except IOError: resp = NotFound() return resp(environ, start_response)
class MetadataGeneration(object): def __init__(self, conf, key, idp_conf, xmlsec_path): """ Constructor. Initiates the class. :param conf: Specific metadata conf :param key: A RSA key to be used for encryption. :param idp_conf: idp_conf see IdpProxy/idp_conf.example.py :param xmlsec_path: :raise: """ if (conf is None) or (key is None): raise ValueError( "A new instance must include a value for logger, conf and key." ) #Key to be used for encryption. self.key = RSAKey(key=key) self.key.serialize() self.alg = 'RSA-OAEP' self.enc = 'A128CBC-HS256' #Used for presentation of mako files. self.lookup = TemplateLookup(directories=[ CONST_STATIC_MAKO + 'templates', CONST_STATIC_MAKO + 'htdocs' ], module_directory='modules', input_encoding='utf-8', output_encoding='utf-8') #A list of all social services used by this IdPproxy. self.social_service_key_list = [] for key in conf: self.social_service_key_list.append(conf[key]["name"]) #A list of all service providers used by this sp. self.sp_key_list = idp_conf.metadata.service_providers() self.xmlsec_path = xmlsec_path @staticmethod def verify_handle_request(path): """ Verifies if the given path should be handled by this class. :param path: A path. :return: True if the path should be handled by this class, otherwise false. """ return re.match(CONST_METADATA + ".*", path) @staticmethod def get_query_dict(environ): """ Retrieves a dictionary with query parameters. :param environ: The wsgi enviroment. :return: A dictionary with query parameters. """ qs = {} query = environ.get("QUERY_STRING", "") if not query: post_env = environ.copy() post_env['QUERY_STRING'] = '' query = cgi.FieldStorage(fp=environ['wsgi.input'], environ=post_env, keep_blank_values=True) if query is not None: try: for item in query: qs[query[item].name] = query[item].value except: qs[CONST_BODY] = query.file.read() else: qs = dict((k, v if len(v) > 1 else v[0]) for k, v in parse_qs(query).iteritems()) return qs def handle_request(self, environ, start_response, path): """ Call this method from the wsgi application. Handles the request if the path matched by verify_handle_request and any static file or CONST_METADATA or CONST_METADATASAVE. :param environ: wsgi enviroment :param start_response: the start response :param path: the requested path :return: a response fitted for wsgi application. """ try: if path == CONST_METADATA: return self.handle_metadata(environ, start_response) elif path == CONST_METADATAVERIFY: return self.handle_metadata_verify(environ, start_response) elif path == CONST_METADATAVERIFYJSON: return self.handle_metadata_verify_json( environ, start_response, self.get_query_dict(environ)) elif path == CONST_METADATASAVE: return self.handle_metadata_save(environ, start_response, self.get_query_dict(environ)) else: filename = CONST_STATIC_FILE + self.get_static_file_name(path) if self.verify_static(filename): return self.handle_static(environ, start_response, filename) else: return self.handle_static(environ, start_response, CONST_UNKNOWFILE) except Exception: _logger.fatal('Unknown error in handle_request.', exc_info=True) return self.handle_static(environ, start_response, CONST_UNKNOWERROR) def get_static_file_name(self, path): """ Parses out the static file name from the path. :param path: The requested path. :return: The static file name. """ if self.verify_handle_request(path): try: return path[len(CONST_METADATA) + 1:] except: pass return "" @staticmethod def verify_static(filename): """ Verifies if a static file exists in the folder IdPproxy/src/idpproxy/metadata/files/static :param filename: The name of the file. :return: True if the file exists, otherwise false. """ try: with open(filename): pass except IOError: return False return True def handle_metadata(self, environ, start_response): """ Creates the response for the first page in the metadata generation. :param environ: wsgi enviroment :param start_response: wsgi start respons :return: wsgi response for the mako file metadata.mako. """ resp = Response(mako_template="metadata.mako", template_lookup=self.lookup, headers=[]) argv = { "action": CONST_METADATASAVE, "sociallist": sorted(self.social_service_key_list), "spKeyList": sorted(self.sp_key_list), "verify": CONST_METADATAVERIFY, } return resp(environ, start_response, **argv) def handle_metadata_save(self, environ, start_response, qs): """ Takes the input for the page metadata.mako. Encrypts entity id and secret information for the social services. Creates the partial xml to be added to the metadata for the service provider. :param environ: wsgi enviroment :param start_response: wsgi start respons :param qs: Query parameters in a dictionary. :return: wsgi response for the mako file metadatasave.mako. """ resp = Response(mako_template="metadatasave.mako", template_lookup=self.lookup, headers=[]) if "entityId" not in qs or "secret" not in qs: xml = ("Xml could not be generated because no entityId or secret" "has been sent to the service.") _logger.warning(xml) else: try: secret_data = json.dumps({ "entityId": json.loads(qs["entityId"]), "secret": json.loads(qs["secret"]) }) # create a JWE jwe = JWE(secret_data, alg=self.alg, enc=self.enc) secret_data_encrypted = jwe.encrypt([self.key]) val = AttributeValue() val.set_text(secret_data_encrypted) attr = Attribute(name_format=NAME_FORMAT_URI, name="http://social2saml.nordu.net/customer", attribute_value=[val]) eattr = mdattr.EntityAttributes(attribute=[attr]) nspair = { "mdattr": "urn:oasis:names:tc:SAML:metadata:attribute", "samla": "urn:oasis:names:tc:SAML:2.0:assertion", } xml = eattr.to_string(nspair) xml_list = xml.split("\n", 1) if len(xml_list) == 2: xml = xml_list[1] except Exception: _logger.fatal('Unknown error in handle_metadata_save.', exc_info=True) xml = "Xml could not be generated." argv = { "home": CONST_METADATA, "action": CONST_METADATAVERIFY, "xml": xml } return resp(environ, start_response, **argv) def handle_metadata_verify(self, environ, start_response): """ Will show the page for metadata verification (metadataverify.mako). :param environ: wsgi enviroment :param start_response: wsgi start respons :return: wsgi response for the mako file metadatasave.mako. """ resp = Response(mako_template="metadataverify.mako", template_lookup=self.lookup, headers=[]) argv = {"home": CONST_METADATA, "action": CONST_METADATAVERIFYJSON} return resp(environ, start_response, **argv) def handle_metadata_verify_json(self, environ, start_response, qs): """ Handles JSON metadata verifications. The post body must contains a JSON message like { 'xml' : 'a metadata file'} :param environ: wsgi enviroment :param start_response: wsgi start respons :param qs: Query parameters in a dictionary. :return: wsgi response contaning a JSON response. The JSON message will contain the parameter ok and services. ok will contain true if the metadata file can be parsed, otherwise false. services will contain a list of all the service names contained in the metadata file. """ ok = False services = "[]" try: if CONST_BODY in qs: json_message = json.loads(qs[CONST_BODY]) if "xml" in json_message: xml = json_message["xml"] xml = xml.strip() metadata_ok = False ci = None mds = MetadataStore( CONST_ONTS.values(), CONST_ATTRCONV, self.xmlsec_path, disable_ssl_certificate_validation=True) _md = MetaData(CONST_ONTS.values(), CONST_ATTRCONV, metadata=xml) try: _md.load() except: _logger.info( 'Could not parse the metadata file in handleMetadataVerifyJSON.', exc_info=True) else: entity_id = _md.entity.keys()[0] mds.metadata[entity_id] = _md args = {"metad": mds, "dkeys": [self.key]} ci = utils.ConsumerInfo(['metadata'], **args) metadata_ok = True services = "[" first = True if ci is not None: for item in ci.info: if item.ava is not None and entity_id in item.ava: for social in item.ava[entity_id]: if not first: services += "," else: first = False services += '"' + social + '"' services += "]" if metadata_ok: ok = True except: _logger.fatal('Unknown error in handleMetadataVerifyJSON.', exc_info=True) resp = Response('{"ok":"' + str(ok) + '", "services":' + services + '}', headers=[('Content-Type', CONST_TYPEJSON)]) return resp(environ, start_response) @staticmethod def handle_static(environ, start_response, path): """ Creates a response for a static file. :param environ: wsgi enviroment :param start_response: wsgi start response :param path: the static file and path to the file. :return: wsgi response for the static file. """ try: text = open(path).read() if path.endswith(".ico"): resp = Response(text, headers=[('Content-Type', "image/x-icon")]) elif path.endswith(".html"): resp = Response(text, headers=[('Content-Type', 'text/html')]) elif path.endswith(".txt"): resp = Response(text, headers=[('Content-Type', 'text/plain')]) elif path.endswith(".css"): resp = Response(text, headers=[('Content-Type', 'text/css')]) else: resp = Response(text, headers=[('Content-Type', 'text/xml')]) except IOError: resp = NotFound() return resp(environ, start_response)