Esempio n. 1
0
    def test_session_state_in_auth_req_for_session_support(self):
        provider = Provider(
            "foo",
            SessionDB(SERVER_INFO["issuer"]),
            CDB,
            AUTHN_BROKER,
            USERINFO,
            AUTHZ,
            verify_client,
            SYMKEY,
            urlmap=URLMAP,
            keyjar=KEYJAR,
            capabilities={"check_session_iframe": "https://op.example.com/check_session"},
        )

        req_args = {
            "scope": ["openid"],
            "redirect_uri": "http://localhost:8087/authz",
            "response_type": ["code"],
            "client_id": "a1b2c3",
        }
        areq = AuthorizationRequest(**req_args)
        resp = provider.authorization_endpoint(request=areq.to_urlencoded())
        aresp = self.cons.parse_response(AuthorizationResponse, resp.message, sformat="urlencoded")
        assert "session_state" in aresp
Esempio n. 2
0
    def test_session_state_in_auth_req_for_session_support(self):
        provider = Provider(SERVER_INFO["issuer"],
                            SessionDB(SERVER_INFO["issuer"]),
                            CDB,
                            AUTHN_BROKER,
                            USERINFO,
                            AUTHZ,
                            verify_client,
                            SYMKEY,
                            urlmap=URLMAP,
                            keyjar=KEYJAR)

        provider.capabilities.update(
            {"check_session_iframe": "https://op.example.com/check_session"})

        req_args = {
            "scope": ["openid"],
            "redirect_uri": "http://localhost:8087/authz",
            "response_type": ["code"],
            "client_id": "number5"
        }
        areq = AuthorizationRequest(**req_args)
        resp = provider.authorization_endpoint(request=areq.to_urlencoded())
        aresp = self.cons.parse_response(AuthorizationResponse,
                                         resp.message,
                                         sformat="urlencoded")
        assert "session_state" in aresp
Esempio n. 3
0
    def __init__(self, name, sdb, cdb, authn_broker, userinfo, authz,
                 client_authn, symkey, trusted_domains, verify_signer_ssl=True, **kwarg):
        Provider.__init__(self, name, sdb, cdb, authn_broker, userinfo, authz,
                          client_authn, symkey, **kwarg)

        self.trusted_domains = trusted_domains
        self.verify_signer_ssl = verify_signer_ssl
Esempio n. 4
0
    def __init__(self,
                 name,
                 sdb,
                 cdb,
                 userinfo,
                 client_authn,
                 urlmap=None,
                 ca_certs="",
                 keyjar=None,
                 hostname="",
                 dist_claims_mode=None):
        Provider.__init__(self, name, sdb, cdb, None, userinfo, None,
                          client_authn, "", urlmap, ca_certs, keyjar, hostname)

        if keyjar is None:
            keyjar = KeyJar(ca_certs)

        for cid, _dic in cdb.items():
            try:
                keyjar.add_symmetric(cid, _dic["client_secret"],
                                     ["sig", "ver"])
            except KeyError:
                pass

        self.srvmethod = OICCServer(keyjar=keyjar)
        self.dist_claims_mode = dist_claims_mode
        self.info_store = {}
        self.claims_userinfo_endpoint = ""
Esempio n. 5
0
    def setup_consumer(self, session_db_factory):
        client_config = {
            "client_id": CLIENT_ID,
            "client_authn_method": CLIENT_AUTHN_METHOD,
        }

        self.consumer = Consumer(DictSessionBackend(), CONFIG, client_config,
                                 SERVER_INFO)
        self.consumer.keyjar = CLIKEYS
        self.consumer.redirect_uris = ["https://example.com/authz"]
        self.consumer.client_secret = "hemlig"
        self.consumer.secret_type = "basic"
        self.consumer.issuer = ISSUER_ID

        self.provider = Provider(
            ISSUER_ID,
            session_db_factory(ISSUER_ID),
            CDB,
            AUTHN_BROKER,
            USERINFO,
            AUTHZ,
            verify_client,
            SYMKEY,
            urlmap=URLMAP,
            keyjar=SRVKEYS,
        )
        self.provider.baseurl = self.provider.name
Esempio n. 6
0
    def __init__(
        self,
        name,
        sdb,
        cdb,
        userinfo,
        client_authn,
        urlmap=None,
        ca_certs="",
        keyjar=None,
        hostname="",
        dist_claims_mode=None,
    ):
        Provider.__init__(
            self, name, sdb, cdb, None, userinfo, None, client_authn, "", urlmap, ca_certs, keyjar, hostname
        )

        if keyjar is None:
            keyjar = KeyJar(ca_certs)

        for cid, _dic in cdb.items():
            try:
                keyjar.add_symmetric(cid, _dic["client_secret"], ["sig", "ver"])
            except KeyError:
                pass

        self.srvmethod = OICCServer(keyjar=keyjar)
        self.dist_claims_mode = dist_claims_mode
        self.info_store = {}
        self.claims_userinfo_endpoint = ""
Esempio n. 7
0
 def __init__(self, name, sdb, cdb, function, userdb, urlmap=None,
              debug=0, ca_certs="", jwt_keys=None):
     Provider.__init__(self, name, sdb, cdb, function, userdb, urlmap,
                       ca_certs, jwt_keys)
     self.test_mode = True
     self.trace_log = {}
     self.sessions = []
     self.max_sessions = 100
Esempio n. 8
0
 def __init__(self, name, sdb, cdb, function, userdb, urlmap=None,
              debug=0, ca_certs="", jwt_keys=None):
     Provider.__init__(self, name, sdb, cdb, function, userdb, urlmap,
                       ca_certs, jwt_keys)
     self.test_mode = True
     self.trace_log = {}
     self.sessions = []
     self.max_sessions = 100
Esempio n. 9
0
def test_provider_key_setup():
    provider = Provider("pyoicserv", SessionDB(), None, None, None)
    provider.baseurl = "http://www.example.com/"
    provider.key_setup("static", sig={"format": "jwk", "alg": "rsa"})

    keys = provider.keyjar.get_signing_key("rsa")
    assert len(keys) == 1
    assert provider.jwk[0] == "http://www.example.com/static/jwk.json"
Esempio n. 10
0
    def test_provider_key_setup(self):
        provider = Provider("pyoicserv", SessionDB(SERVER_INFO["issuer"]), None,
                            None, None, None, None, "")
        provider.baseurl = "http://www.example.com/"
        provider.key_setup("static", sig={"format": "jwk", "alg": "RSA"})

        keys = provider.keyjar.get_signing_key("RSA")
        assert len(keys) == 1
        assert provider.jwks_uri == "http://www.example.com/static/jwks"
Esempio n. 11
0
def test_provider_key_setup():
    provider = Provider("pyoicserv", SessionDB(SERVER_INFO["issuer"]), None,
                        None, None, None, None, "")
    provider.baseurl = "http://www.example.com/"
    provider.key_setup("static", sig={"format": "jwk", "alg": "RSA"})

    keys = provider.keyjar.get_signing_key("RSA")
    assert len(keys) == 1
    assert provider.jwks_uri == "http://www.example.com/static/jwks"
Esempio n. 12
0
    def test_provider_key_setup(self, tmpdir):
        path = tmpdir.strpath
        provider = Provider("pyoicserv", SessionDB(SERVER_INFO["issuer"]), None, None, None, None, None, "")
        provider.baseurl = "http://www.example.com"
        provider.key_setup(path, path, sig={"format": "jwk", "alg": "RSA"})

        keys = provider.keyjar.get_signing_key("RSA")
        assert len(keys) == 1
        assert provider.jwks_uri == "http://www.example.com/{}/jwks".format(path)
Esempio n. 13
0
    def test_provider_key_setup(self, tmpdir):
        path = tmpdir.strpath
        provider = Provider("pyoicserv", SessionDB(SERVER_INFO["issuer"]), None,
                            None, None, None, None, "")
        provider.baseurl = "http://www.example.com"
        provider.key_setup(path, path, sig={"format": "jwk", "alg": "RSA"})

        keys = provider.keyjar.get_signing_key("RSA")
        assert len(keys) == 1
        assert provider.jwks_uri == "http://www.example.com/{}/jwks".format(
            path)
Esempio n. 14
0
    def create_provider(self):
        self.provider = Provider("pyoicserv", SessionDB(SERVER_INFO["issuer"]),
                                 CDB,
                                 AUTHN_BROKER, USERINFO,
                                 AUTHZ, verify_client, SYMKEY, urlmap=URLMAP,
                                 keyjar=KEYJAR)
        self.provider.baseurl = self.provider.name

        self.cons = Consumer({}, CONSUMER_CONFIG, CLIENT_CONFIG,
                             server_info=SERVER_INFO, )
        self.cons.behaviour = {
            "request_object_signing_alg": DEF_SIGN_ALG["openid_request_object"]}
        self.cons.keyjar[""] = KC_RSA
Esempio n. 15
0
    def __init__(
        self,
        name,
        sdb,
        cdb,
        userinfo,
        client_authn,
        urlmap=None,
        keyjar=None,
        hostname="",
        dist_claims_mode=None,
        verify_ssl=None,
        settings=None,
    ):
        self.settings = settings or OicProviderSettings()
        if verify_ssl is not None:
            warnings.warn(
                "`verify_ssl` is deprecated, please use `settings` instead if you need to set a non-default value.",
                DeprecationWarning,
                stacklevel=2,
            )
            self.settings.verify_ssl = verify_ssl
        Provider.__init__(
            self,
            name,
            sdb,
            cdb,
            None,
            userinfo,
            None,
            client_authn,
            None,
            urlmap,
            keyjar,
            hostname,
            settings=self.settings,
        )

        if keyjar is None:
            keyjar = KeyJar(verify_ssl=verify_ssl)

        for cid, _dic in cdb.items():
            try:
                keyjar.add_symmetric(cid, _dic["client_secret"], ["sig", "ver"])
            except KeyError:
                pass

        self.srvmethod = OICCServer(keyjar=keyjar)
        self.dist_claims_mode = dist_claims_mode
        self.info_store = {}  # type: Dict[str, Any]
        self.claims_userinfo_endpoint = ""
Esempio n. 16
0
    def test_registered_redirect_uri_with_query_component(self):
        provider2 = Provider("FOOP", {}, {}, None, None, None, None, "")

        rr = RegistrationRequest(operation="register",
                                 redirect_uris=["http://example.org/cb?foo=bar"],
                                 response_types=["code"])

        registration_req = rr.to_json()
        resp = provider2.registration_endpoint(request=registration_req)

        regresp = RegistrationResponse().from_json(resp.message)

        print regresp.to_dict()

        faulty = [
            "http://example.org/cb",
            "http://example.org/cb/foo",
            "http://example.org/cb?got=you",
            "http://example.org/cb?foo=you"
            "http://example.org/cb?foo=bar&got=you",
            "http://example.org/cb?foo=you&foo=bar"
        ]
        correct = [
            "http://example.org/cb?foo=bar",
        ]

        cid = regresp["client_id"]

        for ruri in faulty:
            areq = AuthorizationRequest(redirect_uri=ruri,
                                        client_id=cid,
                                        scope="openid",
                                        response_type="code")

            print areq
            try:
                provider2._verify_redirect_uri(areq)
            except RedirectURIError:
                pass

        for ruri in correct:
            areq = AuthorizationRequest(redirect_uri=ruri,
                                        client_id=cid, scope="openid",
                                        response_type="code")

            resp = provider2._verify_redirect_uri(areq)
            print resp
            assert resp is None
Esempio n. 17
0
    def _create_op(self, issuer, endpoint_baseurl, jwks_uri):
        """
        Create the necessary Provider instance.
        :type issuer: str
        :type endpoint_baseurl: str
        :type jwks_uri: str
        :param issuer: issuer URL for the OP
        :param endpoint_baseurl: baseurl to build endpoint URL from
        :param jwks_uri: URL to where the JWKS will be published
        """
        kj = KeyJar()
        signing_key = KeyBundle(source="file://{}".format(self.conf["signing_key_path"]),
                                fileformat="der", keyusage=["sig"])
        kj.add_kb("", signing_key)
        capabilities = {
            "response_types_supported": ["id_token"],
            "id_token_signing_alg_values_supported": [self.sign_alg],
            "response_modes_supported": ["fragment", "query"],
            "subject_types_supported": ["public", "pairwise"],
            "grant_types_supported": ["implicit"],
            "claim_types_supported": ["normal"],
            "claims_parameter_supported": True,
            "request_parameter_supported": False,
            "request_uri_parameter_supported": False,
        }

        if "client_db_path" in self.conf:
            cdb = shelve_wrapper.open(self.conf["client_db_path"])
        else:
            cdb = {}  # client db in memory only

        self.provider = Provider(issuer, None, cdb, None, None, None, None, None, keyjar=kj,
                                 capabilities=capabilities, jwks_uri=jwks_uri)
        self.provider.baseurl = endpoint_baseurl
        self.provider.endp = [RegistrationEndpoint, AuthorizationEndpoint]
Esempio n. 18
0
 def test_key_rollover(self):
     provider2 = Provider("FOOP", {}, {}, None, None, None, None, "")
     provider2.keyjar = KEYJAR
     # Number of KeyBundles
     assert len(provider2.keyjar.issuer_keys[""]) == 1
     kb = ec_init({"type": "EC", "crv": "P-256", "use": ["sig"]})
     provider2.do_key_rollover(json.loads(kb.jwks()), "b%d")
     assert len(provider2.keyjar.issuer_keys[""]) == 2
     kb = ec_init({"type": "EC", "crv": "P-256", "use": ["sig"]})
     provider2.do_key_rollover(json.loads(kb.jwks()), "b%d")
     assert len(provider2.keyjar.issuer_keys[""]) == 3
     provider2.remove_inactive_keys(-1)
     assert len(provider2.keyjar.issuer_keys[""]) == 2
Esempio n. 19
0
 def __init__(self):
     op_base_url = TestConfiguration.get_instance().rp_config.OP_URL
     self.provider = Provider("https://op.tester.se/",
                              SessionDB(op_base_url),
                              CDB,
                              AUTHN_BROKER,
                              USERINFO,
                              AUTHZ,
                              verify_client,
                              SYMKEY,
                              urlmap=None,
                              keyjar=KEYJAR)
     self.provider.baseurl = TestConfiguration.get_instance(
     ).rp_config.OP_URL
     self.op_base = TestConfiguration.get_instance().rp_config.OP_URL
     self.redirect_urls = TestConfiguration.get_instance(
     ).rp_config.CLIENTS[PROVIDER]["client_info"]["redirect_uris"]
Esempio n. 20
0
    def __init__(self, name, sdb, cdb, function, userdb, urlmap=None,
                 debug=0, ca_certs="", jwt_keys=None):
        Provider.__init__(self, name, sdb, cdb, function, userdb, urlmap,
                          ca_certs, jwt_keys)

        if jwt_keys is None:
            jwt_keys = []

        for cid, _dic in cdb.items():
            jwt_keys.append([_dic["client_secret"], "hmac", "sig", cid])
            jwt_keys.append([_dic["client_secret"], "hmac", "ver", cid])

        self.srvmethod = OICCServer(jwt_keys=jwt_keys)
        self.keystore = self.srvmethod.keystore
        self.claims_mode = "aggregate"
        self.info_store = {}
        self.claims_userinfo_endpoint = ""
Esempio n. 21
0
    def _create_op(self, issuer, endpoint_baseurl, jwks_uri):
        """
        Create the necessary Provider instance.
        :type issuer: str
        :type endpoint_baseurl: str
        :type jwks_uri: str
        :param issuer: issuer URL for the OP
        :param endpoint_baseurl: baseurl to build endpoint URL from
        :param jwks_uri: URL to where the JWKS will be published
        """
        kj = KeyJar()
        signing_key = KeyBundle(source="file://{}".format(
            self.conf["signing_key_path"]),
                                fileformat="der",
                                keyusage=["sig"])
        kj.add_kb("", signing_key)
        capabilities = {
            "response_types_supported": ["id_token"],
            "id_token_signing_alg_values_supported": [self.sign_alg],
            "response_modes_supported": ["fragment", "query"],
            "subject_types_supported": ["public", "pairwise"],
            "grant_types_supported": ["implicit"],
            "claim_types_supported": ["normal"],
            "claims_parameter_supported": True,
            "request_parameter_supported": False,
            "request_uri_parameter_supported": False,
        }

        if "client_db_path" in self.conf:
            cdb = shelve_wrapper.open(self.conf["client_db_path"])
        else:
            cdb = {}  # client db in memory only

        self.provider = Provider(issuer,
                                 None,
                                 cdb,
                                 None,
                                 None,
                                 None,
                                 None,
                                 None,
                                 keyjar=kj,
                                 capabilities=capabilities,
                                 jwks_uri=jwks_uri)
        self.provider.baseurl = endpoint_baseurl
        self.provider.endp = [RegistrationEndpoint, AuthorizationEndpoint]
Esempio n. 22
0
def test_registered_redirect_uri_with_query_component():
    provider2 = Provider("FOOP", {}, {}, None, None)
    environ = {}

    rr = RegistrationRequest(operation="register",
                             redirect_uris=["http://example.org/cb?foo=bar"])

    registration_req = rr.to_urlencoded()
    resp = provider2.registration_endpoint(environ, start_response,
                                    query=registration_req)

    regresp = RegistrationResponse().from_json(resp[0])

    print regresp.to_dict()

    faulty = [
        "http://example.org/cb",
        "http://example.org/cb/foo",
        "http://example.org/cb?got=you",
        "http://example.org/cb?foo=you"
    ]
    correct = [
        "http://example.org/cb?foo=bar",
        "http://example.org/cb?foo=bar&got=you",
        "http://example.org/cb?foo=bar&foo=you"
    ]

    for ruri in faulty:
        areq = AuthorizationRequest(redirect_uri=ruri,
                                    client_id=regresp["client_id"],
                                    scope="openid",
                                    response_type="code")

        print areq
        assert provider2._verify_redirect_uri(areq) != None


    for ruri in correct:
        areq = AuthorizationRequest(redirect_uri= ruri,
                                    client_id=regresp["client_id"])

        resp = provider2._verify_redirect_uri(areq)
        print resp
        assert resp == None
Esempio n. 23
0
    def __init__(
        self,
        name,
        sdb,
        cdb,
        userinfo,
        client_authn,
        urlmap=None,
        keyjar=None,
        hostname="",
        dist_claims_mode=None,
        verify_ssl=True,
    ):
        Provider.__init__(
            self,
            name,
            sdb,
            cdb,
            None,
            userinfo,
            None,
            client_authn,
            None,
            urlmap,
            keyjar,
            hostname,
            verify_ssl=verify_ssl,
        )

        if keyjar is None:
            keyjar = KeyJar(verify_ssl=verify_ssl)

        for cid, _dic in cdb.items():
            try:
                keyjar.add_symmetric(cid, _dic["client_secret"],
                                     ["sig", "ver"])
            except KeyError:
                pass

        self.srvmethod = OICCServer(keyjar=keyjar)
        self.dist_claims_mode = dist_claims_mode
        self.info_store = {}  # type: Dict[str, Any]
        self.claims_userinfo_endpoint = ""
