def verify(self, request, key_type, **kwargs): _context = self.server_get("endpoint_context") _jwt = JWT(_context.keyjar, msg_cls=JsonWebToken) try: ca_jwt = _jwt.unpack(request["client_assertion"]) except (Invalid, MissingKey, BadSignature) as err: logger.info("%s" % sanitize(err)) raise AuthnFailure("Could not verify client_assertion.") _sign_alg = ca_jwt.jws_header.get("alg") if _sign_alg and _sign_alg.startswith("HS"): if key_type == "private_key": raise AttributeError("Wrong key type") keys = _context.keyjar.get("sig", "oct", ca_jwt["iss"], ca_jwt.jws_header.get("kid")) _secret = _context.cdb[ca_jwt["iss"]].get("client_secret") if _secret and keys[0].key != as_bytes(_secret): raise AttributeError( "Oct key used for signing not client_secret") else: if key_type == "client_secret": raise AttributeError("Wrong key type") authtoken = sanitize(ca_jwt.to_dict()) logger.debug("authntoken: {}".format(authtoken)) _endpoint = kwargs.get("endpoint") if _endpoint is None or not _endpoint: if _context.issuer in ca_jwt["aud"]: pass else: raise NotForMe("Not for me!") else: if set(ca_jwt["aud"]).intersection( _endpoint.allowed_target_uris()): pass else: raise NotForMe("Not for me!") # If there is a jti use it to make sure one-time usage is true _jti = ca_jwt.get("jti") if _jti: _key = "{}:{}".format(ca_jwt["iss"], _jti) if _key in _context.jti_db: raise MultipleUsage("Have seen this token once before") else: _context.jti_db[_key] = utc_time_sans_frac() request[verified_claim_name("client_assertion")] = ca_jwt client_id = kwargs.get("client_id") or ca_jwt["iss"] return {"client_id": client_id, "jwt": ca_jwt}
def post_parse_request( self, request: Union[Message, dict], client_id: Optional[str] = "", **kwargs ): """ This is where clients come to get their access tokens :param request: The request :param client_id: Client identifier :returns: """ _mngr = self.endpoint.server_get("endpoint_context").session_manager try: _session_info = _mngr.get_session_info_by_token(request["code"], grant=True) except (KeyError, UnknownToken): logger.error("Access Code invalid") return self.error_cls(error="invalid_grant", error_description="Unknown code") grant = _session_info["grant"] code = grant.get_token(request["code"]) if not isinstance(code, AuthorizationCode): return self.error_cls(error="invalid_request", error_description="Wrong token type") if code.is_active() is False: return self.error_cls(error="invalid_request", error_description="Code inactive") _auth_req = grant.authorization_request if "client_id" not in request: # Optional for access token request request["client_id"] = _auth_req["client_id"] logger.debug("%s: %s" % (request.__class__.__name__, sanitize(request))) return request
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.server_get("endpoint_context").httpc.get( si_url, **self.server_get("endpoint_context").httpc_params) logger.debug("sector_identifier_uri => %s", sanitize(res.text)) except Exception as err: logger.error(err) # res = None raise InvalidSectorIdentifier( "Couldn't read from sector_identifier_uri") 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 verify(self, request=None, **kwargs): _context = self.server_get("endpoint_context") _jwt = JWT(_context.keyjar, msg_cls=JsonWebToken) try: _jwt = _jwt.unpack(request["request"]) except (Invalid, MissingKey, BadSignature) as err: logger.info("%s" % sanitize(err)) raise AuthnFailure("Could not verify client_assertion.") # If there is a jti use it to make sure one-time usage is true _jti = _jwt.get("jti") if _jti: _key = "{}:{}".format(_jwt["iss"], _jti) if _key in _context.jti_db: raise MultipleUsage("Have seen this token once before") else: _context.jti_db[_key] = utc_time_sans_frac() request[verified_claim_name("client_assertion")] = _jwt client_id = kwargs.get("client_id") or _jwt["iss"] return {"client_id": client_id, "jwt": _jwt}
def parse_request(self, request: Union[Message, dict, str], http_info: Optional[dict] = None, **kwargs): """ :param request: The request the server got :param http_info: HTTP information in connection with the request. This is a dictionary with keys: headers, url, cookies. :param kwargs: extra keyword arguments :return: """ LOGGER.debug("- {} -".format(self.endpoint_name)) LOGGER.info("Request: %s" % sanitize(request)) _context = self.server_get("endpoint_context") if http_info is None: http_info = {} if request: if isinstance(request, (dict, Message)): req = self.request_cls(**request) else: _cls_inst = self.request_cls() if self.request_format == "jwt": req = _cls_inst.deserialize( request, "jwt", keyjar=_context.keyjar, verify=_context.httpc_params["verify"], **kwargs) elif self.request_format == "url": parts = urlparse(request) scheme, netloc, path, params, query, fragment = parts[:6] req = _cls_inst.deserialize(query, "urlencoded") else: req = _cls_inst.deserialize(request, self.request_format) else: req = self.request_cls() # Verify that the client is allowed to do this _client_id = "" auth_info = self.client_authentication(req, http_info, endpoint=self, **kwargs) if "client_id" in auth_info: req["client_id"] = auth_info["client_id"] _client_id = auth_info["client_id"] else: _client_id = req.get("client_id") keyjar = _context.keyjar # verify that the request message is correct try: req.verify(keyjar=keyjar, opponent_id=_client_id) except (MissingRequiredAttribute, ValueError, MissingRequiredValue) as err: return self.error_cls(error="invalid_request", error_description="%s" % err) LOGGER.info("Parsed and verified request: %s" % sanitize(req)) # Do any endpoint specific parsing return self.do_post_parse_request(request=req, client_id=_client_id, http_info=http_info, **kwargs)
def client_registration_setup(self, request, new_id=True, set_secret=True): try: request.verify() except (MessageException, ValueError) as err: logger.error("request.verify() on %s", request) 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.server_get("endpoint_context") if new_id: if self.kwargs.get("client_id_generator"): cid_generator = importer( self.kwargs["client_id_generator"]["class"]) cid_gen_kwargs = self.kwargs["client_id_generator"].get( "kwargs", {}) else: cid_generator = importer( "oidcop.oidc.registration.random_client_id") cid_gen_kwargs = {} client_id = cid_generator(reserved=_context.cdb.keys(), **cid_gen_kwargs) if "client_id" in request: del request["client_id"] else: client_id = request.get("client_id") if not client_id: raise ValueError("Missing client_id") _cinfo = {"client_id": client_id, "client_salt": rndstr(8)} if self.server_get("endpoint", "registration_read"): self.add_registration_api(_cinfo, client_id, _context) if new_id: _cinfo["client_id_issued_at"] = utc_time_sans_frac() client_secret = "" if set_secret: client_secret = self.add_client_secret(_cinfo, client_id, _context) logger.debug( "Stored client info in CDB under cid={}".format(client_id)) _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 self.response_cls.c_param]) comb_uri(args) response = self.response_cls(**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)) logger.debug( "Stored updated client info in CDB under cid={}".format(client_id)) logger.debug("ClientInfo: {}".format(_cinfo)) _context.cdb[client_id] = _cinfo # Not all databases can be sync'ed if hasattr(_context.cdb, "sync") and callable(_context.cdb.sync): _context.cdb.sync() msg = "registration_response: {}" logger.info(msg.format(sanitize(response.to_dict()))) return response
def do_client_registration(self, request, client_id, ignore=None): if ignore is None: ignore = [] _context = self.server_get("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 = self.error_cls( error="invalid_configuration_parameter", error_description= "post_logout_redirect_uris contains fragment", ) return err plruri.append(split_uri(uri)) _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 self.error_cls(error="invalid_redirect_uri", error_description=str(e)) if "request_uris" in request: _uris = [] for uri in request["request_uris"]: _up = urlparse(uri) if _up.query: err = self.error_cls( error="invalid_configuration_parameter", error_description="request_uris contains query part", ) return err if _up.fragment: # store base and fragment _uris.append(uri.split("#")) else: _uris.append([uri, ""]) _cinfo["request_uris"] = _uris 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], issuer_id=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"]) n_keys = 0 for kb in _context.keyjar.get(client_id, []): n_keys += len(kb.keys()) msg = "found {} keys for client_id={}" logger.debug(msg.format(n_keys, client_id)) return _cinfo