From 94dec632f1d3b0e9d995372d6ba2b583cc7a3ef8 Mon Sep 17 00:00:00 2001 From: Kristof Bajnok Date: Thu, 13 May 2021 14:00:57 +0200 Subject: [PATCH] OIDC frontend: support Redis and session expiration Support all storage backends from recent pyop. Add automatic expiration TTL for the different collections so that the session databases does not grow without bounds. TODO: add pyop version requirement once there is an official release. --- doc/README.md | 8 +++--- setup.py | 2 +- src/satosa/frontends/openid_connect.py | 35 ++++++++++++++++++++------ 3 files changed, 33 insertions(+), 12 deletions(-) diff --git a/doc/README.md b/doc/README.md index cb053975e..29c806d5b 100644 --- a/doc/README.md +++ b/doc/README.md @@ -423,14 +423,14 @@ Connect Relying Parties (RPs). The default configuration file can be found [here](../example/plugins/frontends/openid_connect_frontend.yaml.example). As opposed to the other plugins, this plugin is NOT stateless (due to the nature of OpenID Connect using any other -flow than "Implicit Flow"). However, the frontend supports using a MongoDB instance as its backend storage, so as long +flow than "Implicit Flow"). However, the frontend supports using a MongoDB or Redis instance as its backend storage, so as long that's reachable from all machines it should not be a problem. The configuration parameters available: * `signing_key_path`: path to a RSA Private Key file (PKCS#1). MUST be configured. -* `db_uri`: connection URI to MongoDB instance where the data will be persisted, if it's not specified all data will only +* `db_uri`: connection URI to MongoDB or Redis instance where the data will be persisted, if it's not specified all data will only be stored in-memory (not suitable for production use). -* `client_db_uri`: connection URI to MongoDB instance where the client data will be persistent, if it's not specified the clients list will be received from the `client_db_path`. +* `client_db_uri`: connection URI to MongoDB or Redis instance where the client data will be persistent, if it's not specified the clients list will be received from the `client_db_path`. * `client_db_path`: path to a file containing the client database in json format. It will only be used if `client_db_uri` is not set. If `client_db_uri` and `client_db_path` are not set, clients will only be stored in-memory (not suitable for production use). * `sub_hash_salt`: salt which is hashed into the `sub` claim. If it's not specified, SATOSA will generate a random salt on each startup, which means that users will get new `sub` value after every restart. * `provider`: provider configuration information. MUST be configured, the following configuration are supported: @@ -438,7 +438,7 @@ The configuration parameters available: * `subject_types_supported` (default: `[pairwise]`): list of all supported subject identifier types, see [Section 8 of OIDC Core](http://openid.net/specs/openid-connect-core-1_0.html#SubjectIDTypes) * `scopes_supported` (default: `[openid]`): list of all supported scopes, see [Section 5.4 of OIDC Core](http://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims) * `client_registration_supported` (default: `No`): boolean whether [dynamic client registration is supported](https://openid.net/specs/openid-connect-registration-1_0.html). - If dynamic client registration is not supported all clients must exist in the MongoDB instance configured by the `db_uri` in the `"clients"` collection of the `"satosa"` database. + If dynamic client registration is not supported all clients must exist in the MongoDB or Redis instance configured by the `db_uri` in the `"clients"` collection of the `"satosa"` database. The registration info must be stored using the client id as a key, and use the parameter names of a [OIDC Registration Response](https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationResponse). * `authorization_code_lifetime`: how long authorization codes should be valid, see [default](https://github.com/SUNET/pyop#token-lifetimes) * `access_token_lifetime`: how long access tokens should be valid, see [default](https://github.com/SUNET/pyop#token-lifetimes) diff --git a/setup.py b/setup.py index ff12945e0..25afb1824 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ packages=find_packages('src/'), package_dir={'': 'src'}, install_requires=[ - "pyop >= 3.0.1", + "pyop >= 3.0.1", # TODO "pysaml2 >= 6.5.1", "pycryptodomex", "requests", diff --git a/src/satosa/frontends/openid_connect.py b/src/satosa/frontends/openid_connect.py index 1acc80583..176c0766c 100644 --- a/src/satosa/frontends/openid_connect.py +++ b/src/satosa/frontends/openid_connect.py @@ -16,7 +16,7 @@ from pyop.exceptions import (InvalidAuthenticationRequest, InvalidClientRegistrationRequest, InvalidClientAuthentication, OAuthError, BearerTokenError, InvalidAccessToken) from pyop.provider import Provider -from pyop.storage import MongoWrapper +from pyop.storage import StorageBase from pyop.subject_identifier import HashBasedSubjectIdentifierFactory from pyop.userinfo import Userinfo from pyop.util import should_fragment_encode @@ -80,13 +80,22 @@ def _create_provider(self, endpoint_baseurl): client_db_uri = self.config.get("client_db_uri") cdb_file = self.config.get("client_db_path") if client_db_uri: - cdb = MongoWrapper(client_db_uri, "satosa", "clients") + cdb = StorageBase.from_uri( + client_db_uri, db_name="satosa", collection="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.user_db = ( + StorageBase.from_uri(db_uri, db_name="satosa", collection="authz_codes") + if db_uri + else {} + ) + #XXX What is the correct ttl for user_db? Is it the same as authz_code_db? + self.provider = Provider( self.signing_key, capabilities, @@ -101,10 +110,22 @@ 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") + authz_code_db = StorageBase.from_uri( + db_uri, db_name="satosa", collection="authz_codes", + ) + authz_code_db.ttl = self.config["provider"].get("authorization_code_lifetime", 600) + access_token_db = StorageBase.from_uri( + db_uri, db_name="satosa", collection="access_tokens" + ) + access_token_db.ttl = self.config["provider"].get("access_token_lifetime", 3600) + refresh_token_db = StorageBase.from_uri( + db_uri, db_name="satosa", collection="refresh_tokens" + ) + refresh_token_db.ttl = self.config["provider"].get("refresh_token_lifetime", None) + sub_db = StorageBase.from_uri( + db_uri, db_name="satosa", collection="subject_identifiers" + ) + #XXX what is the correct TTL for sub_db? else: authz_code_db = None access_token_db = None