Esempio n. 24
0
 def setUp(self):
     self.server = Provider(
         "example",
         sentinel.session_db,
         {},
         None,
         sentinel.userinfo,
         sentinel.authz,
         sentinel.client_authn,
     )
     self.jwt = JWS({"iss": "some_cid"}).sign_compact()
Esempio n. 25
0
def provider(session_db):
    issuer = "https://op.example.com"
    client_db = {}  # type: Dict[str, Any]
    verification_function = verify_client
    authz_handler = AuthzHandling()
    symkey = None
    user_info_store = None
    authn_broker = None
    return Provider(issuer, session_db, client_db, authn_broker,
                    user_info_store, authz_handler, verification_function,
                    symkey)
Esempio n. 26
0
    def setup_class(self):
        self.server = Provider("pyoicserv", SessionDB(SERVER_INFO["issuer"]), CDB,
                               AUTHN_BROKER, USERINFO,
                               AUTHZ, verify_client, SYMKEY, urlmap=URLMAP,
                               keyjar=KEYJAR)

        self.cons = Consumer({}, CONSUMER_CONFIG, CLIENT_CONFIG,
                               server_info=SERVER_INFO, )
        self.cons.behaviour = {"request_object_signing_alg": DEF_SIGN_ALG["openid_request_object"]}
        self.cons.debug = True
        self.cons.keyjar[""] = KC_RSA
Esempio n. 27
0
    def __init__(self, name, sdb, cdb, authn_broker, userinfo, authz,
                 client_authn, symkey, urlmap=None, keyjar=None,
                 hostname="", configuration=None, ca_certs="",
                 template_lookup=None, verify_login_template=None,
                 base_url=""):

        OIDCProvider.__init__(self, name, sdb, cdb, authn_broker, userinfo,
                              authz, client_authn, symkey, urlmap, ca_certs,
                              keyjar, hostname, template_lookup,
                              verify_login_template)
        UmaAS.__init__(self, configuration, baseurl=base_url)

        if keyjar:
            self.keyjar = keyjar
        else:
            self.keyjar = KeyJar()

        self.hostname = hostname or socket.gethostname
        #self.jwks_uri = []
        self.endp = UmaAS.endp[:]
        self.endp.extend(OIDCProvider.endp)
Esempio n. 28
0
def provider():
    issuer = "https://op.foo.com"
    client_db = {}
    session_db = SessionDB(issuer),
    verification_function = verify_client
    authz_handler = AuthzHandling()
    symkey = None
    user_info_store = None
    authn_broker = None
    return Provider(issuer, session_db, client_db, authn_broker,
                    user_info_store, authz_handler, verification_function,
                    symkey)
Esempio n. 29
0
def main():
    parser = argparse.ArgumentParser(description='Example OIDC Provider.')
    parser.add_argument("-p", "--port", default=80, type=int)
    parser.add_argument("-b", "--base", default="https://localhost", type=str)
    parser.add_argument("-d", "--debug", action="store_true")
    parser.add_argument("settings")
    args = parser.parse_args()

    # Load configuration
    with open(args.settings, "r") as f:
        settings = yaml.load(f)

    baseurl = args.base.rstrip("/")
    issuer = "{base}:{port}".format(base=baseurl, port=args.port)

    template_dirs = settings["server"].get("template_dirs", "templates")
    jinja_env = Environment(loader=FileSystemLoader(template_dirs))
    authn_broker, auth_routing = setup_authentication_methods(settings["authn"],
                                                              jinja_env)

    # Setup userinfo
    userinfo_conf = settings["userinfo"]
    cls = make_cls_from_name(userinfo_conf["class"])
    i = cls(**userinfo_conf["kwargs"])
    userinfo = UserInfo(i)

    client_db = {}
    provider = Provider(issuer, SessionDB(baseurl), client_db, authn_broker,
                        userinfo, AuthzHandling(), verify_client, None)
    provider.baseurl = issuer
    provider.symkey = rndstr(16)

    # Setup keys
    path = os.path.join(os.path.dirname(__file__), "static")
    try:
        os.makedirs(os.path.dirname(path))
    except OSError, e:
        if e.errno != errno.EEXIST:
            raise e
        pass
Esempio n. 30
0
    def create_provider(self):
        self.provider = Provider(SERVER_INFO["issuer"], SessionDB(SERVER_INFO["issuer"]),
                                 CDB,
                                 AUTHN_BROKER, USERINFO,
                                 AUTHZ, verify_client, SYMKEY, urlmap=URLMAP,
                                 keyjar=KEYJAR)
        self.provider.baseurl = self.provider.name

        self.cons = Consumer({}, CONSUMER_CONFIG, CLIENT_CONFIG,
                             server_info=SERVER_INFO, )
        self.cons.behaviour = {
            "request_object_signing_alg": DEF_SIGN_ALG["openid_request_object"]}
        self.cons.keyjar[""] = KC_RSA
Esempio n. 31
0
def main():
    parser = argparse.ArgumentParser(description='Example OIDC Provider.')
    parser.add_argument("-p", "--port", default=80, type=int)
    parser.add_argument("-b", "--base", default="https://localhost", type=str)
    parser.add_argument("-d", "--debug", action="store_true")
    parser.add_argument("settings")
    args = parser.parse_args()

    # Load configuration
    with open(args.settings, "r") as f:
        settings = yaml.load(f)

    issuer = args.base.rstrip("/")

    template_dirs = settings["server"].get("template_dirs", "templates")
    jinja_env = Environment(loader=FileSystemLoader(template_dirs))
    authn_broker, auth_routing = setup_authentication_methods(
        settings["authn"], jinja_env)

    # Setup userinfo
    userinfo_conf = settings["userinfo"]
    cls = make_cls_from_name(userinfo_conf["class"])
    i = cls(**userinfo_conf["kwargs"])
    userinfo = UserInfo(i)

    client_db = {}
    provider = Provider(issuer, SessionDB(issuer), client_db, authn_broker,
                        userinfo, AuthzHandling(), verify_client, None)
    provider.baseurl = issuer
    provider.symkey = rndstr(16)

    # Setup keys
    path = os.path.join(os.path.dirname(__file__), "static")
    try:
        os.makedirs(path)
    except OSError, e:
        if e.errno != errno.EEXIST:
            raise e
        pass
Esempio n. 32
0
    def registration_endpoint(self, environ, start_response, **kwargs):
        before = self.cdb.keys()

        res = Provider.l_registration_endpoint(self, environ, **kwargs)

        after = [i for i in self.cdb.keys() if i not in before]
        print >> sys.stderr, after

        cid = after[0]
        for err in ["A", "B", "C"]:
            self.cdb["%s_ERR%s" % (cid,err)] = self.cdb[cid]

        return res(environ, start_response)
Esempio n. 33
0
def test_registered_redirect_uri_without_query_component():
    provider = Provider("FOO", {}, {}, None, None)
    rr = RegistrationRequest(operation="register",
                             redirect_uris=["http://example.org/cb"])

    registration_req = rr.to_urlencoded()

    provider.registration_endpoint({}, start_response,
                                   query=registration_req)

    correct = [
        "http://example.org/cb",
        "http://example.org/cb/foo",
        "http://example.org/cb?got=you",
        "http://example.org/cb/foo?got=you"
    ]
    faulty = [
        "http://example.org/foo",
        "http://example.com/cb",
    ]

    for ruri in faulty:
        areq = AuthorizationRequest(redirect_uri=ruri,
                                    client_id=provider.cdb.keys()[0],
                                    response_type="code",
                                    scope="openid")

        print areq
        assert provider._verify_redirect_uri(areq) != None


    for ruri in correct:
        areq = AuthorizationRequest(redirect_uri= ruri,
                                    client_id=provider.cdb.keys()[0])

        resp = provider._verify_redirect_uri(areq)
        if resp:
            print resp.message
        assert resp is None
Esempio n. 34
0
    def create_provider(self, session_db_factory):
        self.provider = Provider(
            SERVER_INFO["issuer"],
            session_db_factory(SERVER_INFO["issuer"]),
            self.CDB,
            AUTHN_BROKER,
            USERINFO,
            AUTHZ,
            verify_client,
            SYMKEY,
            urlmap=URLMAP,
            keyjar=KEYJAR,
        )
        self.provider.baseurl = self.provider.name
        self.provider.logout_verify_url = "https://127.0.0.1/logout_verify.html"

        self.cons = Consumer(
            DictSessionBackend(),
            CONSUMER_CONFIG.copy(),
            CLIENT_CONFIG,
            server_info=SERVER_INFO,
        )
        self.cons.behaviour = {
            "request_object_signing_alg": DEF_SIGN_ALG["openid_request_object"]
        }
        self.cons.keyjar[""] = KC_RSA
        self.cons.keyjar.import_jwks(self.provider.keyjar.export_jwks(),
                                     self.cons.issuer)

        self.cons2 = Consumer({},
                              CONSUMER_CONFIG.copy(),
                              CLIENT_CONFIG_2,
                              server_info=SERVER_INFO)
        self.cons2.behaviour = {
            "request_object_signing_alg": DEF_SIGN_ALG["openid_request_object"]
        }
        self.cons2.keyjar[""] = KC_RSA
Esempio n. 35
0
    def test_registered_redirect_uri_without_query_component(self):
        provider = Provider("FOO", {}, {}, None, None, None, None, "")
        rr = RegistrationRequest(operation="register",
                                 redirect_uris=["http://example.org/cb"],
                                 response_types=["code"])

        registration_req = rr.to_json()

        provider.registration_endpoint(request=registration_req)

        correct = [
            "http://example.org/cb",
            "http://example.org/cb/foo",
        ]
        faulty = [
            "http://example.org/foo", "http://example.com/cb",
            "http://example.org/cb?got=you",
            "http://example.org/cb/foo?got=you"
        ]

        cid = self._client_id(provider.cdb)

        for ruri in faulty:
            areq = AuthorizationRequest(redirect_uri=ruri,
                                        client_id=cid,
                                        response_type="code",
                                        scope="openid")

            print areq
            try:
                provider._verify_redirect_uri(areq)
                assert False
            except RedirectURIError:
                pass

        for ruri in correct:
            areq = AuthorizationRequest(redirect_uri=ruri,
                                        client_id=cid,
                                        response_type="code",
                                        scope="openid")

            print areq
            try:
                provider._verify_redirect_uri(areq)
            except RedirectURIError, err:
                print err
                assert False
Esempio n. 36
0
 def __init__(self):
     op_base_url = TestConfiguration.get_instance().rp_config.OP_URL
     self.provider = Provider(
         "https://op.tester.se/",
         SessionDB(op_base_url),
         CDB,
         AUTHN_BROKER,
         USERINFO,
         AUTHZ,
         verify_client,
         SYMKEY,
         urlmap=None,
         keyjar=KEYJAR
     )
     self.provider.baseurl = TestConfiguration.get_instance().rp_config.OP_URL
     self.op_base = TestConfiguration.get_instance().rp_config.OP_URL
     self.redirect_urls = TestConfiguration.get_instance().rp_config.CLIENTS[PROVIDER]["client_info"][
         "redirect_uris"]
Esempio n. 37
0
    def test_registered_redirect_uri_without_query_component(self):
        provider = Provider("FOO", {}, {}, None, None, None, None, "")
        rr = RegistrationRequest(operation="register",
                                 redirect_uris=["http://example.org/cb"],
                                 response_types=["code"])

        registration_req = rr.to_json()

        provider.registration_endpoint(request=registration_req)

        correct = [
            "http://example.org/cb",
            "http://example.org/cb/foo",
        ]
        faulty = [
            "http://example.org/foo",
            "http://example.com/cb",
            "http://example.org/cb?got=you",
            "http://example.org/cb/foo?got=you"
        ]

        cid = self._client_id(provider.cdb)

        for ruri in faulty:
            areq = AuthorizationRequest(redirect_uri=ruri,
                                        client_id=cid,
                                        response_type="code",
                                        scope="openid")

            print areq
            try:
                provider._verify_redirect_uri(areq)
                assert False
            except RedirectURIError:
                pass

        for ruri in correct:
            areq = AuthorizationRequest(redirect_uri=ruri,
                                        client_id=cid,
                                        response_type="code", scope="openid")

            print areq
            try:
                provider._verify_redirect_uri(areq)
            except RedirectURIError, err:
                print err
                assert False
Esempio n. 38
0
    def test_registered_redirect_uri_with_query_component(self):
        provider2 = Provider("FOOP", {}, {}, None, None, None, None, "")

        rr = RegistrationRequest(
            operation="register",
            redirect_uris=["http://example.org/cb?foo=bar"],
            response_types=["code"])

        registration_req = rr.to_json()
        resp = provider2.registration_endpoint(request=registration_req)

        regresp = RegistrationResponse().from_json(resp.message)

        print regresp.to_dict()

        faulty = [
            "http://example.org/cb", "http://example.org/cb/foo",
            "http://example.org/cb?got=you", "http://example.org/cb?foo=you"
            "http://example.org/cb?foo=bar&got=you",
            "http://example.org/cb?foo=you&foo=bar"
        ]
        correct = [
            "http://example.org/cb?foo=bar",
        ]

        cid = regresp["client_id"]

        for ruri in faulty:
            areq = AuthorizationRequest(redirect_uri=ruri,
                                        client_id=cid,
                                        scope="openid",
                                        response_type="code")

            print areq
            try:
                provider2._verify_redirect_uri(areq)
            except RedirectURIError:
                pass

        for ruri in correct:
            areq = AuthorizationRequest(redirect_uri=ruri,
                                        client_id=cid,
                                        scope="openid",
                                        response_type="code")

            resp = provider2._verify_redirect_uri(areq)
            print resp
            assert resp is None
Esempio n. 39
0
    def _get_user_info(self, user_attributes, requested_claims=None, scopes=None):
        """
        Filter user attributes to return to the client  (as claims in the ID Token) based on what
        was requested in request 'claims' parameter and in the 'scope'.
        :type user_attributes: dict[str, str]
        :type requested_claims: dict[str, Optional[dict]]
        :type scopes: list[str]
        :rtype: dict[str, str]

        :param user_attributes: attributes provided by the backend
        :param requested_claims: claims requested by the client through the 'claims' request param
        :param scopes: the scopes requested by the client
        :return: all attributes/claims to return to the client
        """
        requested_claims = requested_claims or {}
        scopes = scopes or []
        claims_requested_by_scope = Provider._scope2claims(scopes)
        claims_requested_by_scope.update(
                requested_claims)  # let explicit claims request override scope

        return UserInfo().filter(user_attributes, claims_requested_by_scope)
Esempio n. 40
0
    def _get_user_info(self,
                       user_attributes,
                       requested_claims=None,
                       scopes=None):
        """
        Filter user attributes to return to the client  (as claims in the ID Token) based on what
        was requested in request 'claims' parameter and in the 'scope'.
        :type user_attributes: dict[str, str]
        :type requested_claims: dict[str, Optional[dict]]
        :type scopes: list[str]
        :rtype: dict[str, str]

        :param user_attributes: attributes provided by the backend
        :param requested_claims: claims requested by the client through the 'claims' request param
        :param scopes: the scopes requested by the client
        :return: all attributes/claims to return to the client
        """
        requested_claims = requested_claims or {}
        scopes = scopes or []
        claims_requested_by_scope = Provider._scope2claims(scopes)
        claims_requested_by_scope.update(
            requested_claims)  # let explicit claims request override scope

        return UserInfo().filter(user_attributes, claims_requested_by_scope)
Esempio n. 41
0
    def __init__(self, base_url, client_metadata_func):
        # Read OP configuration from file
        with open("conf/op_config.json", "r") as f:
            op_capabilities = json.load(f)
        for key, value in op_capabilities.iteritems():
            if isinstance(value, basestring):
                op_capabilities[key] = value.format(base=base_url)  # replace placeholder with the actual base name

        self.OP = Provider(base_url, {}, client_metadata_func, None, None, None, None, None,
                           capabilities=op_capabilities)
        self.OP.baseurl = op_capabilities["issuer"]

        # Setup up keys for signing and encrypting
        self.OP.keyjar = KeyJar()
        kb = keybundle_from_local_file("inAcademia", "RSA", ["sig", "enc"])
        self.OP.keyjar.add_kb("", kb)

        try:
            file_name = "static/jwks.json"
            dump_jwks([kb], file_name)
            self.OP.jwks_uri.append("{}/{}".format(base_url, file_name))
        except Exception as e:
            logger.exception("Signing and encryption keys could not be written to jwks.json.")
            raise
Esempio n. 42
0
        "template": {"form_post": "form_response.mako"},
        # "template_args": {"form_post": {"action": "form_post"}}
    }

    # Should I care about verifying the certificates used by other entities
    if args.insecure:
        kwargs["verify_ssl"] = False
    else:
        kwargs["verify_ssl"] = True

    if args.capabilities:
        kwargs["capabilities"] = json.loads(open(args.capabilities).read())
    else:
        pass

    OAS = Provider(_issuer, SessionDB(_issuer), cdb, ac, None,
                   authz, verify_client, config.SYM_KEY, **kwargs)
    OAS.baseurl = _issuer

    for authn in ac:
        authn.srv = OAS

    if config.USERINFO == "SIMPLE":
        # User info is a simple dictionary in this case statically defined in
        # the configuration file
        OAS.userinfo = UserInfo(config.USERDB)
    elif config.USERINFO == "SAML":
        OAS.userinfo = UserInfo(config.SAML)
    elif config.USERINFO == "AA":
        OAS.userinfo = AaUserInfo(config.SP_CONFIG, _issuer, config.SAML)
    else:
        raise Exception("Unsupported userinfo source")
Esempio n. 43
0
        kwargs = {"verify_ssl": False}
    else:
        kwargs = {"verify_ssl": True}

    if args.test:
        URLS.append((r'tracelog', trace_log))
        OAS = TestProvider(config.issuer, SessionDB(config.baseurl), cdb, ac,
                           None, authz, config.SYM_KEY)
    elif args.XpressConnect:
        from XpressConnect import XpressConnectProvider

        OAS = XpressConnectProvider(config.issuer, SessionDB(config.baseurl),
                                    cdb, ac, None, authz, verify_client,
                                    config.SYM_KEY)
    else:
        OAS = Provider(config.issuer, SessionDB(config.baseurl), cdb, ac, None,
                       authz, verify_client, config.SYM_KEY, **kwargs)

    try:
        OAS.cookie_ttl = config.COOKIETTL
    except AttributeError:
        pass

    try:
        OAS.cookie_name = config.COOKIENAME
    except AttributeError:
        pass

    #print URLS
    if args.debug:
        OAS.debug = True
    if args.test:
