def create_authn_request(self, session, acr_value=None, **kwargs): session["state"] = rndstr(32) request_args = { "response_type": self.behaviour["response_type"], "scope": self.behaviour["scope"], "state": session["state"], "redirect_uri": self.registration_response["redirect_uris"][0], } if self.oidc: session["nonce"] = rndstr(32) request_args["nonce"] = session["nonce"] if acr_value is not None: request_args["acr_values"] = acr_value request_args.update(kwargs) cis = self.construct_AuthorizationRequest(request_args=request_args) logger.debug("request: %s" % sanitize(cis)) url, body, ht_args, cis = self.uri_and_body( AuthorizationRequest, cis, method="GET", request_args=request_args ) self.authz_req[request_args["state"]] = cis logger.debug("body: %s" % sanitize(body)) logger.info("URL: %s" % sanitize(url)) logger.debug("ht_args: %s" % sanitize(ht_args)) resp = Redirect(str(url)) if ht_args: resp.headers.extend([(a, b) for a, b in ht_args.items()]) logger.debug("resp_headers: %s" % sanitize(resp.headers)) return resp
def create_authn_request(self, session, acr_value=None, **kwargs): session["state"] = rndstr(32) request_args = { "response_type": self.behaviour["response_type"], "scope": self.behaviour["scope"], "state": session["state"], "redirect_uri": self.registration_response["redirect_uris"][0] } if self.oidc: session["nonce"] = rndstr(32) request_args['nonce'] = session['nonce'] if acr_value is not None: request_args["acr_values"] = acr_value request_args.update(kwargs) cis = self.construct_AuthorizationRequest(request_args=request_args) logger.debug("request: %s" % sanitize(cis)) url, body, ht_args, cis = self.uri_and_body(AuthorizationRequest, cis, method="GET", request_args=request_args) self.authz_req[request_args['state']] = cis logger.debug("body: %s" % sanitize(body)) logger.info("URL: %s" % sanitize(url)) logger.debug("ht_args: %s" % sanitize(ht_args)) resp = Redirect(str(url)) if ht_args: resp.headers.extend([(a, b) for a, b in ht_args.items()]) logger.debug("resp_headers: %s" % sanitize(resp.headers)) return resp
def verify_header(reqresp, body_type): logger.debug("resp.headers: %s" % (sanitize(reqresp.headers), )) logger.debug("resp.txt: %s" % (sanitize(reqresp.text), )) if body_type == "": _ctype = reqresp.headers["content-type"] if match_to_("application/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 not match_to_("application/json", reqresp.headers["content-type"]): if match_to_("application/jwt", reqresp.headers["content-type"]): body_type = "jwt" else: raise ValueError("content-type: %s" % (reqresp.headers["content-type"], )) elif body_type == "jwt": if not match_to_("application/jwt", reqresp.headers["content-type"]): raise ValueError("Wrong content-type in header, got: {} expected " "'application/jwt'".format( reqresp.headers["content-type"])) elif body_type == "urlencoded": if not match_to_(DEFAULT_POST_CONTENT_TYPE, reqresp.headers["content-type"]): if not match_to_("text/plain", reqresp.headers["content-type"]): raise ValueError('Wrong content-type') else: raise ValueError("Unknown return format: %s" % body_type) return body_type
def verify_header(reqresp, body_type): logger.debug("resp.headers: %s" % (sanitize(reqresp.headers),)) logger.debug("resp.txt: %s" % (sanitize(reqresp.text),)) if body_type == "": _ctype = reqresp.headers["content-type"] if match_to_("application/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 not match_to_("application/json", reqresp.headers["content-type"]): if match_to_("application/jwt", reqresp.headers["content-type"]): body_type = "jwt" else: raise ValueError("content-type: %s" % (reqresp.headers["content-type"],)) elif body_type == "jwt": if not match_to_("application/jwt", reqresp.headers["content-type"]): raise ValueError("Wrong content-type in header, got: {} expected " "'application/jwt'".format(reqresp.headers["content-type"])) elif body_type == "urlencoded": if not match_to_(DEFAULT_POST_CONTENT_TYPE, reqresp.headers["content-type"]): if not match_to_("text/plain", reqresp.headers["content-type"]): raise ValueError('Wrong content-type') else: raise ValueError("Unknown return format: %s" % body_type) return body_type
def registration_endpoint(self, request, authn=None, **kwargs): logger.debug("@registration_endpoint: <<{}>>".format( sanitize(request))) if isinstance(request, dict): request = ClientMetadataStatement(**request) else: try: request = ClientMetadataStatement().deserialize( request, "json") except ValueError: request = ClientMetadataStatement().deserialize(request) logger.info("registration_request:{}".format( sanitize(request.to_dict()))) request_args = self.get_metadata_statement(request) request = RegistrationRequest(**request_args) result = self.client_registration_setup(request) if isinstance(result, Response): return result return Created(result.to_json(), content="application/json", headers=[("Cache-Control", "no-store")])
def _do_code(self, response, authresp): """Perform code flow.""" # get the access token try: args = { "code": authresp["code"], "redirect_uri": self.registration_response["redirect_uris"][0], "client_id": self.client_id, "client_secret": self.client_secret, } try: args["scope"] = response["scope"] except KeyError: pass atresp = self.do_access_token_request( state=authresp["state"], request_args=args, authn_method=self.registration_response["token_endpoint_auth_method"], ) msg = "Access token response: {}" logger.info(msg.format(sanitize(atresp))) except Exception as err: logger.error("%s" % err) raise if isinstance(atresp, ErrorResponse): msg = "Error response: {}" self._err(msg.format(sanitize(atresp.to_dict()))) _token = atresp["access_token"] _id_token = atresp.get("id_token") return _token, _id_token
def registration_endpoint(self, request, authn=None, **kwargs): logger.debug("@registration_endpoint: <<{}>>".format( sanitize(request))) if isinstance(request, dict): request = ClientMetadataStatement(**request) else: try: request = ClientMetadataStatement().deserialize( request, "json") except ValueError: request = ClientMetadataStatement().deserialize(request) logger.info("registration_request:{}".format( sanitize(request.to_dict()))) res = self.federation_entity.get_metadata_statement(request) if res: request = self.federation_entity.pick_by_priority(res) else: # Nothing I can use return error(error='invalid_request', descr='No signed metadata statement I could use') result = self.client_registration_setup(request) if isinstance(result, Response): return result return Created(result.to_json(), content="application/json", headers=[("Cache-Control", "no-store")])
def token_endpoint(self, authn="", **kwargs): """ This is where clients come to get their access tokens """ _sdb = self.sdb logger.debug("- token -") body = kwargs["request"] logger.debug("body: %s" % sanitize(body)) areq = AccessTokenRequest().deserialize(body, "urlencoded") try: self.client_authn(self, areq, authn) except FailedAuthentication as err: logger.error(err) err = TokenErrorResponse(error="unauthorized_client", error_description="%s" % err) return Response(err.to_json(), content="application/json", status="401 Unauthorized") logger.debug("AccessTokenRequest: %s" % sanitize(areq)) try: assert areq["grant_type"] == "authorization_code" except AssertionError: err = TokenErrorResponse(error="invalid_request", error_description="Wrong grant type") return Response(err.to_json(), content="application/json", status="401 Unauthorized") # assert that the code is valid _info = _sdb[areq["code"]] resp = self.token_scope_check(areq, _info) if resp: return resp # If redirect_uri was in the initial authorization request # verify that the one given here is the correct one. if "redirect_uri" in _info: assert areq["redirect_uri"] == _info["redirect_uri"] try: _tinfo = _sdb.upgrade_to_token(areq["code"], issue_refresh=True) except AccessCodeUsed: err = TokenErrorResponse(error="invalid_grant", error_description="Access grant used") return Response(err.to_json(), content="application/json", status="401 Unauthorized") logger.debug("_tinfo: %s" % sanitize(_tinfo)) atr = AccessTokenResponse(**by_schema(AccessTokenResponse, **_tinfo)) logger.debug("AccessTokenResponse: %s" % sanitize(atr)) return Response(atr.to_json(), content="application/json")
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 verify(self, request, **kwargs): """ Verifies that the given username and password was correct :param request: Either the query part of a URL a urlencoded body of a HTTP message or a parse such. :param kwargs: Catch whatever else is sent. :return: redirect back to where ever the base applications wants the user after authentication. """ logger.debug("verify(%s)" % sanitize(request)) if isinstance(request, six.string_types): _dict = compact(parse_qs(request)) elif isinstance(request, dict): _dict = request else: raise ValueError("Wrong type of input") logger.debug("dict: %s" % sanitize(_dict)) # verify username and password try: self._verify(_dict["password"], _dict["login"]) # dict origin except TypeError: try: self._verify(_dict["password"][0], _dict["login"][0]) except (AssertionError, KeyError) as err: logger.debug("Password verification failed: {}".format(err)) resp = Unauthorized("Unknown user or wrong password") return resp, False else: try: _qp = _dict["query"] except KeyError: _qp = self.get_multi_auth_cookie(kwargs['cookie']) except (AssertionError, KeyError) as err: logger.debug("Password verification failed: {}".format(err)) resp = Unauthorized("Unknown user or wrong password") return resp, False else: try: _qp = _dict["query"] except KeyError: _qp = self.get_multi_auth_cookie(kwargs['cookie']) logger.debug("Password verification succeeded.") # if "cookie" not in kwargs or self.srv.cookie_name not in kwargs["cookie"]: headers = [self.create_cookie(_dict["login"], "upm")] try: return_to = self.generate_return_url(kwargs["return_to"], _qp) except KeyError: try: return_to = self.generate_return_url(self.return_to, _qp, kwargs["path"]) except KeyError: return_to = self.generate_return_url(self.return_to, _qp) return SeeOther(return_to, headers=headers), True
def token_endpoint(self, authn="", **kwargs): """ This is where clients come to get their access tokens """ _sdb = self.sdb logger.debug("- token -") body = kwargs["request"] logger.debug("body: %s" % sanitize(body)) areq = AccessTokenRequest().deserialize(body, "urlencoded") try: self.client_authn(self, areq, authn) except FailedAuthentication as err: logger.error(err) err = TokenErrorResponse(error="unauthorized_client", error_description="%s" % err) return Response(err.to_json(), content="application/json", status_code=401) logger.debug("AccessTokenRequest: %s" % sanitize(areq)) if areq["grant_type"] != "authorization_code": err = TokenErrorResponse(error="invalid_request", error_description="Wrong grant type") return Response(err.to_json(), content="application/json", status="401 Unauthorized") # assert that the code is valid _info = _sdb[areq["code"]] resp = self.token_scope_check(areq, _info) if resp: return resp # If redirect_uri was in the initial authorization request # verify that the one given here is the correct one. if "redirect_uri" in _info and areq["redirect_uri"] != _info["redirect_uri"]: logger.error('Redirect_uri mismatch') err = TokenErrorResponse(error="unauthorized_client") return Unauthorized(err.to_json(), content="application/json") try: _tinfo = _sdb.upgrade_to_token(areq["code"], issue_refresh=True) except AccessCodeUsed: err = TokenErrorResponse(error="invalid_grant", error_description="Access grant used") return Response(err.to_json(), content="application/json", status="401 Unauthorized") logger.debug("_tinfo: %s" % sanitize(_tinfo)) atr = AccessTokenResponse(**by_schema(AccessTokenResponse, **_tinfo)) logger.debug("AccessTokenResponse: %s" % sanitize(atr)) return Response(atr.to_json(), content="application/json", headers=OAUTH2_NOCACHE_HEADERS)
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 claims_info_endpoint(self, request, authn): _log_info = logger.info _log_info("Claims_info_endpoint query: '%s'" % sanitize(request)) ucreq = self.srvmethod.parse_userinfo_claims_request(request) # Access_token is mandatory in UserInfoClaimsRequest uiresp = OpenIDSchema(**self.info_store[ucreq["access_token"]]) _log_info("returning: %s" % sanitize(uiresp.to_dict())) return Response(uiresp.to_json(), content="application/json")
def parse_authz_response(self, query): aresp = self.parse_response(AuthorizationResponse, info=query, sformat="urlencoded", keyjar=self.keyjar) if aresp.type() == "ErrorResponse": logger.info("ErrorResponse: %s" % sanitize(aresp)) raise AuthzError(aresp.error) logger.info("Aresp: %s" % sanitize(aresp)) return aresp
def dynamic_client(self, userid='', issuer=''): client = self.client_cls(client_authn_method=CLIENT_AUTHN_METHOD, verify_ssl=self.verify_ssl, **self.jwks_info) if userid: issuer = client.wf.discovery_query(userid) if not issuer: raise OIDCError('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()))) # 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 claims_info_endpoint(self, request, authn): _log_info = logger.info _log_info("Claims_info_endpoint query: '%s'" % sanitize(request)) ucreq = self.srvmethod.parse_userinfo_claims_request(request) # Bearer header or body access_token = bearer_auth(ucreq, authn) uiresp = OpenIDSchema(**self.info_store[access_token]) _log_info("returning: %s" % sanitize(uiresp.to_dict())) return Response(uiresp.to_json(), content="application/json")
def registration_endpoint(self, request, authn=None, **kwargs): """ :param request: :param authn: :param kwargs: :return: """ logger.debug("@registration_endpoint: <<{}>>".format(sanitize(request))) if isinstance(request, dict): request = ClientMetadataStatement(**request) else: try: request = ClientMetadataStatement().deserialize(request, "json") except ValueError: request = ClientMetadataStatement().deserialize(request) try: request.verify() except Exception as err: return error('Invalid request') logger.info( "registration_request:{}".format(sanitize(request.to_dict()))) ms_list = self.federation_entity.get_metadata_statement(request, 'registration') if ms_list: ms = self.federation_entity.pick_by_priority(ms_list) self.federation = ms.fo else: # Nothing I can use return error(error='invalid_request', descr='No signed metadata statement I could use') request = RegistrationRequest(**ms.le) result = self.client_registration_setup(request) if isinstance(result, Response): return result # TODO This is where the OP should sign the response if ms.fo: _fo = ms.fo sms = self.signer.create_signed_metadata_statement( result, 'response', [_fo], single=True) self.federation_entity.extend_with_ms(result, {_fo: sms}) return Created(result.to_json(), content="application/json", headers=[("Cache-Control", "no-store")])
def parse_authz_response(self, query): aresp = self.parse_response( self.message_factory.get_response_type("authorization_endpoint"), info=query, sformat="urlencoded", keyjar=self.keyjar, ) if aresp.type() == "ErrorResponse": logger.info("ErrorResponse: %s" % sanitize(aresp)) raise AuthzError(aresp.error) logger.info("Aresp: %s" % sanitize(aresp)) return aresp
def verify(self, areq, **kwargs): try: try: argv = {'sender': areq['client_id']} except KeyError: argv = {} bjwt = AuthnToken().from_jwt(areq["client_assertion"], keyjar=self.cli.keyjar, **argv) except (Invalid, MissingKey) as err: logger.info("%s" % sanitize(err)) raise AuthnFailure("Could not verify client_assertion.") logger.debug("authntoken: %s" % sanitize(bjwt.to_dict())) areq['parsed_client_assertion'] = bjwt # logger.debug("known clients: %s" % sanitize(self.cli.cdb.keys())) try: cid = kwargs["client_id"] except KeyError: cid = bjwt["iss"] try: # There might not be a client_id in the request assert str(cid) in self.cli.cdb # It's a client I know except KeyError: pass # aud can be a string or a list _aud = bjwt["aud"] logger.debug("audience: %s, baseurl: %s" % (_aud, self.cli.baseurl)) # figure out authn method if alg2keytype(bjwt.jws_header['alg']) == 'oct': # Symmetric key authn_method = 'client_secret_jwt' else: authn_method = 'private_key_jwt' try: if isinstance(_aud, six.string_types): assert str(_aud).startswith(self.cli.baseurl) else: for target in _aud: if target.startswith(self.cli.baseurl): return cid, authn_method raise NotForMe("Not for me!") except AssertionError: raise NotForMe("Not for me!") return cid, authn_method
def verify(self, areq, **kwargs): try: try: argv = {"sender": areq["client_id"]} except KeyError: argv = {} bjwt = AuthnToken().from_jwt(areq["client_assertion"], keyjar=self.cli.keyjar, **argv) except (Invalid, MissingKey) as err: logger.info("%s" % sanitize(err)) raise AuthnFailure("Could not verify client_assertion.") logger.debug("authntoken: %s" % sanitize(bjwt.to_dict())) areq["parsed_client_assertion"] = bjwt # logger.debug("known clients: %s" % sanitize(self.cli.cdb.keys())) try: cid = kwargs["client_id"] except KeyError: cid = bjwt["iss"] try: # There might not be a client_id in the request assert str(cid) in self.cli.cdb # It's a client I know except KeyError: pass # aud can be a string or a list _aud = bjwt["aud"] logger.debug("audience: %s, baseurl: %s" % (_aud, self.cli.baseurl)) # figure out authn method if alg2keytype(bjwt.jws_header["alg"]) == "oct": # Symmetric key authn_method = "client_secret_jwt" else: authn_method = "private_key_jwt" try: if isinstance(_aud, six.string_types): assert str(_aud).startswith(self.cli.baseurl) else: for target in _aud: if target.startswith(self.cli.baseurl): return cid, authn_method raise NotForMe("Not for me!") except AssertionError: raise NotForMe("Not for me!") return cid, authn_method
def load_keys(self, request, client_id, client_secret): try: self.keyjar.load_keys(request, client_id) try: n_keys = len(self.keyjar[client_id]) msg = "Found {} keys for client_id={}" logger.debug(msg.format(n_keys, client_id)) except KeyError: pass except Exception as err: msg = "Failed to load client keys: {}" logger.error(msg.format(sanitize(request.to_dict()))) logger.error("%s", err) err = ClientRegistrationError( error="invalid_configuration_parameter", error_description="%s" % err) return Response(err.to_json(), content="application/json", status="400 Bad Request") # Add the client_secret as a symmetric key to the keyjar _kc = KeyBundle([{"kty": "oct", "key": client_secret, "use": "ver"}, {"kty": "oct", "key": client_secret, "use": "sig"}]) try: self.keyjar[client_id].append(_kc) except KeyError: self.keyjar[client_id] = [_kc]
def authenticated_as(self, cookie=None, **kwargs): if cookie is None: return None, 0 else: logger.debug("kwargs: %s" % sanitize(kwargs)) try: val = self.getCookieValue(cookie, self.srv.cookie_name) except (InvalidCookieSign, AssertionError): val = None if val is None: return None, 0 else: uid, _ts, typ = val if typ == "uam": # short lived _now = int(time.time()) if _now > (int(_ts) + int(self.cookie_ttl * 60)): logger.debug("Authentication timed out") raise ToOld("%d > (%d + %d)" % (_now, int(_ts), int(self.cookie_ttl * 60))) else: if "max_age" in kwargs and kwargs["max_age"]: _now = int(time.time()) if _now > (int(_ts) + int(kwargs["max_age"])): logger.debug("Authentication too old") raise ToOld("%d > (%d + %d)" % ( _now, int(_ts), int(kwargs["max_age"]))) return {"uid": uid}, _ts
def parse_authz(self, query="", **kwargs): """ Parse authorization response from server. Couple of cases ["code"] ["code", "token"] ["code", "id_token", "token"] ["id_token"] ["id_token", "token"] ["token"] :return: A AccessTokenResponse instance """ _log_info = logger.info logger.debug("- authorization -") if not query: return http_util.BadRequest("Missing query") _log_info("response: %s" % sanitize(query)) if "code" in self.consumer_config["response_type"]: aresp, _state = self._parse_authz(query, **kwargs) # May have token and id_token information too if "access_token" in aresp: atr = clean_response(aresp) self.access_token = atr # update the grant object self.get_grant(state=_state).add_token(atr) else: atr = None self._backup(_state) try: idt = aresp["id_token"] except KeyError: idt = None return aresp, atr, idt elif "token" in self.consumer_config["response_type"]: # implicit flow _log_info("Expect Access Token Response") atr = self.parse_response(AccessTokenResponse, info=query, sformat="urlencoded", keyjar=self.keyjar, **kwargs) if isinstance(atr, ErrorResponse): raise TokenError(atr.get("error"), atr) idt = None return None, atr, idt else: # only id_token aresp, _state = self._parse_authz(query, **kwargs) try: idt = aresp["id_token"] except KeyError: idt = None return None, None, idt
def permissions(self, cookie=None, **kwargs): if cookie is None: return None else: logger.debug("kwargs: %s" % sanitize(kwargs)) val = self.getCookieValue(cookie, self.srv.cookie_name) if val is None: return None else: uid, _ts, typ = val if typ == "uam": # short lived _now = int(time.time()) if _now > (int(_ts) + int(self.cookie_ttl * 60)): logger.debug("Authentication timed out") raise ToOld("%d > (%d + %d)" % (_now, int(_ts), int(self.cookie_ttl * 60))) else: if "max_age" in kwargs and kwargs["max_age"]: _now = int(time.time()) if _now > (int(_ts) + int(kwargs["max_age"])): logger.debug("Authentication too old") raise ToOld("%d > (%d + %d)" % ( _now, int(_ts), int(kwargs["max_age"]))) return self.permdb[uid]
def providerinfo_endpoint(self, handle="", **kwargs): _log_info = logger.info _log_info("@providerinfo_endpoint") try: _response = self.create_providerinfo() msg = "provider_info_response: {}" _log_info(msg.format(sanitize(_response.to_dict()))) if self.events: self.events.store("Protocol response", _response) headers = [("Cache-Control", "no-store")] if handle: (key, timestamp) = handle if key.startswith(STR) and key.endswith(STR): cookie = self.cookie_func(key, self.cookie_name, "pinfo", self.sso_ttl) headers.append(cookie) resp = Response(_response.to_json(), content="application/json", headers=headers) except Exception: message = traceback.format_exception(*sys.exc_info()) logger.error(message) resp = error_response("service_error", message) return resp
def load_keys(self, request, client_id, client_secret): try: self.keyjar.load_keys(request, client_id) try: n_keys = len(self.keyjar[client_id]) msg = "Found {} keys for client_id={}" logger.debug(msg.format(n_keys, client_id)) except KeyError: pass except Exception as err: msg = "Failed to load client keys: {}" logger.error(msg.format(sanitize(request.to_dict()))) logger.error("%s", err) err = ClientRegistrationError( error="invalid_configuration_parameter", error_description="%s" % err) return Response(err.to_json(), content="application/json", status_code="400 Bad Request") # Add the client_secret as a symmetric key to the keyjar _kc = KeyBundle([{ "kty": "oct", "key": client_secret, "use": "ver" }, { "kty": "oct", "key": client_secret, "use": "sig" }]) try: self.keyjar[client_id].append(_kc) except KeyError: self.keyjar[client_id] = [_kc]
def providerinfo_endpoint(self, handle="", **kwargs): """ The Provider info endpoint. A request for provider info should be handled by this method. It will work as well for requests from federation aware RPs as for non-federation aware RPs. :param handle: (key, timestamp) tuple used at cookie construction :param kwargs: Extra key word arguments. :return: Provider Info response """ logger.info("@providerinfo_endpoint") try: _response = self.create_fed_providerinfo() msg = "provider_info_response: {}" logger.info(msg.format(sanitize(_response.to_dict()))) if self.events: self.events.store('Protocol response', _response) headers = [("Cache-Control", "no-store"), ("x-ffo", "bar")] if handle: (key, timestamp) = handle if key.startswith(STR) and key.endswith(STR): cookie = self.cookie_func(key, self.cookie_name, "pinfo", self.sso_ttl) headers.append(cookie) resp = Response(_response.to_json(), content="application/json", headers=headers) except Exception: message = traceback.format_exception(*sys.exc_info()) logger.error(message) resp = error('service_error', message) return resp
def complete(self, state): """ Do the access token request, the last step in a code flow. If Implicit flow was used then this method is never used. """ args = {"redirect_uri": self.redirect_uris[0]} if "password" in self.consumer_config and self.consumer_config["password"]: logger.info("basic auth") http_args = {"password": self.consumer_config["password"]} elif self.client_secret: logger.info("request_body auth") http_args = {} args.update({"client_secret": self.client_secret, "client_id": self.client_id, "secret_type": self.secret_type}) else: raise PyoidcError("Nothing to authenticate with") resp = self.do_access_token_request(state=state, request_args=args, http_args=http_args) logger.info("Access Token Response: %s" % sanitize(resp)) if resp.type() == "ErrorResponse": raise TokenError(resp.error, resp) # self._backup(self.sdb["seed:%s" % _cli.seed]) self._backup(state) return resp
def complete(self, state): """ Do the access token request, the last step in a code flow. If Implicit flow was used then this method is never used. """ args = {"redirect_uri": self.redirect_uris[0]} if "password" in self.consumer_config and self.consumer_config[ "password"]: logger.info("basic auth") http_args = {"password": self.consumer_config["password"]} elif self.client_secret: logger.info("request_body auth") http_args = {} args.update({ "client_secret": self.client_secret, "client_id": self.client_id, "secret_type": self.secret_type }) else: raise PyoidcError("Nothing to authenticate with") resp = self.do_access_token_request(state=state, request_args=args, http_args=http_args) logger.info("Access Token Response: %s" % sanitize(resp)) if resp.type() == "ErrorResponse": raise TokenError(resp.error, resp) # self._backup(self.sdb["seed:%s" % _cli.seed]) self._backup(state) return resp
def authenticated_as(self, cookie=None, **kwargs): if cookie is None: return None, 0 else: logger.debug("kwargs: %s" % sanitize(kwargs)) try: val = self.getCookieValue(cookie, self.srv.cookie_name) except (InvalidCookieSign, AssertionError): val = None if val is None: return None, 0 else: uid, _ts, typ = val if typ == "uam": # shortlived _now = int(time.time()) if _now > (int(_ts) + int(self.cookie_ttl * 60)): logger.debug("Authentication timed out") raise ToOld("%d > (%d + %d)" % (_now, int(_ts), int(self.cookie_ttl * 60))) else: if "max_age" in kwargs and kwargs["max_age"]: _now = int(time.time()) if _now > (int(_ts) + int(kwargs["max_age"])): logger.debug("Authentication too old") raise ToOld("%d > (%d + %d)" % (_now, int(_ts), int(kwargs["max_age"]))) return {"uid": uid}, _ts
def permissions(self, cookie=None, **kwargs): if cookie is None: return None else: logger.debug("kwargs: %s" % sanitize(kwargs)) val = self.getCookieValue(cookie, self.srv.cookie_name) if val is None: return None else: uid, _ts, typ = val if typ == "uam": # shortlived _now = int(time.time()) if _now > (int(_ts) + int(self.cookie_ttl * 60)): logger.debug("Authentication timed out") raise ToOld("%d > (%d + %d)" % (_now, int(_ts), int(self.cookie_ttl * 60))) else: if "max_age" in kwargs and kwargs["max_age"]: _now = int(time.time()) if _now > (int(_ts) + int(kwargs["max_age"])): logger.debug("Authentication too old") raise ToOld("%d > (%d + %d)" % (_now, int(_ts), int(kwargs["max_age"]))) return self.permdb[uid]
def parse_authz_response(self, query): aresp = self.parse_response( self.message_factory.get_response_type("authorization_endpoint"), info=query, sformat="urlencoded", keyjar=self.keyjar, ) if isinstance(aresp, ErrorResponse): logger.info("ErrorResponse: %s" % sanitize(aresp)) raise AuthzError( aresp.error # type: ignore # Messages have no classical attrs ) logger.info("Aresp: %s" % sanitize(aresp)) return aresp
def get_client_id(cdb, req, authn): """ Verify the client and return the client id :param req: The request :param authn: Authentication information from the HTTP header :return: """ logger.debug("REQ: %s" % sanitize(req.to_dict())) if authn: if authn.startswith("Basic "): logger.debug("Basic auth") (_id, _secret) = base64.b64decode(authn[6:].encode("utf-8")).decode("utf-8").split(":") _bid = as_bytes(_id) _cinfo = None try: _cinfo = cdb[_id] except KeyError: try: _cinfo[_bid] except AttributeError: pass if not _cinfo: logger.debug("Unknown client_id") raise FailedAuthentication("Unknown client_id") else: if not valid_client_info(_cinfo): logger.debug("Invalid Client info") raise FailedAuthentication("Invalid Client") if _secret != _cinfo["client_secret"]: logger.debug("Incorrect secret") raise FailedAuthentication("Incorrect secret") else: if authn[:6].lower() == "bearer": logger.debug("Bearer auth") _token = authn[7:] else: raise FailedAuthentication("AuthZ type I don't know") try: _id = cdb[_token] except KeyError: logger.debug("Unknown access token") raise FailedAuthentication("Unknown access token") else: try: _id = str(req["client_id"]) if _id not in cdb: logger.debug("Unknown client_id") raise FailedAuthentication("Unknown client_id") if not valid_client_info(cdb[_id]): raise FailedAuthentication("Invalid client_id") except KeyError: raise FailedAuthentication("Missing client_id") return _id
def parse_request_response(self, reqresp, response, body_type, state="", **kwargs): if reqresp.status_code in SUCCESSFUL: body_type = verify_header(reqresp, body_type) elif reqresp.status_code in [302, 303]: # redirect pass elif reqresp.status_code == 500: logger.error("(%d) %s" % (reqresp.status_code, sanitize(reqresp.text))) raise ParseError("ERROR: Something went wrong: %s" % reqresp.text) elif reqresp.status_code in [400, 401]: # expecting an error response if issubclass(response, ErrorResponse): pass else: logger.error("(%d) %s" % (reqresp.status_code, sanitize(reqresp.text))) raise HttpError("HTTP ERROR: %s [%s] on %s" % (reqresp.text, reqresp.status_code, reqresp.url)) if body_type: if response: return self.parse_response(response, reqresp.text, body_type, state, **kwargs) # could be an error response if reqresp.status_code in [200, 400]: if body_type == 'txt': body_type = 'urlencoded' try: err = ErrorResponse().deserialize(reqresp.message, method=body_type) try: err.verify() except PyoidcError: pass else: return err except Exception: pass return reqresp
def verify(self, areq, **kwargs): try: try: argv = {"sender": areq["client_id"]} except KeyError: argv = {} bjwt = AuthnToken().from_jwt(areq["client_assertion"], keyjar=self.cli.keyjar, **argv) except (Invalid, MissingKey) as err: logger.info("%s" % sanitize(err)) raise AuthnFailure("Could not verify client_assertion.") logger.debug("authntoken: %s" % sanitize(bjwt.to_dict())) areq["parsed_client_assertion"] = bjwt try: cid = kwargs["client_id"] except KeyError: cid = bjwt["iss"] # There might not be a client_id in the request if cid not in self.cli.cdb: raise AuthnFailure("Unknown client id") # aud can be a string or a list _aud = bjwt["aud"] logger.debug("audience: %s, baseurl: %s" % (_aud, self.cli.baseurl)) # figure out authn method if alg2keytype(bjwt.jws_header["alg"]) == "oct": # Symmetric key authn_method = "client_secret_jwt" else: authn_method = "private_key_jwt" if isinstance(_aud, str): if not str(_aud).startswith(self.cli.baseurl): raise NotForMe("Not for me!") else: for target in _aud: if target.startswith(self.cli.baseurl): return cid, authn_method raise NotForMe("Not for me!") return cid, authn_method
def _aggregation(self, info): jwt_key = self.keyjar.get_signing_key() cresp = UserClaimsResponse(jwt=info.to_jwt(key=jwt_key, algorithm="RS256"), claims_names=list(info.keys())) logger.info("RESPONSE: %s" % (sanitize(cresp.to_dict()),)) return cresp
def __call__(self, cookie=None, end_point_index=0, **kwargs): """Put up the login form.""" resp = Response() argv = self.templ_arg_func(end_point_index, **kwargs) logger.info("do_authentication argv: %s" % sanitize(argv)) mte = self.template_lookup.get_template(self.mako_template) resp.message = mte.render(**argv).decode("utf-8") return resp
def _aggregation(self, info): jwt_key = self.keyjar.get_signing_key() cresp = UserClaimsResponse(jwt=info.to_jwt(key=jwt_key, algorithm="RS256"), claims_names=list(info.keys())) logger.info("RESPONSE: %s" % (sanitize(cresp.to_dict()), )) return cresp
def __call__(self, userid, client_id, user_info_claims=None, first_only=True, **kwargs): _filter = self.filter_pattern % userid logger.debug("CLAIMS: %s" % sanitize(user_info_claims)) _attr = self.attr if user_info_claims: try: _claims = user_info_claims["claims"] except KeyError: pass else: avaspec = {} for key, val in _claims.items(): try: attr = self.openid2ldap[key] except KeyError: logger.warn("OIDC attribute '%s' not defined in map" % key) else: try: avaspec[attr].append(val) except KeyError: avaspec[attr] = [val] _attr.extend(list(avaspec.keys())) arg = [self.base, self.scope, _filter, _attr, self.attrsonly] try: res = self.ld.search_s(*arg) except Exception: # FIXME: This should catch specific exception from `self.ld.search_s()` try: self.ld.close() except Exception: # FIXME: This should catch specific exception from `self.ld.close()` pass self.bind() res = self.ld.search_s(*arg) if len(res) == 1: # should only be one entry and the information per entry is # the tuple (dn, ava) newres = {} for key, val in res[0][1].items(): if first_only: val = val[0] # if more than one just return the first try: newres[self.ldap2openid[key]] = val except KeyError: newres[key] = val return newres else: return {}
def __call__(self, cookie=None, end_point_index=0, **kwargs): """ Put up the login form """ resp = Response() argv = self.templ_arg_func(end_point_index, **kwargs) logger.info("do_authentication argv: %s" % sanitize(argv)) mte = self.template_lookup.get_template(self.mako_template) resp.message = mte.render(**argv).decode("utf-8") return resp
def claims_endpoint(self, request, http_authz, *args): _log_info = logger.info ucreq = self.srvmethod.parse_user_claims_request(request) _log_info("request: %s" % sanitize(ucreq)) try: resp = self.client_authn(self, ucreq, http_authz) except Exception as err: _log_info("Failed to verify client due to: %s" % err) resp = False if "claims_names" in ucreq: args = dict([(n, { "optional": True }) for n in ucreq["claims_names"]]) uic = Claims(**args) else: uic = None _log_info("User info claims: %s" % sanitize(uic)) #oicsrv, userdb, subject, client_id="", user_info_claims=None info = self.userinfo(ucreq["sub"], user_info_claims=uic, client_id=ucreq["client_id"]) _log_info("User info: %s" % sanitize(info)) # Convert to message format info = OpenIDSchema(**info) if self.do_aggregation(info, ucreq["sub"]): cresp = self._aggregation(info) else: cresp = self._distributed(info) _log_info("response: %s" % sanitize(cresp.to_dict())) return Response(cresp.to_json(), content="application/json")
def _parse_authz(self, query="", **kwargs): _log_info = logger.info # Might be an error response _log_info("Expect Authorization Response") aresp = self.parse_response( AuthorizationResponse, info=query, sformat="urlencoded", keyjar=self.keyjar ) if isinstance(aresp, ErrorResponse): _log_info("ErrorResponse: %s" % sanitize(aresp)) raise AuthzError(aresp.get("error"), aresp) _log_info("Aresp: %s" % sanitize(aresp)) _state = aresp["state"] try: self.update(_state) except KeyError: raise UnknownState(_state, aresp) self.redirect_uris = [self.sdb[_state]["redirect_uris"]] return aresp, _state
def claims_endpoint(self, request, http_authz, *args): _log_info = logger.info ucreq = self.srvmethod.parse_user_claims_request(request) _log_info("request: %s" % sanitize(ucreq)) try: resp = self.client_authn(self, ucreq, http_authz) except Exception as err: _log_info("Failed to verify client due to: %s" % err) resp = False if "claims_names" in ucreq: args = dict([(n, {"optional": True}) for n in ucreq["claims_names"]]) uic = Claims(**args) else: uic = None _log_info("User info claims: %s" % sanitize(uic)) #oicsrv, userdb, subject, client_id="", user_info_claims=None info = self.userinfo(ucreq["sub"], user_info_claims=uic, client_id=ucreq["client_id"]) _log_info("User info: %s" % sanitize(info)) # Convert to message format info = OpenIDSchema(**info) if self.do_aggregation(info, ucreq["sub"]): cresp = self._aggregation(info) else: cresp = self._distributed(info) _log_info("response: %s" % sanitize(cresp.to_dict())) return Response(cresp.to_json(), content="application/json")
def _parse_authz(self, query="", **kwargs): _log_info = logger.info # Might be an error response _log_info("Expect Authorization Response") aresp = self.parse_response(AuthorizationResponse, info=query, sformat="urlencoded", keyjar=self.keyjar) if isinstance(aresp, ErrorResponse): _log_info("ErrorResponse: %s" % sanitize(aresp)) raise AuthzError(aresp.get('error'), aresp) _log_info("Aresp: %s" % sanitize(aresp)) _state = aresp["state"] try: self.update(_state) except KeyError: raise UnknownState(_state, aresp) self.redirect_uris = [self.sdb[_state]["redirect_uris"]] return aresp, _state
def handle_authorization_response(self, query="", **kwargs): """ This is where we get redirect back to after authorization at the authorization server has happened. :param query: The query part of the request :return: A AccessTokenResponse instance """ logger.debug("- authorization - %s flow -" % self.flow_type) logger.debug("QUERY: %s" % sanitize(query)) if "code" in self.response_type: # Might be an error response try: aresp = self.parse_response(AuthorizationResponse, info=query, sformat="urlencoded") except Exception as err: logger.error("%s" % err) raise if isinstance(aresp, Message): if aresp.type().endswith("ErrorResponse"): raise AuthzError(aresp["error"]) try: self.update(aresp["state"]) except KeyError: raise UnknownState(aresp["state"]) self._backup(aresp["state"]) return aresp else: # implicit flow atr = self.parse_response(AccessTokenResponse, info=query, sformat="urlencoded", extended=True) if isinstance(atr, Message): if atr.type().endswith("ErrorResponse"): raise TokenError(atr["error"]) try: self.update(atr["state"]) except KeyError: raise UnknownState(atr["state"]) self.seed = self.grant[atr["state"]].seed return atr
def http_request(self, url, method="GET", **kwargs): """ Run a HTTP request to fetch the given url This wraps the requests library, so you can pass most requests kwargs to this method to override defaults. :param url: The URL to fetch :param method: The HTTP method to use. :param kwargs: Additional keyword arguments to pass through. """ _kwargs = copy.copy(self.request_args) if kwargs: _kwargs.update(kwargs) if self.cookiejar: _kwargs["cookies"] = self._cookies() logger.debug("SENT {} COOKIES".format(len(_kwargs["cookies"]))) if self.req_callback is not None: _kwargs = self.req_callback(method, url, **_kwargs) try: 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"] # Telekom fix # set_cookie = set_cookie.replace( # "=;Path=/;Expires=Thu, 01-Jan-1970 00:00:01 GMT;HttpOnly,", "") logger.debug("RECEIVED COOKIE") try: set_cookie(self.cookiejar, SimpleCookie(_cookie)) except CookieError as err: logger.error(err) raise NonFatalException(r, "{}".format(err)) except (AttributeError, KeyError): pass return r
def get_client_id(cdb, req, authn): """ Verify the client and return the client id :param req: The request :param authn: Authentication information from the HTTP header :return: """ logger.debug("REQ: %s" % sanitize(req.to_dict())) _secret = None if not authn: try: _id = str(req["client_id"]) except KeyError: raise FailedAuthentication("Missing client_id") elif authn.startswith("Basic "): logger.debug("Basic auth") (_id, _secret) = base64.b64decode(authn[6:].encode("utf-8")).decode("utf-8").split(":") # Either as string or encoded if _id not in cdb: _bid = as_bytes(_id) _id = _bid elif authn[:6].lower() == "bearer": logger.debug("Bearer auth") _token = authn[7:] try: _id = cdb[_token] except KeyError: logger.debug("Unknown access token") raise FailedAuthentication("Unknown access token") else: raise FailedAuthentication("AuthZ type I don't know") # We have the client_id by now, so let's verify it _cinfo = cdb.get(_id) if _cinfo is None: raise FailedAuthentication("Unknown client") if not valid_client_info(_cinfo): logger.debug("Invalid Client info") raise FailedAuthentication("Invalid Client") if _secret is not None: if _secret != _cinfo["client_secret"]: logger.debug("Incorrect secret") raise FailedAuthentication("Incorrect secret") # All should be good, so return it return _id
def create_return_url(base, query, **kwargs): """ Add a query string plus extra parameters to a base URL which may contain a query part already. :param base: redirect_uri may contain a query part, no fragment allowed. :param query: Old query part as a string :param kwargs: extra query parameters :return: """ part = urlsplit(base) if part.fragment: raise ValueError("Base URL contained parts it shouldn't") for key, values in parse_qs(query).items(): if key in kwargs: if isinstance(kwargs[key], str): kwargs[key] = [kwargs[key]] kwargs[key].extend(values) else: kwargs[key] = values if part.query: for key, values in parse_qs(part.query).items(): if key in kwargs: if isinstance(kwargs[key], str): kwargs[key] = [kwargs[key]] kwargs[key].extend(values) else: kwargs[key] = values _pre = base.split("?")[0] else: _pre = base logger.debug("kwargs: %s" % sanitize(kwargs)) if kwargs: return "%s?%s" % (_pre, url_encode_params(kwargs)) else: return _pre
def begin(self, baseurl, request, response_type="", **kwargs): """ Begin the OAuth2 flow :param baseurl: The RPs base :param request: The Authorization query :param response_type: The response type the AS should use. Default 'code'. :return: A URL to which the user should be redirected """ logger.debug("- begin -") # Store the request and the redirect uri used self.redirect_uris = ["%s%s" % (baseurl, self.authz_page)] self._request = request # Put myself in the dictionary of sessions, keyed on session-id if not self.seed: self.seed = rndstr() sid = stateID(request, self.seed) self.grant[sid] = Grant(seed=self.seed) self._backup(sid) self.sdb["seed:%s" % self.seed] = sid if not response_type: if self.response_type: response_type = self.response_type else: self.response_type = response_type = "code" location = self.request_info( AuthorizationRequest, method="GET", scope=self.scope, request_args={"state": sid, "response_type": response_type})[0] logger.debug("Redirecting to: %s" % (sanitize(location),)) return sid, location
def http_request(self, url, method="GET", **kwargs): _kwargs = copy.copy(self.request_args) if kwargs: _kwargs.update(kwargs) if self.cookiejar: _kwargs["cookies"] = self._cookies() logger.debug("SENT {} COOKIES".format(len(_kwargs["cookies"]))) if self.req_callback is not None: _kwargs = self.req_callback(method, url, **_kwargs) try: 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"] # Telekom fix # set_cookie = set_cookie.replace( # "=;Path=/;Expires=Thu, 01-Jan-1970 00:00:01 GMT;HttpOnly,", "") logger.debug("RECEIVED COOKIE") try: 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 r
def parse_authz(self, query="", **kwargs): """ This is where we get redirect back to after authorization at the authorization server has happened. Couple of cases ["code"] ["code", "token"] ["code", "id_token", "token"] ["id_token"] ["id_token", "token"] ["token"] :return: A AccessTokenResponse instance """ _log_info = logger.info logger.debug("- authorization -") if not query: return http_util.BadRequest("Missing query") _log_info("response: %s" % sanitize(query)) if "code" in self.consumer_config["response_type"]: aresp, _state = self._parse_authz(query, **kwargs) # May have token and id_token information too if "access_token" in aresp: atr = clean_response(aresp) self.access_token = atr # update the grant object self.get_grant(state=_state).add_token(atr) else: atr = None self._backup(_state) try: idt = aresp["id_token"] except KeyError: idt = None return aresp, atr, idt elif "token" in self.consumer_config["response_type"]: # implicit flow _log_info("Expect Access Token Response") atr = self.parse_response(AccessTokenResponse, info=query, sformat="urlencoded", keyjar=self.keyjar, **kwargs) if isinstance(atr, ErrorResponse): raise TokenError(atr.get("error"), atr) idt = None return None, atr, idt else: # only id_token aresp, _state = self._parse_authz(query, **kwargs) try: idt = aresp["id_token"] except KeyError: idt = None return None, None, idt
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 = cookielib.Cookie(**std_attr) cookiejar.set_cookie(new_cookie)
def test_sanitize(raw, expected): assert sanitize(raw) == expected
def callback(self, response, session, format='dict'): """ This is the method that should be called when an AuthN response has been received from the OP. :param response: The URL returned by the OP :return: """ try: authresp = self.parse_response(AuthorizationResponse, response, sformat=format, keyjar=self.keyjar) except ResponseError: msg = "Could not parse response: '{}'" logger.error(msg.format(sanitize(response))) raise OIDCError("Problem parsing response") logger.info("AuthorizationReponse: {}".format(sanitize(authresp))) if isinstance(authresp, ErrorResponse): if authresp["error"] == "login_required": return self.create_authn_request(session) else: raise OIDCError("Access denied") _state = authresp["state"] # if session["state"] != authresp["state"]: # self._err("Received state not the same as expected.") try: _id_token = authresp['id_token'] except KeyError: _id_token = None else: if _id_token['nonce'] != self.authz_req[_state]['nonce']: self._err("Received nonce not the same as expected.") if self.behaviour["response_type"] == "code": # get the access token try: args = { "code": authresp["code"], "redirect_uri": self.registration_response[ "redirect_uris"][0], "client_id": self.client_id, "client_secret": self.client_secret, } try: args['scope'] = response['scope'] except KeyError: pass atresp = self.do_access_token_request( state=authresp["state"], request_args=args, authn_method=self.registration_response[ "token_endpoint_auth_method"]) msg = 'Access token response: {}' logger.info(msg.format(sanitize(atresp))) except Exception as err: logger.error("%s" % err) raise if isinstance(atresp, ErrorResponse): msg = 'Error response: {}' self._err(msg.format(sanitize(atresp.to_dict()))) _token = atresp['access_token'] try: _id_token = atresp['id_token'] except KeyError: pass else: _token = authresp['access_token'] if not self.oidc: return {'access_token': _token} if _id_token is None: self._err("Invalid response: no IdToken") if _id_token['iss'] != self.provider_info['issuer']: self._err("Issuer mismatch") if _id_token['nonce'] != self.authz_req[_state]['nonce']: self._err("Nonce mismatch") if not self.allow_sign_alg_none: if _id_token.jws_header['alg'] == 'none': self._err('Do not allow "none" signature algorithm') user_id = '{}:{}'.format(_id_token['iss'], _id_token['sub']) if self.get_userinfo: if self.userinfo_request_method: kwargs = {"method": self.userinfo_request_method} else: kwargs = {} if self.has_access_token(state=authresp["state"]): inforesp = self.do_user_info_request(state=authresp["state"], **kwargs) if isinstance(inforesp, ErrorResponse): self._err("Invalid response %s." % inforesp["error"]) userinfo = inforesp.to_dict() if _id_token['sub'] != userinfo['sub']: self._err("Invalid response: userid mismatch") logger.debug("UserInfo: %s" % sanitize(inforesp)) try: self.id_token[user_id] = _id_token except TypeError: self.id_token = {user_id: _id_token} else: userinfo = {} for attr in OpenIDSchema.c_param: try: userinfo[attr] = _id_token[attr] except KeyError: pass return {'user_id': user_id, 'userinfo': userinfo, 'id_token': _id_token, 'access_token': _token} else: return {'user_id': user_id, 'id_token': _id_token, 'access_token': _token}
def from_jwt(self, txt, key=None, verify=True, keyjar=None, **kwargs): """ Given a signed and/or encrypted JWT, verify its correctness and then create a class instance from the content. :param txt: The JWT :param key: keys that might be used to decrypt and/or verify the signature of the JWT :param verify: Whether the signature should be verified or not :param keyjar: A KeyJar that might contain the necessary key. :param kwargs: Extra key word arguments :return: A class instance """ # if key is None and keyjar is not None: # key = keyjar.get_verify_key(owner="") # elif key is None: # key = [] # # if keyjar is not None and "sender" in kwargs: # key.extend(keyjar.get_verify_key(owner=kwargs["sender"])) _jw = jwe.factory(txt) if _jw: logger.debug("JWE headers: {}".format(_jw.jwt.headers)) if "algs" in kwargs and "encalg" in kwargs["algs"]: if kwargs["algs"]["encalg"] != _jw["alg"]: raise WrongEncryptionAlgorithm("%s != %s" % (_jw["alg"], kwargs["algs"]["encalg"])) if kwargs["algs"]["encenc"] != _jw["enc"]: raise WrongEncryptionAlgorithm("%s != %s" % (_jw["enc"], kwargs["algs"]["encenc"])) if keyjar: dkeys = keyjar.get_decrypt_key(owner="") if "sender" in kwargs: dkeys.extend(keyjar.get_verify_key(owner=kwargs["sender"])) elif key: dkeys = key else: dkeys = [] logger.debug('Decrypt class: {}'.format(_jw.__class__)) _res = _jw.decrypt(txt, dkeys) logger.debug('decrypted message:{}'.format(_res)) if isinstance(_res, tuple): txt = as_unicode(_res[0]) elif isinstance(_res, list) and len(_res) == 2: txt = as_unicode(_res[0]) else: txt = as_unicode(_res) self.jwe_header = _jw.jwt.headers _jw = jws.factory(txt) if _jw: if "algs" in kwargs and "sign" in kwargs["algs"]: _alg = _jw.jwt.headers["alg"] if kwargs["algs"]["sign"] != _alg: raise WrongSigningAlgorithm("%s != %s" % (_alg, kwargs["algs"]["sign"])) try: _jwt = JWT().unpack(txt) jso = _jwt.payload() _header = _jwt.headers if key is None and keyjar is not None: key = keyjar.get_verify_key(owner="") elif key is None: key = [] if keyjar is not None and "sender" in kwargs: key.extend(keyjar.get_verify_key(owner=kwargs["sender"])) logger.debug("Raw JSON: {}".format(sanitize(jso))) logger.debug("JWS header: {}".format(sanitize(_header))) if _header["alg"] == "none": pass elif verify: if keyjar: key = self.get_verify_keys(keyjar, key, jso, _header, _jw, **kwargs) if "alg" in _header and _header["alg"] != "none": if not key: raise MissingSigningKey( "alg=%s" % _header["alg"]) logger.debug("Found signing key.") try: _jw.verify_compact(txt, key) except NoSuitableSigningKeys: if keyjar: update_keyjar(keyjar) key = self.get_verify_keys(keyjar, key, jso, _header, _jw, **kwargs) _jw.verify_compact(txt, key) except Exception: raise else: self.jws_header = _jwt.headers else: jso = json.loads(txt) self.jwt = txt return self.from_dict(jso)
def test_sanitize_preserves_original(): old = {'passwd': 'secret'} new = sanitize(old) assert old['passwd'] == 'secret' assert new['passwd'] == '<REDACTED>'