コード例 #1
0
    def test_rejects_mismatching_request(self, client_preference,
                                         provider_capability, client_value,
                                         provider_value):
        request = {'redirect_uris': ['https://client.example.com/redirect']}
        provider_capabilities = provide_configuration()

        if client_preference.startswith(
            ('request_object_encryption', 'id_token_encrypted',
             'userinfo_encrypted')):
            # provide default value for the metadata params that come in pairs
            param = client_preference[:-4]
            alg_param = param + '_alg'
            request[alg_param] = 'RSA-OAEP'
            provider_capabilities[PREFERENCE2PROVIDER[alg_param]] = [
                'RSA-OAEP'
            ]
            enc_param = param + '_enc'
            request[enc_param] = 'A192CBC-HS256'
            provider_capabilities[PREFERENCE2PROVIDER[enc_param]] = [
                'A192CBC-HS256'
            ]

        request[client_preference] = client_value
        provider_capabilities[provider_capability] = provider_value
        provider = Provider(
            rsa_key(), provider_capabilities,
            AuthorizationState(HashBasedSubjectIdentifierFactory('salt')), {},
            None)
        with pytest.raises(InvalidClientRegistrationRequest):
            provider.handle_client_registration_request(json.dumps(request))
コード例 #2
0
ファイル: openid_connect.py プロジェクト: mrvanes/SATOSA
    def _create_provider(self, endpoint_baseurl):
        response_types_supported = self.config["provider"].get("response_types_supported", ["id_token"])
        subject_types_supported = self.config["provider"].get("subject_types_supported", ["pairwise"])
        scopes_supported = self.config["provider"].get("scopes_supported", ["openid"])
        capabilities = {
            "issuer": self.base_url,
            "authorization_endpoint": "{}/{}".format(endpoint_baseurl, AuthorizationEndpoint.url),
            "jwks_uri": "{}/jwks".format(endpoint_baseurl),
            "response_types_supported": response_types_supported,
            "id_token_signing_alg_values_supported": [self.signing_key.alg],
            "response_modes_supported": ["fragment", "query"],
            "subject_types_supported": subject_types_supported,
            "claim_types_supported": ["normal"],
            "claims_parameter_supported": True,
            "claims_supported": [attribute_map["openid"][0]
                                 for attribute_map in self.internal_attributes["attributes"].values()
                                 if "openid" in attribute_map],
            "request_parameter_supported": False,
            "request_uri_parameter_supported": False,
            "scopes_supported": scopes_supported
        }

        if self.config["provider"].get("client_registration_supported", False):
            capabilities["registration_endpoint"] = "{}/{}".format(endpoint_baseurl, RegistrationEndpoint.url)

        authz_state = self._init_authorization_state()
        db_uri = self.config.get("db_uri")
        cdb = MongoWrapper(db_uri, "satosa", "clients") if db_uri else {}
        self.user_db = MongoWrapper(db_uri, "satosa", "authz_codes") if db_uri else {}
        self.provider = Provider(self.signing_key, capabilities, authz_state, cdb, Userinfo(self.user_db))
コード例 #3
0
ファイル: openid_connect.py プロジェクト: its-dirg/SATOSA
    def _create_provider(self, endpoint_baseurl):
        response_types_supported = self.config["provider"].get("response_types_supported", ["id_token"])
        subject_types_supported = self.config["provider"].get("subject_types_supported", ["pairwise"])
        scopes_supported = self.config["provider"].get("scopes_supported", ["openid"])
        capabilities = {
            "issuer": self.base_url,
            "authorization_endpoint": "{}/{}".format(endpoint_baseurl, AuthorizationEndpoint.url),
            "jwks_uri": "{}/jwks".format(endpoint_baseurl),
            "response_types_supported": response_types_supported,
            "id_token_signing_alg_values_supported": [self.signing_key.alg],
            "response_modes_supported": ["fragment", "query"],
            "subject_types_supported": subject_types_supported,
            "claim_types_supported": ["normal"],
            "claims_parameter_supported": True,
            "claims_supported": [
                attribute_map["openid"][0]
                for attribute_map in self.internal_attributes["attributes"].values()
                if "openid" in attribute_map
            ],
            "request_parameter_supported": False,
            "request_uri_parameter_supported": False,
            "scopes_supported": scopes_supported,
        }

        if self.config["provider"].get("client_registration_supported", False):
            capabilities["registration_endpoint"] = "{}/{}".format(endpoint_baseurl, RegistrationEndpoint.url)

        authz_state = self._init_authorization_state()
        db_uri = self.config.get("db_uri")
        cdb = MongoWrapper(db_uri, "satosa", "clients") if db_uri else {}
        self.user_db = MongoWrapper(db_uri, "satosa", "authz_codes") if db_uri else {}
        self.provider = Provider(self.signing_key, capabilities, authz_state, cdb, Userinfo(self.user_db))
コード例 #4
0
ファイル: app.py プロジェクト: jkakavas/loidc-op
def init_oidc_provider(app):
    with app.app_context():
        issuer = url_for('oidc_provider.index')[:-1]
        authentication_endpoint = url_for('oidc_provider.authentication_endpoint')
        jwks_uri = url_for('oidc_provider.jwks_uri')
        token_endpoint = url_for('oidc_provider.token_endpoint')
        userinfo_endpoint = url_for('oidc_provider.userinfo_endpoint')
        registration_endpoint = url_for('oidc_provider.registration_endpoint')
        end_session_endpoint = url_for('oidc_provider.end_session_endpoint')
        userinfo_ldap = LdapUserInfo()

    configuration_information = {
        'issuer': issuer,
        'authorization_endpoint': authentication_endpoint,
        'jwks_uri': jwks_uri,
        'token_endpoint': token_endpoint,
        'userinfo_endpoint': userinfo_endpoint,
        'registration_endpoint': registration_endpoint,
        'end_session_endpoint': end_session_endpoint,
        'scopes_supported': ['openid', 'profile'],
        'response_types_supported': ['code', 'code id_token', 'code token', 'code id_token token'],  # code and hybrid
        'response_modes_supported': ['query', 'fragment'],
        'grant_types_supported': ['authorization_code', 'implicit'],
        'subject_types_supported': ['pairwise'],
        'token_endpoint_auth_methods_supported': ['client_secret_basic'],
        'claims_parameter_supported': True
    }

    signing_key = RSAKey(key=rsa_load('signing_key.pem'), alg='RS256')
    provider = Provider(signing_key, configuration_information,
                        AuthorizationState(HashBasedSubjectIdentifierFactory(app.config['SUBJECT_ID_HASH_SALT'])),
                        {}, userinfo_ldap)

    return provider