Esempio n. 44
0
def main():
    parser = argparse.ArgumentParser(description='Example OIDC Provider.')
    parser.add_argument("-p", "--port", default=80, type=int)
    parser.add_argument("-b", "--base", default="https://localhost", type=str)
    parser.add_argument("-d", "--debug", action="store_true")
    parser.add_argument("settings")
    args = parser.parse_args()

    # Load configuration
    with open(args.settings, "r") as f:
        settings = yaml.load(f)

    issuer = args.base.rstrip("/")

    template_dirs = settings["server"].get("template_dirs", "templates")
    jinja_env = Environment(loader=FileSystemLoader(template_dirs))
    authn_broker, auth_routing = setup_authentication_methods(settings["authn"],
                                                              jinja_env)

    # Setup userinfo
    userinfo_conf = settings["userinfo"]
    cls = make_cls_from_name(userinfo_conf["class"])
    i = cls(**userinfo_conf["kwargs"])
    userinfo = UserInfo(i)

    client_db = {}
    session_db = create_session_db(issuer,
                                   secret=rndstr(32), password=rndstr(32))
    provider = Provider(issuer, session_db, client_db, authn_broker,
                        userinfo, AuthzHandling(), verify_client, None)
    provider.baseurl = issuer
    provider.symkey = rndstr(16)

    # Setup keys
    path = os.path.join(os.path.dirname(__file__), "static")
    try:
        os.makedirs(path)
    except OSError as e:
        if e.errno != errno.EEXIST:
            raise e
        pass
    jwks = keyjar_init(provider, settings["provider"]["keys"])
    name = "jwks.json"
    with open(os.path.join(path, name), "w") as f:
        f.write(json.dumps(jwks))

    #TODO: I take this out and it still works, what was this for?
    #provider.jwks_uri.append(
    #    "{}/static/{}".format(provider.baseurl, name))

    # Mount the WSGI callable object (app) on the root directory
    app_routing = setup_endpoints(provider)
    app_routing["/.well-known/openid-configuration"] = pyoidcMiddleware(
        provider.providerinfo_endpoint)
    app_routing["/.well-known/webfinger"] = pyoidcMiddleware(
        partial(_webfinger, provider))
    routing = dict(list(auth_routing.items()) + list(app_routing.items()))
    routing["/static"] = make_static_handler(path)
    dispatcher = WSGIPathInfoDispatcher(routing)
    server = wsgiserver.CherryPyWSGIServer(('0.0.0.0', args.port), dispatcher)

    # Setup SSL
    if provider.baseurl.startswith("https://"):
        server.ssl_adapter = BuiltinSSLAdapter(
        settings["server"]["cert"], settings["server"]["key"],
        settings["server"]["cert_chain"])

    # Start the CherryPy WSGI web server
    try:
        print("Server started: {}".format(issuer))
        server.start()
    except KeyboardInterrupt:
        server.stop()
