def get_deserialization_method(reqresp): """ :param reqresp: Class instance with attributes: ['status', 'text', 'headers', 'url'] :return: Verified body content type """ logger.debug("resp.headers: %s" % (sanitize(reqresp.headers), )) logger.debug("resp.txt: %s" % (sanitize(reqresp.text), )) try: _ctype = reqresp.headers["content-type"] except KeyError: return 'urlencoded' # reasonable default ?? if match_to_("application/json", _ctype) or match_to_( 'application/jrd+json', _ctype): deser_method = 'json' elif match_to_("application/jwt", _ctype): deser_method = "jwt" elif match_to_("application/jose", _ctype): deser_method = "jose" elif match_to_(URL_ENCODED, _ctype): deser_method = 'urlencoded' elif match_to_("text/plain", _ctype) or match_to_("test/html", _ctype): deser_method = '' else: deser_method = '' # reasonable default ?? return deser_method
def verify_header(reqresp, body_type): """ :param reqresp: Class instance with attributes: ['status', 'text', 'headers', 'url'] :param body_type: If information returned in the body part :return: Verified body content type """ logger.debug("resp.headers: %s" % (sanitize(reqresp.headers), )) logger.debug("resp.txt: %s" % (sanitize(reqresp.text), )) try: _ctype = reqresp.headers["content-type"] except KeyError: if body_type: return body_type else: return 'txt' # reasonable default ?? logger.debug('Expected body type: "{}"'.format(body_type)) if body_type == "": if match_to_("application/json", _ctype) or match_to_( 'application/jrd+json', _ctype): body_type = 'json' elif match_to_("application/jwt", _ctype): body_type = "jwt" elif match_to_(URL_ENCODED, _ctype): body_type = 'urlencoded' else: body_type = 'txt' # reasonable default ?? elif body_type == "json": if match_to_("application/json", _ctype) or match_to_( 'application/jrd+json', _ctype): pass elif match_to_("application/jwt", _ctype): body_type = "jwt" else: raise WrongContentType(_ctype) elif body_type == "jwt": if not match_to_("application/jwt", _ctype): raise WrongContentType(_ctype) elif body_type == "urlencoded": if not match_to_(DEFAULT_POST_CONTENT_TYPE, _ctype): # I can live with text/plain if not match_to_("text/plain", _ctype): raise WrongContentType(_ctype) elif body_type == 'txt': if match_to_("text/plain", _ctype): pass elif match_to_("text/html", _ctype): pass else: raise WrongContentType(_ctype) else: raise ValueError("Unknown return format: %s" % body_type) logger.debug('Got body type: "{}"'.format(body_type)) return body_type
def collect_user_info(endpoint_context, session, userinfo_claims=None): """ Collect information about a user. This can happen in two cases, either when constructing an IdToken or when returning user info through the UserInfo endpoint :param session: Session information :param userinfo_claims: user info claims :return: User info """ authn_req = session['authn_req'] if userinfo_claims is None: uic = scope2claims(authn_req["scope"]) # Get only keys allowed by user and update the dict if such info # is stored in session perm_set = session.get('permission') if perm_set: uic = {key: uic[key] for key in uic if key in perm_set} uic = update_claims(session, "userinfo", uic) if uic: userinfo_claims = Claims(**uic) else: userinfo_claims = None logger.debug("userinfo_claim: %s" % sanitize(userinfo_claims.to_dict())) logger.debug("Session info: %s" % sanitize(session)) authn_event = session['authn_event'] if authn_event: uid = authn_event["uid"] else: uid = session['uid'] info = endpoint_context.userinfo(uid, authn_req['client_id'], userinfo_claims) if "sub" in userinfo_claims: if not claims_match(session["sub"], userinfo_claims["sub"]): raise FailedAuthentication("Unmatched sub claim") info["sub"] = session["sub"] try: logger.debug("user_info_response: {}".format(info)) except UnicodeEncodeError: try: logger.debug("user_info_response: {}".format(info.encode('utf-8'))) except Exception: pass return info
def __call__(self, url, method="GET", **kwargs): """ Send a HTTP request to a URL using a specified method :param url: The URL to access :param method: The method to use (GET, POST, ..) :param kwargs: extra HTTP request parameters :return: A Response """ # copy the default set before starting to modify it. _kwargs = copy.copy(self.request_args) if kwargs: _kwargs.update(kwargs) # If I have cookies add them all to the request if self.cookiejar: _kwargs["cookies"] = self._cookies() logger.debug("SENT {} COOKIES".format(len(_kwargs["cookies"]))) # If I want to modify the request arguments based on URL, method # and current arguments I can use this call back function. if self.req_callback is not None: _kwargs = self.req_callback(method, url, **_kwargs) try: # Do the request r = requests.request(method, url, **_kwargs) except Exception as err: logger.error( "http_request failed: %s, url: %s, htargs: %s, method: %s" % ( err, url, sanitize(_kwargs), method)) raise if self.events is not None: self.events.store('HTTP response', r, ref=url) try: _cookie = r.headers["set-cookie"] logger.debug("RECEIVED COOKIE") try: # add received cookies to the cookie jar set_cookie(self.cookiejar, SimpleCookie(_cookie)) except CookieError as err: logger.error(err) raise NonFatalException(r, "{}".format(err)) except (AttributeError, KeyError) as err: pass # return the response return r
def _get_signing_key(self, algorithm, context, kid=None): ktype = alg2keytype(algorithm) try: if kid: signing_key = [self._get_key_by_kid(kid, algorithm, context)] elif ktype in context.kid["sig"]: try: signing_key = [ self._get_key_by_kid(context.kid["sig"][ktype], algorithm, context) ] except KeyError: signing_key = self.get_signing_key_from_keyjar( algorithm, context) else: signing_key = self.get_signing_key_from_keyjar( algorithm, context) except (MissingKey, ) as err: LOGGER.error("%s", sanitize(err)) raise return signing_key
def __call__(self, url, method="GET", **kwargs): """ Send a HTTP request to a URL using a specified method :param url: The URL to access :param method: The method to use (GET, POST, ..) :param kwargs: extra HTTP request parameters :return: A Response """ # copy the default set before starting to modify it. _kwargs = copy.copy(self.request_args) if kwargs: _kwargs.update(kwargs) # If I have cookies add them all to the request self.add_cookies(kwargs) # If I want to modify the request arguments based on URL, method # and current arguments I can use this call back function. self.run_req_callback(url, method, kwargs) try: # Do the request r = requests.request(method, url, **_kwargs) except Exception as err: logger.error( "http_request failed: %s, url: %s, htargs: %s, method: %s" % (err, url, sanitize(_kwargs), method)) raise if self.events is not None: self.events.store('HTTP response', r, ref=url) self.set_cookie(r) # return the response return r
def _verify_sector_identifier(self, request): """ Verify `sector_identifier_uri` is reachable and that it contains `redirect_uri`s. :param request: Provider registration request :return: si_redirects, sector_id :raises: InvalidSectorIdentifier """ si_url = request["sector_identifier_uri"] try: res = self.endpoint_context.httpc.get(si_url) except Exception as err: logger.error(err) res = None if not res: raise InvalidSectorIdentifier("Couldn't read from sector_identifier_uri") logger.debug("sector_identifier_uri => %s", sanitize(res.text)) try: si_redirects = json.loads(res.text) except ValueError: raise InvalidSectorIdentifier( "Error deserializing sector_identifier_uri content" ) if "redirect_uris" in request: logger.debug("redirect_uris: %s", request["redirect_uris"]) for uri in request["redirect_uris"]: if uri not in si_redirects: raise InvalidSectorIdentifier( "redirect_uri missing from sector_identifiers" ) return si_redirects, si_url
def set_cookie(cookiejar, kaka): """PLaces a cookie (a cookielib.Cookie based on a set-cookie header line) in the cookie jar. Always chose the shortest expires time. :param cookiejar: :param kaka: Cookie """ # default rfc2109=False # max-age, httponly for cookie_name, morsel in kaka.items(): std_attr = ATTRS.copy() std_attr["name"] = cookie_name _tmp = morsel.coded_value if _tmp.startswith('"') and _tmp.endswith('"'): std_attr["value"] = _tmp[1:-1] else: std_attr["value"] = _tmp std_attr["version"] = 0 attr = "" # copy attributes that have values try: for attr in morsel.keys(): if attr in ATTRS: if morsel[attr]: if attr == "expires": std_attr[attr] = http2time(morsel[attr]) else: std_attr[attr] = morsel[attr] elif attr == "max-age": if morsel[attr]: std_attr["expires"] = http2time(morsel[attr]) except TimeFormatError: # Ignore cookie logger.info( "Time format error on %s parameter in received cookie" % (sanitize(attr), )) continue for att, spec in PAIRS.items(): if std_attr[att]: std_attr[spec] = True if std_attr["domain"] and std_attr["domain"].startswith("."): std_attr["domain_initial_dot"] = True if morsel["max-age"] is 0: try: cookiejar.clear(domain=std_attr["domain"], path=std_attr["path"], name=std_attr["name"]) except ValueError: pass else: # Fix for Microsoft cookie error if "version" in std_attr: try: std_attr["version"] = std_attr["version"].split(",")[0] except (TypeError, AttributeError): pass new_cookie = Cookie(**std_attr) cookiejar.set_cookie(new_cookie)
def client_registration_setup(self, request, new_id=True, set_secret=True): try: request.verify() except MessageException as err: if "type" not in request: return ResponseMessage(error="invalid_type", error_description="%s" % err) else: return ResponseMessage(error="invalid_configuration_parameter", error_description="%s" % err) request.rm_blanks() try: self.match_client_request(request) except CapabilitiesMisMatch as err: return ResponseMessage( error="invalid_request", error_description="Don't support proposed %s" % err) _context = self.endpoint_context if new_id: # create new id och secret client_id = rndstr(12) while client_id in _context.cdb: client_id = rndstr(12) else: try: client_id = request['client_id'] except KeyError: raise ValueError('Missing client_id') _rat = rndstr(32) _cinfo = { "client_id": client_id, "registration_access_token": _rat, "registration_client_uri": "%s?client_id=%s" % (self.endpoint_path, client_id), "client_salt": rndstr(8) } if new_id: _cinfo["client_id_issued_at"] = utc_time_sans_frac() if set_secret: client_secret = secret(_context.seed, client_id) _cinfo.update({ "client_secret": client_secret, "client_secret_expires_at": client_secret_expiration_time() }) else: client_secret = '' _context.cdb[client_id] = _cinfo _context.cdb[_rat] = client_id _cinfo = self.do_client_registration( request, client_id, ignore=["redirect_uris", "policy_uri", "logo_uri", "tos_uri"]) if isinstance(_cinfo, ResponseMessage): return _cinfo args = dict([(k, v) for k, v in _cinfo.items() if k in RegistrationResponse.c_param]) self.comb_uri(args) response = RegistrationResponse(**args) # Add the client_secret as a symmetric key to the key jar if client_secret: _context.keyjar.add_symmetric(client_id, str(client_secret)) _context.cdb[client_id] = _cinfo try: _context.cdb.sync() except AttributeError: # Not all databases can be sync'ed pass logger.info("registration_response: %s" % sanitize(response.to_dict())) return response
def do_client_registration(self, request, client_id, ignore=None): if ignore is None: ignore = [] _context = self.endpoint_context _cinfo = _context.cdb[client_id].copy() logger.debug("_cinfo: %s" % sanitize(_cinfo)) for key, val in request.items(): if key not in ignore: _cinfo[key] = val if "post_logout_redirect_uris" in request: plruri = [] for uri in request["post_logout_redirect_uris"]: if urlparse(uri).fragment: err = ClientRegistrationErrorResponse( error="invalid_configuration_parameter", error_description="post_logout_redirect_uris " "contains " "fragment") return err base, query = splitquery(uri) if query: plruri.append((base, parse_qs(query))) else: plruri.append((base, query)) _cinfo["post_logout_redirect_uris"] = plruri if "redirect_uris" in request: try: ruri = self.verify_redirect_uris(request) _cinfo["redirect_uris"] = ruri except InvalidRedirectURIError as e: return ClientRegistrationErrorResponse( error="invalid_redirect_uri", error_description=str(e)) if "sector_identifier_uri" in request: try: _cinfo["si_redirects"], _cinfo[ "sector_id"] = self._verify_sector_identifier(request) except InvalidSectorIdentifier as err: return ResponseMessage(error="invalid_configuration_parameter", error_description=err) elif "redirect_uris" in request: if len(request["redirect_uris"]) > 1: # check that the hostnames are the same host = "" for url in request["redirect_uris"]: part = urlparse(url) _host = part.netloc.split(":")[0] if not host: host = _host else: try: assert host == _host except AssertionError: return ResponseMessage( error="invalid_configuration_parameter", error_description="'sector_identifier_uri' " "must be registered") for item in ["policy_uri", "logo_uri", "tos_uri"]: if item in request: if verify_url(request[item], _cinfo["redirect_uris"]): _cinfo[item] = request[item] else: return ResponseMessage( error="invalid_configuration_parameter", error_description="%s pointed to illegal URL" % item) # Do I have the necessary keys for item in [ "id_token_signed_response_alg", "userinfo_signed_response_alg" ]: if item in request: if request[item] in _context.provider_info[ PREFERENCE2PROVIDER[item]]: ktyp = alg2keytype(request[item]) # do I have this ktyp and for EC type keys the curve if ktyp not in ["none", "oct"]: _k = [] for iss in ['', _context.issuer]: _k.extend( _context.keyjar.get_signing_key( ktyp, alg=request[item], owner=iss)) if not _k: logger.warning('Lacking support for "{}"'.format( request[item])) del _cinfo[item] t = {'jwks_uri': '', 'jwks': None} for item in ['jwks_uri', 'jwks']: if item in request: t[item] = request[item] try: _context.keyjar.load_keys(client_id, jwks_uri=t['jwks_uri'], jwks=t['jwks']) try: n_keys = len(_context.keyjar[client_id]) msg = "found {} keys for client_id={}" logger.debug(msg.format(n_keys, client_id)) except KeyError: pass except Exception as err: logger.error("Failed to load client keys: %s" % sanitize(request.to_dict())) logger.error("%s", err) logger.debug('Verify SSL: {}'.format(_context.keyjar.verify_ssl)) return ClientRegistrationErrorResponse( error="invalid_configuration_parameter", error_description="%s" % err) return _cinfo
def client_registration_setup(self, request, new_id=True, set_secret=True): try: request.verify() except (MessageException, ValueError) as err: return ResponseMessage( error="invalid_configuration_request", error_description="%s" % err ) request.rm_blanks() try: self.match_client_request(request) except CapabilitiesMisMatch as err: return ResponseMessage( error="invalid_request", error_description="Don't support proposed %s" % err, ) _context = self.endpoint_context if new_id: # create new id och secret client_id = rndstr(12) while client_id in _context.cdb: client_id = rndstr(12) else: try: client_id = request["client_id"] except KeyError: raise ValueError("Missing client_id") _cinfo = {"client_id": client_id, "client_salt": rndstr(8)} if "registration_api" in self.endpoint_context.endpoint: self.add_registration_api(_cinfo, client_id, _context) if new_id: _cinfo["client_id_issued_at"] = utc_time_sans_frac() if set_secret: client_secret = self.add_client_secret(_cinfo, client_id, _context) else: client_secret = "" _context.cdb[client_id] = _cinfo _cinfo = self.do_client_registration( request, client_id, ignore=["redirect_uris", "policy_uri", "logo_uri", "tos_uri"], ) if isinstance(_cinfo, ResponseMessage): return _cinfo args = dict( [(k, v) for k, v in _cinfo.items() if k in RegistrationResponse.c_param] ) comb_uri(args) response = RegistrationResponse(**args) # Add the client_secret as a symmetric key to the key jar if client_secret: _context.keyjar.add_symmetric(client_id, str(client_secret)) _context.cdb[client_id] = _cinfo try: _context.cdb.sync() except AttributeError: # Not all databases can be sync'ed pass logger.info("registration_response: %s" % sanitize(response.to_dict())) return response
def do_client_registration(self, request, client_id, ignore=None): if ignore is None: ignore = [] _context = self.endpoint_context _cinfo = _context.cdb[client_id].copy() logger.debug("_cinfo: %s" % sanitize(_cinfo)) for key, val in request.items(): if key not in ignore: _cinfo[key] = val if "post_logout_redirect_uris" in request: plruri = [] for uri in request["post_logout_redirect_uris"]: if urlparse(uri).fragment: err = ClientRegistrationErrorResponse( error="invalid_configuration_parameter", error_description="post_logout_redirect_uris " "contains " "fragment", ) return err base, query = splitquery(uri) if query: plruri.append((base, parse_qs(query))) else: plruri.append((base, query)) _cinfo["post_logout_redirect_uris"] = plruri if "redirect_uris" in request: try: ruri = self.verify_redirect_uris(request) _cinfo["redirect_uris"] = ruri except InvalidRedirectURIError as e: return ClientRegistrationErrorResponse( error="invalid_redirect_uri", error_description=str(e) ) if "sector_identifier_uri" in request: try: _cinfo["si_redirects"], _cinfo[ "sector_id" ] = self._verify_sector_identifier(request) except InvalidSectorIdentifier as err: return ResponseMessage( error="invalid_configuration_parameter", error_description=str(err) ) for item in ["policy_uri", "logo_uri", "tos_uri"]: if item in request: if verify_url(request[item], _cinfo["redirect_uris"]): _cinfo[item] = request[item] else: return ResponseMessage( error="invalid_configuration_parameter", error_description="%s pointed to illegal URL" % item, ) # Do I have the necessary keys for item in ["id_token_signed_response_alg", "userinfo_signed_response_alg"]: if item in request: if request[item] in _context.provider_info[PREFERENCE2PROVIDER[item]]: ktyp = alg2keytype(request[item]) # do I have this ktyp and for EC type keys the curve if ktyp not in ["none", "oct"]: _k = [] for iss in ["", _context.issuer]: _k.extend( _context.keyjar.get_signing_key( ktyp, alg=request[item], owner=iss ) ) if not _k: logger.warning( 'Lacking support for "{}"'.format(request[item]) ) del _cinfo[item] t = {"jwks_uri": "", "jwks": None} for item in ["jwks_uri", "jwks"]: if item in request: t[item] = request[item] # if it can't load keys because the URL is false it will # just silently fail. Waiting for better times. _context.keyjar.load_keys(client_id, jwks_uri=t["jwks_uri"], jwks=t["jwks"]) try: n_keys = 0 for kb in _context.keyjar[client_id]: n_keys += len(kb.keys()) msg = "found {} keys for client_id={}" logger.debug(msg.format(n_keys, client_id)) except KeyError: pass return _cinfo