コード例 #5
0
ファイル: test_provider.py プロジェクト: s-hal/pyop
def inject_provider(request):
    clients = {
        TEST_CLIENT_ID: {
            'subject_type': 'pairwise',
            'redirect_uris': [TEST_REDIRECT_URI],
            'response_types': ['code'],
            'client_secret': TEST_CLIENT_SECRET,
            'token_endpoint_auth_method': 'client_secret_post',
            'post_logout_redirect_uris':
            ['https://client.example.com/post_logout']
        }
    }

    userinfo = Userinfo({
        TEST_USER_ID: {
            'name': 'The T. Tester',
            'family_name': 'Tester',
            'given_name': 'The',
            'middle_name': 'Theodore',
            'nickname': 'testster',
            'email': '*****@*****.**',
        }
    })
    request.instance.provider = Provider(
        rsa_key(), {'issuer': ISSUER},
        AuthorizationState(HashBasedSubjectIdentifierFactory('salt')), clients,
        userinfo)
コード例 #6
0
def init_oidc_provider(app):
    with app.app_context():
        issuer = url_for("oidc_provider.index")[:-1]
        authentication_endpoint = url_for("oidc_provider.authentication_endpoint")
        jwks_uri = url_for("oidc_provider.jwks_uri")
        token_endpoint = url_for("oidc_provider.token_endpoint")
        userinfo_endpoint = url_for("oidc_provider.userinfo_endpoint")
        registration_endpoint = url_for("oidc_provider.registration_endpoint")
        end_session_endpoint = url_for("oidc_provider.end_session_endpoint")

    configuration_information = {
        "issuer": issuer,
        "authorization_endpoint": authentication_endpoint,
        "jwks_uri": jwks_uri,
        "token_endpoint": token_endpoint,
        "userinfo_endpoint": userinfo_endpoint,
        "registration_endpoint": registration_endpoint,
        "end_session_endpoint": end_session_endpoint,
        "scopes_supported": ["openid", "profile"],
        "response_types_supported": [
            "code",
            "code id_token",
            "code token",
            "code id_token token",
        ],  # code and hybrid
        "response_modes_supported": ["query", "fragment"],
        "grant_types_supported": ["authorization_code", "implicit"],
        "subject_types_supported": ["pairwise"],
        "token_endpoint_auth_methods_supported": [
            "client_secret_post",
            "client_secret_basic",
        ],
        "claims_parameter_supported": True,
    }

    clients = {
        "sample": {
            "client_secret": "sample",
            "redirect_uris": ["http://localhost:5000/test_auth_callback",],
            "response_types": ["code"],
        }
    }

    userinfo_db = Userinfo(app.users)
    signing_key = RSAKey(key=rsa_load("signing_key.pem"), alg="RS256")
    provider = Provider(
        signing_key,
        configuration_information,
        AuthorizationState(
            HashBasedSubjectIdentifierFactory(app.config["SUBJECT_ID_HASH_SALT"])
        ),
        clients,
        userinfo_db,
    )

    return provider
コード例 #7
0
 def test_matches_common_set_of_metadata_values(self, client_preference,
                                                provider_capability,
                                                client_value,
                                                provider_value):
     provider_capabilities = provide_configuration()
     provider_capabilities.update({provider_capability: provider_value})
     provider = Provider(
         rsa_key(), provider_capabilities,
         AuthorizationState(HashBasedSubjectIdentifierFactory('salt')), {},
         None)
     request = {
         'redirect_uris': ['https://client.example.com/redirect'],
         client_preference: client_value
     }
     response = provider.handle_client_registration_request(
         json.dumps(request))
     expected_values = set(client_value).intersection(provider_value)
     assert Counter(frozenset(v.split()) for v in response[client_preference]) == \
            Counter(frozenset(v.split()) for v in expected_values)
コード例 #8
0
 def plugin_enable(self) -> None:
     # pylint: disable=attribute-defined-outside-init
     self.signing_key = RSAKey(key=rsa_load("signing_key.pem"),
                               use="sig",
                               alg="RS256")
     # pylint: disable=attribute-defined-outside-init
     self.oidc_configuration_information = {
         "issuer":
         self.app.config["URL_BASE"].replace("http://", "https://"),
         "authorization_endpoint":
         self.app.config["URL_BASE"] + "/oidc/authorization",
         "jwks_uri":
         self.app.config["URL_BASE"] + "/oidc/jwks",
         "token_endpoint":
         self.app.config["URL_BASE"] + "/oidc/token",
         "userinfo_endpoint":
         self.app.config["URL_BASE"] + "/oidc/userinfo",
         "response_types_supported": ["code"],
         "id_token_signing_alg_values_supported": [self.signing_key.alg],
         "response_modes_supported": ["fragment", "query"],
         "subject_types_supported": ["public", "pairwise"],
         "grant_types_supported": ["authorization_code", "implicit"],
         "claim_types_supported": ["normal"],
         "claims_parameter_supported":
         True,
         "claims_supported":
         ["sub", "name", "given_name", "family_name", "email", "profile"],
         "scopes_supported": ["openid", "email", "profile"]
     }
     subject_id_factory = HashBasedSubjectIdentifierFactory(
         self.app.config["SECRET_KEY"])
     # pylint: disable=attribute-defined-outside-init
     self.provider = Provider(
         self.signing_key, self.oidc_configuration_information,
         AuthorizationState(
             subject_id_factory,
             SQLWrapper(self.storage, 'authz_codes'),
             SQLWrapper(self.storage, 'access_tokens'),
             SQLWrapper(self.storage, 'refresh_tokens'),
             SQLWrapper(self.storage, 'subject_identifiers', True),
         ), SQLWrapper(self.storage, 'clients'),
         Userinfo(PersonWrapper(self.storage)))