Esempio n. 45
0
class TestProvider(object):
    @pytest.fixture(autouse=True)
    def create_provider(self, session_db_factory):
        self.provider = Provider(SERVER_INFO["issuer"],
                                 session_db_factory(SERVER_INFO["issuer"]),
                                 CDB,
                                 AUTHN_BROKER,
                                 USERINFO,
                                 AUTHZ,
                                 verify_client,
                                 SYMKEY,
                                 urlmap=URLMAP,
                                 keyjar=KEYJAR)
        self.provider.baseurl = self.provider.name

        self.cons = Consumer(
            {},
            CONSUMER_CONFIG,
            CLIENT_CONFIG,
            server_info=SERVER_INFO,
        )
        self.cons.behaviour = {
            "request_object_signing_alg": DEF_SIGN_ALG["openid_request_object"]
        }
        self.cons.keyjar[""] = KC_RSA

    def test_providerinfo(self):
        self.provider.baseurl = 'http://example.com/path1/path2'
        resp = self.provider.create_providerinfo()
        assert resp.to_dict(
        )['authorization_endpoint'] == 'http://example.com/path1/path2/authorization'

    def test_providerinfo_trailing(self):
        self.provider.baseurl = 'http://example.com/path1/path2/'
        resp = self.provider.create_providerinfo()
        assert resp.to_dict(
        )['authorization_endpoint'] == 'http://example.com/path1/path2/authorization'

    def test_authorization_endpoint(self):
        bib = {
            "scope": ["openid"],
            "state": "id-6da9ca0cc23959f5f33e8becd9b08cae",
            "redirect_uri": "http://*****:*****@patch('oic.oic.provider.utc_time_sans_frac', Mock(return_value=123456))
    def test_client_secret_expiration_time(self):
        exp_time = self.provider.client_secret_expiration_time()
        assert exp_time == 209856

    def test_registration_endpoint(self):
        req = RegistrationRequest()

        req["application_type"] = "web"
        req["client_name"] = "My super service"
        req["redirect_uris"] = ["http://example.com/authz"]
        req["contacts"] = ["*****@*****.**"]
        req["response_types"] = ["code"]

        resp = self.provider.registration_endpoint(request=req.to_json())

        regresp = RegistrationResponse().deserialize(resp.message, "json")
        assert _eq(regresp.keys(), [
            'redirect_uris', 'contacts', 'application_type', 'client_name',
            'registration_client_uri', 'client_secret_expires_at',
            'registration_access_token', 'client_id', 'client_secret',
            'client_id_issued_at', 'response_types'
        ])

    def test_registration_endpoint_unicode(self):
        data = 'application_type=web&client_name=M%C3%A1+supe%C5%99+service&' \
               'redirect_uris=http%3A%2F%2Fexample.com%2Fauthz&response_types=code'
        resp = self.provider.registration_endpoint(request=data)

        regresp = RegistrationResponse().deserialize(resp.message, "json")
        assert _eq(regresp.keys(), [
            'redirect_uris', 'application_type', 'client_name',
            'registration_client_uri', 'client_secret_expires_at',
            'registration_access_token', 'client_id', 'client_secret',
            'client_id_issued_at', 'response_types'
        ])

    def test_registration_endpoint_with_non_https_redirect_uri_implicit_flow(
            self):
        params = {
            "application_type": "web",
            "redirect_uris": ["http://example.com/authz"],
            "response_types": ["id_token", "token"]
        }
        req = RegistrationRequest(**params)
        resp = self.provider.registration_endpoint(request=req.to_json())

        assert resp.status == "400 Bad Request"
        error = json.loads(resp.message)
        assert error["error"] == "invalid_redirect_uri"

    def test_verify_redirect_uris_with_https_code_flow(self):
        params = {
            "application_type": "web",
            "redirect_uris": ["http://example.com/authz"],
            "response_types": ["code"]
        }
        request = RegistrationRequest(**params)
        verified_uris = self.provider.verify_redirect_uris(request)
        assert verified_uris == [("http://example.com/authz", None)]

    def test_verify_redirect_uris_with_non_https_redirect_uri_implicit_flow(
            self):
        params = {
            "application_type": "web",
            "redirect_uris": ["http://example.com/authz"],
            "response_types": ["id_token", "token"]
        }
        request = RegistrationRequest(**params)

        with pytest.raises(InvalidRedirectURIError) as exc_info:
            self.provider.verify_redirect_uris(request)

        assert str(exc_info.value) == "None https redirect_uri not allowed"

    def test_provider_key_setup(self, tmpdir, session_db_factory):
        path = tmpdir.strpath
        provider = Provider("pyoicserv",
                            session_db_factory(SERVER_INFO["issuer"]), None,
                            None, None, None, None, None)
        provider.baseurl = "http://www.example.com"
        provider.key_setup(path, path, sig={"format": "jwk", "alg": "RSA"})

        keys = provider.keyjar.get_signing_key("RSA")
        assert len(keys) == 1
        assert provider.jwks_uri == "http://www.example.com/{}/jwks".format(
            path)

    @pytest.mark.parametrize("uri", [
        "http://example.org/foo", "http://example.com/cb",
        "http://example.org/cb?got=you", "http://example.org/cb/foo?got=you"
    ])
    def test_verify_redirect_uri_faulty_without_query(self, uri):
        rr = RegistrationRequest(operation="register",
                                 redirect_uris=["http://example.org/cb"],
                                 response_types=["code"])
        registration_req = rr.to_json()

        resp = self.provider.registration_endpoint(request=registration_req)
        regresp = RegistrationResponse().from_json(resp.message)
        cid = regresp["client_id"]

        areq = AuthorizationRequest(redirect_uri=uri,
                                    client_id=cid,
                                    response_type="code",
                                    scope="openid")

        with pytest.raises(RedirectURIError):
            self.provider._verify_redirect_uri(areq)

    @pytest.mark.parametrize("uri", [
        "http://example.org/cb",
    ])
    def test_verify_redirect_uri_correct_without_query(self, uri):
        rr = RegistrationRequest(operation="register",
                                 redirect_uris=["http://example.org/cb"],
                                 response_types=["code"])
        registration_req = rr.to_json()
        resp = self.provider.registration_endpoint(request=registration_req)
        regresp = RegistrationResponse().from_json(resp.message)
        cid = regresp["client_id"]

        areq = AuthorizationRequest(redirect_uri=uri,
                                    client_id=cid,
                                    response_type="code",
                                    scope="openid")

        self.provider._verify_redirect_uri(areq)

    def test_verify_sector_identifier_nonreachable(self):
        rr = RegistrationRequest(operation="register",
                                 sector_identifier_uri="https://example.com")
        with responses.RequestsMock() as rsps, LogCapture(
                level=logging.DEBUG) as logcap:
            rsps.add(rsps.GET, "https://example.com", status=404)
            message = "Couldn't open sector_identifier_uri"
            with pytest.raises(InvalidSectorIdentifier, message=message):
                self.provider._verify_sector_identifier(rr)

        assert len(logcap.records) == 0

    def test_verify_sector_identifier_error(self):
        rr = RegistrationRequest(operation="register",
                                 sector_identifier_uri="https://example.com")
        error = ConnectionError('broken connection')
        with responses.RequestsMock() as rsps, LogCapture(
                level=logging.DEBUG) as logcap:
            rsps.add(rsps.GET, "https://example.com", body=error)
            with pytest.raises(InvalidSectorIdentifier,
                               message="Couldn't open sector_identifier_uri"):
                self.provider._verify_sector_identifier(rr)

        assert len(logcap.records) == 2
        # First log record is from server...
        assert logcap.records[1].msg == error

    def test_verify_sector_identifier_malformed(self):
        rr = RegistrationRequest(operation="register",
                                 sector_identifier_uri="https://example.com")
        body = "This is not the JSON you are looking for"
        with responses.RequestsMock() as rsps, LogCapture(
                level=logging.DEBUG) as logcap:
            rsps.add(rsps.GET, "https://example.com", body=body)
            with pytest.raises(
                    InvalidSectorIdentifier,
                    message="Error deserializing sector_identifier_uri content"
            ):
                self.provider._verify_sector_identifier(rr)

        assert len(logcap.records) == 1
        assert logcap.records[0].msg == "sector_identifier_uri => %s"
        assert logcap.records[0].args == (body, )

    def test_verify_sector_identifier_ru_missing_in_si(self):
        """Redirect_uris is not present in the sector_identifier_uri content."""
        rr = RegistrationRequest(operation="register",
                                 sector_identifier_uri="https://example.com",
                                 redirect_uris=["http://example.com/missing"])
        with responses.RequestsMock() as rsps, LogCapture(
                level=logging.DEBUG) as logcap:
            rsps.add(rsps.GET,
                     "https://example.com",
                     body=json.dumps(["http://example.com/present"]))
            with pytest.raises(
                    InvalidSectorIdentifier,
                    message="redirect uri missing from sector_identifiers"):
                self.provider._verify_sector_identifier(rr)

        assert len(logcap.records) == 2
        assert logcap.records[0].msg == "sector_identifier_uri => %s"
        assert logcap.records[0].args == ('["http://example.com/present"]', )
        assert logcap.records[1].msg == "redirect_uris: %s"
        assert logcap.records[1].args == (["http://example.com/missing"], )

    def test_verify_sector_identifier_ru_missing(self):
        """Redirect_uris is not present in the request."""
        rr = RegistrationRequest(operation="register",
                                 sector_identifier_uri="https://example.com")
        redirects = ["http://example.com/present"]

        with responses.RequestsMock() as rsps, LogCapture(
                level=logging.DEBUG) as logcap:
            rsps.add(rsps.GET,
                     "https://example.com",
                     body=json.dumps(redirects))
            si_redirects, si_url = self.provider._verify_sector_identifier(rr)

        assert si_url == "https://example.com"
        assert si_redirects == redirects
        assert len(logcap.records) == 1
        assert logcap.records[0].msg == "sector_identifier_uri => %s"
        assert logcap.records[0].args == ('["http://example.com/present"]', )

    def test_verify_sector_identifier_ru_ok(self):
        """Redirect_uris is present in the sector_identifier_uri content."""
        rr = RegistrationRequest(operation="register",
                                 sector_identifier_uri="https://example.com",
                                 redirect_uris=["http://example.com/present"])
        redirects = ["http://example.com/present"]

        with responses.RequestsMock() as rsps, LogCapture(
                level=logging.DEBUG) as logcap:
            rsps.add(rsps.GET,
                     "https://example.com",
                     body=json.dumps(redirects))
            si_redirects, si_url = self.provider._verify_sector_identifier(rr)

        assert si_url == "https://example.com"
        assert si_redirects == redirects
        assert len(logcap.records) == 2
        assert logcap.records[0].msg == "sector_identifier_uri => %s"
        assert logcap.records[0].args == ('["http://example.com/present"]', )
        assert logcap.records[1].msg == "redirect_uris: %s"
        assert logcap.records[1].args == (["http://example.com/present"], )

    @pytest.mark.parametrize("uri", [
        "http://example.org/cb", "http://example.org/cb?got=you",
        "http://example.org/cb?foo=you"
        "http://example.org/cb?foo=bar&got=you",
        "http://example.org/cb?foo=you&foo=bar"
    ])
    def test_registered_redirect_uri_faulty_with_query_component(self, uri):
        rr = RegistrationRequest(
            operation="register",
            redirect_uris=["http://example.org/cb?foo=bar"],
            response_types=["code"])

        registration_req = rr.to_json()
        resp = self.provider.registration_endpoint(request=registration_req)
        regresp = RegistrationResponse().from_json(resp.message)
        cid = regresp["client_id"]

        areq = AuthorizationRequest(redirect_uri=uri,
                                    client_id=cid,
                                    scope="openid",
                                    response_type="code")

        with pytest.raises(RedirectURIError):
            self.provider._verify_redirect_uri(areq)

    def test_registered_redirect_uri_correct_with_query_component(self):
        rr = RegistrationRequest(
            operation="register",
            redirect_uris=["http://example.org/cb?foo=bar"],
            response_types=["code"])

        registration_req = rr.to_json()
        resp = self.provider.registration_endpoint(request=registration_req)
        regresp = RegistrationResponse().from_json(resp.message)
        cid = regresp["client_id"]

        areq = AuthorizationRequest(
            redirect_uri="http://example.org/cb?foo=bar",
            client_id=cid,
            scope="openid",
            response_type="code")

        self.provider._verify_redirect_uri(areq)

    def test_verify_redirect_uri_native_http_localhost(self):
        areq = RegistrationRequest(redirect_uris=["http://localhost/cb"],
                                   application_type='native')

        self.provider.verify_redirect_uris(areq)

    def test_verify_redirect_uri_native_loopback(self):
        areq = RegistrationRequest(redirect_uris=["http://127.0.0.1/cb"],
                                   application_type='native')

        self.provider.verify_redirect_uris(areq)

    def test_verify_redirect_uri_native_http_non_localhost(self):
        areq = RegistrationRequest(redirect_uris=["http://example.org/cb"],
                                   application_type='native')

        try:
            self.provider.verify_redirect_uris(areq)
        except InvalidRedirectURIError:
            assert True

    def test_verify_redirect_uri_native_custom(self):
        areq = RegistrationRequest(
            redirect_uris=["com.example.app:/oauth2redirect"],
            application_type='native')

        self.provider.verify_redirect_uris(areq)

    def test_verify_redirect_uri_native_https(self):
        areq = RegistrationRequest(redirect_uris=["https://example.org/cb"],
                                   application_type='native')

        try:
            self.provider.verify_redirect_uris(areq)
        except InvalidRedirectURIError:
            assert True

    def test_read_registration(self):
        rr = RegistrationRequest(operation="register",
                                 redirect_uris=["http://example.org/new"],
                                 response_types=["code"])
        registration_req = rr.to_json()
        resp = self.provider.registration_endpoint(request=registration_req)
        regresp = RegistrationResponse().from_json(resp.message)

        authn = ' '.join(['Bearer', regresp['registration_access_token']])
        query = '='.join(['client_id', regresp['client_id']])
        resp = self.provider.read_registration(authn, query)

        assert json.loads(resp.message) == regresp.to_dict()

    def test_read_registration_wrong_authn(self):
        resp = self.provider.read_registration('wrong string', 'request')
        assert resp.status == '400 Bad Request'
        assert json.loads(resp.message) == {
            'error': 'invalid_request',
            'error_description': None
        }

    def test_key_rollover(self):
        provider2 = Provider("FOOP", {}, {}, None, None, None, None, None)
        provider2.keyjar = KEYJAR
        # Number of KeyBundles
        assert len(provider2.keyjar.issuer_keys[""]) == 1
        kb = ec_init({"type": "EC", "crv": "P-256", "use": ["sig"]})
        provider2.do_key_rollover(json.loads(kb.jwks()), "b%d")
        assert len(provider2.keyjar.issuer_keys[""]) == 2
        kb = ec_init({"type": "EC", "crv": "P-256", "use": ["sig"]})
        provider2.do_key_rollover(json.loads(kb.jwks()), "b%d")
        assert len(provider2.keyjar.issuer_keys[""]) == 3
        provider2.remove_inactive_keys(-1)
        assert len(provider2.keyjar.issuer_keys[""]) == 2

    def test_endsession_endpoint(self):
        resp = self.provider.endsession_endpoint("")
        self._assert_cookies_expired(resp.headers)

        # End session not allowed if no cookie is sent (can't determine session)
        resp = self.provider.endsession_endpoint("", cookie="FAIL")
        assert resp.status == "400 Bad Request"

    def test_endsession_endpoint_with_id_token_hint(self):
        id_token = self._auth_with_id_token()
        assert self.provider.sdb.get_sids_by_sub(
            id_token["sub"])  # verify we got valid session

        id_token_hint = id_token.to_jwt(algorithm="none")
        resp = self.provider.endsession_endpoint(
            urlencode({"id_token_hint": id_token_hint}))
        assert not self.provider.sdb.get_sids_by_sub(
            id_token["sub"])  # verify session has been removed
        self._assert_cookies_expired(resp.headers)

    def test_endsession_endpoint_with_post_logout_redirect_uri(self):
        id_token = self._auth_with_id_token()
        assert self.provider.sdb.get_sids_by_sub(
            id_token["sub"])  # verify we got valid session

        post_logout_redirect_uri = \
            CDB[CLIENT_CONFIG["client_id"]]["post_logout_redirect_uris"][0][0]
        resp = self.provider.endsession_endpoint(
            urlencode({"post_logout_redirect_uri": post_logout_redirect_uri}))
        assert isinstance(resp, SeeOther)
        assert not self.provider.sdb.get_sids_by_sub(
            id_token["sub"])  # verify session has been removed
        self._assert_cookies_expired(resp.headers)

    def test_session_state_in_auth_req_for_session_support(
            self, session_db_factory):
        provider = Provider(SERVER_INFO["issuer"],
                            session_db_factory(SERVER_INFO["issuer"]),
                            CDB,
                            AUTHN_BROKER,
                            USERINFO,
                            AUTHZ,
                            verify_client,
                            SYMKEY,
                            urlmap=URLMAP,
                            keyjar=KEYJAR)

        provider.capabilities.update(
            {"check_session_iframe": "https://op.example.com/check_session"})

        req_args = {
            "scope": ["openid"],
            "redirect_uri": "http://localhost:8087/authz",
            "response_type": ["code"],
            "client_id": "number5"
        }
        areq = AuthorizationRequest(**req_args)
        resp = provider.authorization_endpoint(request=areq.to_urlencoded())
        aresp = self.cons.parse_response(AuthorizationResponse,
                                         resp.message,
                                         sformat="urlencoded")
        assert "session_state" in aresp

    def _assert_cookies_expired(self, http_headers):
        cookies_string = ";".join(
            [c[1] for c in http_headers if c[0] == "Set-Cookie"])
        all_cookies = SimpleCookie()

        try:
            cookies_string = cookies_string.decode()
        except (AttributeError, UnicodeDecodeError):
            pass

        all_cookies.load(cookies_string)

        now = datetime.datetime.utcnow()  #
        for c in [
                self.provider.cookie_name, self.provider.session_cookie_name
        ]:
            dt = datetime.datetime.strptime(all_cookies[c]["expires"],
                                            "%a, %d-%b-%Y %H:%M:%S GMT")
            assert dt < now  # make sure the cookies have expired to be cleared

    def _auth_with_id_token(self):
        state, location = self.cons.begin("openid",
                                          "id_token",
                                          path="http://localhost:8087")
        resp = self.provider.authorization_endpoint(
            request=location.split("?")[1])
        aresp = self.cons.parse_response(AuthorizationResponse,
                                         resp.message,
                                         sformat="urlencoded")
        return aresp["id_token"]

    def test_id_token_RS512_sign(self):
        self.provider.capabilities['id_token_signing_alg_values_supported'] = [
            'RS512'
        ]
        self.provider.build_jwx_def()
        id_token = self._auth_with_id_token()
        assert id_token.jws_header['alg'] == "RS512"

    def test_refresh_access_token_request(self):
        authreq = AuthorizationRequest(state="state",
                                       redirect_uri="http://example.com/authz",
                                       client_id=CLIENT_ID,
                                       response_type="code",
                                       scope=["openid", 'offline_access'],
                                       prompt='consent')

        _sdb = self.provider.sdb
        sid = _sdb.access_token.key(user="******", areq=authreq)
        access_grant = _sdb.access_token(sid=sid)
        ae = AuthnEvent("user", "salt")
        _sdb[sid] = {
            "oauth_state": "authz",
            "authn_event": ae.to_json(),
            "authzreq": authreq.to_json(),
            "client_id": CLIENT_ID,
            "code": access_grant,
            "code_used": False,
            "scope": ["openid", 'offline_access'],
            "redirect_uri": "http://example.com/authz",
        }
        _sdb.do_sub(sid, "client_salt")

        # Construct Access token request
        areq = AccessTokenRequest(code=access_grant,
                                  client_id=CLIENT_ID,
                                  redirect_uri="http://example.com/authz",
                                  client_secret=CLIENT_SECRET,
                                  grant_type='authorization_code')

        txt = areq.to_urlencoded()

        resp = self.provider.token_endpoint(request=txt)
        atr = AccessTokenResponse().deserialize(resp.message, "json")

        rareq = RefreshAccessTokenRequest(grant_type="refresh_token",
                                          refresh_token=atr['refresh_token'],
                                          client_id=CLIENT_ID,
                                          client_secret=CLIENT_SECRET,
                                          scope=['openid'])

        resp = self.provider.token_endpoint(request=rareq.to_urlencoded())
        atr2 = AccessTokenResponse().deserialize(resp.message, "json")
        assert atr2['access_token'] != atr['access_token']
        assert atr2['refresh_token'] == atr['refresh_token']
        assert atr2['token_type'] == 'Bearer'
Esempio n. 46
0
 def __init__(self, name, sdb, cdb, function, userdb, urlmap=None,
              debug=0, ca_certs="", jwt_keys=None):
     Provider.__init__(self, name, sdb, cdb, function, userdb, urlmap,
                       ca_certs, jwt_keys)
Esempio n. 47
0
class TestProvider(object):
    @pytest.fixture(autouse=True)
    def create_provider(self):
        self.provider = Provider("pyoicserv", SessionDB(SERVER_INFO["issuer"]),
                                 CDB,
                                 AUTHN_BROKER, USERINFO,
                                 AUTHZ, verify_client, SYMKEY, urlmap=URLMAP,
                                 keyjar=KEYJAR)
        self.provider.baseurl = self.provider.name

        self.cons = Consumer({}, CONSUMER_CONFIG, CLIENT_CONFIG,
                             server_info=SERVER_INFO, )
        self.cons.behaviour = {
            "request_object_signing_alg": DEF_SIGN_ALG["openid_request_object"]}
        self.cons.keyjar[""] = KC_RSA

    def test_authorization_endpoint(self):
        bib = {"scope": ["openid"],
               "state": "id-6da9ca0cc23959f5f33e8becd9b08cae",
               "redirect_uri": "http://*****:*****@example.com"]
        req["response_types"] = ["code"]

        resp = self.provider.registration_endpoint(request=req.to_json())

        regresp = RegistrationResponse().deserialize(resp.message, "json")
        assert _eq(regresp.keys(),
                   ['redirect_uris', 'contacts', 'application_type',
                    'client_name', 'registration_client_uri',
                    'client_secret_expires_at',
                    'registration_access_token',
                    'client_id', 'client_secret',
                    'client_id_issued_at', 'response_types'])

    def test_registration_endpoint_with_non_https_redirect_uri_implicit_flow(
            self):
        params = {"application_type": "web",
                  "redirect_uris": ["http://example.com/authz"],
                  "response_types": ["id_token", "token"]}
        req = RegistrationRequest(**params)
        resp = self.provider.registration_endpoint(request=req.to_json())

        assert resp.status == "400 Bad Request"
        error = json.loads(resp.message)
        assert error["error"] == "invalid_redirect_uri"

    def test_verify_redirect_uris_with_https_code_flow(self):
        params = {"application_type": "web",
                  "redirect_uris": ["http://example.com/authz"],
                  "response_types": ["code"]}
        request = RegistrationRequest(**params)
        verified_uris = self.provider._verify_redirect_uris(request)
        assert verified_uris == [("http://example.com/authz", None)]

    def test_verify_redirect_uris_with_non_https_redirect_uri_implicit_flow(self):
        params = {"application_type": "web",
                  "redirect_uris": ["http://example.com/authz"],
                  "response_types": ["id_token", "token"]}
        request = RegistrationRequest(**params)

        with pytest.raises(InvalidRedirectURIError) as exc_info:
            self.provider._verify_redirect_uris(request)

        assert str(exc_info.value) == "None https redirect_uri not allowed"

    @pytest.mark.network
    def test_registration_endpoint_openid4us(self):
        req = RegistrationRequest(
            **{'token_endpoint_auth_method': u'client_secret_post',
               'redirect_uris': [
                   u'https://connect.openid4.us:5443/phpRp/index.php/callback',
                   u'https://connect.openid4.us:5443/phpRp/authcheck.php/authcheckcb'],
               'jwks_uri': u'https://connect.openid4.us:5443/phpRp/rp/rp.jwk',
               'userinfo_encrypted_response_alg': u'RSA1_5',
               'contacts': [u'*****@*****.**'],
               'userinfo_encrypted_response_enc': u'A128CBC-HS256',
               'application_type': u'web',
               'client_name': u'ABRP-17',
               'grant_types': [u'authorization_code', u'implicit'],
               'post_logout_redirect_uris': [
                   u'https://connect.openid4.us:5443/phpRp/index.php/logoutcb'],
               'subject_type': u'public',
               'response_types': [u'code', u'token', u'id_token', u'code token',
                                  u'code id_token', u'id_token token',
                                  u'code id_token token'],
               'policy_uri': u'https://connect.openid4.us:5443/phpRp/index.php/policy',
               'logo_uri': u'https://connect.openid4.us:5443/phpRp/media/logo.png'})

        resp = self.provider.registration_endpoint(request=req.to_json())

        regresp = RegistrationResponse().deserialize(resp.message, "json")
        assert _eq(regresp.keys(), list(req.keys()) +
                   ['registration_client_uri',
                    'client_secret_expires_at',
                    'registration_access_token',
                    'client_id', 'client_secret',
                    'client_id_issued_at'])

    def test_provider_key_setup(self, tmpdir):
        path = tmpdir.strpath
        provider = Provider("pyoicserv", SessionDB(SERVER_INFO["issuer"]), None,
                            None, None, None, None, "")
        provider.baseurl = "http://www.example.com"
        provider.key_setup(path, path, sig={"format": "jwk", "alg": "RSA"})

        keys = provider.keyjar.get_signing_key("RSA")
        assert len(keys) == 1
        assert provider.jwks_uri == "http://www.example.com/{}/jwks".format(
            path)

    @pytest.mark.parametrize("uri", [
        "http://example.org/foo",
        "http://example.com/cb",
        "http://example.org/cb?got=you",
        "http://example.org/cb/foo?got=you"
    ])
    def test_verify_redirect_uri_faulty_without_query(self, uri):
        rr = RegistrationRequest(operation="register",
                                 redirect_uris=["http://example.org/cb"],
                                 response_types=["code"])
        registration_req = rr.to_json()

        resp = self.provider.registration_endpoint(request=registration_req)
        regresp = RegistrationResponse().from_json(resp.message)
        cid = regresp["client_id"]

        areq = AuthorizationRequest(redirect_uri=uri,
                                    client_id=cid,
                                    response_type="code",
                                    scope="openid")

        with pytest.raises(RedirectURIError):
            self.provider._verify_redirect_uri(areq)

    @pytest.mark.parametrize("uri", [
        "http://example.org/cb",
        "http://example.org/cb/foo"
    ])
    def test_verify_redirect_uri_correct_without_query(self, uri):
        rr = RegistrationRequest(operation="register",
                                 redirect_uris=["http://example.org/cb"],
                                 response_types=["code"])
        registration_req = rr.to_json()
        resp = self.provider.registration_endpoint(request=registration_req)
        regresp = RegistrationResponse().from_json(resp.message)
        cid = regresp["client_id"]

        areq = AuthorizationRequest(redirect_uri=uri,
                                    client_id=cid,
                                    response_type="code",
                                    scope="openid")

        self.provider._verify_redirect_uri(areq)

    @pytest.mark.parametrize("uri", [
        "http://example.org/cb",
        "http://example.org/cb/foo",
        "http://example.org/cb?got=you",
        "http://example.org/cb?foo=you"
        "http://example.org/cb?foo=bar&got=you",
        "http://example.org/cb?foo=you&foo=bar"
    ])
    def test_registered_redirect_uri_faulty_with_query_component(self, uri):
        rr = RegistrationRequest(operation="register",
                                 redirect_uris=[
                                     "http://example.org/cb?foo=bar"],
                                 response_types=["code"])

        registration_req = rr.to_json()
        resp = self.provider.registration_endpoint(request=registration_req)
        regresp = RegistrationResponse().from_json(resp.message)
        cid = regresp["client_id"]

        areq = AuthorizationRequest(redirect_uri=uri,
                                    client_id=cid,
                                    scope="openid",
                                    response_type="code")

        with pytest.raises(RedirectURIError):
            self.provider._verify_redirect_uri(areq)

    def test_registered_redirect_uri_correct_with_query_component(self):
        rr = RegistrationRequest(operation="register",
                                 redirect_uris=[
                                     "http://example.org/cb?foo=bar"],
                                 response_types=["code"])

        registration_req = rr.to_json()
        resp = self.provider.registration_endpoint(request=registration_req)
        regresp = RegistrationResponse().from_json(resp.message)
        cid = regresp["client_id"]

        areq = AuthorizationRequest(
            redirect_uri="http://example.org/cb?foo=bar",
            client_id=cid, scope="openid",
            response_type="code")

        self.provider._verify_redirect_uri(areq)

    def test_key_rollover(self):
        provider2 = Provider("FOOP", {}, {}, None, None, None, None, "")
        provider2.keyjar = KEYJAR
        # Number of KeyBundles
        assert len(provider2.keyjar.issuer_keys[""]) == 1
        kb = ec_init({"type": "EC", "crv": "P-256", "use": ["sig"]})
        provider2.do_key_rollover(json.loads(kb.jwks()), "b%d")
        assert len(provider2.keyjar.issuer_keys[""]) == 2
        kb = ec_init({"type": "EC", "crv": "P-256", "use": ["sig"]})
        provider2.do_key_rollover(json.loads(kb.jwks()), "b%d")
        assert len(provider2.keyjar.issuer_keys[""]) == 3
        provider2.remove_inactive_keys(-1)
        assert len(provider2.keyjar.issuer_keys[""]) == 2

    def test_endsession_endpoint(self):
        resp = self.provider.endsession_endpoint("")
        self._assert_cookies_expired(resp.headers)

        # End session not allowed if no cookie is sent (can't determine session)
        resp = self.provider.endsession_endpoint("", cookie="FAIL")
        assert resp.status == "400 Bad Request"

    def test_endsession_endpoint_with_id_token_hint(self):
        id_token = self._auth_with_id_token()
        assert self.provider.sdb.get_sids_by_sub(
            id_token["sub"])  # verify we got valid session

        id_token_hint = id_token.to_jwt(algorithm="none")
        resp = self.provider.endsession_endpoint(
            urlencode({"id_token_hint": id_token_hint}))
        assert not self.provider.sdb.get_sids_by_sub(
            id_token["sub"])  # verify session has been removed
        self._assert_cookies_expired(resp.headers)

    def test_endsession_endpoint_with_post_logout_redirect_uri(self):
        id_token = self._auth_with_id_token()
        assert self.provider.sdb.get_sids_by_sub(
            id_token["sub"])  # verify we got valid session

        post_logout_redirect_uri = \
            CDB[CLIENT_CONFIG["client_id"]]["post_logout_redirect_uris"][0][0]
        resp = self.provider.endsession_endpoint(urlencode(
            {"post_logout_redirect_uri": post_logout_redirect_uri}))
        assert isinstance(resp, Redirect)
        assert not self.provider.sdb.get_sids_by_sub(
            id_token["sub"])  # verify session has been removed
        self._assert_cookies_expired(resp.headers)

    def test_session_state_in_auth_req_for_session_support(self):
        provider = Provider("foo", SessionDB(SERVER_INFO["issuer"]), CDB,
                            AUTHN_BROKER, USERINFO,
                            AUTHZ, verify_client, SYMKEY, urlmap=URLMAP,
                            keyjar=KEYJAR, capabilities={
                "check_session_iframe": "https://op.example.com/check_session"})

        req_args = {"scope": ["openid"],
                    "redirect_uri": "http://localhost:8087/authz",
                    "response_type": ["code"],
                    "client_id": "a1b2c3"
                    }
        areq = AuthorizationRequest(**req_args)
        resp = provider.authorization_endpoint(
            request=areq.to_urlencoded())
        aresp = self.cons.parse_response(AuthorizationResponse, resp.message,
                                         sformat="urlencoded")
        assert "session_state" in aresp

    def _assert_cookies_expired(self, http_headers):
        cookies_string = ";".join(
            [c[1] for c in http_headers if c[0] == "Set-Cookie"])
        all_cookies = SimpleCookie()
        all_cookies.load(cookies_string)

        now = datetime.datetime.now()
        for c in [self.provider.cookie_name, self.provider.session_cookie_name]:
            dt = datetime.datetime.strptime(all_cookies[c]["expires"],
                                            "%a, %d-%b-%Y %H:%M:%S GMT")
            assert dt < now  # make sure the cookies have expired to be cleared

    def _auth_with_id_token(self):
        state, location = self.cons.begin("openid", "id_token",
                                          path="http://localhost:8087")
        resp = self.provider.authorization_endpoint(
            request=location.split("?")[1])
        aresp = self.cons.parse_response(AuthorizationResponse, resp.message,
                                         sformat="urlencoded")
        return aresp["id_token"]
Esempio n. 48
0
class TestOICProvider(object):
    def setup_class(self):
        self.server = Provider("pyoicserv", SessionDB(SERVER_INFO["issuer"]), CDB,
                               AUTHN_BROKER, USERINFO,
                               AUTHZ, verify_client, SYMKEY, urlmap=URLMAP,
                               keyjar=KEYJAR)

        self.cons = Consumer({}, CONSUMER_CONFIG, CLIENT_CONFIG,
                               server_info=SERVER_INFO, )
        self.cons.behaviour = {"request_object_signing_alg": DEF_SIGN_ALG["openid_request_object"]}
        self.cons.debug = True
        self.cons.keyjar[""] = KC_RSA

    def test_server_init(self):
        assert self.server
        assert self.server.authn_broker == AUTHN_BROKER
        print self.server.urlmap
        assert self.server.urlmap["client_1"] == ["https://example.com/authz"]

    def test_server_authorization_endpoint(self):
        bib = {"scope": ["openid"],
               "state": "id-6da9ca0cc23959f5f33e8becd9b08cae",
               "redirect_uri": "http://*****:*****@example.com"]
        req["response_types"] = ["code"]

        print req.to_dict()

        resp = self.server.registration_endpoint(request=req.to_json())

        print resp.message
        regresp = RegistrationResponse().deserialize(resp.message, "json")
        print regresp.keys()
        assert _eq(regresp.keys(), ['redirect_uris', 'contacts', 'application_type',
                                    'client_name', 'registration_client_uri',
                                    'client_secret_expires_at',
                                    'registration_access_token',
                                    'client_id', 'client_secret',
                                    'client_id_issued_at', 'response_types'])

    def test_provider_key_setup(self):
        provider = Provider("pyoicserv", SessionDB(SERVER_INFO["issuer"]), None,
                            None, None, None, None, "")
        provider.baseurl = "http://www.example.com/"
        provider.key_setup("static", sig={"format": "jwk", "alg": "RSA"})

        keys = provider.keyjar.get_signing_key("RSA")
        assert len(keys) == 1
        assert provider.jwks_uri == "http://www.example.com/static/jwks"

    def _client_id(self, cdb):
        cid = None
        for k, item in cdb.items():
            if item in cdb.keys():
                cid = item
                break

        return cid

    def test_registered_redirect_uri_without_query_component(self):
        provider = Provider("FOO", {}, {}, None, None, None, None, "")
        rr = RegistrationRequest(operation="register",
                                 redirect_uris=["http://example.org/cb"],
                                 response_types=["code"])

        registration_req = rr.to_json()

        provider.registration_endpoint(request=registration_req)

        correct = [
            "http://example.org/cb",
            "http://example.org/cb/foo",
        ]
        faulty = [
            "http://example.org/foo",
            "http://example.com/cb",
            "http://example.org/cb?got=you",
            "http://example.org/cb/foo?got=you"
        ]

        cid = self._client_id(provider.cdb)

        for ruri in faulty:
            areq = AuthorizationRequest(redirect_uri=ruri,
                                        client_id=cid,
                                        response_type="code",
                                        scope="openid")

            print areq
            try:
                provider._verify_redirect_uri(areq)
                assert False
            except RedirectURIError:
                pass

        for ruri in correct:
            areq = AuthorizationRequest(redirect_uri=ruri,
                                        client_id=cid,
                                        response_type="code", scope="openid")

            print areq
            try:
                provider._verify_redirect_uri(areq)
            except RedirectURIError, err:
                print err
                assert False
Esempio n. 49
0
    # In-Memory non-persistent SessionDB issuing DefaultTokens
    sessionDB = create_session_db(config.ISSUER,
                                  secret=rndstr(32),
                                  password=rndstr(32))

    provider = Provider(
        name=config.ISSUER,  # name
        sdb=sessionDB,  # session database.
        cdb=clientDB,  # client database
        authn_broker=authnBroker,  # authn broker
        userinfo=None,  # user information
        authz=authz,  # authz
        client_authn=verify_client,  # client authentication
        symkey=config.SYM_KEY,  # Used for Symmetric key authentication
        # urlmap = None,                               # ?
        # keyjar = None,                               # ?
        # hostname = "",                               # ?
        template_renderer=mako_renderer,  # Rendering custom templates
        # verify_ssl = True,                           # Enable SSL certs
        # capabilities = None,                         # ?
        # schema = OpenIDSchema,                       # ?
        # jwks_uri = '',                               # ?
        # jwks_name = '',                              # ?
        baseurl=config.ISSUER,
        # client_cert = None                           # ?
    )

    # SessionDB:
    # This is database where the provider keeps information about
    # the authenticated/authorised users. It includes information
    # such as "what has been asked for (claims, scopes, and etc. )"
Esempio n. 50
0
class OIDCFrontend(FrontendModule):
    """
    A OpenID Connect frontend module
    """

    MANDATORY_CONFIG = {"issuer", "signing_key_path"}

    def __init__(self, auth_req_callback_func, internal_attributes, conf):
        self._validate_config(conf)
        super(OIDCFrontend, self).__init__(auth_req_callback_func,
                                           internal_attributes)

        self.state_id = type(self).__name__
        self.sign_alg = "RS256"
        self.subject_type_default = "pairwise"
        self.conf = conf

    def handle_authn_response(self, context, internal_resp):
        """
        See super class method satosa.frontends.base.FrontendModule#handle_authn_response
        :type context: satosa.context.Context
        :type internal_response: satosa.internal_data.InternalResponse
        :rtype oic.utils.http_util.Response
        """
        auth_req = self._get_authn_request_from_state(context.state)

        # filter attributes to return in ID Token as claims
        attributes = self.converter.from_internal(
            "openid", internal_resp.get_attributes())
        satosa_logging(
            LOGGER, logging.DEBUG,
            "Attributes delivered by backend to OIDC frontend: {}".format(
                json.dumps(attributes)), context.state)
        flattened_attributes = {k: v[0] for k, v in attributes.items()}
        requested_id_token_claims = auth_req.get("claims", {}).get("id_token")
        user_claims = self._get_user_info(flattened_attributes,
                                          requested_id_token_claims,
                                          auth_req["scope"])
        satosa_logging(
            LOGGER, logging.DEBUG,
            "Attributes filtered by requested claims/scope: {}".format(
                json.dumps(user_claims)), context.state)

        # construct epoch timestamp of reported authentication time
        auth_time = datetime.datetime.strptime(
            internal_resp.auth_info.timestamp, "%Y-%m-%dT%H:%M:%SZ")
        epoch_timestamp = (auth_time -
                           datetime.datetime(1970, 1, 1)).total_seconds()

        base_claims = {
            "client_id": auth_req["client_id"],
            "sub": internal_resp.get_user_id(),
            "nonce": auth_req["nonce"]
        }
        id_token = self.provider.id_token_as_signed_jwt(
            base_claims,
            user_info=user_claims,
            auth_time=epoch_timestamp,
            loa="",
            alg=self.sign_alg)

        oidc_client_state = auth_req.get("state")
        kwargs = {}
        if oidc_client_state:  # inlcude any optional 'state' sent by the client in the authn req
            kwargs["state"] = oidc_client_state

        auth_resp = AuthorizationResponse(id_token=id_token, **kwargs)
        http_response = auth_resp.request(
            auth_req["redirect_uri"], self._should_fragment_encode(auth_req))
        return SeeOther(http_response)

    def handle_backend_error(self, exception):
        """
        See super class satosa.frontends.base.FrontendModule
        :type exception: satosa.exception.SATOSAError
        :rtype: oic.utils.http_util.Response
        """
        auth_req = self._get_authn_request_from_state(exception.state)
        error_resp = AuthorizationErrorResponse(
            error="access_denied", error_description=exception.message)
        satosa_logging(LOGGER, logging.DEBUG, exception.message,
                       exception.state)
        return SeeOther(
            error_resp.request(auth_req["redirect_uri"],
                               self._should_fragment_encode(auth_req)))

    def register_endpoints(self, providers):
        """
        See super class satosa.frontends.base.FrontendModule
        :type providers: list[str]
        :rtype: list[(str, ((satosa.context.Context, Any) -> satosa.response.Response, Any))]
        :raise ValueError: if more than one backend is configured
        """
        if len(providers) != 1:
            raise ValueError(
                "OpenID Connect frontend only supports one backend.")
        backend = providers[0]

        endpoint_baseurl = "{}/{}".format(self.conf["issuer"], backend)
        jwks_uri = "{}/jwks".format(self.conf["issuer"])
        self._create_op(self.conf["issuer"], endpoint_baseurl, jwks_uri)

        provider_config = ("^.well-known/openid-configuration$",
                           self._provider_config)
        jwks_uri = ("^jwks$", self._jwks)
        dynamic_client_registration = ("^{}/{}".format(
            backend, RegistrationEndpoint.url), self._register_client)
        authentication = ("^{}/{}".format(backend, AuthorizationEndpoint.url),
                          self.handle_authn_request)

        url_map = [
            provider_config, jwks_uri, dynamic_client_registration,
            authentication
        ]
        return url_map

    def _create_op(self, issuer, endpoint_baseurl, jwks_uri):
        """
        Create the necessary Provider instance.
        :type issuer: str
        :type endpoint_baseurl: str
        :type jwks_uri: str
        :param issuer: issuer URL for the OP
        :param endpoint_baseurl: baseurl to build endpoint URL from
        :param jwks_uri: URL to where the JWKS will be published
        """
        kj = KeyJar()
        signing_key = KeyBundle(source="file://{}".format(
            self.conf["signing_key_path"]),
                                fileformat="der",
                                keyusage=["sig"])
        kj.add_kb("", signing_key)
        capabilities = {
            "response_types_supported": ["id_token"],
            "id_token_signing_alg_values_supported": [self.sign_alg],
            "response_modes_supported": ["fragment", "query"],
            "subject_types_supported": ["public", "pairwise"],
            "grant_types_supported": ["implicit"],
            "claim_types_supported": ["normal"],
            "claims_parameter_supported": True,
            "request_parameter_supported": False,
            "request_uri_parameter_supported": False,
        }

        if "client_db_path" in self.conf:
            cdb = shelve_wrapper.open(self.conf["client_db_path"])
        else:
            cdb = {}  # client db in memory only

        self.provider = Provider(issuer,
                                 None,
                                 cdb,
                                 None,
                                 None,
                                 None,
                                 None,
                                 None,
                                 keyjar=kj,
                                 capabilities=capabilities,
                                 jwks_uri=jwks_uri)
        self.provider.baseurl = endpoint_baseurl
        self.provider.endp = [RegistrationEndpoint, AuthorizationEndpoint]

    def _get_user_info(self,
                       user_attributes,
                       requested_claims=None,
                       scopes=None):
        """
        Filter user attributes to return to the client  (as claims in the ID Token) based on what
        was requested in request 'claims' parameter and in the 'scope'.
        :type user_attributes: dict[str, str]
        :type requested_claims: dict[str, Optional[dict]]
        :type scopes: list[str]
        :rtype: dict[str, str]

        :param user_attributes: attributes provided by the backend
        :param requested_claims: claims requested by the client through the 'claims' request param
        :param scopes: the scopes requested by the client
        :return: all attributes/claims to return to the client
        """
        requested_claims = requested_claims or {}
        scopes = scopes or []
        claims_requested_by_scope = Provider._scope2claims(scopes)
        claims_requested_by_scope.update(
            requested_claims)  # let explicit claims request override scope

        return UserInfo().filter(user_attributes, claims_requested_by_scope)

    def _validate_config(self, config):
        """
        Validates that all necessary config parameters are specified.
        :type config: dict[str, dict[str, Any] | str]
        :param config: the module config
        """
        if config is None:
            raise ValueError("OIDCFrontend conf can't be 'None'.")

        for k in self.MANDATORY_CONFIG:
            if k not in config:
                raise ValueError(
                    "Missing configuration parameter '{}' for OpenID Connect frontend."
                    .format(k))

    def _should_fragment_encode(self, authn_req):
        """
        Determine, based on the clients request, whether the authentication/error response should
        be fragment encoded or not.
        :type authn_req: oic.oic.message.AuthorizationRequest
        :rtype: bool

        :param authn_req: parsed authentication request from the client
        :return: True if the response should be fragment encoded
        """
        return authn_req.get("response_mode", "fragment") == "fragment"

    def _get_authn_request_from_state(self, state):
        """
        Extract the clietns request stoed in the SATOSA state.
        :type state: satosa.state.State
        :rtype: oic.oic.message.AuthorizationRequest

        :param state: the current state
        :return: the parsed authentication request
        """
        stored_state = state.get(self.state_id)
        oidc_request = stored_state["oidc_request"]
        return AuthorizationRequest().deserialize(oidc_request)

    def _register_client(self, context):
        """
        Handle the OIDC dynamic client registration.
        :type context: satosa.context.Context
        :rtype: oic.utils.http_util.Response

        :param context: the current context
        :return: HTTP response to the client
        """
        http_resp = self.provider.registration_endpoint(
            json.dumps(context.request))
        if not isinstance(http_resp, Created):
            return http_resp

        return self._fixup_registration_response(http_resp)

    def _fixup_registration_response(self, http_resp):
        # remove client_secret since no token endpoint is published
        response = RegistrationResponse().deserialize(http_resp.message,
                                                      "json")
        del response["client_secret"]
        # specify supported id token signing alg
        response["id_token_signed_response_alg"] = self.sign_alg

        http_resp.message = response.to_json()
        return http_resp

    def _provider_config(self, context):
        """
        Construct the provider configuration information (served at /.well-known/openid-configuration).
        :type context: satosa.context.Context
        :rtype: oic.utils.http_util.Response

        :param context: the current context
        :return: HTTP response to the client
        """
        http_resp = self.provider.providerinfo_endpoint()
        if not isinstance(http_resp, Response):
            return http_resp
        provider_config = ProviderConfigurationResponse().deserialize(
            http_resp.message, "json")
        del provider_config["token_endpoint_auth_methods_supported"]
        del provider_config["require_request_uri_registration"]

        http_resp.message = provider_config.to_json()
        return http_resp

    def handle_authn_request(self, context):
        """
        Parse and verify the authentication request and pass it on to the backend.
        :type context: satosa.context.Context
        :rtype: oic.utils.http_util.Response

        :param context: the current context
        :return: HTTP response to the client
        """

        # verify auth req (correct redirect_uri, contains nonce and response_type='id_token')
        request = urlencode(context.request)
        satosa_logging(LOGGER, logging.DEBUG,
                       "Authn req from client: {}".format(request),
                       context.state)

        info = self.provider.auth_init(request,
                                       request_class=AuthorizationRequest)
        if isinstance(info, Response):
            satosa_logging(LOGGER, logging.ERROR,
                           "Error in authn req: {}".format(info.message),
                           context.state)
            return info

        client_id = info["areq"]["client_id"]

        context.state.add(self.state_id, {"oidc_request": request})
        hash_type = oidc_subject_type_to_hash_type(
            self.provider.cdb[client_id].get("subject_type",
                                             self.subject_type_default))
        internal_req = InternalRequest(
            hash_type, client_id,
            self.provider.cdb[client_id].get("client_name"))

        return self.auth_req_callback_func(context, internal_req)

    def _jwks(self, context):
        """
        Construct the JWKS document (served at /jwks).
        :type context: satosa.context.Context
        :rtype: oic.utils.http_util.Response

        :param context: the current context
        :return: HTTP response to the client
        """
        return Response(json.dumps(self.provider.keyjar.export_jwks()),
                        content="application/json")
Esempio n. 51
0
    kwargs = {
        "template_lookup": LOOKUP,
        "template": {
            "form_post": "form_response.mako"
        },
        #"template_args": {"form_post": {"action": "form_post"}}
    }

    # Should I care about verifying the certificates used other entities
    if args.insecure:
        kwargs["verify_ssl"] = False
    else:
        kwargs["verify_ssl"] = True

    OAS = Provider(config.issuer, SessionDB(), cdb, ac, None, authz,
                   verify_client, config.SYM_KEY, **kwargs)

    for authn in ac:
        authn.srv = OAS

    if config.USERINFO == "SIMPLE":
        # User info is a simple dictionary in this case statically defined in
        # the configuration file
        OAS.userinfo = UserInfo(config.USERDB)
    elif config.USERINFO == "SAML":
        OAS.userinfo = UserInfo(config.SAML)
    else:
        raise Exception("Unsupported userinfo source")

    try:
        OAS.cookie_ttl = config.COOKIETTL
Esempio n. 52
0
class TestProvider(object):
    CDB = {
        "number5": {
            "password":
            "******",
            "client_secret":
            "drickyoughurt",
            "redirect_uris": [("http://localhost:8087/authz", None)],
            "post_logout_redirect_uris":
            [("https://example.com/post_logout", None)],
            "client_salt":
            "salted",
            "response_types": [
                "code",
                "token",
                "code id_token",
                "none",
                "code token",
                "id_token",
            ],
        },
        "a1b2c3": {
            "redirect_uris": [("http://localhost:8088/authz", None)],
            "client_salt": "salted",
            "client_secret": "very_secret",
            "response_types": ["code", "token", "code id_token"],
        },
        "client0": {
            "redirect_uris": [("http://www.example.org/authz", None)],
            "client_secret":
            "very_secret",
            "post_logout_redirect_uris":
            [("https://www.example.org/post_logout", None)],
            "client_salt":
            "salted",
            "response_types": ["code", "token", "code id_token"],
        },
        CLIENT_ID: {
            "client_secret": CLIENT_SECRET,
            "redirect_uris": [("http://localhost:8087/authz", None)],
            "client_salt": "salted",
            "token_endpoint_auth_method": "client_secret_post",
            "response_types": ["code", "token", "code id_token"],
        },
    }  # type: Dict[str, Dict[str, Any]]

    @pytest.fixture(autouse=True)
    def create_provider(self, session_db_factory):
        self.provider = Provider(
            SERVER_INFO["issuer"],
            session_db_factory(SERVER_INFO["issuer"]),
            self.CDB,
            AUTHN_BROKER,
            USERINFO,
            AUTHZ,
            verify_client,
            SYMKEY,
            urlmap=URLMAP,
            keyjar=KEYJAR,
        )
        self.provider.baseurl = self.provider.name
        self.provider.logout_verify_url = "https://127.0.0.1/logout_verify.html"

        self.cons = Consumer(
            DictSessionBackend(),
            CONSUMER_CONFIG.copy(),
            CLIENT_CONFIG,
            server_info=SERVER_INFO,
        )
        self.cons.behaviour = {
            "request_object_signing_alg": DEF_SIGN_ALG["openid_request_object"]
        }
        self.cons.keyjar[""] = KC_RSA
        self.cons.keyjar.import_jwks(self.provider.keyjar.export_jwks(),
                                     self.cons.issuer)

        self.cons2 = Consumer({},
                              CONSUMER_CONFIG.copy(),
                              CLIENT_CONFIG_2,
                              server_info=SERVER_INFO)
        self.cons2.behaviour = {
            "request_object_signing_alg": DEF_SIGN_ALG["openid_request_object"]
        }
        self.cons2.keyjar[""] = KC_RSA

    def _code_auth(self):
        state, location = self.cons.begin("openid",
                                          "code",
                                          path="http://localhost:8087")
        return self.provider.authorization_endpoint(
            request=location.split("?")[1])

    def _code_auth2(self):
        state, location = self.cons2.begin("openid",
                                           "code",
                                           path="http://www.example.org")
        return self.provider.authorization_endpoint(
            request=location.split("?")[1])

    def _auth_with_id_token(self):
        state, location = self.cons.begin("openid",
                                          "id_token",
                                          path="http://localhost:8087")
        resp = self.provider.authorization_endpoint(
            request=location.split("?")[1])
        aresp = self.cons.parse_response(AuthorizationResponse,
                                         resp.message,
                                         sformat="urlencoded")
        return aresp["id_token"]

    def _create_cookie(self, user, client_id, c_type="sso"):
        cd = CookieDealer(self.provider)
        set_cookie = cd.create_cookie("{}][{}".format(user, client_id), c_type,
                                      self.provider.sso_cookie_name)
        cookies_string = set_cookie[1]
        all_cookies = SimpleCookie()  # type: SimpleCookie

        try:
            cookies_string = cookies_string.decode()
        except (AttributeError, UnicodeDecodeError):
            pass

        all_cookies.load(cookies_string)

        return all_cookies

    def test_missing_post_logout_redirect_uri(self):
        esr = EndSessionRequest(state="foo")
        assert self.provider.verify_post_logout_redirect_uri(esr,
                                                             CLIENT_ID) is None

    def test_wrong_post_logout_redirect_uri(self):
        self.provider.cdb[CLIENT_ID]["post_logout_redirect_uris"] = [
            "https://example.com/plru"
        ]
        esr = EndSessionRequest(
            state="foo",
            post_logout_redirect_uri="https://localhost:8087/plru")
        assert self.provider.verify_post_logout_redirect_uri(esr,
                                                             CLIENT_ID) is None

    def test_no_post_logout_redirect_uri(self):
        self.provider.cdb[CLIENT_ID]["post_logout_redirect_uris"] = [
            "https://example.com/plru",
            "https://example.com/plru2",
        ]
        esr = EndSessionRequest(state="foo")

        assert self.provider.verify_post_logout_redirect_uri(esr,
                                                             CLIENT_ID) is None

    def test_let_user_verify_logout(self):
        self.provider.cdb[CLIENT_ID]["post_logout_redirect_uris"] = [
            "https://localhost:8087/plru"
        ]
        esr = EndSessionRequest(
            state="foo",
            post_logout_redirect_uri="https://localhost:8087/plru")
        res = self.provider.let_user_verify_logout("user", esr, None, None)
        assert isinstance(res, Response)
        assert res.headers == [("Content-type", "text/html")]
        assert res.status_code == 200

    def test_let_user_verify_logout_with_cookie(self):
        self.provider.cdb[CLIENT_ID]["post_logout_redirect_uris"] = [
            "https://localhost:8087/plru"
        ]
        esr = EndSessionRequest(
            state="foo",
            post_logout_redirect_uri="https://localhost:8087/plru")
        res = self.provider.let_user_verify_logout("user", esr,
                                                   [("Set-Cookie", "kaka")],
                                                   None)
        assert isinstance(res, Response)
        assert set(res.headers) == {
            ("Content-type", "text/html"),
            ("Set-Cookie", "kaka"),
        }
        assert res.status_code == 200

    def test_let_user_verify_logout_with_redirect(self):
        self.provider.cdb[CLIENT_ID]["post_logout_redirect_uris"] = [
            "https://localhost:8087/plru"
        ]
        esr = EndSessionRequest(
            state="foo",
            post_logout_redirect_uri="https://localhost:8087/plru")
        res = self.provider.let_user_verify_logout(
            "user", esr, None, "https://example.com/redirect")
        assert isinstance(res, Response)
        assert set(res.headers) == {("Content-type", "text/html")}
        assert res.status_code == 200
        # make sure the redirect was propagated
        txt = '<input type="hidden" name="{}" value="{}"/>'.format(
            "post_logout_redirect_uri", "https://localhost:8087/plru")
        assert txt in res.message

    def test_let_user_verify_logout_with_id_token_hint(self):
        self.provider.cdb[CLIENT_ID]["post_logout_redirect_uris"] = [
            "https://localhost:8087/plru"
        ]

        esr = EndSessionRequest(
            state="foo",
            post_logout_redirect_uri="https://localhost:8087/plru",
            id_token_hint="J.W.S",
        )
        res = self.provider.let_user_verify_logout("user", esr, None, None)
        assert isinstance(res, Response)
        assert set(res.headers) == {("Content-type", "text/html")}
        assert res.status_code == 200
        # make sure the id_token_hint was propagated
        txt = '<input type="hidden" name="{}" value="{}"/>'.format(
            "id_token_hint", "J.W.S")
        assert txt in res.message

    def test_end_session_endpoint_with_cookie(self):
        self.provider.events = DummyEventStore()

        self._code_auth()
        cookie = self._create_cookie("username", "number5")

        resp = self.provider.end_session_endpoint(urlencode({"state":
                                                             "abcde"}),
                                                  cookie=cookie)

        # returns a SeeOther instance
        p = urlparse(resp.message)
        qs = parse_qs(p.query)

        jwt_info = self.provider.unpack_signed_jwt(qs["sjwt"][0])

        assert jwt_info["state"] == "abcde"
        assert jwt_info["uid"] == "username"
        assert jwt_info["client_id"] == "number5"
        assert jwt_info["redirect_uri"] == "https://example.com/post_logout"

    def test_end_session_endpoint_with_wrong_cookie(self):
        # Need cookie and ID Token to figure this out
        id_token = self._auth_with_id_token()
        assert session_get(self.provider.sdb, "sub",
                           id_token["sub"])  # verify we got valid session

        id_token_hint = id_token.to_jwt(algorithm="none")
        cookie = self._create_cookie("diggins", "number5")

        resp = self.provider.end_session_endpoint(urlencode(
            {"id_token_hint": id_token_hint}),
                                                  cookie=cookie)

        assert isinstance(resp, Response)
        _err = ErrorResponse().from_json(resp.message)
        assert _err["error"] == "invalid_request"
        assert _err["error_description"] == "Wrong user"

    def test_end_session_endpoint_with_cookie_wrong_user(self):
        # Need cookie and ID Token to figure this out
        id_token = self._auth_with_id_token()
        assert session_get(self.provider.sdb, "sub", id_token["sub"])

        id_token_hint = id_token.to_jwt(algorithm="none")
        cookie = self._create_cookie("diggins", "number5")

        resp = self.provider.end_session_endpoint(urlencode(
            {"id_token_hint": id_token_hint}),
                                                  cookie=cookie)

        assert isinstance(resp, Response)
        _err = ErrorResponse().from_json(resp.message)
        assert _err["error"] == "invalid_request"
        assert _err["error_description"] == "Wrong user"

    def test_end_session_endpoint_with_cookie_wrong_client(self):
        # Need cookie and ID Token to figure this out
        id_token = self._auth_with_id_token()
        assert session_get(self.provider.sdb, "sub", id_token["sub"])

        id_token_hint = id_token.to_jwt(algorithm="none")
        # Wrong client_id
        cookie = self._create_cookie("username", "a1b2c3")

        resp = self.provider.end_session_endpoint(urlencode(
            {"id_token_hint": id_token_hint}),
                                                  cookie=cookie)

        assert isinstance(resp, Response)
        _err = ErrorResponse().from_json(resp.message)
        assert _err["error"] == "invalid_request"

    def test_end_session_endpoint_with_cookie_dual_login(self):
        self._code_auth()
        self._code_auth2()
        cookie = self._create_cookie("username", "client0")

        resp = self.provider.end_session_endpoint(urlencode({"state":
                                                             "abcde"}),
                                                  cookie=cookie)

        # returns a SeeOther instance
        p = urlparse(resp.message)
        qs = parse_qs(p.query)

        jwt_info = self.provider.unpack_signed_jwt(qs["sjwt"][0])

        assert jwt_info["state"] == "abcde"
        assert jwt_info["uid"] == "username"
        assert jwt_info["client_id"] == "client0"
        assert jwt_info[
            "redirect_uri"] == "https://www.example.org/post_logout"

    def test_end_session_endpoint_with_cookie_dual_login_wrong_client(self):
        self._code_auth()
        self._code_auth2()
        # The cookie states that a user has a session at a client and this
        # statement is false.
        cookie = self._create_cookie("username", "a1b2c3")

        resp = self.provider.end_session_endpoint(urlencode({"state":
                                                             "abcde"}),
                                                  cookie=cookie)

        assert isinstance(resp, Response)
        _err = ErrorResponse().from_json(resp.message)
        assert _err["error"] == "invalid_request"

    def test_end_session_endpoint_with_id_token_hint_only(self):
        id_token = self._auth_with_id_token()
        assert session_get(self.provider.sdb, "sub", id_token["sub"])

        id_token_hint = id_token.to_jwt(algorithm="none")

        resp = self.provider.end_session_endpoint(
            urlencode({"id_token_hint": id_token_hint}))

        # returns a SeeOther instance
        p = urlparse(resp.message)
        qs = parse_qs(p.query)

        jwt_info = self.provider.unpack_signed_jwt(qs["sjwt"][0])

        assert jwt_info["uid"] == "username"
        assert jwt_info["client_id"] == "number5"
        assert jwt_info["redirect_uri"] == "https://example.com/post_logout"

    def test_end_session_endpoint_with_id_token_hint_and_cookie(self):
        id_token = self._auth_with_id_token()
        assert session_get(self.provider.sdb, "sub", id_token["sub"])

        id_token_hint = id_token.to_jwt(algorithm="none")
        cookie = self._create_cookie("username", "number5")

        resp = self.provider.end_session_endpoint(urlencode(
            {"id_token_hint": id_token_hint}),
                                                  cookie=cookie)

        # returns a SeeOther instance
        p = urlparse(resp.message)
        qs = parse_qs(p.query)

        jwt_info = self.provider.unpack_signed_jwt(qs["sjwt"][0])

        assert jwt_info["uid"] == "username"
        assert jwt_info["client_id"] == "number5"
        assert jwt_info["redirect_uri"] == "https://example.com/post_logout"

    def test_end_session_endpoint_with_post_logout_redirect_uri(self):
        self._code_auth()
        cookie = self._create_cookie("username", "number5")

        post_logout_redirect_uri = self.CDB[str(
            CLIENT_CONFIG["client_id"])]["post_logout_redirect_uris"][0][0]
        resp = self.provider.end_session_endpoint(
            urlencode({
                "post_logout_redirect_uri": post_logout_redirect_uri,
                "state": "abcde"
            }),
            cookie=cookie,
        )

        # returns a SeeOther instance
        p = urlparse(resp.message)
        qs = parse_qs(p.query)

        jwt_info = self.provider.unpack_signed_jwt(qs["sjwt"][0])

        assert jwt_info["state"] == "abcde"
        assert jwt_info["uid"] == "username"
        assert jwt_info["client_id"] == "number5"
        assert jwt_info["redirect_uri"] == "https://example.com/post_logout"

    def test_end_session_endpoint_without_post_logout_redirect_uri(self):
        # default post logout page registered
        self.provider.post_logout_page = "https://foo.example.com/def_post"
        # No post_logout_redirect_uris registered
        _plru = self.provider.cdb["number5"]["post_logout_redirect_uris"]
        del self.provider.cdb["number5"]["post_logout_redirect_uris"]
        self._code_auth()
        cookie = self._create_cookie("username", "number5")

        resp = self.provider.end_session_endpoint(urlencode({"state":
                                                             "abcde"}),
                                                  cookie=cookie)

        # returns a SeeOther instance
        p = urlparse(resp.message)
        qs = parse_qs(p.query)

        jwt_info = self.provider.unpack_signed_jwt(qs["sjwt"][0])
        assert jwt_info["state"] == "abcde"
        assert jwt_info["uid"] == "username"
        assert jwt_info["client_id"] == "number5"
        assert jwt_info["redirect_uri"] == "https://foo.example.com/def_post"

        # restore
        self.provider.cdb["number5"]["post_logout_redirect_uris"] = _plru

    def test_end_session_endpoint_without_post_logout_redirect_uri_no_default(
            self):
        # No post_logout_redirect_uris registered
        _plru = self.provider.cdb["number5"]["post_logout_redirect_uris"]
        del self.provider.cdb["number5"]["post_logout_redirect_uris"]
        self._code_auth()
        cookie = self._create_cookie("username", "number5")

        resp = self.provider.end_session_endpoint(urlencode({"state":
                                                             "abcde"}),
                                                  cookie=cookie)

        assert isinstance(resp, Response)
        _err = ErrorResponse().from_json(resp.message)
        assert _err["error"] == "server_error"
        # restore
        self.provider.cdb["number5"]["post_logout_redirect_uris"] = _plru

    def test_end_session_endpoint_bogus_sjwt(self):
        self._code_auth()
        cookie = self._create_cookie("username", "number5")

        post_logout_redirect_uri = self.CDB[str(
            CLIENT_CONFIG["client_id"])]["post_logout_redirect_uris"][0][0]
        resp = self.provider.end_session_endpoint(
            urlencode({
                "post_logout_redirect_uri": post_logout_redirect_uri,
                "state": "abcde"
            }),
            cookie=cookie,
        )

        # returns a SeeOther instance
        p = urlparse(resp.message)
        qs = parse_qs(p.query)

        _sjwt = qs["sjwt"][0]
        _sjwt = ".".join(_sjwt.split(".")[:2]) + "."  # Not signed
        with pytest.raises(ValueError):
            self.provider.unpack_signed_jwt(_sjwt)

    def test_end_session_endpoint_with_wrong_post_logout_redirect_uri(self):
        self._code_auth()
        cookie = self._create_cookie("username", "number5")

        post_logout_redirect_uri = "https://www.example.com/logout"
        resp = self.provider.end_session_endpoint(
            urlencode({
                "post_logout_redirect_uri": post_logout_redirect_uri,
                "state": "abcde"
            }),
            cookie=cookie,
        )

        assert isinstance(resp, Response)
        _err = ErrorResponse().from_json(resp.message)
        assert _err["error"] == "invalid_request"

    def test_end_session_endpoint_with_registered_post_logout_redirect_uri_with_query_part(
        self, ):
        self._code_auth()
        cookie = self._create_cookie("username", "number5")

        self.provider.cdb["number5"]["post_logout_redirect_uris"] = [
            ("https://www.example.com/logout", {
                "foo": ["bar"]
            })
        ]

        # No post_logout_redirect_uri in request
        resp = self.provider.end_session_endpoint(urlencode({"state":
                                                             "abcde"}),
                                                  cookie=cookie)

        assert isinstance(resp, Response)
        _qp = parse_qs(resp.message.split("?")[1])
        _jwt = self.provider.unpack_signed_jwt(_qp["sjwt"][0])
        assert _jwt["redirect_uri"] == "https://www.example.com/logout?foo=bar"

    def test_back_channel_logout_no_uri(self):
        self._code_auth()

        res = self.provider.do_back_channel_logout(
            self.provider.cdb[CLIENT_ID], "username", "sid")
        assert res is None

    def test_back_channel_logout(self):
        self._code_auth()

        _cdb = copy.copy(self.provider.cdb[CLIENT_ID])
        _cdb["backchannel_logout_uri"] = "https://example.com/bc_logout"
        _cdb["client_id"] = CLIENT_ID
        res = self.provider.do_back_channel_logout(_cdb, "username", "_sid_")
        assert isinstance(res, tuple)
        assert res[0] == "https://example.com/bc_logout"
        _jwt = self.provider.unpack_signed_jwt(res[1])
        assert _jwt
        assert _jwt["iss"] == SERVER_INFO["issuer"]
        assert _jwt["aud"] == [CLIENT_ID]
        assert _jwt["sub"] == "username"
        assert _jwt["sid"] == "_sid_"

    def test_front_channel_logout(self):
        self._code_auth()

        _cdb = copy.copy(self.provider.cdb[CLIENT_ID])
        _cdb["frontchannel_logout_uri"] = "https://example.com/fc_logout"
        _cdb["client_id"] = CLIENT_ID
        res = self.provider.do_front_channel_logout_iframe(
            _cdb, str(SERVER_INFO["issuer"]), "_sid_")
        assert res == '<iframe src="https://example.com/fc_logout">'

    def test_front_channel_logout_session_required(self):
        self._code_auth()

        _cdb = copy.copy(self.provider.cdb[CLIENT_ID])
        _cdb["frontchannel_logout_uri"] = "https://example.com/fc_logout"
        _cdb["frontchannel_logout_session_required"] = True
        _cdb["client_id"] = CLIENT_ID
        res = self.provider.do_front_channel_logout_iframe(
            _cdb, str(SERVER_INFO["issuer"]), "_sid_")
        m = re.match(r'<iframe src="([^"]+)">', str(res))
        assert m
        _q = parse_qs(str(m.group(1)).split("?")[1])
        assert set(_q.keys()) == {"iss", "sid"}

    def test_front_channel_logout_session_required_uri_query(self):
        self._code_auth()

        _cdb = copy.copy(self.provider.cdb[CLIENT_ID])
        _cdb[
            "frontchannel_logout_uri"] = "https://example.com/fc_logout?foo=bar"
        _cdb["frontchannel_logout_session_required"] = True
        _cdb["client_id"] = CLIENT_ID
        res = self.provider.do_front_channel_logout_iframe(
            _cdb, str(SERVER_INFO["issuer"]), "_sid_")
        m = re.match(r'<iframe src="([^"]+)">', str(res))
        assert m
        _q = parse_qs(str(m.group(1)).split("?")[1])
        assert set(_q.keys()) == {"foo", "iss", "sid"}

    def test_front_channel_logout_missing_url(self):
        self._code_auth()

        _cdb = copy.copy(self.provider.cdb[CLIENT_ID])
        _cdb["client_id"] = CLIENT_ID
        res = self.provider.do_front_channel_logout_iframe(
            _cdb, str(SERVER_INFO["issuer"]), "_sid_")
        assert res is None

    def test_logout_from_client_bc(self):
        self._code_auth()
        self.provider.cdb[CLIENT_ID][
            "backchannel_logout_uri"] = "https://example.com/bc_logout"
        self.provider.cdb[CLIENT_ID]["client_id"] = CLIENT_ID
        # Get a session ID, anyone will do.
        # I know the session backend DB is a DictSessionBackend so I can use that
        _sid = list(self.provider.sdb._db.storage.keys())[0]
        res = self.provider.logout_info_for_one_client(_sid, CLIENT_ID)
        assert set(res.keys()) == {"back_channel", "front_channel"}
        assert res["back_channel"] != {}
        assert res["front_channel"] == {}
        assert set(res["back_channel"].keys()) == {CLIENT_ID}
        _spec = res["back_channel"][CLIENT_ID]
        assert _spec[0] == "https://example.com/bc_logout"
        _jwt = self.provider.unpack_signed_jwt(_spec[1])
        assert _jwt
        assert _jwt["iss"] == SERVER_INFO["issuer"]
        assert _jwt["aud"] == [CLIENT_ID]
        assert _jwt["sid"] == _sid

    def test_logout_from_client_fc(self):
        self._code_auth()
        del self.provider.cdb[CLIENT_ID]["backchannel_logout_uri"]
        self.provider.cdb[CLIENT_ID][
            "frontchannel_logout_uri"] = "https://example.com/fc_logout"
        self.provider.cdb[CLIENT_ID]["client_id"] = CLIENT_ID
        # Get a session ID, anyone will do.
        # I know the session backend DB is a DictSessionBackend so I can use that
        _sid = list(self.provider.sdb._db.storage.keys())[0]
        res = self.provider.logout_info_for_one_client(_sid, CLIENT_ID)
        assert set(res.keys()) == {"front_channel", "back_channel"}
        assert res["back_channel"] == {}
        assert set(res["front_channel"].keys()) == {CLIENT_ID}
        _spec = res["front_channel"][CLIENT_ID]
        assert _spec == '<iframe src="https://example.com/fc_logout">'

    def test_logout_from_client(self):
        self._code_auth()
        self._code_auth2()

        # client0
        self.provider.cdb["client0"][
            "backchannel_logout_uri"] = "https://example.com/bc_logout"
        self.provider.cdb["client0"]["client_id"] = "client0"
        self.provider.cdb["number5"][
            "frontchannel_logout_uri"] = "https://example.com/fc_logout"
        self.provider.cdb["number5"]["client_id"] = CLIENT_ID

        # Get a session ID, anyone will do.
        # I know the session backend DB is a DictSessionBackend so I can use that
        _sid = list(self.provider.sdb._db.storage.keys())[0]
        res = self.provider.logout_info_for_all_clients(sid=_sid)
        assert res
        assert set(res.keys()) == {"back_channel", "front_channel"}
        assert set(res["front_channel"].keys()) == {"number5"}
        _spec = res["front_channel"]["number5"]
        assert _spec == '<iframe src="https://example.com/fc_logout">'
        assert set(res["back_channel"].keys()) == {"client0"}
        _spec = res["back_channel"]["client0"]
        assert _spec[0] == "https://example.com/bc_logout"
        _jwt = self.provider.unpack_signed_jwt(_spec[1])
        assert _jwt
        assert _jwt["iss"] == SERVER_INFO["issuer"]
        assert _jwt["aud"] == ["client0"]

    def test_logout_spec_all(self):
        self._code_auth()
        self._code_auth2()

        # client0
        self.provider.cdb["client0"][
            "backchannel_logout_uri"] = "https://example.com/bc_logout"
        self.provider.cdb["client0"]["client_id"] = "client0"
        self.provider.cdb["number5"][
            "frontchannel_logout_uri"] = "https://example.com/fc_logout"
        self.provider.cdb["number5"]["client_id"] = CLIENT_ID

        # Get a session ID, anyone will do.
        # I know the session backend DB is a DictSessionBackend so I can use that
        _sid = list(self.provider.sdb._db.storage.keys())[0]

        logout_spec_all = self.provider.logout_info_for_all_clients(sid=_sid)

        assert set(logout_spec_all.keys()) == {"back_channel", "front_channel"}
        assert set(logout_spec_all["back_channel"].keys()) == {"client0"}
        assert set(logout_spec_all["front_channel"].keys()) == {"number5"}

    def test_do_verified_logout_all(self):
        self._code_auth()
        self._code_auth2()

        # client0
        self.provider.cdb["client0"][
            "backchannel_logout_uri"] = "https://example.com/bc_logout"
        self.provider.cdb["client0"]["client_id"] = "client0"
        self.provider.cdb["number5"][
            "frontchannel_logout_uri"] = "https://example.com/fc_logout"
        self.provider.cdb["number5"]["client_id"] = CLIENT_ID

        # Get a session ID, anyone will do.
        # I know the session backend DB is a DictSessionBackend so I can use that
        _sid = list(self.provider.sdb._db.storage.keys())[0]

        with responses.RequestsMock() as rsps:
            rsps.add(rsps.POST, "https://example.com/bc_logout", status=200)
            res = self.provider.do_verified_logout(_sid, CLIENT_ID, alla=True)

        assert set(res.keys()) == {"iframe", "cookie"}

    def test_do_verified_logout_just_the_one(self):
        self.provider.events = DummyEventStore()

        self._code_auth()
        self._code_auth2()

        # client0
        self.provider.cdb["client0"][
            "backchannel_logout_uri"] = "https://example.com/bc_logout"
        self.provider.cdb["client0"]["client_id"] = "client0"
        self.provider.cdb["number5"][
            "frontchannel_logout_uri"] = "https://example.com/fc_logout"
        self.provider.cdb["number5"]["client_id"] = CLIENT_ID

        # Get a session ID, anyone will do.
        # I know the session backend DB is a DictSessionBackend so I can use that
        _sid = list(self.provider.sdb._db.storage.keys())[0]

        # There is no back channel logout, hence there should be no HTTP POST
        exception = requests.ConnectionError()
        with responses.RequestsMock(
                assert_all_requests_are_fired=False) as rsps:
            rsps.add(responses.POST,
                     "https://example.com/bc_logout",
                     body=exception)
        res = self.provider.do_verified_logout(_sid, CLIENT_ID, alla=False)

        assert set(res.keys()) == {"iframe", "cookie"}

    def test_do_verified_logout_the_other(self):
        self._code_auth()
        self._code_auth2()

        # client0
        self.provider.cdb["client0"][
            "backchannel_logout_uri"] = "https://example.com/bc_logout"
        self.provider.cdb["client0"]["client_id"] = "client0"
        self.provider.cdb["number5"][
            "frontchannel_logout_uri"] = "https://example.com/fc_logout"
        self.provider.cdb["number5"]["client_id"] = CLIENT_ID

        # Get a session ID, anyone will do.
        # I know the session backend DB is a DictSessionBackend so I can use that
        _sid = list(self.provider.sdb._db.storage.keys())[0]

        # This only does back channel logout
        with responses.RequestsMock() as rsps:
            rsps.add(rsps.POST, "https://example.com/bc_logout", status=200)
            res = self.provider.do_verified_logout(_sid, "client0", alla=False)

        assert set(res.keys()) == {"cookie"}

    def test_do_verified_logout_the_other_back_channel_failed(self):
        self._code_auth()
        self._code_auth2()

        # client0
        self.provider.cdb["client0"][
            "backchannel_logout_uri"] = "https://example.com/bc_logout"
        self.provider.cdb["client0"]["client_id"] = "client0"
        self.provider.cdb["number5"][
            "frontchannel_logout_uri"] = "https://example.com/fc_logout"
        self.provider.cdb["number5"]["client_id"] = CLIENT_ID

        # Get a session ID, anyone will do.
        # I know the session backend DB is a DictSessionBackend so I can use that
        _sid = list(self.provider.sdb._db.storage.keys())[0]

        # Does back channel logout and it will fail
        with responses.RequestsMock() as rsps:
            rsps.add(rsps.POST, "https://example.com/bc_logout", status=400)
            res = self.provider.do_verified_logout(_sid, "client0", alla=False)

        assert list(res.keys()) == []

    def test_end_session_endpoint_no_post_logout_redirect_uri(self):
        self._code_auth()
        cookie = self._create_cookie("username", "number5")

        self.provider.cdb["number5"]["post_logout_redirect_uris"] = [
            ("https://example.com/plru", ""),
            ("https://example.com/plru2", ""),
        ]

        res = self.provider.end_session_endpoint(urlencode({"state": "abcde"}),
                                                 cookie=cookie)
        assert isinstance(res, Response)
        assert res.status_code == 400

    def test_logout_info_for_all_clients_no_params(self):
        with pytest.raises(ParameterError):
            self.provider.logout_info_for_all_clients()

    def test_do_back_channel_logout_no_backchannel(self):
        self._code_auth()

        # Get a session ID, anyone will do.
        # I know the session backend DB is a DictSessionBackend so I can use that
        _sid = list(self.provider.sdb._db.storage.keys())[0]
        _sub = self.provider.sdb[_sid]["sub"]
        #
        if "backchannel_logout_uri" in self.provider.cdb["number5"]:
            del self.provider.cdb["number5"]["backchannel_logout_uri"]

        res = self.provider.do_back_channel_logout(
            self.provider.cdb["number5"], _sub, _sid)
        assert res is None

    def test_id_token_hint_multiple_aud(self):
        id_token = self._auth_with_id_token()
        assert session_get(self.provider.sdb, "sub",
                           id_token["sub"])  # verify we got valid session

        self.provider.cdb["number5"]["post_logout_redirect_uris"] = [
            ("https://example.com/plru", "")
        ]

        # add another aud and an azp.
        id_token["azp"] = id_token["aud"][0]
        id_token["aud"].append("foobar")
        id_token_hint = id_token.to_jwt(algorithm="none")

        resp = self.provider.end_session_endpoint(
            urlencode({"id_token_hint": id_token_hint}))

        assert isinstance(resp, SeeOther)

    def test_id_token_hint_aud_does_not_match_client_id(self):
        id_token = self._auth_with_id_token()
        assert session_get(self.provider.sdb, "sub",
                           id_token["sub"])  # verify we got valid session

        # add another aud and an azp.
        id_token_hint = id_token.to_jwt(algorithm="none")

        # Mess with the session DB
        _sid = list(self.provider.sdb._db.storage.keys())[0]
        self.provider.sdb[_sid]["client_id"] = "something else"
        resp = self.provider.end_session_endpoint(
            urlencode({"id_token_hint": id_token_hint}))

        assert isinstance(resp, Response)
        assert resp.status_code == 400

    def test_no_back_or_front_channel_logout(self):
        self._code_auth()

        # Mess with client DB
        for c in ["backchannel_logout_uri", "frontchannel_logout_uri"]:
            if c in self.provider.cdb["number5"]:
                del self.provider.cdb["number5"][c]

        resp = self.provider.do_verified_logout(sid=list(
            self.provider.sdb._db.storage.keys())[0],
                                                client_id="number5")

        assert resp == {}

    def test_back_channel_logout_fails(self):
        self._code_auth()

        # client0
        self.provider.cdb["client0"][
            "backchannel_logout_uri"] = "https://example.com/bc_logout"
        self.provider.cdb["client0"]["client_id"] = "client0"

        # Get a session ID, anyone will do.
        # I know the session backend DB is a DictSessionBackend so I can use that
        _sid = list(self.provider.sdb._db.storage.keys())[0]

        # There is no back channel logout, hence there should be no HTTP POST
        with responses.RequestsMock():
            res = self.provider.do_verified_logout(_sid, "client0", alla=False)

        assert res == {}

    def test_logout_info_for_one_client_no_logout_info(self):
        self._code_auth()

        # Mess with client DB
        for c in ["backchannel_logout_uri", "frontchannel_logout_uri"]:
            if c in self.provider.cdb["number5"]:
                del self.provider.cdb["number5"][c]

        # Get a session ID, anyone will do.
        # I know the session backend DB is a DictSessionBackend so I can use that
        _sid = list(self.provider.sdb._db.storage.keys())[0]
        resp = self.provider.logout_info_for_one_client(_sid, "number5")

        assert resp == {"back_channel": {}, "front_channel": {}}

    def test_unknown_client(self):
        self._code_auth()
        cookie = self._create_cookie("username", "unknown")

        resp = self.provider.end_session_endpoint(urlencode({"state":
                                                             "abcde"}),
                                                  cookie=cookie)

        assert isinstance(resp, Response)
        assert resp.status_code == 400

    def test_no_cookie_no_id_token_hint(self):
        self._code_auth()

        resp = self.provider.end_session_endpoint(urlencode({"state":
                                                             "abcde"}))

        assert isinstance(resp, Response)
        assert resp.status_code == 400

    def test_back_channel_logout_failed_front_channel_logout_exists(self):
        self._code_auth()

        # client0
        self.provider.cdb["number5"][
            "backchannel_logout_uri"] = "https://example.com/bc_logout"
        self.provider.cdb["number5"][
            "frontchannel_logout_uri"] = "https://example.com/fc_logout"
        self.provider.cdb["number5"]["client_id"] = "number5"

        # Get a session ID, anyone will do.
        # I know the session backend DB is a DictSessionBackend so I can use that
        _sid = list(self.provider.sdb._db.storage.keys())[0]

        # Does back channel logout and it will fail
        with responses.RequestsMock() as rsps:
            rsps.add(rsps.POST, "https://example.com/bc_logout", status=400)
            res = self.provider.do_verified_logout(_sid, "client0", alla=True)

        assert set(res.keys()) == {"cookie", "iframe"}

    def test_logout_from_clients_one_without_logout_info(self):
        self._code_auth()
        self._code_auth2()

        # Mess with client DB
        # neither back channel nor front channel
        for c in ["backchannel_logout_uri", "frontchannel_logout_uri"]:
            if c in self.provider.cdb["client0"]:
                del self.provider.cdb["client0"][c]

        self.provider.cdb["client0"]["client_id"] = "client0"

        # both back channel and front channel
        self.provider.cdb["number5"][
            "frontchannel_logout_uri"] = "https://example.com/fc_logout"
        self.provider.cdb["number5"]["client_id"] = "number5"

        # Get a session ID, anyone will do.
        # I know the session backend DB is a DictSessionBackend so I can use that
        _sid = list(self.provider.sdb._db.storage.keys())[0]
        res = self.provider.logout_info_for_all_clients(sid=_sid)
        assert set(res.keys()) == {"back_channel", "front_channel"}
        assert set(res["back_channel"].keys()) == {"number5"}
        assert set(res["front_channel"].keys()) == {"number5"}
Esempio n. 53
0

#AUTHN = UsernamePasswordMako(None, "login.mako", tl, PASSWD, "authenticated")
AUTHN_BROKER = AuthnBroker()
AUTHN_BROKER.add("UNDEFINED", DummyAuthn(None, "username"))

# dealing with authorization
AUTHZ = AuthzHandling()
SYMKEY = rndstr(16)  # symmetric key used to encrypt cookie info
USERINFO = UserInfo(USERDB)

provider_init = Provider("pyoicserv",
                         SessionDB(SERVER_INFO["issuer"]),
                         CDB,
                         AUTHN_BROKER,
                         USERINFO,
                         AUTHZ,
                         verify_client,
                         SYMKEY,
                         urlmap=URLMAP,
                         keyjar=KEYJAR)


def _eq(l1, l2):
    return set(l1) == set(l2)


def test_server_init():
    server = provider_init

    assert server
    assert server.authn_broker == AUTHN_BROKER
Esempio n. 54
0
class OIDCFrontend(FrontendModule):
    """
    A OpenID Connect frontend module
    """

    MANDATORY_CONFIG = {"issuer", "signing_key_path"}

    def __init__(self, auth_req_callback_func, internal_attributes, conf):
        self._validate_config(conf)
        super(OIDCFrontend, self).__init__(auth_req_callback_func, internal_attributes)

        self.state_id = type(self).__name__
        self.sign_alg = "RS256"
        self.subject_type_default = "pairwise"
        self.conf = conf

    def handle_authn_response(self, context, internal_resp):
        """
        See super class method satosa.frontends.base.FrontendModule#handle_authn_response
        :type context: satosa.context.Context
        :type internal_response: satosa.internal_data.InternalResponse
        :rtype oic.utils.http_util.Response
        """
        auth_req = self._get_authn_request_from_state(context.state)

        # filter attributes to return in ID Token as claims
        attributes = self.converter.from_internal("openid", internal_resp.get_attributes())
        satosa_logging(LOGGER, logging.DEBUG,
                       "Attributes delivered by backend to OIDC frontend: {}".format(
                               json.dumps(attributes)), context.state)
        flattened_attributes = {k: v[0] for k, v in attributes.items()}
        requested_id_token_claims = auth_req.get("claims", {}).get("id_token")
        user_claims = self._get_user_info(flattened_attributes,
                                          requested_id_token_claims,
                                          auth_req["scope"])
        satosa_logging(LOGGER, logging.DEBUG,
                       "Attributes filtered by requested claims/scope: {}".format(
                               json.dumps(user_claims)), context.state)

        # construct epoch timestamp of reported authentication time
        auth_time = datetime.datetime.strptime(internal_resp.auth_info.timestamp,
                                               "%Y-%m-%dT%H:%M:%SZ")
        epoch_timestamp = (auth_time - datetime.datetime(1970, 1, 1)).total_seconds()

        base_claims = {"client_id": auth_req["client_id"],
                       "sub": internal_resp.get_user_id(),
                       "nonce": auth_req["nonce"]}
        id_token = self.provider.id_token_as_signed_jwt(base_claims, user_info=user_claims,
                                                        auth_time=epoch_timestamp,
                                                        loa="",
                                                        alg=self.sign_alg)

        oidc_client_state = auth_req.get("state")
        kwargs = {}
        if oidc_client_state:  # inlcude any optional 'state' sent by the client in the authn req
            kwargs["state"] = oidc_client_state

        auth_resp = AuthorizationResponse(id_token=id_token, **kwargs)
        http_response = auth_resp.request(auth_req["redirect_uri"],
                                          self._should_fragment_encode(auth_req))
        return SeeOther(http_response)

    def handle_backend_error(self, exception):
        """
        See super class satosa.frontends.base.FrontendModule
        :type exception: satosa.exception.SATOSAError
        :rtype: oic.utils.http_util.Response
        """
        auth_req = self._get_authn_request_from_state(exception.state)
        error_resp = AuthorizationErrorResponse(error="access_denied",
                                                error_description=exception.message)
        satosa_logging(LOGGER, logging.DEBUG, exception.message, exception.state)
        return SeeOther(
                error_resp.request(auth_req["redirect_uri"],
                                   self._should_fragment_encode(auth_req)))

    def register_endpoints(self, providers):
        """
        See super class satosa.frontends.base.FrontendModule
        :type providers: list[str]
        :rtype: list[(str, ((satosa.context.Context, Any) -> satosa.response.Response, Any))]
        :raise ValueError: if more than one backend is configured
        """
        if len(providers) != 1:
            raise ValueError("OpenID Connect frontend only supports one backend.")
        backend = providers[0]

        endpoint_baseurl = "{}/{}".format(self.conf["issuer"], backend)
        jwks_uri = "{}/jwks".format(self.conf["issuer"])
        self._create_op(self.conf["issuer"], endpoint_baseurl, jwks_uri)

        provider_config = (
            "^.well-known/openid-configuration$", self._provider_config)
        jwks_uri = ("^jwks$", self._jwks)
        dynamic_client_registration = (
            "^{}/{}".format(backend, RegistrationEndpoint.url), self._register_client)
        authentication = (
            "^{}/{}".format(backend, AuthorizationEndpoint.url), self.handle_authn_request)

        url_map = [provider_config, jwks_uri, dynamic_client_registration, authentication]
        return url_map

    def _create_op(self, issuer, endpoint_baseurl, jwks_uri):
        """
        Create the necessary Provider instance.
        :type issuer: str
        :type endpoint_baseurl: str
        :type jwks_uri: str
        :param issuer: issuer URL for the OP
        :param endpoint_baseurl: baseurl to build endpoint URL from
        :param jwks_uri: URL to where the JWKS will be published
        """
        kj = KeyJar()
        signing_key = KeyBundle(source="file://{}".format(self.conf["signing_key_path"]),
                                fileformat="der", keyusage=["sig"])
        kj.add_kb("", signing_key)
        capabilities = {
            "response_types_supported": ["id_token"],
            "id_token_signing_alg_values_supported": [self.sign_alg],
            "response_modes_supported": ["fragment", "query"],
            "subject_types_supported": ["public", "pairwise"],
            "grant_types_supported": ["implicit"],
            "claim_types_supported": ["normal"],
            "claims_parameter_supported": True,
            "request_parameter_supported": False,
            "request_uri_parameter_supported": False,
        }

        if "client_db_path" in self.conf:
            cdb = shelve_wrapper.open(self.conf["client_db_path"])
        else:
            cdb = {}  # client db in memory only

        self.provider = Provider(issuer, None, cdb, None, None, None, None, None, keyjar=kj,
                                 capabilities=capabilities, jwks_uri=jwks_uri)
        self.provider.baseurl = endpoint_baseurl
        self.provider.endp = [RegistrationEndpoint, AuthorizationEndpoint]

    def _get_user_info(self, user_attributes, requested_claims=None, scopes=None):
        """
        Filter user attributes to return to the client  (as claims in the ID Token) based on what
        was requested in request 'claims' parameter and in the 'scope'.
        :type user_attributes: dict[str, str]
        :type requested_claims: dict[str, Optional[dict]]
        :type scopes: list[str]
        :rtype: dict[str, str]

        :param user_attributes: attributes provided by the backend
        :param requested_claims: claims requested by the client through the 'claims' request param
        :param scopes: the scopes requested by the client
        :return: all attributes/claims to return to the client
        """
        requested_claims = requested_claims or {}
        scopes = scopes or []
        claims_requested_by_scope = Provider._scope2claims(scopes)
        claims_requested_by_scope.update(
                requested_claims)  # let explicit claims request override scope

        return UserInfo().filter(user_attributes, claims_requested_by_scope)

    def _validate_config(self, config):
        """
        Validates that all necessary config parameters are specified.
        :type config: dict[str, dict[str, Any] | str]
        :param config: the module config
        """
        if config is None:
            raise ValueError("OIDCFrontend conf can't be 'None'.")

        for k in self.MANDATORY_CONFIG:
            if k not in config:
                raise ValueError(
                        "Missing configuration parameter '{}' for OpenID Connect frontend.".format(
                                k))

    def _should_fragment_encode(self, authn_req):
        """
        Determine, based on the clients request, whether the authentication/error response should
        be fragment encoded or not.
        :type authn_req: oic.oic.message.AuthorizationRequest
        :rtype: bool

        :param authn_req: parsed authentication request from the client
        :return: True if the response should be fragment encoded
        """
        return authn_req.get("response_mode", "fragment") == "fragment"

    def _get_authn_request_from_state(self, state):
        """
        Extract the clietns request stoed in the SATOSA state.
        :type state: satosa.state.State
        :rtype: oic.oic.message.AuthorizationRequest

        :param state: the current state
        :return: the parsed authentication request
        """
        stored_state = state.get(self.state_id)
        oidc_request = stored_state["oidc_request"]
        return AuthorizationRequest().deserialize(oidc_request)

    def _register_client(self, context):
        """
        Handle the OIDC dynamic client registration.
        :type context: satosa.context.Context
        :rtype: oic.utils.http_util.Response

        :param context: the current context
        :return: HTTP response to the client
        """
        http_resp = self.provider.registration_endpoint(json.dumps(context.request))
        if not isinstance(http_resp, Created):
            return http_resp

        return self._fixup_registration_response(http_resp)

    def _fixup_registration_response(self, http_resp):
        # remove client_secret since no token endpoint is published
        response = RegistrationResponse().deserialize(http_resp.message, "json")
        del response["client_secret"]
        # specify supported id token signing alg
        response["id_token_signed_response_alg"] = self.sign_alg

        http_resp.message = response.to_json()
        return http_resp

    def _provider_config(self, context):
        """
        Construct the provider configuration information (served at /.well-known/openid-configuration).
        :type context: satosa.context.Context
        :rtype: oic.utils.http_util.Response

        :param context: the current context
        :return: HTTP response to the client
        """
        http_resp = self.provider.providerinfo_endpoint()
        if not isinstance(http_resp, Response):
            return http_resp
        provider_config = ProviderConfigurationResponse().deserialize(http_resp.message, "json")
        del provider_config["token_endpoint_auth_methods_supported"]
        del provider_config["require_request_uri_registration"]

        http_resp.message = provider_config.to_json()
        return http_resp

    def handle_authn_request(self, context):
        """
        Parse and verify the authentication request and pass it on to the backend.
        :type context: satosa.context.Context
        :rtype: oic.utils.http_util.Response

        :param context: the current context
        :return: HTTP response to the client
        """

        # verify auth req (correct redirect_uri, contains nonce and response_type='id_token')
        request = urlencode(context.request)
        satosa_logging(LOGGER, logging.DEBUG, "Authn req from client: {}".format(request),
                       context.state)

        info = self.provider.auth_init(request, request_class=AuthorizationRequest)
        if isinstance(info, Response):
            satosa_logging(LOGGER, logging.ERROR, "Error in authn req: {}".format(info.message),
                           context.state)
            return info

        client_id = info["areq"]["client_id"]

        context.state.add(self.state_id, {"oidc_request": request})
        hash_type = oidc_subject_type_to_hash_type(
                self.provider.cdb[client_id].get("subject_type", self.subject_type_default))
        internal_req = InternalRequest(hash_type, client_id,
                                       self.provider.cdb[client_id].get("client_name"))

        return self.auth_req_callback_func(context, internal_req)

    def _jwks(self, context):
        """
        Construct the JWKS document (served at /jwks).
        :type context: satosa.context.Context
        :rtype: oic.utils.http_util.Response

        :param context: the current context
        :return: HTTP response to the client
        """
        return Response(json.dumps(self.provider.keyjar.export_jwks()), content="application/json")
Esempio n. 55
0
    # dealing with authorization
    authz = AuthzHandling()

    kwargs = {
        "template_lookup": LOOKUP,
        "template": {"form_post": "form_response.mako"},
        #"template_args": {"form_post": {"action": "form_post"}}
    }

    # Should I care about verifying the certificates used by other entities
    if args.insecure:
        kwargs["verify_ssl"] = False
    else:
        kwargs["verify_ssl"] = True

    OAS = Provider(config.issuer, SessionDB(), cdb, ac, None, authz,
                   verify_client, config.SYM_KEY, **kwargs)

    for authn in ac:
        authn.srv = OAS

    if config.USERINFO == "SIMPLE":
        # User info is a simple dictionary in this case statically defined in
        # the configuration file
        OAS.userinfo = UserInfo(config.USERDB)
    elif config.USERINFO == "SAML":
        OAS.userinfo = UserInfo(config.SAML)
    elif config.USERINFO == "AA":
        OAS.userinfo = AaUserInfo(config.SP_CONFIG, config.issuer, config.SAML)
    else:
        raise Exception("Unsupported userinfo source")
Esempio n. 56
0
        },
        #"template_args": {"form_post": {"action": "form_post"}}
    }

    # Should I care about verifying the certificates used by other entities
    if args.insecure:
        kwargs["verify_ssl"] = False
    else:
        kwargs["verify_ssl"] = True

    if args.capabilities:
        kwargs["capabilities"] = json.loads(open(args.capabilities).read())
    else:
        pass

    OAS = Provider(config.issuer, SessionDB(config.baseurl), cdb, ac, None,
                   authz, verify_client, config.SYM_KEY, **kwargs)
    OAS.baseurl = config.issuer

    for authn in ac:
        authn.srv = OAS

    if config.USERINFO == "SIMPLE":
        # User info is a simple dictionary in this case statically defined in
        # the configuration file
        OAS.userinfo = UserInfo(config.USERDB)
    elif config.USERINFO == "SAML":
        OAS.userinfo = UserInfo(config.SAML)
    elif config.USERINFO == "AA":
        OAS.userinfo = AaUserInfo(config.SP_CONFIG, config.issuer, config.SAML)
    else:
        raise Exception("Unsupported userinfo source")
