def discover(self, *arg, **kwargs): wf = WebFinger(OIC_ISSUER) wf.httpd = PBase() _url = wf.query(kwargs["principal"]) self.trace.request("URL: %s" % _url) url = wf.discovery_query(kwargs["principal"]) return url
def dynamic_client(self, issuer="", userid=""): client = self.client_cls( client_authn_method=CLIENT_AUTHN_METHOD, verify_ssl=self.verify_ssl, **self.jwks_info ) if userid: try: issuer = client.wf.discovery_query(userid) except AttributeError: wf = WebFinger(httpd=client) issuer = wf.discovery_query(userid) if not issuer: raise OAuth2Error("Missing issuer") logger.info("issuer: {}".format(issuer)) if issuer in self.client: return self.client[issuer] else: # Gather OP information _pcr = client.provider_config(issuer) logger.info("Provider info: {}".format(sanitize(_pcr.to_dict()))) issuer = _pcr["issuer"] # So no hickup later about trailing '/' # register the client _cinfo = self.config.CLIENTS[""]["client_info"] reg_args = copy.copy(_cinfo) h = hashlib.sha256(self.seed) h.update(issuer.encode("utf8")) # issuer has to be bytes base_urls = _cinfo["redirect_uris"] reg_args["redirect_uris"] = [ u.format(base=self.base_url, iss=h.hexdigest()) for u in base_urls ] try: reg_args["post_logout_redirect_uris"] = [ u.format(base=self.base_url, iss=h.hexdigest()) for u in reg_args["post_logout_redirect_uris"] ] except KeyError: pass self.get_path(reg_args["redirect_uris"], issuer) if client.jwks_uri: reg_args["jwks_uri"] = client.jwks_uri rr = client.register(_pcr["registration_endpoint"], **reg_args) msg = "Registration response: {}" logger.info(msg.format(sanitize(rr.to_dict()))) try: client.behaviour.update(**self.config.CLIENTS[""]["behaviour"]) except KeyError: pass self.client[issuer] = client return client
def dynamic_client(self, issuer='', userid=''): client = self.client_cls(client_authn_method=CLIENT_AUTHN_METHOD, verify_ssl=self.verify_ssl, **self.jwks_info) if userid: try: issuer = client.wf.discovery_query(userid) except AttributeError: wf = WebFinger(httpd=client) issuer = wf.discovery_query(userid) if not issuer: raise OAuth2Error('Missing issuer') logger.info('issuer: {}'.format(issuer)) if issuer in self.client: return self.client[issuer] else: # Gather OP information _pcr = client.provider_config(issuer) logger.info('Provider info: {}'.format(sanitize(_pcr.to_dict()))) issuer = _pcr['issuer'] # So no hickup later about trailing '/' # register the client _cinfo = self.config.CLIENTS[""]["client_info"] reg_args = copy.copy(_cinfo) h = hashlib.sha256(self.seed) h.update(issuer.encode('utf8')) # issuer has to be bytes base_urls = _cinfo["redirect_uris"] reg_args['redirect_uris'] = [ u.format(base=self.base_url, iss=h.hexdigest()) for u in base_urls] try: reg_args['post_logout_redirect_uris'] = [ u.format(base=self.base_url, iss=h.hexdigest()) for u in reg_args['post_logout_redirect_uris'] ] except KeyError: pass self.get_path(reg_args['redirect_uris'], issuer) if client.jwks_uri: reg_args['jwks_uri'] = client.jwks_uri rr = client.register(_pcr["registration_endpoint"], **reg_args) msg = 'Registration response: {}' logger.info(msg.format(sanitize(rr.to_dict()))) try: client.behaviour.update(**self.config.CLIENTS[""]["behaviour"]) except KeyError: pass self.client[issuer] = client return client
def find_srv_discovery_url(self, resource): """ Use Webfinger to find the OP, The input is a unique identifier of the user. Allowed forms are the acct, mail, http and https urls. If no protocol specification is given like if only an email like identifier is given. It will be translated if possible to one of the allowed formats. :param resource: unique identifier of the user. :return: """ wf = WebFinger(httpd=PBase(ca_certs=self.extra["ca_bundle"])) return wf.discovery_query(resource)
import sys from oic.oauth2 import PBase from oic.utils.webfinger import OIC_ISSUER from oic.utils.webfinger import WebFinger __author__ = 'roland' wf = WebFinger(OIC_ISSUER) wf.httpd = PBase() print (wf.discovery_query(sys.argv[1]))
class Client(oauth2.Client): _endpoints = ENDPOINTS def __init__(self, client_id=None, ca_certs=None, client_prefs=None, client_authn_method=None, keyjar=None, verify_ssl=True): oauth2.Client.__init__(self, client_id, ca_certs, client_authn_method=client_authn_method, keyjar=keyjar, verify_ssl=verify_ssl) self.file_store = "./file/" self.file_uri = "http://localhost/" # OpenID connect specific endpoints for endpoint in ENDPOINTS: setattr(self, endpoint, "") self.id_token = None self.log = None self.request2endpoint = REQUEST2ENDPOINT self.response2error = RESPONSE2ERROR self.grant_class = Grant self.token_class = Token self.provider_info = None self.registration_response = None self.client_prefs = client_prefs or {} self.behaviour = { "request_object_signing_alg": DEF_SIGN_ALG["openid_request_object"] } self.wf = WebFinger(OIC_ISSUER) self.wf.httpd = self self.allow = {} self.post_logout_redirect_uris = [] self.registration_expires = 0 self.registration_access_token = None # Default key by kid for different key types # For instance {"RSA":"abc"} self.kid = {"sig": {}, "enc": {}} def _get_id_token(self, **kwargs): try: return kwargs["id_token"] except KeyError: grant = self.get_grant(**kwargs) if grant: try: _scope = kwargs["scope"] except KeyError: _scope = None for token in grant.tokens: if token.scope and _scope: flag = True for item in _scope: try: assert item in token.scope except AssertionError: flag = False break if not flag: break if token.id_token: return token.id_token return None def construct_AuthorizationRequest(self, request=AuthorizationRequest, request_args=None, extra_args=None, request_param=None, **kwargs): if request_args is not None: # if "claims" in request_args: # kwargs["claims"] = request_args["claims"] # del request_args["claims"] if "nonce" not in request_args: _rt = request_args["response_type"] if "token" in _rt or "id_token" in _rt: request_args["nonce"] = rndstr(12) elif "response_type" in kwargs: if "token" in kwargs["response_type"]: request_args = {"nonce": rndstr(12)} else: # Never wrong to specify a nonce request_args = {"nonce": rndstr(12)} if "request_method" in kwargs: if kwargs["request_method"] == "file": request_param = "request_uri" del kwargs["request_method"] areq = oauth2.Client.construct_AuthorizationRequest( self, request, request_args, extra_args, **kwargs) if request_param: alg = self.behaviour["request_object_signing_alg"] if "algorithm" not in kwargs: kwargs["algorithm"] = alg if "keys" not in kwargs and alg: _kty = alg2keytype(alg) try: kwargs["keys"] = self.keyjar.get_signing_key( _kty, kid=self.kid["sig"][_kty]) except KeyError: kwargs["keys"] = self.keyjar.get_signing_key(_kty) _req = make_openid_request(areq, **kwargs) if request_param == "request": areq["request"] = _req else: _filedir = kwargs["local_dir"] _webpath = kwargs["base_path"] _name = rndstr(10) filename = os.path.join(_filedir, _name) while os.path.exists(filename): _name = rndstr(10) filename = os.path.join(_filedir, _name) fid = open(filename, mode="w") fid.write(_req) fid.close() _webname = "%s%s" % (_webpath, _name) areq["request_uri"] = _webname return areq #noinspection PyUnusedLocal def construct_AccessTokenRequest(self, request=AccessTokenRequest, request_args=None, extra_args=None, **kwargs): return oauth2.Client.construct_AccessTokenRequest( self, request, request_args, extra_args, **kwargs) def construct_RefreshAccessTokenRequest(self, request=RefreshAccessTokenRequest, request_args=None, extra_args=None, **kwargs): return oauth2.Client.construct_RefreshAccessTokenRequest( self, request, request_args, extra_args, **kwargs) def construct_UserInfoRequest(self, request=UserInfoRequest, request_args=None, extra_args=None, **kwargs): if request_args is None: request_args = {} if "access_token" in request_args: pass else: if "scope" not in kwargs: kwargs["scope"] = "openid" token = self.get_token(**kwargs) if token is None: raise PyoidcError("No valid token available") request_args["access_token"] = token.access_token return self.construct_request(request, request_args, extra_args) #noinspection PyUnusedLocal def construct_RegistrationRequest(self, request=RegistrationRequest, request_args=None, extra_args=None, **kwargs): return self.construct_request(request, request_args, extra_args) #noinspection PyUnusedLocal def construct_RefreshSessionRequest(self, request=RefreshSessionRequest, request_args=None, extra_args=None, **kwargs): return self.construct_request(request, request_args, extra_args) def _id_token_based(self, request, request_args=None, extra_args=None, **kwargs): if request_args is None: request_args = {} try: _prop = kwargs["prop"] except KeyError: _prop = "id_token" if _prop in request_args: pass else: id_token = self._get_id_token(**kwargs) if id_token is None: raise PyoidcError("No valid id token available") request_args[_prop] = id_token return self.construct_request(request, request_args, extra_args) def construct_CheckSessionRequest(self, request=CheckSessionRequest, request_args=None, extra_args=None, **kwargs): return self._id_token_based(request, request_args, extra_args, **kwargs) def construct_CheckIDRequest(self, request=CheckIDRequest, request_args=None, extra_args=None, **kwargs): # access_token is where the id_token will be placed return self._id_token_based(request, request_args, extra_args, prop="access_token", **kwargs) def construct_EndSessionRequest(self, request=EndSessionRequest, request_args=None, extra_args=None, **kwargs): if request_args is None: request_args = {} if "state" in kwargs: request_args["state"] = kwargs["state"] elif "state" in request_args: kwargs["state"] = request_args["state"] # if "redirect_url" not in request_args: # request_args["redirect_url"] = self.redirect_url return self._id_token_based(request, request_args, extra_args, **kwargs) # ------------------------------------------------------------------------ def authorization_request_info(self, request_args=None, extra_args=None, **kwargs): return self.request_info(AuthorizationRequest, "GET", request_args, extra_args, **kwargs) # ------------------------------------------------------------------------ def do_authorization_request(self, request=AuthorizationRequest, state="", body_type="", method="GET", request_args=None, extra_args=None, http_args=None, response_cls=AuthorizationResponse): return oauth2.Client.do_authorization_request(self, request, state, body_type, method, request_args, extra_args, http_args, response_cls) def do_access_token_request(self, request=AccessTokenRequest, scope="", state="", body_type="json", method="POST", request_args=None, extra_args=None, http_args=None, response_cls=AccessTokenResponse, authn_method="", **kwargs): return oauth2.Client.do_access_token_request(self, request, scope, state, body_type, method, request_args, extra_args, http_args, response_cls, authn_method, **kwargs) def do_access_token_refresh(self, request=RefreshAccessTokenRequest, state="", body_type="json", method="POST", request_args=None, extra_args=None, http_args=None, response_cls=AccessTokenResponse, **kwargs): return oauth2.Client.do_access_token_refresh(self, request, state, body_type, method, request_args, extra_args, http_args, response_cls, **kwargs) def do_registration_request(self, request=RegistrationRequest, scope="", state="", body_type="json", method="POST", request_args=None, extra_args=None, http_args=None, response_cls=None): url, body, ht_args, csi = self.request_info(request, method=method, request_args=request_args, extra_args=extra_args, scope=scope, state=state) if http_args is None: http_args = ht_args else: http_args.update(http_args) if response_cls is None: response_cls = RegistrationResponse response = self.request_and_return(url, response_cls, method, body, body_type, state=state, http_args=http_args) return response def do_check_session_request(self, request=CheckSessionRequest, scope="", state="", body_type="json", method="GET", request_args=None, extra_args=None, http_args=None, response_cls=IdToken): url, body, ht_args, csi = self.request_info(request, method=method, request_args=request_args, extra_args=extra_args, scope=scope, state=state) if http_args is None: http_args = ht_args else: http_args.update(http_args) return self.request_and_return(url, response_cls, method, body, body_type, state=state, http_args=http_args) def do_check_id_request(self, request=CheckIDRequest, scope="", state="", body_type="json", method="GET", request_args=None, extra_args=None, http_args=None, response_cls=IdToken): url, body, ht_args, csi = self.request_info(request, method=method, request_args=request_args, extra_args=extra_args, scope=scope, state=state) if http_args is None: http_args = ht_args else: http_args.update(http_args) return self.request_and_return(url, response_cls, method, body, body_type, state=state, http_args=http_args) def do_end_session_request(self, request=EndSessionRequest, scope="", state="", body_type="", method="GET", request_args=None, extra_args=None, http_args=None, response_cls=None): url, body, ht_args, csi = self.request_info(request, method=method, request_args=request_args, extra_args=extra_args, scope=scope, state=state) if http_args is None: http_args = ht_args else: http_args.update(http_args) return self.request_and_return(url, response_cls, method, body, body_type, state=state, http_args=http_args) def user_info_request(self, method="GET", state="", scope="", **kwargs): uir = UserInfoRequest() logger.debug("[user_info_request]: kwargs:%s" % (kwargs, )) if "token" in kwargs: if kwargs["token"]: uir["access_token"] = kwargs["token"] token = Token() token.token_type = "Bearer" token.access_token = kwargs["token"] kwargs["behavior"] = "use_authorization_header" else: # What to do ? Need a callback token = None elif "access_token" in kwargs and kwargs["access_token"]: uir["access_token"] = kwargs["access_token"] del kwargs["access_token"] token = None else: token = self.grant[state].get_token(scope) if token.is_valid(): uir["access_token"] = token.access_token if token.token_type == "Bearer" and method == "GET": kwargs["behavior"] = "use_authorization_header" else: # raise oauth2.OldAccessToken if self.log: self.log.info("do access token refresh") try: self.do_access_token_refresh(token=token) token = self.grant[state].get_token(scope) uir["access_token"] = token.access_token except Exception: raise uri = self._endpoint("userinfo_endpoint", **kwargs) # If access token is a bearer token it might be sent in the # authorization header # 3-ways of sending the access_token: # - POST with token in authorization header # - POST with token in message body # - GET with token in authorization header if "behavior" in kwargs: _behav = kwargs["behavior"] _token = uir["access_token"] try: _ttype = kwargs["token_type"] except KeyError: try: _ttype = token.token_type except AttributeError: raise MissingParameter("Unspecified token type") # use_authorization_header, token_in_message_body if "use_authorization_header" in _behav and _ttype == "Bearer": bh = "Bearer %s" % _token if "headers" in kwargs: kwargs["headers"].update({"Authorization": bh}) else: kwargs["headers"] = {"Authorization": bh} if not "token_in_message_body" in _behav: # remove the token from the request del uir["access_token"] path, body, kwargs = self.get_or_post(uri, method, uir, **kwargs) h_args = dict([(k, v) for k, v in kwargs.items() if k in HTTP_ARGS]) return path, body, method, h_args def do_user_info_request(self, method="POST", state="", scope="openid", request="openid", **kwargs): kwargs["request"] = request path, body, method, h_args = self.user_info_request( method, state, scope, **kwargs) logger.debug("[do_user_info_request] PATH:%s BODY:%s H_ARGS: %s" % (path, body, h_args)) try: resp = self.http_request(path, method, data=body, **h_args) except oauth2.MissingRequiredAttribute: raise if resp.status_code == 200: try: assert "application/json" in resp.headers["content-type"] sformat = "json" except AssertionError: assert "application/jwt" in resp.headers["content-type"] sformat = "jwt" elif resp.status_code == 500: raise PyoidcError("ERROR: Something went wrong: %s" % resp.text) else: raise PyoidcError("ERROR: Something went wrong [%s]: %s" % (resp.status_code, resp.text)) try: _schema = kwargs["user_info_schema"] except KeyError: _schema = OpenIDSchema logger.debug("Reponse text: '%s'" % resp.text) if sformat == "json": return _schema().from_json(txt=resp.text) else: algo = self.client_prefs["userinfo_signed_response_alg"] _kty = alg2keytype(algo) # Keys of the OP ? try: keys = self.keyjar.get_signing_key(_kty, self.kid["sig"][_kty]) except KeyError: keys = self.keyjar.get_signing_key(_kty) return _schema().from_jwt(resp.text, keys) def get_userinfo_claims(self, access_token, endpoint, method="POST", schema_class=OpenIDSchema, **kwargs): uir = UserInfoRequest(access_token=access_token) h_args = dict([(k, v) for k, v in kwargs.items() if k in HTTP_ARGS]) if "authn_method" in kwargs: http_args = self.init_authentication_method(**kwargs) else: # If nothing defined this is the default http_args = self.init_authentication_method( uir, "bearer_header", **kwargs) h_args.update(http_args) path, body, kwargs = self.get_or_post(endpoint, method, uir, **kwargs) try: resp = self.http_request(path, method, data=body, **h_args) except oauth2.MissingRequiredAttribute: raise if resp.status_code == 200: assert "application/json" in resp.headers["content-type"] elif resp.status_code == 500: raise PyoidcError("ERROR: Something went wrong: %s" % resp.text) else: raise PyoidcError("ERROR: Something went wrong [%s]" % resp.status_code) return schema_class().from_json(txt=resp.text) def handle_provider_config(self, pcr, issuer, keys=True, endpoints=True): """ Deal with Provider Config Response :param pcr: The ProviderConfigResponse instance :param issuer: The one I thought should be the issuer of the config :param keys: Should I deal with keys :param endpoints: Should I deal with endpoints, that is store them as attributes in self. """ if "issuer" in pcr: _pcr_issuer = pcr["issuer"] if pcr["issuer"].endswith("/"): if issuer.endswith("/"): _issuer = issuer else: _issuer = issuer + "/" else: if issuer.endswith("/"): _issuer = issuer[:-1] else: _issuer = issuer try: _ = self.allow["issuer_mismatch"] except KeyError: try: assert _issuer == _pcr_issuer except AssertionError: raise IssuerMismatch( "'%s' != '%s'" % (_issuer, _pcr_issuer), pcr) self.provider_info = pcr else: _pcr_issuer = issuer if endpoints: for key, val in pcr.items(): if key.endswith("_endpoint"): setattr(self, key, val) if keys: if self.keyjar is None: self.keyjar = KeyJar(verify_ssl=self.verify_ssl) self.keyjar.load_keys(pcr, _pcr_issuer) def provider_config(self, issuer, keys=True, endpoints=True, response_cls=ProviderConfigurationResponse, serv_pattern=OIDCONF_PATTERN): if issuer.endswith("/"): _issuer = issuer[:-1] else: _issuer = issuer url = serv_pattern % _issuer pcr = None r = self.http_request(url) if r.status_code == 200: pcr = response_cls().from_json(r.text) elif r.status_code == 302: while r.status_code == 302: r = self.http_request(r.headers["location"]) if r.status_code == 200: pcr = response_cls().from_json(r.text) break #logger.debug("Provider info: %s" % pcr) if pcr is None: raise PyoidcError("Trying '%s', status %s" % (url, r.status_code)) self.handle_provider_config(pcr, issuer, keys, endpoints) return pcr def unpack_aggregated_claims(self, userinfo): if userinfo._claim_sources: for csrc, spec in userinfo._claim_sources.items(): if "JWT" in spec: if not csrc in self.keyjar: self.provider_config(csrc, endpoints=False) keycol = self.keyjar.get_verify_key(owner=csrc) for typ, keyl in self.keyjar.get_verify_key().items(): try: keycol[typ].extend(keyl) except KeyError: keycol[typ] = keyl info = json.loads(JWS().verify(str(spec["JWT"]), keycol)) attr = [ n for n, s in userinfo._claim_names.items() if s == csrc ] assert attr == info.keys() for key, vals in info.items(): userinfo[key] = vals return userinfo def fetch_distributed_claims(self, userinfo, callback=None): for csrc, spec in userinfo._claim_sources.items(): if "endpoint" in spec: #pcr = self.provider_config(csrc, keys=False, endpoints=False) if "access_token" in spec: _uinfo = self.do_user_info_request( token=spec["access_token"], userinfo_endpoint=spec["endpoint"]) else: _uinfo = self.do_user_info_request( token=callback(csrc), userinfo_endpoint=spec["endpoint"]) attr = [ n for n, s in userinfo._claim_names.items() if s == csrc ] assert attr == _uinfo.keys() for key, vals in _uinfo.items(): userinfo[key] = vals return userinfo def verify_alg_support(self, alg, usage, other): """ Verifies that the algorithm to be used are supported by the other side. :param alg: The algorithm specification :param usage: In which context the 'alg' will be used. The following values are supported: - userinfo - id_token - request_object - token_endpoint_auth :param other: The identifier for the other side :return: True or False """ try: _pcr = self.provider_info supported = _pcr["%s_algs_supported" % usage] except KeyError: try: supported = getattr(self, "%s_algs_supported" % usage) except AttributeError: supported = None if supported is None: return True else: if alg in supported: return True else: return False def match_preferences(self, pcr=None, issuer=None): """ Match the clients preferences against what the provider can do. :param pcr: Provider configuration response if available :param issuer: The issuer identifier """ if not pcr: pcr = self.provider_info regreq = RegistrationRequest for _pref, _prov in PREFERENCE2PROVIDER.items(): try: vals = self.client_prefs[_pref] except KeyError: continue try: _pvals = pcr[_prov] except KeyError: try: self.behaviour[_pref] = PROVIDER_DEFAULT[_pref] except KeyError: #self.behaviour[_pref]= vals[0] if isinstance(pcr.c_param[_prov][0], list): self.behaviour[_pref] = [] else: self.behaviour[_pref] = None continue if isinstance(vals, basestring): if vals in _pvals: self.behaviour[_pref] = vals else: vtyp = regreq.c_param[_pref] if isinstance(vtyp[0], list): _list = True else: _list = False for val in vals: if val in _pvals: if not _list: self.behaviour[_pref] = val break else: try: self.behaviour[_pref].append(val) except KeyError: self.behaviour[_pref] = [val] if _pref not in self.behaviour: raise ConfigurationError( "OP couldn't match preference:%s" % _pref, pcr) for key, val in self.client_prefs.items(): if key in self.behaviour: continue try: vtyp = regreq.c_param[key] if isinstance(vtyp[0], list): pass elif isinstance(val, list) and not isinstance(val, basestring): val = val[0] except KeyError: pass if key not in PREFERENCE2PROVIDER: self.behaviour[key] = val def store_registration_info(self, reginfo): self.registration_response = reginfo if "token_endpoint_auth_method" not in self.registration_response: self.registration_response[ "token_endpoint_auth_method"] = "client_secret_post" self.client_secret = reginfo["client_secret"] self.client_id = reginfo["client_id"] try: self.registration_expires = reginfo["client_secret_expires_at"] except KeyError: pass try: self.registration_access_token = reginfo[ "registration_access_token"] except KeyError: pass def handle_registration_info(self, response): if response.status_code == 200: resp = RegistrationResponse().deserialize(response.text, "json") self.store_registration_info(resp) else: err = ErrorResponse().deserialize(response.text, "json") raise PyoidcError("Registration failed: %s" % err.get_json()) return resp def registration_read(self, url="", registration_access_token=None): if not url: url = self.registration_response["registration_client_uri"] if not registration_access_token: registration_access_token = self.registration_access_token headers = [("Authorization", "Bearer %s" % registration_access_token)] rsp = self.http_request(url, "GET", headers=headers) return self.handle_registration_info(rsp) def create_registration_request(self, **kwargs): """ Create a registration request :param kwargs: parameters to the registration request :return: """ req = RegistrationRequest() for prop in req.parameters(): try: req[prop] = kwargs[prop] except KeyError: try: req[prop] = self.behaviour[prop] except KeyError: pass if "post_logout_redirect_uris" not in req: try: req["post_logout_redirect_uris"] = self.post_logout_redirect_uris except AttributeError: pass if "redirect_uris" not in req: try: req["redirect_uris"] = self.redirect_uris except AttributeError: raise MissingRequiredAttribute("redirect_uris", req) return req def register(self, url, **kwargs): """ Register the client at an OP :param url: The OPs registration endpoint :param kwargs: parameters to the registration request :return: """ req = self.create_registration_request(**kwargs) headers = {"content-type": "application/json"} rsp = self.http_request(url, "POST", data=req.to_json(), headers=headers) return self.handle_registration_info(rsp) def normalization(self, principal, idtype="mail"): if idtype == "mail": (local, domain) = principal.split("@") subject = "acct:%s" % principal elif idtype == "url": p = urlparse.urlparse(principal) domain = p.netloc subject = principal else: domain = "" subject = principal return subject, domain def discover(self, principal): #subject, host = self.normalization(principal) return self.wf.discovery_query(principal)
#!/usr/bin/env python from oic.oauth2 import PBase from oic.oic import Client from oic.utils.webfinger import WebFinger __author__ = 'roland' import argparse parser = argparse.ArgumentParser() parser.add_argument('-w', dest='webfinger') parser.add_argument('-p', dest='providerinfo') cargs = parser.parse_args() issuer = "" if cargs.webfinger: _httpd = PBase(verify_ssl=False) w = WebFinger(httpd=_httpd) issuer = w.discovery_query(cargs.webfinger) print issuer if cargs.providerinfo: cli = Client(verify_ssl=False) if cargs.providerinfo != "-": issuer = cargs.providerinfo cli.provider_config(issuer) print cli.provider_info
#!/usr/bin/env python import json import requests from oic.oauth2 import PBase from oic.oic import OIDCONF_PATTERN from oic.utils.webfinger import WebFinger # This is a complete discovery example with OpenID Connect. After having retrieved the provide URL, its information # is retrieved and printed. The standard URL format for obtaining OP information is: # https://<op.servername>/.well-known/openid-configuration userid = "[email protected]:8060" wf = WebFinger() wf.httpd = PBase(verify_ssl=False) url = wf.discovery_query("acct:%s" % userid) print "Provider:", url if url[-1] == '/': url = url[:-1] url = OIDCONF_PATTERN % url print "Provider info url:", url r = requests.request("GET", url, verify=False) jwt = json.loads(r.text) print "---- provider configuration info ----" print json.dumps(jwt, sort_keys=True, indent=4, separators=(',', ': '))
class Client(oauth2.Client): _endpoints = ENDPOINTS def __init__( self, client_id=None, ca_certs=None, client_prefs=None, client_authn_method=None, keyjar=None, verify_ssl=True ): oauth2.Client.__init__( self, client_id, ca_certs, client_authn_method=client_authn_method, keyjar=keyjar, verify_ssl=verify_ssl ) self.file_store = "./file/" self.file_uri = "http://localhost/" # OpenID connect specific endpoints for endpoint in ENDPOINTS: setattr(self, endpoint, "") self.id_token = None self.log = None self.request2endpoint = REQUEST2ENDPOINT self.response2error = RESPONSE2ERROR self.grant_class = Grant self.token_class = Token self.provider_info = None self.registration_response = None self.client_prefs = client_prefs or {} self.behaviour = {} self.wf = WebFinger(OIC_ISSUER) self.wf.httpd = self self.allow = {} self.post_logout_redirect_uris = [] self.registration_expires = 0 self.registration_access_token = None self.id_token_max_age = 0 # Default key by kid for different key types # For instance {"RSA":"abc"} self.kid = {"sig": {}, "enc": {}} def _get_id_token(self, **kwargs): try: return kwargs["id_token"] except KeyError: grant = self.get_grant(**kwargs) if grant: try: _scope = kwargs["scope"] except KeyError: _scope = None for token in grant.tokens: if token.scope and _scope: flag = True for item in _scope: try: assert item in token.scope except AssertionError: flag = False break if not flag: break if token.id_token: return token.id_token return None def request_object_encryption(self, msg, **kwargs): try: encalg = kwargs["request_object_encryption_alg"] except KeyError: try: encalg = self.behaviour["request_object_encryption_alg"] except KeyError: return msg try: encenc = kwargs["request_object_encryption_enc"] except KeyError: try: encenc = self.behaviour["request_object_encryption_enc"] except KeyError: raise MissingRequiredAttribute("No request_object_encryption_enc specified") _jwe = JWE(msg, alg=encalg, enc=encenc) _kty = jwe.alg2keytype(encalg) try: _kid = kwargs["enc_kid"] except KeyError: _kid = "" if "target" not in kwargs: raise MissingRequiredAttribute("No target specified") if _kid: _keys = self.keyjar.get_encrypt_key(_kty, owner=kwargs["target"], kid=_kid) _jwe["kid"] = _kid else: _keys = self.keyjar.get_encrypt_key(_kty, owner=kwargs["target"]) return _jwe.encrypt(_keys) def construct_AuthorizationRequest( self, request=AuthorizationRequest, request_args=None, extra_args=None, request_param=None, **kwargs ): if request_args is not None: # if "claims" in request_args: # kwargs["claims"] = request_args["claims"] # del request_args["claims"] if "nonce" not in request_args: _rt = request_args["response_type"] if "token" in _rt or "id_token" in _rt: request_args["nonce"] = rndstr(12) elif "response_type" in kwargs: if "token" in kwargs["response_type"]: request_args = {"nonce": rndstr(12)} else: # Never wrong to specify a nonce request_args = {"nonce": rndstr(12)} if "request_method" in kwargs: if kwargs["request_method"] == "file": request_param = "request_uri" else: request_param = "request" del kwargs["request_method"] areq = oauth2.Client.construct_AuthorizationRequest(self, request, request_args, extra_args, **kwargs) if request_param: alg = None for arg in ["request_object_signing_alg", "algorithm"]: try: # Trumps everything alg = kwargs[arg] except KeyError: pass else: break if not alg: try: alg = self.behaviour["request_object_signing_alg"] except KeyError: alg = "none" kwargs["request_object_signing_alg"] = alg if "keys" not in kwargs and alg and alg != "none": _kty = jws.alg2keytype(alg) try: _kid = kwargs["sig_kid"] except KeyError: _kid = self.kid["sig"].get(_kty, None) kwargs["keys"] = self.keyjar.get_signing_key(_kty, kid=_kid) _req = make_openid_request(areq, **kwargs) # Should the request be encrypted _req = self.request_object_encryption(_req, **kwargs) if request_param == "request": areq["request"] = _req else: _filedir = kwargs["local_dir"] if not os.path.isdir(_filedir): os.makedirs(_filedir) _webpath = kwargs["base_path"] _name = rndstr(10) + ".jwt" filename = os.path.join(_filedir, _name) while os.path.exists(filename): _name = rndstr(10) filename = os.path.join(_filedir, _name) fid = open(filename, mode="w") fid.write(_req) fid.close() _webname = "%s%s" % (_webpath, _name) areq["request_uri"] = _webname return areq # noinspection PyUnusedLocal def construct_AccessTokenRequest(self, request=AccessTokenRequest, request_args=None, extra_args=None, **kwargs): return oauth2.Client.construct_AccessTokenRequest(self, request, request_args, extra_args, **kwargs) def construct_RefreshAccessTokenRequest( self, request=RefreshAccessTokenRequest, request_args=None, extra_args=None, **kwargs ): return oauth2.Client.construct_RefreshAccessTokenRequest(self, request, request_args, extra_args, **kwargs) def construct_UserInfoRequest(self, request=UserInfoRequest, request_args=None, extra_args=None, **kwargs): if request_args is None: request_args = {} if "access_token" in request_args: pass else: if "scope" not in kwargs: kwargs["scope"] = "openid" token = self.get_token(**kwargs) if token is None: raise PyoidcError("No valid token available") request_args["access_token"] = token.access_token return self.construct_request(request, request_args, extra_args) # noinspection PyUnusedLocal def construct_RegistrationRequest(self, request=RegistrationRequest, request_args=None, extra_args=None, **kwargs): return self.construct_request(request, request_args, extra_args) # noinspection PyUnusedLocal def construct_RefreshSessionRequest( self, request=RefreshSessionRequest, request_args=None, extra_args=None, **kwargs ): return self.construct_request(request, request_args, extra_args) def _id_token_based(self, request, request_args=None, extra_args=None, **kwargs): if request_args is None: request_args = {} try: _prop = kwargs["prop"] except KeyError: _prop = "id_token" if _prop in request_args: pass else: id_token = self._get_id_token(**kwargs) if id_token is None: raise PyoidcError("No valid id token available") request_args[_prop] = id_token return self.construct_request(request, request_args, extra_args) def construct_CheckSessionRequest(self, request=CheckSessionRequest, request_args=None, extra_args=None, **kwargs): return self._id_token_based(request, request_args, extra_args, **kwargs) def construct_CheckIDRequest(self, request=CheckIDRequest, request_args=None, extra_args=None, **kwargs): # access_token is where the id_token will be placed return self._id_token_based(request, request_args, extra_args, prop="access_token", **kwargs) def construct_EndSessionRequest(self, request=EndSessionRequest, request_args=None, extra_args=None, **kwargs): if request_args is None: request_args = {} if "state" in kwargs: request_args["state"] = kwargs["state"] elif "state" in request_args: kwargs["state"] = request_args["state"] # if "redirect_url" not in request_args: # request_args["redirect_url"] = self.redirect_url return self._id_token_based(request, request_args, extra_args, **kwargs) # ------------------------------------------------------------------------ def authorization_request_info(self, request_args=None, extra_args=None, **kwargs): return self.request_info(AuthorizationRequest, "GET", request_args, extra_args, **kwargs) # ------------------------------------------------------------------------ def do_authorization_request( self, request=AuthorizationRequest, state="", body_type="", method="GET", request_args=None, extra_args=None, http_args=None, response_cls=AuthorizationResponse, ): algs = self.sign_enc_algs("id_token") return oauth2.Client.do_authorization_request( self, request, state, body_type, method, request_args, extra_args, http_args, response_cls, algs=algs ) def do_access_token_request( self, request=AccessTokenRequest, scope="", state="", body_type="json", method="POST", request_args=None, extra_args=None, http_args=None, response_cls=AccessTokenResponse, authn_method="client_secret_basic", **kwargs ): return oauth2.Client.do_access_token_request( self, request, scope, state, body_type, method, request_args, extra_args, http_args, response_cls, authn_method, **kwargs ) def do_access_token_refresh( self, request=RefreshAccessTokenRequest, state="", body_type="json", method="POST", request_args=None, extra_args=None, http_args=None, response_cls=AccessTokenResponse, **kwargs ): return oauth2.Client.do_access_token_refresh( self, request, state, body_type, method, request_args, extra_args, http_args, response_cls, **kwargs ) def do_registration_request( self, request=RegistrationRequest, scope="", state="", body_type="json", method="POST", request_args=None, extra_args=None, http_args=None, response_cls=None, ): url, body, ht_args, csi = self.request_info( request, method=method, request_args=request_args, extra_args=extra_args, scope=scope, state=state ) if http_args is None: http_args = ht_args else: http_args.update(http_args) if response_cls is None: response_cls = RegistrationResponse response = self.request_and_return(url, response_cls, method, body, body_type, state=state, http_args=http_args) return response def do_check_session_request( self, request=CheckSessionRequest, scope="", state="", body_type="json", method="GET", request_args=None, extra_args=None, http_args=None, response_cls=IdToken, ): url, body, ht_args, csi = self.request_info( request, method=method, request_args=request_args, extra_args=extra_args, scope=scope, state=state ) if http_args is None: http_args = ht_args else: http_args.update(http_args) return self.request_and_return(url, response_cls, method, body, body_type, state=state, http_args=http_args) def do_check_id_request( self, request=CheckIDRequest, scope="", state="", body_type="json", method="GET", request_args=None, extra_args=None, http_args=None, response_cls=IdToken, ): url, body, ht_args, csi = self.request_info( request, method=method, request_args=request_args, extra_args=extra_args, scope=scope, state=state ) if http_args is None: http_args = ht_args else: http_args.update(http_args) return self.request_and_return(url, response_cls, method, body, body_type, state=state, http_args=http_args) def do_end_session_request( self, request=EndSessionRequest, scope="", state="", body_type="", method="GET", request_args=None, extra_args=None, http_args=None, response_cls=None, ): url, body, ht_args, csi = self.request_info( request, method=method, request_args=request_args, extra_args=extra_args, scope=scope, state=state ) if http_args is None: http_args = ht_args else: http_args.update(http_args) return self.request_and_return(url, response_cls, method, body, body_type, state=state, http_args=http_args) def user_info_request(self, method="GET", state="", scope="", **kwargs): uir = UserInfoRequest() logger.debug("[user_info_request]: kwargs:%s" % (kwargs,)) if "token" in kwargs: if kwargs["token"]: uir["access_token"] = kwargs["token"] token = Token() token.token_type = "Bearer" token.access_token = kwargs["token"] kwargs["behavior"] = "use_authorization_header" else: # What to do ? Need a callback token = None elif "access_token" in kwargs and kwargs["access_token"]: uir["access_token"] = kwargs["access_token"] del kwargs["access_token"] token = None else: token = self.grant[state].get_token(scope) if token.is_valid(): uir["access_token"] = token.access_token if token.token_type == "Bearer" and method == "GET": kwargs["behavior"] = "use_authorization_header" else: # raise oauth2.OldAccessToken if self.log: self.log.info("do access token refresh") try: self.do_access_token_refresh(token=token) token = self.grant[state].get_token(scope) uir["access_token"] = token.access_token except Exception: raise uri = self._endpoint("userinfo_endpoint", **kwargs) # If access token is a bearer token it might be sent in the # authorization header # 3-ways of sending the access_token: # - POST with token in authorization header # - POST with token in message body # - GET with token in authorization header if "behavior" in kwargs: _behav = kwargs["behavior"] _token = uir["access_token"] try: _ttype = kwargs["token_type"] except KeyError: try: _ttype = token.token_type except AttributeError: raise MissingParameter("Unspecified token type") # use_authorization_header, token_in_message_body if "use_authorization_header" in _behav and _ttype == "Bearer": bh = "Bearer %s" % _token if "headers" in kwargs: kwargs["headers"].update({"Authorization": bh}) else: kwargs["headers"] = {"Authorization": bh} if "token_in_message_body" not in _behav: # remove the token from the request del uir["access_token"] path, body, kwargs = get_or_post(uri, method, uir, **kwargs) h_args = dict([(k, v) for k, v in kwargs.items() if k in HTTP_ARGS]) return path, body, method, h_args def do_user_info_request(self, method="POST", state="", scope="openid", request="openid", **kwargs): kwargs["request"] = request path, body, method, h_args = self.user_info_request(method, state, scope, **kwargs) logger.debug("[do_user_info_request] PATH:%s BODY:%s H_ARGS: %s" % (path, body, h_args)) try: resp = self.http_request(path, method, data=body, **h_args) except oauth2.MissingRequiredAttribute: raise if resp.status_code == 200: try: assert "application/json" in resp.headers["content-type"] sformat = "json" except AssertionError: assert "application/jwt" in resp.headers["content-type"] sformat = "jwt" elif resp.status_code == 500: raise PyoidcError("ERROR: Something went wrong: %s" % resp.text) else: raise PyoidcError("ERROR: Something went wrong [%s]: %s" % (resp.status_code, resp.text)) try: _schema = kwargs["user_info_schema"] except KeyError: _schema = OpenIDSchema logger.debug("Reponse text: '%s'" % resp.text) _txt = resp.text if sformat == "json": res = _schema().from_json(txt=_txt) else: res = _schema().from_jwt(_txt, keyjar=self.keyjar, sender=self.provider_info["issuer"]) self.store_response(res, _txt) return res def get_userinfo_claims(self, access_token, endpoint, method="POST", schema_class=OpenIDSchema, **kwargs): uir = UserInfoRequest(access_token=access_token) h_args = dict([(k, v) for k, v in kwargs.items() if k in HTTP_ARGS]) if "authn_method" in kwargs: http_args = self.init_authentication_method(**kwargs) else: # If nothing defined this is the default http_args = self.init_authentication_method(uir, "bearer_header", **kwargs) h_args.update(http_args) path, body, kwargs = get_or_post(endpoint, method, uir, **kwargs) try: resp = self.http_request(path, method, data=body, **h_args) except oauth2.MissingRequiredAttribute: raise if resp.status_code == 200: assert "application/json" in resp.headers["content-type"] elif resp.status_code == 500: raise PyoidcError("ERROR: Something went wrong: %s" % resp.text) else: raise PyoidcError("ERROR: Something went wrong [%s]: %s" % (resp.status_code, resp.text)) res = schema_class().from_json(txt=resp.text) self.store_response(res, resp.txt) return res def handle_provider_config(self, pcr, issuer, keys=True, endpoints=True): """ Deal with Provider Config Response :param pcr: The ProviderConfigResponse instance :param issuer: The one I thought should be the issuer of the config :param keys: Should I deal with keys :param endpoints: Should I deal with endpoints, that is store them as attributes in self. """ if "issuer" in pcr: _pcr_issuer = pcr["issuer"] if pcr["issuer"].endswith("/"): if issuer.endswith("/"): _issuer = issuer else: _issuer = issuer + "/" else: if issuer.endswith("/"): _issuer = issuer[:-1] else: _issuer = issuer try: _ = self.allow["issuer_mismatch"] except KeyError: try: assert _issuer == _pcr_issuer except AssertionError: raise IssuerMismatch("'%s' != '%s'" % (_issuer, _pcr_issuer), pcr) self.provider_info = pcr else: _pcr_issuer = issuer if endpoints: for key, val in pcr.items(): if key.endswith("_endpoint"): setattr(self, key, val) if keys: if self.keyjar is None: self.keyjar = KeyJar(verify_ssl=self.verify_ssl) self.keyjar.load_keys(pcr, _pcr_issuer) def provider_config( self, issuer, keys=True, endpoints=True, response_cls=ProviderConfigurationResponse, serv_pattern=OIDCONF_PATTERN, ): if issuer.endswith("/"): _issuer = issuer[:-1] else: _issuer = issuer url = serv_pattern % _issuer pcr = None r = self.http_request(url) if r.status_code == 200: try: pcr = response_cls().from_json(r.text) except: logger.error("Faulty provider config response: {}".format(r.text)) elif r.status_code == 302 or r.status_code == 301: while r.status_code == 302 or r.status_code == 301: r = self.http_request(r.headers["location"]) if r.status_code == 200: pcr = response_cls().from_json(r.text) break # logger.debug("Provider info: %s" % pcr) if pcr is None: raise PyoidcError("Trying '%s', status %s" % (url, r.status_code)) self.store_response(pcr, r.text) self.handle_provider_config(pcr, issuer, keys, endpoints) return pcr def unpack_aggregated_claims(self, userinfo): if userinfo["_claim_sources"]: for csrc, spec in userinfo["_claim_sources"].items(): if "JWT" in spec: aggregated_claims = Message().from_jwt(spec["JWT"].encode("utf-8"), keyjar=self.keyjar, sender=csrc) claims = [value for value, src in userinfo["_claim_names"].items() if src == csrc] assert claims == aggregated_claims.keys() for key, vals in aggregated_claims.items(): userinfo[key] = vals return userinfo def fetch_distributed_claims(self, userinfo, callback=None): for csrc, spec in userinfo["_claim_sources"].items(): if "endpoint" in spec: if "access_token" in spec: _uinfo = self.do_user_info_request(token=spec["access_token"], userinfo_endpoint=spec["endpoint"]) else: _uinfo = self.do_user_info_request(token=callback(csrc), userinfo_endpoint=spec["endpoint"]) claims = [value for value, src in userinfo["_claim_names"].items() if src == csrc] assert claims == _uinfo.keys() for key, vals in _uinfo.items(): userinfo[key] = vals return userinfo def verify_alg_support(self, alg, usage, other): """ Verifies that the algorithm to be used are supported by the other side. :param alg: The algorithm specification :param usage: In which context the 'alg' will be used. The following values are supported: - userinfo - id_token - request_object - token_endpoint_auth :param other: The identifier for the other side :return: True or False """ try: _pcr = self.provider_info supported = _pcr["%s_algs_supported" % usage] except KeyError: try: supported = getattr(self, "%s_algs_supported" % usage) except AttributeError: supported = None if supported is None: return True else: if alg in supported: return True else: return False def match_preferences(self, pcr=None, issuer=None): """ Match the clients preferences against what the provider can do. :param pcr: Provider configuration response if available :param issuer: The issuer identifier """ if not pcr: pcr = self.provider_info regreq = RegistrationRequest for _pref, _prov in PREFERENCE2PROVIDER.items(): try: vals = self.client_prefs[_pref] except KeyError: continue try: _pvals = pcr[_prov] except KeyError: try: self.behaviour[_pref] = PROVIDER_DEFAULT[_pref] except KeyError: # self.behaviour[_pref]= vals[0] if isinstance(pcr.c_param[_prov][0], list): self.behaviour[_pref] = [] else: self.behaviour[_pref] = None continue if isinstance(vals, basestring): if vals in _pvals: self.behaviour[_pref] = vals else: vtyp = regreq.c_param[_pref] if isinstance(vtyp[0], list): self.behaviour[_pref] = [] for val in vals: if val in _pvals: self.behaviour[_pref].append(val) else: for val in vals: if val in _pvals: self.behaviour[_pref] = val break if _pref not in self.behaviour: raise ConfigurationError("OP couldn't match preference:%s" % _pref, pcr) for key, val in self.client_prefs.items(): if key in self.behaviour: continue try: vtyp = regreq.c_param[key] if isinstance(vtyp[0], list): pass elif isinstance(val, list) and not isinstance(val, basestring): val = val[0] except KeyError: pass if key not in PREFERENCE2PROVIDER: self.behaviour[key] = val def store_registration_info(self, reginfo): self.registration_response = reginfo if "token_endpoint_auth_method" not in self.registration_response: self.registration_response["token_endpoint_auth_method"] = "client_secret_post" self.client_id = reginfo["client_id"] try: self.client_secret = reginfo["client_secret"] except KeyError: # Not required pass else: try: self.registration_expires = reginfo["client_secret_expires_at"] except KeyError: pass try: self.registration_access_token = reginfo["registration_access_token"] except KeyError: pass def handle_registration_info(self, response): if response.status_code == 200: resp = RegistrationResponse().deserialize(response.text, "json") self.store_response(resp, response.text) self.store_registration_info(resp) else: err = ErrorResponse().deserialize(response.text, "json") raise PyoidcError("Registration failed: %s" % err.to_json()) return resp def registration_read(self, url="", registration_access_token=None): if not url: url = self.registration_response["registration_client_uri"] if not registration_access_token: registration_access_token = self.registration_access_token headers = [("Authorization", "Bearer %s" % registration_access_token)] rsp = self.http_request(url, "GET", headers=headers) return self.handle_registration_info(rsp) def create_registration_request(self, **kwargs): """ Create a registration request :param kwargs: parameters to the registration request :return: """ req = RegistrationRequest() for prop in req.parameters(): try: req[prop] = kwargs[prop] except KeyError: try: req[prop] = self.behaviour[prop] except KeyError: pass if "post_logout_redirect_uris" not in req: try: req["post_logout_redirect_uris"] = self.post_logout_redirect_uris except AttributeError: pass if "redirect_uris" not in req: try: req["redirect_uris"] = self.redirect_uris except AttributeError: raise MissingRequiredAttribute("redirect_uris", req) return req def register(self, url, **kwargs): """ Register the client at an OP :param url: The OPs registration endpoint :param kwargs: parameters to the registration request :return: """ req = self.create_registration_request(**kwargs) headers = {"content-type": "application/json"} rsp = self.http_request(url, "POST", data=req.to_json(), headers=headers) return self.handle_registration_info(rsp) def normalization(self, principal, idtype="mail"): if idtype == "mail": (local, domain) = principal.split("@") subject = "acct:%s" % principal elif idtype == "url": p = urlparse(principal) domain = p.netloc subject = principal else: domain = "" subject = principal return subject, domain def discover(self, principal): # subject, host = self.normalization(principal) return self.wf.discovery_query(principal) def sign_enc_algs(self, typ): resp = {} for key, val in PARAMMAP.items(): try: resp[key] = self.registration_response[val % typ] except (TypeError, KeyError): if key == "sign": resp[key] = DEF_SIGN_ALG["id_token"] return resp def _verify_id_token(self, id_token, nonce="", acr_values=None, auth_time=0, max_age=0): """ If the JWT alg Header Parameter uses a MAC based algorithm s uch as HS256, HS384, or HS512, the octets of the UTF-8 representation of the client_secret corresponding to the client_id contained in the aud (audience) Claim are used as the key to validate the signature. For MAC based algorithms, the behavior is unspecified if the aud is multi-valued or if an azp value is present that is different than the aud value. :param id_token: The ID Token tp check :param nonce: The nonce specified in the authorization request :param acr_values: Asked for acr values :param auth_time: An auth_time claim :param max_age: Max age of authentication """ try: assert self.provider_info["issuer"] == id_token["iss"] except AssertionError: raise OtherError("issuer != iss") _now = time_util.utc_time_sans_frac() try: assert _now < id_token["exp"] except AssertionError: raise OtherError("Passed best before date") if self.id_token_max_age: try: assert _now < int(id_token["iat"]) + self.id_token_max_age except AssertionError: raise OtherError("I think this ID token is to old") if nonce: try: assert nonce == id_token["nonce"] except AssertionError: raise OtherError("nonce mismatch") if acr_values: try: assert id_token["acr"] in acr_values except AssertionError: raise OtherError("acr mismatch") if max_age: try: assert _now < int(id_token["auth_time"]) + max_age except AssertionError: raise AuthnToOld("To old authentication") if auth_time: if not claims_match(id_token["auth_time"], {"auth_time": auth_time}): raise AuthnToOld("To old authentication") def verify_id_token(self, id_token, authn_req): kwa = {} try: kwa["nonce"] = authn_req["nonce"] except KeyError: pass for param in ["acr_values", "max_age"]: try: kwa[param] = authn_req[param] except KeyError: pass self._verify_id_token(id_token, **kwa)
__author__ = 'roland' # ===================================================================== # Using only very basic functions and methods # Initiate the WebFinger class wf = WebFinger() # contruct the webfinger query URL query = wf.query("acct:[email protected]", rel=OIC_ISSUER) print(query) r = requests.request("GET", query, verify=False) # parse the JSON returned by the website and dump the content to # standard out jwt = json.loads(r.text) print(json.dumps(jwt, sort_keys=True, indent=4, separators=(',', ': '))) # ===================================================================== # A bit more high level wf = WebFinger() # PBase is a wrapper around requests wf.httpd = PBase(verify_ssl=False) # discover_query will follow webfinger redirects url = wf.discovery_query("acct:[email protected]") print(url)
import sys from oic.oauth2 import PBase from oic.utils.webfinger import WebFinger, OIC_ISSUER __author__ = 'roland' wf = WebFinger(OIC_ISSUER) wf.httpd = PBase() print wf.discovery_query(sys.argv[1])
import sys from oic.oauth2 import PBase from oic.utils.webfinger import WebFinger, OIC_ISSUER __author__ = 'roland' wf = WebFinger(OIC_ISSUER) wf.httpd = PBase() print(wf.discovery_query(sys.argv[1]))
class Client(oauth2.Client): _endpoints = ENDPOINTS def __init__(self, client_id=None, ca_certs=None, grant_expire_in=600, jwt_keys=None, client_timeout=0, client_prefs=None): oauth2.Client.__init__(self, client_id, ca_certs, grant_expire_in, client_timeout=client_timeout, jwt_keys=jwt_keys) self.file_store = "./file/" self.file_uri = "http://localhost/" # OpenID connect specific endpoints for endpoint in ENDPOINTS: setattr(self, endpoint, "") self.id_token = None self.log = None self.request2endpoint = REQUEST2ENDPOINT self.response2error = RESPONSE2ERROR self.grant_class = Grant self.token_class = Token self.authn_method = AUTHN_METHOD self.provider_info = {} self.client_prefs = client_prefs or {} self.behaviour = {"require_signed_request_object": DEF_SIGN_ALG["openid_request_object"]} self.wf = WebFinger(OIC_ISSUER) self.wf.httpd = self def _get_id_token(self, **kwargs): try: return kwargs["id_token"] except KeyError: grant = self.get_grant(**kwargs) if grant: try: _scope = kwargs["scope"] except KeyError: _scope = None for token in grant.tokens: if token.scope and _scope: flag = True for item in _scope: try: assert item in token.scope except AssertionError: flag = False break if not flag: break if token.id_token: return token.id_token return None def construct_AuthorizationRequest(self, request=AuthorizationRequest, request_args=None, extra_args=None, **kwargs): if request_args is not None: for arg in ["idtoken_claims", "userinfo_claims"]: if arg in request_args: kwargs[arg] = request_args[arg] del request_args[arg] if "nonce" not in request_args: _rt = request_args["response_type"] if "token" in _rt or "id_token" in _rt: request_args["nonce"] = rndstr(12) elif "response_type" in kwargs: if "token" in kwargs["response_type"]: request_args = {"nonce": rndstr(12)} else: # Never wrong to specify a nonce request_args = {"nonce": rndstr(12)} if "idtoken_claims" in kwargs or "userinfo_claims" in kwargs: request_param = "request" else: request_param = None if "request_method" in kwargs: if kwargs["request_method"] == "file": request_param = "request_uri" del kwargs["request_method"] areq = oauth2.Client.construct_AuthorizationRequest(self, request, request_args, extra_args, **kwargs) if request_param: alg = self.behaviour["require_signed_request_object"] if "algorithm" not in kwargs: kwargs["algorithm"] = alg if "keys" not in kwargs and alg: atype = alg2keytype(alg) kwargs["keys"] = self.keyjar.get_signing_key(atype) _req = make_openid_request(areq, **kwargs) if request_param == "request": areq["request"] = _req else: _filedir = kwargs["local_dir"] _webpath = kwargs["base_path"] _name = rndstr(10) filename = os.path.join(_filedir, _name) while os.path.exists(filename): _name = rndstr(10) filename = os.path.join(_filedir, _name) fid = open(filename, mode="w") fid.write(_req) fid.close() _webname = "%s%s" % (_webpath,_name) areq["request_uri"] = _webname return areq #noinspection PyUnusedLocal def construct_AccessTokenRequest(self, request=AccessTokenRequest, request_args=None, extra_args=None, **kwargs): return oauth2.Client.construct_AccessTokenRequest(self, request, request_args, extra_args, **kwargs) def construct_RefreshAccessTokenRequest(self, request=RefreshAccessTokenRequest, request_args=None, extra_args=None, **kwargs): return oauth2.Client.construct_RefreshAccessTokenRequest(self, request, request_args, extra_args, **kwargs) def construct_UserInfoRequest(self, request=UserInfoRequest, request_args=None, extra_args=None, **kwargs): if request_args is None: request_args = {} if "access_token" in request_args: pass else: if "scope" not in kwargs: kwargs["scope"] = "openid" token = self.get_token(**kwargs) if token is None: raise Exception("No valid token available") request_args["access_token"] = token.access_token return self.construct_request(request, request_args, extra_args) #noinspection PyUnusedLocal def construct_RegistrationRequest(self, request=RegistrationRequest, request_args=None, extra_args=None, **kwargs): return self.construct_request(request, request_args, extra_args) #noinspection PyUnusedLocal def construct_RefreshSessionRequest(self, request=RefreshSessionRequest, request_args=None, extra_args=None, **kwargs): return self.construct_request(request, request_args, extra_args) def _id_token_based(self, request, request_args=None, extra_args=None, **kwargs): if request_args is None: request_args = {} try: _prop = kwargs["prop"] except KeyError: _prop = "id_token" if _prop in request_args: pass else: id_token = self._get_id_token(**kwargs) if id_token is None: raise Exception("No valid id token available") request_args[_prop] = id_token return self.construct_request(request, request_args, extra_args) def construct_CheckSessionRequest(self, request=CheckSessionRequest, request_args=None, extra_args=None, **kwargs): return self._id_token_based(request, request_args, extra_args, **kwargs) def construct_CheckIDRequest(self, request=CheckIDRequest, request_args=None, extra_args=None, **kwargs): # access_token is where the id_token will be placed return self._id_token_based(request, request_args, extra_args, prop="access_token", **kwargs) def construct_EndSessionRequest(self, request=EndSessionRequest, request_args=None, extra_args=None, **kwargs): if request_args is None: request_args = {} if "state" in kwargs: request_args["state"] = kwargs["state"] elif "state" in request_args: kwargs["state"] = request_args["state"] # if "redirect_url" not in request_args: # request_args["redirect_url"] = self.redirect_url return self._id_token_based(request, request_args, extra_args, **kwargs) # ------------------------------------------------------------------------ def authorization_request_info(self, request_args=None, extra_args=None, **kwargs): return self.request_info(AuthorizationRequest, "GET", request_args, extra_args, **kwargs) # ------------------------------------------------------------------------ def do_authorization_request(self, request=AuthorizationRequest, state="", body_type="", method="GET", request_args=None, extra_args=None, http_args=None, response_cls=AuthorizationResponse): return oauth2.Client.do_authorization_request(self, request, state, body_type, method, request_args, extra_args, http_args, response_cls) def do_access_token_request(self, request=AccessTokenRequest, scope="", state="", body_type="json", method="POST", request_args=None, extra_args=None, http_args=None, response_cls=AccessTokenResponse, authn_method="", **kwargs): return oauth2.Client.do_access_token_request(self, request, scope, state, body_type, method, request_args, extra_args, http_args, response_cls, authn_method, **kwargs) def do_access_token_refresh(self, request=RefreshAccessTokenRequest, state="", body_type="json", method="POST", request_args=None, extra_args=None, http_args=None, response_cls=AccessTokenResponse, **kwargs): return oauth2.Client.do_access_token_refresh(self, request, state, body_type, method, request_args, extra_args, http_args, response_cls, **kwargs) def do_registration_request(self, request=RegistrationRequest, scope="", state="", body_type="json", method="POST", request_args=None, extra_args=None, http_args=None, response_cls=None): url, body, ht_args, csi = self.request_info(request, method=method, request_args=request_args, extra_args=extra_args, scope=scope, state=state) if http_args is None: http_args = ht_args else: http_args.update(http_args) if response_cls is None: response_cls = RegistrationResponse response = self.request_and_return(url, response_cls, method, body, body_type, state=state, http_args=http_args) # if isinstance(response, Message): # if "token_endpoint_auth_type" not in response: # response["token_endpoint_auth_type"] = "client_secret_basic" return response def do_check_session_request(self, request=CheckSessionRequest, scope="", state="", body_type="json", method="GET", request_args=None, extra_args=None, http_args=None, response_cls=IdToken): url, body, ht_args, csi = self.request_info(request, method=method, request_args=request_args, extra_args=extra_args, scope=scope, state=state) if http_args is None: http_args = ht_args else: http_args.update(http_args) return self.request_and_return(url, response_cls, method, body, body_type, state=state, http_args=http_args) def do_check_id_request(self, request=CheckIDRequest, scope="", state="", body_type="json", method="GET", request_args=None, extra_args=None, http_args=None, response_cls=IdToken): url, body, ht_args, csi = self.request_info(request, method=method, request_args=request_args, extra_args=extra_args, scope=scope, state=state) if http_args is None: http_args = ht_args else: http_args.update(http_args) return self.request_and_return(url, response_cls, method, body, body_type, state=state, http_args=http_args) def do_end_session_request(self, request=EndSessionRequest, scope="", state="", body_type="", method="GET", request_args=None, extra_args=None, http_args=None, response_cls=None): url, body, ht_args, csi = self.request_info(request, method=method, request_args=request_args, extra_args=extra_args, scope=scope, state=state) if http_args is None: http_args = ht_args else: http_args.update(http_args) return self.request_and_return(url, response_cls, method, body, body_type, state=state, http_args=http_args) def user_info_request(self, method="GET", state="", scope="", **kwargs): uir = UserInfoRequest() logger.debug("[user_info_request]: kwargs:%s" % (kwargs,)) if "token" in kwargs: if kwargs["token"]: uir["access_token"] = kwargs["token"] token = Token() token.type = "Bearer" token.access_token = kwargs["token"] kwargs["behavior"] = "use_authorization_header" else: # What to do ? Need a callback token = None elif "access_token" in kwargs and kwargs["access_token"]: uir["access_token"] = kwargs["access_token"] del kwargs["access_token"] token = None else: token = self.grant[state].get_token(scope) if token.is_valid(): uir["access_token"] = token.access_token else: # raise oauth2.OldAccessToken if self.log: self.log.info("do access token refresh") try: self.do_access_token_refresh(token=token) token = self.grant[state].get_token(scope) uir["access_token"] = token.access_token except Exception: raise try: uir["schema"] = kwargs["schema"] except KeyError: pass uri = self._endpoint("userinfo_endpoint", **kwargs) # If access token is a bearer token it might be sent in the # authorization header # 3-ways of sending the access_token: # - POST with token in authorization header # - POST with token in message body # - GET with token in authorization header if "behavior" in kwargs: _behav = kwargs["behavior"] # use_authorization_header, token_in_message_body if "use_authorization_header" in _behav and token.type == "Bearer": bh = "Bearer %s" % token.access_token if "headers" in kwargs: kwargs["headers"]= {"Authorization": bh} else: kwargs["headers"] = {"Authorization": bh} if not "token_in_message_body" in _behav: # remove the token from the request del uir["access_token"] path, body, kwargs = self.get_or_post(uri, method, uir, **kwargs) h_args = dict([(k, v) for k,v in kwargs.items() if k in HTTP_ARGS]) return path, body, method, h_args def do_user_info_request(self, method="POST", state="", scope="openid", request="openid", **kwargs): kwargs["request"] = request path, body, method, h_args = self.user_info_request(method, state, scope, **kwargs) logger.debug("[do_user_info_request] PATH:%s BODY:%s H_ARGS: %s" % ( path, body, h_args)) try: resp = self.http_request(path, method, data=body, **h_args) except oauth2.MissingRequiredAttribute: raise if resp.status_code == 200: try: assert "application/json" in resp.headers["content-type"] format = "json" except AssertionError: assert "application/jwt" in resp.headers["content-type"] format = "jwt" elif resp.status_code == 500: raise Exception("ERROR: Something went wrong: %s" % resp.text) else: raise Exception("ERROR: Something went wrong [%s]: %s" % ( resp.status_code, resp.text)) if format == "json": return OpenIDSchema().from_json(txt=resp.text) else: algo = self.client_prefs["userinfo_signed_response_alg"] # Keys of the OP ? keys = self.keyjar.get_signing_key(alg2keytype(algo)) return OpenIDSchema().from_jwt(resp.text, keys) def get_userinfo_claims(self, access_token, endpoint, method="POST", schema_class=OpenIDSchema, **kwargs): uir = UserInfoRequest(access_token=access_token) try: uir["schema"] = kwargs["schema"] except KeyError: pass h_args = dict([(k, v) for k,v in kwargs.items() if k in HTTP_ARGS]) if "authn_method" in kwargs: http_args = self.init_authentication_method(**kwargs) else: # If nothing defined this is the default http_args = self.init_authentication_method(uir, "bearer_header", **kwargs) h_args.update(http_args) path, body, kwargs = self.get_or_post(endpoint, method, uir, **kwargs) try: resp = self.http_request(path, method, data=body, **h_args) except oauth2.MissingRequiredAttribute: raise if resp.status_code == 200: assert "application/json" in resp.headers["content-type"] elif resp.status_code == 500: raise Exception("ERROR: Something went wrong: %s" % resp.text) else: raise Exception("ERROR: Something went wrong [%s]" % resp.status_code) return schema_class().from_json(txt=resp.text) def provider_config(self, issuer, keys=True, endpoints=True): if issuer.endswith("/"): _issuer = issuer[:-1] else: _issuer = issuer url = OIDCONF_PATTERN % _issuer pcr = None r = self.http_request(url) if r.status_code == 200: pcr = ProviderConfigurationResponse().from_json(r.text) elif r.status_code == 302: while r.status_code == 302: r = self.http_request(r.headers["location"]) if r.status_code == 200: pcr = ProviderConfigurationResponse().from_json(r.text) break if pcr is None: raise Exception("Trying '%s', status %s" % (url, r.status_code)) if "issuer" in pcr: _pcr_issuer = pcr["issuer"] if pcr["issuer"].endswith("/"): if issuer.endswith("/"): _issuer = issuer else: _issuer = issuer + "/" else: if issuer.endswith("/"): _issuer = issuer[:-1] else: _issuer = issuer try: assert _issuer == _pcr_issuer except AssertionError: raise Exception("provider info issuer mismatch '%s' != '%s'" % ( _issuer, _pcr_issuer)) self.provider_info[_pcr_issuer] = pcr else: _pcr_issuer = issuer if endpoints: for key, val in pcr.items(): if key.endswith("_endpoint"): setattr(self, key, val) if keys: self.keyjar.load_keys(pcr, _pcr_issuer) return pcr def unpack_aggregated_claims(self, userinfo): if userinfo._claim_sources: for csrc, spec in userinfo._claim_sources.items(): if "JWT" in spec: if not csrc in self.keyjar: self.provider_config(csrc, endpoints=False) keycol = self.keyjar.get_verify_key(owner=csrc) for typ, keyl in self.keyjar.get_verify_key().items(): try: keycol[typ].extend(keyl) except KeyError: keycol[typ] = keyl info = json.loads(jws.verify(str(spec["JWT"]), keycol)) attr = [n for n, s in userinfo._claim_names.items() if s == csrc] assert attr == info.keys() for key, vals in info.items(): userinfo[key] = vals return userinfo def fetch_distributed_claims(self, userinfo, callback=None): for csrc, spec in userinfo._claim_sources.items(): if "endpoint" in spec: #pcr = self.provider_config(csrc, keys=False, endpoints=False) if "access_token" in spec: _uinfo = self.do_user_info_request( token=spec["access_token"], userinfo_endpoint=spec["endpoint"]) else: _uinfo = self.do_user_info_request(token=callback(csrc), userinfo_endpoint=spec["endpoint"]) attr = [n for n, s in userinfo._claim_names.items() if s == csrc] assert attr == _uinfo.keys() for key, vals in _uinfo.items(): userinfo[key] = vals return userinfo def verify_alg_support(self, alg, usage, other): """ Verifies that the algorithm to be used are supported by the other side. :param alg: The algorithm specification :param usage: In which context the 'alg' will be used. The following values are supported: - userinfo - id_token - request_object - token_endpoint_auth :param other: The identifier for the other side :return: True or False """ try: _pcr = self.provider_info[other] supported = _pcr["%s_algs_supported" % usage] except KeyError: try: supported = getattr(self, "%s_algs_supported" % usage) except AttributeError: supported = None if supported is None: return True else: if alg in supported: return True else: return False def match_preferences(self, pcr=None, issuer=None): """ Match the clients preferences against what the provider can do. :param pcr: Provider configuration response if available :param issuer: The issuer identifier """ if not pcr: pcr = self.provider_info[issuer] for _pref, _prov in PREFERENCE2PROVIDER.items(): try: vals = self.client_prefs[_pref] except KeyError: continue try: _pvals = pcr[_prov] except KeyError: try: self.behaviour[_pref] = PROVIDER_DEFAULT[_pref] except KeyError: #self.behaviour[_pref]= vals[0] self.behaviour[_pref] = None continue for val in vals: if val in _pvals: self.behaviour[_pref]= val break if _pref not in self.behaviour: raise ConfigurationError( "OP couldn't match preferences", "%s" % _pref) for key, val in self.client_prefs.items(): if key not in PREFERENCE2PROVIDER: self.behaviour[key] = val def register(self, url, operation="register", application_type="web", **kwargs): req = RegistrationRequest(operation=operation, application_type=application_type) if operation == "update": req["client_id"] = self.client_id req["client_secret"] = self.client_secret for prop in req.parameters(): if prop in ["operation", "client_id", "client_secret"]: continue try: req[prop] = kwargs[prop] except KeyError: try: req[prop] = self.behaviour[prop] except KeyError: pass if "redirect_uris" not in req: try: req["redirect_uris"] = self.redirect_uris except AttributeError: raise MissingRequiredAttribute("redirect_uris") headers = {"content-type": "application/x-www-form-urlencoded"} if operation == "client_update": headers["Authorization"] = "Bearer %s" % self.registration_access_token rsp = self.http_request(url, "POST", data=req.to_urlencoded(), headers=headers) if rsp.status_code == 200: resp = RegistrationResponse().deserialize(rsp.text, "json") self.client_secret = resp["client_secret"] self.client_id = resp["client_id"] self.registration_expires = resp["expires_at"] self.registration_access_token = resp["registration_access_token"] else: err = ErrorResponse().deserialize(rsp.text, "json") raise Exception("Registration failed: %s" % err.get_json()) return resp def normalization(self, principal, idtype="mail"): if idtype == "mail": (local, domain) = principal.split("@") subject = "acct:%s" % principal elif idtype == "url": p = urlparse.urlparse(principal) domain = p.netloc subject = principal else: domain = "" subject = principal return subject, domain def discover(self, principal): subject, host = self.normalization(principal) return self.wf.discovery_query(host, subject)
def application(environ, start_response): session = environ['beaker.session'] rpSession = RpSession(session) path = environ.get('PATH_INFO', '').lstrip('/') if path == "robots.txt": return static(environ, start_response, LOGGER, "static/robots.txt") if path.startswith("static/"): return static(environ, start_response, LOGGER, path) query = parse_qs(environ["QUERY_STRING"]) if path == "logout": try: logoutUrl = rpSession.getClient().endsession_endpoint logoutUrl += "?" + urllib.urlencode({"post_logout_redirect_uri": SERVER_ENV["base_url"]}) try: logoutUrl += "&" + urllib.urlencode({"id_token_hint": id_token_as_signed_jwt(rpSession.getClient(), "HS256")}) except: pass rpSession.clearSession() resp = Redirect(str(logoutUrl)) return resp(environ, start_response) except: pass if rpSession.getCallback(): for key, _dict in rp_conf.SERVICE.items(): if "opKey" in _dict and _dict["opKey"] == path: rpSession.setCallback(False) func = getattr(rp_conf.SERVICE[key]["instance"], "callback") return func(environ, SERVER_ENV, start_response, query, rpSession) if path == "rpAcr" and "key" in query and query["key"][0] in rp_conf.SERVICE: return chooseAcrValue(environ, start_response, rpSession, query["key"][0]) if path == "rpAuth": #Only called if multiple arc_values (that is authentications) exists. if "acr" in query and query["acr"][0] in rpSession.getAcrvalues() and \ "key" in query and query["key"][0] in rp_conf.SERVICE: func = getattr(rp_conf.SERVICE[query["key"][0]]["instance"], "create_authnrequest") return func(environ, SERVER_ENV, start_response, rpSession, query["acr"][0]) if rpSession.getClient() is not None: rpSession.setCallback(True) func = getattr(rp_conf.SERVICE[rpSession.getService()]["instance"], "begin") return func(environ, SERVER_ENV, start_response, rpSession) if path == "rp": if "key" in query: print "key" key = query["key"][0] if key in rp_conf.SERVICE: rpSession.setCallback(True) func = getattr(rp_conf.SERVICE[key]["instance"], "begin") return func(environ, SERVER_ENV, start_response, rpSession) if "uid" in query: print "uid" _val = URINormalizer().normalize(query["uid"][0]) wf = WebFinger(httpd=Httpd()) link = wf.discovery_query(resource=_val) #requests.get(url, verify=True) md5 = hashlib.md5() md5.update(link) opkey = base64.b16encode(md5.digest()) kwargs = {'opKey': opkey, 'description': 'OIDC server with discovery url: ' + link, 'class': pyoidcOIC, 'srv_discovery_url': link, 'scope': ["openid", "profile", "email", "address", "phone"], 'name': link} rp_conf.SERVICE[opkey] = kwargs rp_conf.SERVICE[opkey]["instance"] = pyoidcOIC(None, None, **kwargs) rpSession.setCallback(True) func = getattr(rp_conf.SERVICE[opkey]["instance"], "begin") return func(environ, SERVER_ENV, start_response, rpSession) if path == "opbyuid": return opbyuid(environ, start_response) if path == "oplist": return oplist(environ, start_response) if path == "about": return about(environ, start_response) return start(environ, start_response)