コード例 #9
0
def _create_provider(
    provider_config,
    endpoint_baseurl,
    internal_attributes,
    signing_key,
    authz_state,
    user_db,
    cdb,
):
    response_types_supported = provider_config.get("response_types_supported",
                                                   ["id_token"])
    subject_types_supported = provider_config.get("subject_types_supported",
                                                  ["pairwise"])
    scopes_supported = provider_config.get("scopes_supported", ["openid"])
    extra_scopes = provider_config.get("extra_scopes")
    capabilities = {
        "issuer":
        provider_config["issuer"],
        "authorization_endpoint":
        "{}/{}".format(endpoint_baseurl, AuthorizationEndpoint.url),
        "jwks_uri":
        "{}/jwks".format(endpoint_baseurl),
        "response_types_supported":
        response_types_supported,
        "id_token_signing_alg_values_supported": [signing_key.alg],
        "response_modes_supported": ["fragment", "query"],
        "subject_types_supported":
        subject_types_supported,
        "claim_types_supported": ["normal"],
        "claims_parameter_supported":
        True,
        "claims_supported": [
            attribute_map["openid"][0]
            for attribute_map in internal_attributes["attributes"].values()
            if "openid" in attribute_map
        ],
        "request_parameter_supported":
        False,
        "request_uri_parameter_supported":
        False,
        "scopes_supported":
        scopes_supported
    }

    if 'code' in response_types_supported:
        capabilities["token_endpoint"] = "{}/{}".format(
            endpoint_baseurl, TokenEndpoint.url)

    if provider_config.get("client_registration_supported", False):
        capabilities["registration_endpoint"] = "{}/{}".format(
            endpoint_baseurl, RegistrationEndpoint.url)

    provider = Provider(
        signing_key,
        capabilities,
        authz_state,
        cdb,
        Userinfo(user_db),
        extra_scopes=extra_scopes,
        id_token_lifetime=provider_config.get("id_token_lifetime", 3600),
    )
    return provider
コード例 #10
0
 def test_jwks(self):
     provider = Provider(rsa_key(), provide_configuration(), None, None,
                         None)
     assert provider.jwks == {'keys': [provider.signing_key.serialize()]}
コード例 #11
0
 def test_provider_configuration(self):
     config = provide_configuration()
     config.update({'foo': 'bar', 'abc': 'xyz'})
     provider = Provider(None, config, None, None, None)
     provider_config = provider.provider_configuration
     assert all(k in provider_config for k in config)
コード例 #12
0
class OIDCPlugin(KosekiPlugin):
    def config(self) -> dict:
        return {}

    def plugin_enable(self) -> None:
        # pylint: disable=attribute-defined-outside-init
        self.signing_key = RSAKey(key=rsa_load("signing_key.pem"),
                                  use="sig",
                                  alg="RS256")
        # pylint: disable=attribute-defined-outside-init
        self.oidc_configuration_information = {
            "issuer":
            self.app.config["URL_BASE"].replace("http://", "https://"),
            "authorization_endpoint":
            self.app.config["URL_BASE"] + "/oidc/authorization",
            "jwks_uri":
            self.app.config["URL_BASE"] + "/oidc/jwks",
            "token_endpoint":
            self.app.config["URL_BASE"] + "/oidc/token",
            "userinfo_endpoint":
            self.app.config["URL_BASE"] + "/oidc/userinfo",
            "response_types_supported": ["code"],
            "id_token_signing_alg_values_supported": [self.signing_key.alg],
            "response_modes_supported": ["fragment", "query"],
            "subject_types_supported": ["public", "pairwise"],
            "grant_types_supported": ["authorization_code", "implicit"],
            "claim_types_supported": ["normal"],
            "claims_parameter_supported":
            True,
            "claims_supported":
            ["sub", "name", "given_name", "family_name", "email", "profile"],
            "scopes_supported": ["openid", "email", "profile"]
        }
        subject_id_factory = HashBasedSubjectIdentifierFactory(
            self.app.config["SECRET_KEY"])
        # pylint: disable=attribute-defined-outside-init
        self.provider = Provider(
            self.signing_key, self.oidc_configuration_information,
            AuthorizationState(
                subject_id_factory,
                SQLWrapper(self.storage, 'authz_codes'),
                SQLWrapper(self.storage, 'access_tokens'),
                SQLWrapper(self.storage, 'refresh_tokens'),
                SQLWrapper(self.storage, 'subject_identifiers', True),
            ), SQLWrapper(self.storage, 'clients'),
            Userinfo(PersonWrapper(self.storage)))

    def create_blueprint(self) -> Blueprint:
        blueprint: Blueprint = Blueprint("oidc",
                                         __name__,
                                         template_folder="./templates")
        blueprint.add_url_rule("/.well-known/openid-configuration",
                               None,
                               self.oidc_config,
                               methods=["GET"])
        blueprint.add_url_rule("/oidc/jwks",
                               None,
                               self.oidc_jwks,
                               methods=["GET"])
        blueprint.add_url_rule("/oidc/authorization",
                               None,
                               self.auth.require_session(
                                   self.oidc_authorization, None),
                               methods=["GET"])
        blueprint.add_url_rule("/oidc/token",
                               None,
                               self.oidc_token,
                               methods=["GET", "POST"])
        blueprint.add_url_rule("/oidc/userinfo",
                               None,
                               self.oidc_userinfo,
                               methods=["GET", "POST"])
        return blueprint

    def oidc_config(self) -> Union[str, Response]:
        return Response(self.provider.provider_configuration.to_json(),
                        mimetype="application/json")

    def oidc_jwks(self) -> Union[str, Response]:
        return jsonify(self.provider.jwks)

    # require_session
    def oidc_authorization(self) -> Union[str, Response]:
        person: Person = self.storage.session.query(Person).filter_by(
            uid=self.util.current_user()).scalar()
        if person.state != "active":
            logging.warning(
                "User {} tried to log in via OIDC without membership!",
                person.uid)
            self.util.alert(
                KosekiAlert(
                    KosekiAlertType.DANGER,
                    "Missing membership!",
                    "OpenID Connect login was successful, but active " +
                    "membership is required to log in to 3rd party services.",
                ))
            return render_template("oidc.html")

        try:
            # Override claims to always only and at least return email for CF
            authn_req = AuthorizationRequest().from_dict({
                **request.args, "claims": {
                    "id_token": {
                        "email": {
                            "essential": True
                        }
                    }
                }
            })
            logging.debug(authn_req)
            authn_req.verify()
            if authn_req["client_id"] not in self.provider.clients:
                self.util.alert(
                    KosekiAlert(
                        KosekiAlertType.DANGER,
                        "Invalid Client",
                        "Unknown Client ID. All applications must be pre-registered.",
                    ))
                return render_template("oidc.html")
            client = self.provider.clients[authn_req["client_id"]]
            if client["redirect_uri"] != authn_req["redirect_uri"]:
                self.util.alert(
                    KosekiAlert(
                        KosekiAlertType.DANGER,
                        "Incorrect Redirect URI",
                        "Unauthorized redirect URI for this client.",
                    ))
                return render_template("oidc.html")

            authn_response = self.provider.authorize(
                authn_req, str(self.util.current_user()))
            return_url = authn_response.request(
                authn_req["redirect_uri"], should_fragment_encode(authn_req))
            return redirect(return_url)
        except Exception as err:  # pylint: disable=broad-except
            logging.error(err)
            self.util.alert(
                KosekiAlert(
                    KosekiAlertType.DANGER,
                    "OIDC Error",
                    "Error: {}".format(str(err)),
                ))
            return render_template("oidc.html")

    def oidc_token(self) -> Union[str, Response]:
        print(request)
        try:
            token_response = self.provider.handle_token_request(
                request.get_data().decode("utf-8"), request.headers)
            return Response(token_response.to_json(),
                            mimetype="application/json")
        except InvalidClientAuthentication as err:
            error_resp = TokenErrorResponse(error="invalid_client",
                                            error_description=str(err))
            http_response = Response(error_resp.to_json(),
                                     status=401,
                                     mimetype="application/json")
            http_response.headers["WWW-Authenticate"] = "Basic"
            logging.error(error_resp.to_dict())
            return http_response
        except OAuthError as err:
            error_resp = TokenErrorResponse(error=err.oauth_error,
                                            error_description=str(err))
            return Response(error_resp.to_json(),
                            status=400,
                            mimetype="application/json")

    def oidc_userinfo(self) -> Union[str, Response]:
        try:
            response = self.provider.handle_userinfo_request(
                request.get_data().decode("utf-8"), request.headers)
            return Response(response.to_json(), mimetype="application/json")
        except (BearerTokenError, InvalidAccessToken) as err:
            error_resp = UserInfoErrorResponse(error="invalid_token",
                                               error_description=str(err))
            http_response = Response(error_resp.to_json(),
                                     status=401,
                                     mimetype="application/json")
            http_response.headers[
                "WWW-Authenticate"] = AccessToken.BEARER_TOKEN_TYPE
            logging.error(error_resp.to_dict())
            return http_response