Esempio n. 57
0
def main():
    parser = argparse.ArgumentParser(description='Example OIDC Provider.')
    parser.add_argument("-p", "--port", default=80, type=int)
    parser.add_argument("-b", "--base", default="https://localhost", type=str)
    parser.add_argument("-d", "--debug", action="store_true")
    parser.add_argument("settings")
    args = parser.parse_args()

    # Load configuration
    with open(args.settings, "r") as f:
        settings = yaml.load(f)

    issuer = args.base.rstrip("/")

    template_dirs = settings["server"].get("template_dirs", "templates")
    jinja_env = Environment(loader=FileSystemLoader(template_dirs))
    authn_broker, auth_routing = setup_authentication_methods(
        settings["authn"], jinja_env)

    # Setup userinfo
    userinfo_conf = settings["userinfo"]
    cls = make_cls_from_name(userinfo_conf["class"])
    i = cls(**userinfo_conf["kwargs"])
    userinfo = UserInfo(i)

    client_db = {}
    provider = Provider(issuer, SessionDB(issuer), client_db, authn_broker,
                        userinfo, AuthzHandling(), verify_client, None)
    provider.baseurl = issuer
    provider.symkey = rndstr(16)

    # Setup keys
    path = os.path.join(os.path.dirname(__file__), "static")
    try:
        os.makedirs(path)
    except OSError as e:
        if e.errno != errno.EEXIST:
            raise e
        pass
    jwks = keyjar_init(provider, settings["provider"]["keys"])
    name = "jwks.json"
    with open(os.path.join(path, name), "w") as f:
        f.write(json.dumps(jwks))

    provider.jwks_uri.append("{}/static/{}".format(provider.baseurl, name))

    # Mount the WSGI callable object (app) on the root directory
    app_routing = setup_endpoints(provider)
    app_routing["/.well-known/openid-configuration"] = pyoidcMiddleware(
        provider.providerinfo_endpoint)
    app_routing["/.well-known/webfinger"] = pyoidcMiddleware(
        partial(_webfinger, provider))
    routing = dict(list(auth_routing.items()) + list(app_routing.items()))
    routing["/static"] = make_static_handler(path)
    dispatcher = WSGIPathInfoDispatcher(routing)
    server = wsgiserver.CherryPyWSGIServer(('0.0.0.0', args.port), dispatcher)

    # Setup SSL
    if provider.baseurl.startswith("https://"):
        server.ssl_adapter = BuiltinSSLAdapter(
            settings["server"]["cert"], settings["server"]["key"],
            settings["server"]["cert_chain"])

    # Start the CherryPy WSGI web server
    try:
        print("Server started: {}".format(issuer))
        server.start()
    except KeyboardInterrupt:
        server.stop()
Esempio n. 58
0
class InAcademiaOpenIDConnectFrontend(object):
    def __init__(self, base_url, client_metadata_func):
        # Read OP configuration from file
        with open("conf/op_config.json", "r") as f:
            op_capabilities = json.load(f)
        for key, value in op_capabilities.iteritems():
            if isinstance(value, basestring):
                op_capabilities[key] = value.format(base=base_url)  # replace placeholder with the actual base name

        self.OP = Provider(base_url, {}, client_metadata_func, None, None, None, None, None,
                           capabilities=op_capabilities)
        self.OP.baseurl = op_capabilities["issuer"]

        # Setup up keys for signing and encrypting
        self.OP.keyjar = KeyJar()
        kb = keybundle_from_local_file("inAcademia", "RSA", ["sig", "enc"])
        self.OP.keyjar.add_kb("", kb)

        try:
            file_name = "static/jwks.json"
            dump_jwks([kb], file_name)
            self.OP.jwks_uri.append("{}/{}".format(base_url, file_name))
        except Exception as e:
            logger.exception("Signing and encryption keys could not be written to jwks.json.")
            raise

    def id_token(self, released_claims, idp_entity_id, transaction_id, transaction_session):
        """Make a JWT encoded id token and pass it to the redirect URI.

        :param released_claims: dictionary containing the following
            user_id: identifier for the user (as delivered by the IdP, dependent on whether transient or persistent
                        id was requested)
            auth_time: time of the authentication reported from the IdP
            idp_entity_id: entity id of the selected IdP
        :param transaction_id:
        :return: raises cherrypy.HTTPRedirect.
        """

        identifier = released_claims["Identifier"]
        auth_time = released_claims["Authentication time"]

        # have to convert text representation into seconds since epoch
        _time = time.mktime(str_to_time(auth_time))

        # construct the OIDC response
        transaction_session["sub"] = identifier

        extra_claims = {k.lower(): released_claims[k] for k in ["Country", "Domain"] if k in released_claims}
        _jwt = self.OP.id_token_as_signed_jwt(transaction_session, loa="", auth_time=_time, exp={"minutes": 30},
                                              extra_claims=extra_claims)

        _elapsed_transaction_time = get_timestamp() - transaction_session["start_time"]
        log_transaction_complete(logger, cherrypy.request, transaction_id,
                                 transaction_session["client_id"],
                                 idp_entity_id, _time, _elapsed_transaction_time,
                                 extra_claims, _jwt)

        try:
            _state = transaction_session["state"]
        except KeyError:
            _state = None
        authzresp = AuthorizationResponse(state=_state, id_token=_jwt)

        if "redirect_uri" in transaction_session:
            _ruri = transaction_session["redirect_uri"]
        else:
            _error_msg = _("We could not complete your validation because an error occurred while "
                           "handling your request. Please return to the service which initiated the "
                           "validation request and try again.")
            try:
                cinfo = self.OP.cdb[transaction_session["client_id"]]
                _ruri = cinfo["redirect_uris"][0]
            except KeyError as e:
                abort_with_enduser_error(transaction_id, transaction_session["client_id"], cherrypy.request, logger,
                                         _error_msg,
                                         "Unknown RP client id '{}': '{}'.".format(transaction_session["client_id"],
                                                                                   str(e)))

        location = authzresp.request(_ruri, True)
        logger.debug("Redirected to: '{}' ({})".format(location, type(location)))
        raise cherrypy.HTTPRedirect(location)

    def _verify_scope(self, scope, client_id):
        """Verifies the scope received from the RP.

        Only one affiliation request is allowed to be specified, and if 'persistent' is specified 'transient'
        is not allowed. In addition, the requested scope is verified against the clients permissions.

        :param scope: requested scope from the RP
        :return: True if the values in scope are valid, otherwise False.
        """

        # Malformed scope requesting validation of more than one affiliation type
        requested_affiliations = [a for a in AFFILIATIONS if a in scope]
        if len(requested_affiliations) != 1:
            return False

        # Malformed scope containing both 'persistent' and 'transient'
        if PERSISTENT_NAMEID in scope and TRANSIENT_NAMEID in scope:
            return False

        # Verify the client is allowed to request this scope
        try:
            client_info = self.OP.cdb[client_id]
            allowed = client_info.get("allowed_scope_values", [])
        except KeyError as e:
            allowed = []

        for value in scope:
            if value == "openid":  # Always allow 'openid' in scope
                continue
            elif value in SCOPE_VALUES and value not in allowed:  # A scope we understand, but client not allowed
                log_internal(logger, "Scope value '{}' not in '{}' for client.".format(value, allowed), None,
                             client_id=client_id)
                return False

        return True

    def verify_authn_request(self, query_string):
        """Verify the incoming authentication request from the RP.

        :param query_string: query string in the request
        :param key_bundle: keys for encrypting the transaction state
        :return: tuple containing the encoded state and the scope requested by the RP.
        """
        try:
            areq = self.OP.server.parse_authorization_request(query=query_string)
        except Exception as e:
            abort_with_enduser_error("-", "-", cherrypy.request, logger,
                                     _("The authentication request could not be processed. Please return to the "
                                       "service which initiated the validation request and try again."),
                                     "The authentication request '{}' could not be processed.".format(query_string),
                                     exc_info=True)

        # Verify it's a client_id I recognize
        client_id = areq["client_id"]
        _error_msg = _("Configuration error for the service.")

        try:
            client_info = self.OP.cdb[client_id]
        except KeyError as e:
            abort_with_enduser_error("-", client_id, cherrypy.request, logger,
                                     _error_msg,
                                     "Unknown RP client id '{}': '{}'.".format(client_id, str(e)))

        # verify that the redirect_uri is sound
        if "redirect_uri" not in areq:
            abort_with_enduser_error("-", client_id, cherrypy.request, logger,
                                     _error_msg,
                                     "Missing redirect URI in authentication request.")
        elif areq["redirect_uri"] not in client_info["redirect_uris"]:
            abort_with_enduser_error("-", client_id, cherrypy.request, logger,
                                     _error_msg,
                                     "Unknown redirect URI in authentication request: '{}' not in '{}'".format(
                                         areq["redirect_uri"],
                                         client_info["redirect_uris"]))

        # Create the state variable
        transaction_session = {
            "client_id": client_id,
            "redirect_uri": areq["redirect_uri"],
            "nonce": areq["nonce"],
            "scope": areq["scope"],
        }

        if "state" in areq:
            transaction_session["state"] = areq["state"]

        if "claims" in areq:
            transaction_session["claims"] = areq["claims"]["id_token"].to_dict()

        # Verify that the response_type if present is id_token
        try:
            assert areq["response_type"] == ["id_token"]
        except (KeyError, AssertionError) as err:  # has to be there and match
            abort_with_client_error("-", transaction_session, cherrypy.request, logger,
                                    "Unsupported response_type '{}'".format(areq["response_type"]),
                                    error="unsupported_response_type",
                                    error_description="Only response_type 'id_token' is supported.")

        if not self._verify_scope(areq["scope"], client_id):
            abort_with_client_error("-", transaction_session, cherrypy.request, logger,
                                    "Invalid scope '{}'".format(areq["scope"]),
                                    error="invalid_scope",
                                    error_description="The specified scope '{}' is not valid.".format(" ".join(areq["scope"])))

        transaction_session.update({
            "start_time": get_timestamp()
        })

        return transaction_session

    def get_claims_to_release(self, user_id, affiliation, identity, auth_time, idp_entity_id, idp_metadata_func,
                              transaction_session):
        """
        Compile a dictionary of a all claims we will release to the client.

        :param user_id: identifier for the user
        :param identity: assertions about the user from the IdP
        :param auth_time: time of authentication reported from the IdP
        :param idp_entity_id: id of the IdP
        :param idp_metadata_func: callable to fetch idp metadata
        :param transaction_session: transaction data
        :return:
        """
        attributes = [N_("Affiliation"), N_("Identifier"), N_("Authentication time")]
        values = [affiliation,
                  self._generate_subject_id(transaction_session["client_id"], user_id, idp_entity_id),
                  auth_time]
        l = zip(attributes, values)

        extra_claims = self._get_extra_claims(idp_metadata_func, identity, idp_entity_id,
                                              transaction_session.get("claims", []), transaction_session["client_id"])
        l.extend(extra_claims)

        return dict(l)

    def _get_extra_claims(self, idp_metadata_func, identity, idp_entity_id, requested_claims, client_id):
        """Create the extra claims requested by the RP.

        Extra claims will only be returned if the RP is allowed to request them and we got them from the IdP.

        :param idp_metadata_func: callable to fetch idp metadata
        :param identity: assertions from the IdP about the user
        :param idp_entity_id: entity id of the IdP
        :param requested_claims: the requested claims from the RP
        :param client_id: RP client id
        :return: a list of tuples with any extra claims to return to the RP with the id token.
        """

        # Verify the client is allowed to request these claims
        try:
            client_info = self.OP.cdb[client_id]
            allowed = client_info.get("allowed_claims", [])
        except KeyError as e:
            allowed = []

        for value in requested_claims:
            if value not in allowed:
                log_internal(logger, "Claim '{}' not in '{}' for client.".format(value, allowed), None,
                             client_id=client_id)

        claims = []
        if DOMAIN in requested_claims and DOMAIN in allowed:
            if "schacHomeOrganization" in identity:
                claims.append((N_("Domain"), identity["schacHomeOrganization"][0]))

        if COUNTRY in requested_claims and COUNTRY in allowed:
            country = self._get_idp_country(idp_metadata_func, idp_entity_id)
            if country is not None:
                claims.append((N_("Country"), country))

        return claims

    def _generate_subject_id(self, client_id, user_id, idp_entity_id):
        """Construct the subject identifier for the ID Token.

        :param client_id: id of the client (RP)
        :param user_id: id of the end user
        :param idp_entity_id: id of the IdP
        """
        return hashlib.sha512(client_id + user_id + idp_entity_id).hexdigest()

    def _get_idp_country(self, metadata, entity_id):
        """Get the country of the IdP.

        :param metadata: function fetching the IdP metadata
        :param entity_id: entity id of the IdP
        """
        idp_info = metadata[entity_id]
        return idp_info.get("country", None)