コード例 #13
0
ファイル: test_provider.py プロジェクト: s-hal/pyop
 def test_jwks(self):
     provider = Provider(rsa_key(), {'issuer': ISSUER}, None, None, None)
     assert provider.jwks == {'keys': [provider.signing_key.serialize()]}
コード例 #14
0
ファイル: test_provider.py プロジェクト: s-hal/pyop
 def test_provider_configuration(self):
     config = {'issuer': ISSUER, 'foo': 'bar', 'abc': 'xyz'}
     provider = Provider(None, config, None, None, None)
     provider_config = provider.provider_configuration
     assert all(k in provider_config for k in config)
コード例 #15
0
def init_oidc_provider(app):
    with app.app_context():
        config = {
            'issuer':
            app.config['OIDC_PROVIDER']['issuer'],
            'authorization_endpoint':
            url_for('oidc_provider.auth'),
            'jwks_uri':
            url_for('oidc_provider.jwks'),
            'token_endpoint':
            url_for('oidc_provider.token'),
            'userinfo_endpoint':
            url_for('oidc_provider.userinfo'),
            'registration_endpoint':
            url_for('oidc_provider.register'),
            'end_session_endpoint':
            url_for('oidc_provider.logout'),
            'scopes_supported': ['openid', 'profile'],
            'response_types_supported':
            ['code', 'code id_token', 'code token', 'code id_token token'],
            'response_modes_supported': ['query', 'fragment'],
            'grant_types_supported': ['authorization_code', 'implicit'],
            'subject_types_supported': ['pairwise'],
            'token_endpoint_auth_methods_supported': ['client_secret_basic'],
            'claims_parameter_supported':
            True
        }
    userinfo_db = Userinfo()
    signing_key = RSAKey(key=rsa_load('signing_key.pem'), alg='RS256')
    authz_state = AuthorizationState(
        HashBasedSubjectIdentifierFactory(
            app.config['OIDC_PROVIDER']['subject_id_hash_salt']))
    clients = {
        'notify-test': {
            'client_name':
            'GOV.UK Notify',
            'client_secret':
            'notify-secret',
            'client_uri':
            'https://admin-ags.notify.works/',
            'post_logout_redirect_uris': [
                'https://admin-ags.notify.works/sign-out',
                'http://localhost:6012/sign-out',
            ],
            'redirect_uris': [
                'https://admin-ags.notify.works/oidc_callback',
                'http://localhost:6012/oidc_callback',
            ],
            'response_types': [
                'code',
            ],
        },
        'test-client': {
            'client_name':
            'Test',
            'client_secret':
            'test-secret',
            'post_logout_redirect_uris': [
                'http://example.com/sign-out',
                'https://sue-my-brother.cloudapps.digital/sign-out',
            ],
            'redirect_uris': [
                'http://example.com/oidc_callback',
                'https://sue-my-brother.cloudapps.digital/oidc_callback',
            ],
            'response_types': [
                'code',
            ],
        },
    }
    provider = Provider(signing_key, config, authz_state, clients, userinfo_db)
    return provider
コード例 #16
0
ファイル: openid_connect.py プロジェクト: its-dirg/SATOSA
class OpenIDConnectFrontend(FrontendModule):
    """
    A OpenID Connect frontend module
    """

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

        self.config = conf
        self.signing_key = RSAKey(key=rsa_load(conf["signing_key_path"]), use="sig", alg="RS256")

    def _create_provider(self, endpoint_baseurl):
        response_types_supported = self.config["provider"].get("response_types_supported", ["id_token"])
        subject_types_supported = self.config["provider"].get("subject_types_supported", ["pairwise"])
        scopes_supported = self.config["provider"].get("scopes_supported", ["openid"])
        capabilities = {
            "issuer": self.base_url,
            "authorization_endpoint": "{}/{}".format(endpoint_baseurl, AuthorizationEndpoint.url),
            "jwks_uri": "{}/jwks".format(endpoint_baseurl),
            "response_types_supported": response_types_supported,
            "id_token_signing_alg_values_supported": [self.signing_key.alg],
            "response_modes_supported": ["fragment", "query"],
            "subject_types_supported": subject_types_supported,
            "claim_types_supported": ["normal"],
            "claims_parameter_supported": True,
            "claims_supported": [
                attribute_map["openid"][0]
                for attribute_map in self.internal_attributes["attributes"].values()
                if "openid" in attribute_map
            ],
            "request_parameter_supported": False,
            "request_uri_parameter_supported": False,
            "scopes_supported": scopes_supported,
        }

        if self.config["provider"].get("client_registration_supported", False):
            capabilities["registration_endpoint"] = "{}/{}".format(endpoint_baseurl, RegistrationEndpoint.url)

        authz_state = self._init_authorization_state()
        db_uri = self.config.get("db_uri")
        cdb = MongoWrapper(db_uri, "satosa", "clients") if db_uri else {}
        self.user_db = MongoWrapper(db_uri, "satosa", "authz_codes") if db_uri else {}
        self.provider = Provider(self.signing_key, capabilities, authz_state, cdb, Userinfo(self.user_db))

    def _init_authorization_state(self):
        sub_hash_salt = self.config.get("sub_hash_salt", rndstr(16))
        db_uri = self.config.get("db_uri")
        if db_uri:
            authz_code_db = MongoWrapper(db_uri, "satosa", "authz_codes")
            access_token_db = MongoWrapper(db_uri, "satosa", "access_tokens")
            refresh_token_db = MongoWrapper(db_uri, "satosa", "refresh_tokens")
            sub_db = MongoWrapper(db_uri, "satosa", "subject_identifiers")
        else:
            authz_code_db = None
            access_token_db = None
            refresh_token_db = None
            sub_db = None

        token_lifetimes = {
            k: self.config["provider"][k]
            for k in [
                "authorization_code_lifetime",
                "access_token_lifetime",
                "refresh_token_lifetime",
                "refresh_token_threshold",
            ]
            if k in self.config["provider"]
        }
        return AuthorizationState(
            HashBasedSubjectIdentifierFactory(sub_hash_salt),
            authz_code_db,
            access_token_db,
            refresh_token_db,
            sub_db,
            **token_lifetimes
        )

    def handle_authn_response(self, context, internal_resp, extra_id_token_claims=None):
        """
        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)

        attributes = self.converter.from_internal("openid", internal_resp.attributes)
        self.user_db[internal_resp.user_id] = {k: v[0] for k, v in attributes.items()}
        auth_resp = self.provider.authorize(auth_req, internal_resp.user_id, extra_id_token_claims)

        del context.state[self.name]
        http_response = auth_resp.request(auth_req["redirect_uri"], 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"], should_fragment_encode(auth_req)))

    def register_endpoints(self, backend_names):
        """
        See super class satosa.frontends.base.FrontendModule
        :type backend_names: list[str]
        :rtype: list[(str, ((satosa.context.Context, Any) -> satosa.response.Response, Any))]
        :raise ValueError: if more than one backend is configured
        """
        backend_name = None
        if len(backend_names) != 1:
            # only supports one backend since there currently is no way to publish multiple authorization endpoints
            # in configuration information and there is no other standard way of authorization_endpoint discovery
            # similar to SAML entity discovery
            # this can be circumvented with a custom RequestMicroService which handles the routing based on something
            # in the authentication request
            logger.warn(
                "More than one backend is configured, make sure to provide a custom routing micro service to "
                "determine which backend should be used per request."
            )
        else:
            backend_name = backend_names[0]

        endpoint_baseurl = "{}/{}".format(self.base_url, self.name)
        self._create_provider(endpoint_baseurl)

        provider_config = ("^.well-known/openid-configuration$", self.provider_config)
        jwks_uri = ("^{}/jwks$".format(self.name), self.jwks)

        if backend_name:
            # if there is only one backend, include its name in the path so the default routing can work
            auth_endpoint = "{}/{}/{}/{}".format(self.base_url, backend_name, self.name, AuthorizationEndpoint.url)
            self.provider.configuration_information["authorization_endpoint"] = auth_endpoint
            auth_path = urlparse(auth_endpoint).path.lstrip("/")
        else:
            auth_path = "{}/{}".format(self.name, AuthorizationEndpoint.url)
        authentication = ("^{}$".format(auth_path), self.handle_authn_request)
        url_map = [provider_config, jwks_uri, authentication]

        if any("code" in v for v in self.provider.configuration_information["response_types_supported"]):
            self.provider.configuration_information["token_endpoint"] = "{}/{}".format(
                endpoint_baseurl, TokenEndpoint.url
            )
            token_endpoint = ("^{}/{}".format(self.name, TokenEndpoint.url), self.token_endpoint)
            url_map.append(token_endpoint)

            self.provider.configuration_information["userinfo_endpoint"] = "{}/{}".format(
                endpoint_baseurl, UserinfoEndpoint.url
            )
            userinfo_endpoint = ("^{}/{}".format(self.name, UserinfoEndpoint.url), self.userinfo_endpoint)
            url_map.append(userinfo_endpoint)
        if "registration_endpoint" in self.provider.configuration_information:
            client_registration = ("^{}/{}".format(self.name, RegistrationEndpoint.url), self.client_registration)
            url_map.append(client_registration)

        return url_map

    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 {"signing_key_path", "provider"}:
            if k not in config:
                raise ValueError("Missing configuration parameter '{}' for OpenID Connect frontend.".format(k))

    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
        """
        return AuthorizationRequest().deserialize(state[self.name]["oidc_request"])

    def client_registration(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
        """
        try:
            resp = self.provider.handle_client_registration_request(json.dumps(context.request))
            return Created(resp.to_json(), content="application/json")
        except InvalidClientRegistrationRequest as e:
            return BadRequest(e.to_json(), content="application/json")

    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
        """
        return Response(self.provider.provider_configuration.to_json(), content="application/json")

    def _get_approved_attributes(self, provider_supported_claims, authn_req):
        requested_claims = list(scope2claims(authn_req["scope"]).keys())
        if "claims" in authn_req:
            for k in ["id_token", "userinfo"]:
                if k in authn_req["claims"]:
                    requested_claims.extend(authn_req["claims"][k].keys())
        return set(provider_supported_claims).intersection(set(requested_claims))

    def _handle_authn_request(self, context):
        """
        Parse and verify the authentication request into an internal request.
        :type context: satosa.context.Context
        :rtype: internal_data.InternalRequest

        :param context: the current context
        :return: the internal request
        """
        request = urlencode(context.request)
        satosa_logging(logger, logging.DEBUG, "Authn req from client: {}".format(request), context.state)

        try:
            authn_req = self.provider.parse_authentication_request(request)
        except InvalidAuthenticationRequest as e:
            satosa_logging(logger, logging.ERROR, "Error in authn req: {}".format(str(e)), context.state)
            error_url = e.to_error_url()

            if error_url:
                return SeeOther(error_url)
            else:
                return BadRequest("Something went wrong: {}".format(str(e)))

        client_id = authn_req["client_id"]
        context.state[self.name] = {"oidc_request": request}
        hash_type = oidc_subject_type_to_hash_type(self.provider.clients[client_id].get("subject_type", "pairwise"))
        client_name = self.provider.clients[client_id].get("client_name")
        if client_name:
            # TODO should process client names for all languages, see OIDC Registration, Section 2.1
            requester_name = [{"lang": "en", "text": client_name}]
        else:
            requester_name = None
        internal_req = InternalRequest(hash_type, client_id, requester_name)

        internal_req.approved_attributes = self.converter.to_internal_filter(
            "openid",
            self._get_approved_attributes(self.provider.configuration_information["claims_supported"], authn_req),
        )
        return internal_req

    def handle_authn_request(self, context):
        """
        Handle an 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
        """
        internal_req = self._handle_authn_request(context)
        if not isinstance(internal_req, InternalRequest):
            return internal_req
        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.jwks), content="application/json")

    def token_endpoint(self, context):
        """
        Handle token requests (served at /token).
        :type context: satosa.context.Context
        :rtype: oic.utils.http_util.Response

        :param context: the current context
        :return: HTTP response to the client
        """
        headers = {"Authorization": context.request_authorization}
        try:
            response = self.provider.handle_token_request(urlencode(context.request), headers)
            return Response(response.to_json(), content="application/json")
        except InvalidClientAuthentication as e:
            logger.debug("invalid client authentication at token endpoint", exc_info=True)
            error_resp = TokenErrorResponse(error="invalid_client", error_description=str(e))
            response = Unauthorized(
                error_resp.to_json(), headers=[("WWW-Authenticate", "Basic")], content="application/json"
            )
            return response
        except OAuthError as e:
            logger.debug("invalid request: %s", str(e), exc_info=True)
            error_resp = TokenErrorResponse(error=e.oauth_error, error_description=str(e))
            return BadRequest(error_resp.to_json(), content="application/json")

    def userinfo_endpoint(self, context):
        headers = {"Authorization": context.request_authorization}

        try:
            response = self.provider.handle_userinfo_request(urlencode(context.request), headers)
            return Response(response.to_json(), content="application/json")
        except (BearerTokenError, InvalidAccessToken) as e:
            error_resp = UserInfoErrorResponse(error="invalid_token", error_description=str(e))
            response = Unauthorized(
                error_resp.to_json(),
                headers=[("WWW-Authenticate", AccessToken.BEARER_TOKEN_TYPE)],
                content="application/json",
            )
            return response
コード例 #17
0
class OpenIDConnectFrontend(FrontendModule):
    """
    A OpenID Connect frontend module
    """
    def __init__(self, auth_req_callback_func, internal_attributes, conf,
                 base_url, name):
        self._validate_config(conf)
        super().__init__(auth_req_callback_func, internal_attributes, base_url,
                         name)

        self.config = conf
        self.signing_key = RSAKey(key=rsa_load(conf["signing_key_path"]),
                                  use="sig",
                                  alg="RS256")

    def _create_provider(self, endpoint_baseurl):
        response_types_supported = self.config["provider"].get(
            "response_types_supported", ["id_token"])
        subject_types_supported = self.config["provider"].get(
            "subject_types_supported", ["pairwise"])
        scopes_supported = self.config["provider"].get("scopes_supported",
                                                       ["openid"])
        capabilities = {
            "issuer":
            self.base_url,
            "authorization_endpoint":
            "{}/{}".format(endpoint_baseurl, AuthorizationEndpoint.url),
            "jwks_uri":
            "{}/jwks".format(endpoint_baseurl),
            "response_types_supported":
            response_types_supported,
            "id_token_signing_alg_values_supported": [self.signing_key.alg],
            "response_modes_supported": ["fragment", "query"],
            "subject_types_supported":
            subject_types_supported,
            "claim_types_supported": ["normal"],
            "claims_parameter_supported":
            True,
            "claims_supported": [
                attribute_map["openid"][0] for attribute_map in
                self.internal_attributes["attributes"].values()
                if "openid" in attribute_map
            ],
            "request_parameter_supported":
            False,
            "request_uri_parameter_supported":
            False,
            "scopes_supported":
            scopes_supported
        }

        if 'code' in response_types_supported:
            capabilities["token_endpoint"] = "{}/{}".format(
                endpoint_baseurl, TokenEndpoint.url)

        if self.config["provider"].get("client_registration_supported", False):
            capabilities["registration_endpoint"] = "{}/{}".format(
                endpoint_baseurl, RegistrationEndpoint.url)

        authz_state = self._init_authorization_state()
        db_uri = self.config.get("db_uri")
        cdb_file = self.config.get("client_db_path")
        if db_uri:
            cdb = MongoWrapper(db_uri, "satosa", "clients")
        elif cdb_file:
            with open(cdb_file) as f:
                cdb = json.loads(f.read())
        else:
            cdb = {}
        self.user_db = MongoWrapper(db_uri, "satosa",
                                    "authz_codes") if db_uri else {}
        self.provider = Provider(self.signing_key, capabilities, authz_state,
                                 cdb, Userinfo(self.user_db))

    def _init_authorization_state(self):
        sub_hash_salt = self.config.get("sub_hash_salt", rndstr(16))
        db_uri = self.config.get("db_uri")
        if db_uri:
            authz_code_db = MongoWrapper(db_uri, "satosa", "authz_codes")
            access_token_db = MongoWrapper(db_uri, "satosa", "access_tokens")
            refresh_token_db = MongoWrapper(db_uri, "satosa", "refresh_tokens")
            sub_db = MongoWrapper(db_uri, "satosa", "subject_identifiers")
        else:
            authz_code_db = None
            access_token_db = None
            refresh_token_db = None
            sub_db = None

        token_lifetimes = {
            k: self.config["provider"][k]
            for k in [
                "authorization_code_lifetime", "access_token_lifetime",
                "refresh_token_lifetime", "refresh_token_threshold"
            ] if k in self.config["provider"]
        }
        return AuthorizationState(
            HashBasedSubjectIdentifierFactory(sub_hash_salt), authz_code_db,
            access_token_db, refresh_token_db, sub_db, **token_lifetimes)

    def handle_authn_response(self,
                              context,
                              internal_resp,
                              extra_id_token_claims=None):
        """
        See super class method satosa.frontends.base.FrontendModule#handle_authn_response
        :type context: satosa.context.Context
        :type internal_response: satosa.internal.InternalData
        :rtype oic.utils.http_util.Response
        """

        auth_req = self._get_authn_request_from_state(context.state)

        attributes = self.converter.from_internal("openid",
                                                  internal_resp.attributes)
        self.user_db[internal_resp.subject_id] = {
            k: v[0]
            for k, v in attributes.items()
        }
        auth_resp = self.provider.authorize(auth_req, internal_resp.subject_id,
                                            extra_id_token_claims)

        del context.state[self.name]
        http_response = auth_resp.request(auth_req["redirect_uri"],
                                          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)
        # If the client sent us a state parameter, we should reflect it back according to the spec
        if 'state' in auth_req:
            error_resp = AuthorizationErrorResponse(
                error="access_denied",
                error_description=exception.message,
                state=auth_req['state'])
        else:
            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"],
                               should_fragment_encode(auth_req)))

    def register_endpoints(self, backend_names):
        """
        See super class satosa.frontends.base.FrontendModule
        :type backend_names: list[str]
        :rtype: list[(str, ((satosa.context.Context, Any) -> satosa.response.Response, Any))]
        :raise ValueError: if more than one backend is configured
        """
        backend_name = None
        if len(backend_names) != 1:
            # only supports one backend since there currently is no way to publish multiple authorization endpoints
            # in configuration information and there is no other standard way of authorization_endpoint discovery
            # similar to SAML entity discovery
            # this can be circumvented with a custom RequestMicroService which handles the routing based on something
            # in the authentication request
            logger.warn(
                "More than one backend is configured, make sure to provide a custom routing micro service to "
                "determine which backend should be used per request.")
        else:
            backend_name = backend_names[0]

        endpoint_baseurl = "{}/{}".format(self.base_url, self.name)
        self._create_provider(endpoint_baseurl)

        provider_config = ("^.well-known/openid-configuration$",
                           self.provider_config)
        jwks_uri = ("^{}/jwks$".format(self.name), self.jwks)

        if backend_name:
            # if there is only one backend, include its name in the path so the default routing can work
            auth_endpoint = "{}/{}/{}/{}".format(self.base_url, backend_name,
                                                 self.name,
                                                 AuthorizationEndpoint.url)
            self.provider.configuration_information[
                "authorization_endpoint"] = auth_endpoint
            auth_path = urlparse(auth_endpoint).path.lstrip("/")
        else:
            auth_path = "{}/{}".format(self.name, AuthorizationEndpoint.url)
        authentication = ("^{}$".format(auth_path), self.handle_authn_request)
        url_map = [provider_config, jwks_uri, authentication]

        if any("code" in v for v in self.provider.
               configuration_information["response_types_supported"]):
            self.provider.configuration_information[
                "token_endpoint"] = "{}/{}".format(endpoint_baseurl,
                                                   TokenEndpoint.url)
            token_endpoint = ("^{}/{}".format(self.name, TokenEndpoint.url),
                              self.token_endpoint)
            url_map.append(token_endpoint)

            self.provider.configuration_information[
                "userinfo_endpoint"] = "{}/{}".format(endpoint_baseurl,
                                                      UserinfoEndpoint.url)
            userinfo_endpoint = ("^{}/{}".format(self.name,
                                                 UserinfoEndpoint.url),
                                 self.userinfo_endpoint)
            url_map.append(userinfo_endpoint)
        if "registration_endpoint" in self.provider.configuration_information:
            client_registration = ("^{}/{}".format(self.name,
                                                   RegistrationEndpoint.url),
                                   self.client_registration)
            url_map.append(client_registration)

        return url_map

    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 {"signing_key_path", "provider"}:
            if k not in config:
                raise ValueError(
                    "Missing configuration parameter '{}' for OpenID Connect frontend."
                    .format(k))

    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
        """
        return AuthorizationRequest().deserialize(
            state[self.name]["oidc_request"])

    def client_registration(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
        """
        try:
            resp = self.provider.handle_client_registration_request(
                json.dumps(context.request))
            return Created(resp.to_json(), content="application/json")
        except InvalidClientRegistrationRequest as e:
            return BadRequest(e.to_json(), content="application/json")

    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
        """
        return Response(self.provider.provider_configuration.to_json(),
                        content="application/json")

    def _get_approved_attributes(self, provider_supported_claims, authn_req):
        requested_claims = list(scope2claims(authn_req["scope"]).keys())
        if "claims" in authn_req:
            for k in ["id_token", "userinfo"]:
                if k in authn_req["claims"]:
                    requested_claims.extend(authn_req["claims"][k].keys())
        return set(provider_supported_claims).intersection(
            set(requested_claims))

    def _handle_authn_request(self, context):
        """
        Parse and verify the authentication request into an internal request.
        :type context: satosa.context.Context
        :rtype: satosa.internal.InternalData

        :param context: the current context
        :return: the internal request
        """
        request = urlencode(context.request)
        satosa_logging(logger, logging.DEBUG,
                       "Authn req from client: {}".format(request),
                       context.state)

        try:
            authn_req = self.provider.parse_authentication_request(request)
        except InvalidAuthenticationRequest as e:
            satosa_logging(logger, logging.ERROR,
                           "Error in authn req: {}".format(str(e)),
                           context.state)
            error_url = e.to_error_url()

            if error_url:
                return SeeOther(error_url)
            else:
                return BadRequest("Something went wrong: {}".format(str(e)))

        client_id = authn_req["client_id"]
        context.state[self.name] = {"oidc_request": request}
        subject_type = self.provider.clients[client_id].get(
            "subject_type", "pairwise")
        client_name = self.provider.clients[client_id].get("client_name")
        if client_name:
            # TODO should process client names for all languages, see OIDC Registration, Section 2.1
            requester_name = [{"lang": "en", "text": client_name}]
        else:
            requester_name = None
        internal_req = InternalData(
            subject_type=subject_type,
            requester=client_id,
            requester_name=requester_name,
        )

        internal_req.attributes = self.converter.to_internal_filter(
            "openid",
            self._get_approved_attributes(
                self.provider.configuration_information["claims_supported"],
                authn_req))
        return internal_req

    def handle_authn_request(self, context):
        """
        Handle an 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
        """
        internal_req = self._handle_authn_request(context)
        if not isinstance(internal_req, InternalData):
            return internal_req
        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.jwks),
                        content="application/json")

    def token_endpoint(self, context):
        """
        Handle token requests (served at /token).
        :type context: satosa.context.Context
        :rtype: oic.utils.http_util.Response

        :param context: the current context
        :return: HTTP response to the client
        """
        headers = {"Authorization": context.request_authorization}
        try:
            response = self.provider.handle_token_request(
                urlencode(context.request), headers)
            return Response(response.to_json(), content="application/json")
        except InvalidClientAuthentication as e:
            logger.debug('invalid client authentication at token endpoint',
                         exc_info=True)
            error_resp = TokenErrorResponse(error='invalid_client',
                                            error_description=str(e))
            response = Unauthorized(error_resp.to_json(),
                                    headers=[("WWW-Authenticate", "Basic")],
                                    content="application/json")
            return response
        except OAuthError as e:
            logger.debug('invalid request: %s', str(e), exc_info=True)
            error_resp = TokenErrorResponse(error=e.oauth_error,
                                            error_description=str(e))
            return BadRequest(error_resp.to_json(), content="application/json")

    def userinfo_endpoint(self, context):
        headers = {"Authorization": context.request_authorization}

        try:
            response = self.provider.handle_userinfo_request(
                urlencode(context.request), headers)
            return Response(response.to_json(), content="application/json")
        except (BearerTokenError, InvalidAccessToken) as e:
            error_resp = UserInfoErrorResponse(error='invalid_token',
                                               error_description=str(e))
            response = Unauthorized(error_resp.to_json(),
                                    headers=[("WWW-Authenticate",
                                              AccessToken.BEARER_TOKEN_TYPE)],
                                    content="application/json")
            return response
コード例 #18
0
def init_oidc_provider(app):
    with app.app_context():
        issuer = url_for("oidc_provider.index")[:-1]
        authentication_endpoint = url_for(
            "oidc_provider.authentication_endpoint")
        jwks_uri = url_for("oidc_provider.jwks_uri")
        token_endpoint = url_for("oidc_provider.token_endpoint")
        userinfo_endpoint = url_for("oidc_provider.userinfo_endpoint")
        registration_endpoint = url_for("oidc_provider.registration_endpoint")
        end_session_endpoint = url_for("oidc_provider.end_session_endpoint")

    configuration_information = {
        "issuer":
        issuer,
        "authorization_endpoint":
        authentication_endpoint,
        "jwks_uri":
        jwks_uri,
        "token_endpoint":
        token_endpoint,
        "userinfo_endpoint":
        userinfo_endpoint,
        "registration_endpoint":
        registration_endpoint,
        "end_session_endpoint":
        end_session_endpoint,
        "scopes_supported":
        app.config["OIDC_SCOPES_SUPPORTED"],
        "response_types_supported":
        app.config["OIDC_RESPONSE_TYPES_SUPPORTED"],
        "response_modes_supported":
        app.config["OIDC_RESPONSE_MODES_SUPPORTED"],
        "grant_types_supported":
        app.config["OIDC_GRANT_TYPES_SUPPORTED"],
        "subject_types_supported":
        app.config["OIDC_SUBJECT_TYPE_SUPPORTED"],
        "token_endpoint_auth_methods_supported":
        app.config["OIDC_TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED"],
        "userinfo_signing_alg_values_supported":
        app.config["OIDC_USERINFO_SIGNING_ALG_VALUES_SUPPORTED"],
        "claims_parameter_supported":
        app.config["OIDC_CLAIMS_PARAMETER_SUPPORTED"],
        "claims_supported":
        app.config["OIDC_CLAIMS_SUPPORTED"],
    }

    userinfo_db = Userinfo(app.sql_backend)
    signing_key = RSAKey(key=rsa_load(app.config["SIGNING_KEY_FILE"]),
                         alg=app.config["SIGNING_KEY_ALG"],
                         kid=app.config["SIGNING_KEY_ID"])
    provider = Provider(
        signing_key,
        configuration_information,
        AuthorizationState(
            HashBasedSubjectIdentifierFactory(
                app.config["SUBJECT_ID_HASH_SALT"])),
        ClientRPSQLWrapper(),
        userinfo_db,
    )

    return provider