Skip to content

Commit fbc42e7

Browse files
Merge pull request #407 from bajnokk/passthrough-subject
oidc_frontend: Add option to mirror public subject
2 parents 385cc09 + 2e3dcf8 commit fbc42e7

File tree

3 files changed

+40
-3
lines changed

3 files changed

+40
-3
lines changed

doc/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,7 @@ The configuration parameters available:
433433
* `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`.
434434
* `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).
435435
* `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.
436+
* `sub_mirror_subject` (default: `No`): if this is set to `Yes` and SATOSA releases a public `sub` claim to the client, then the subject identifier received from the backend will be mirrored to the client. The default is to hash the public subject identifier with `sub_hash_salt`. Pairwise `sub` claims are always hashed.
436437
* `provider`: provider configuration information. MUST be configured, the following configuration are supported:
437438
* `response_types_supported` (default: `[id_token]`): list of all supported response types, see [Section 3 of OIDC Core](http://openid.net/specs/openid-connect-core-1_0.html#Authentication).
438439
* `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)

src/satosa/frontends/openid_connect.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@
4646
logger = logging.getLogger(__name__)
4747

4848

49+
class MirrorPublicSubjectIdentifierFactory(HashBasedSubjectIdentifierFactory):
50+
def create_public_identifier(self, user_id):
51+
return user_id
52+
53+
4954
class OpenIDConnectFrontend(FrontendModule):
5055
"""
5156
A OpenID Connect frontend module
@@ -75,7 +80,10 @@ def __init__(self, auth_req_callback_func, internal_attributes, conf, base_url,
7580
)
7681

7782
sub_hash_salt = self.config.get("sub_hash_salt", rndstr(16))
78-
authz_state = _init_authorization_state(provider_config, db_uri, sub_hash_salt)
83+
mirror_public = self.config.get("sub_mirror_public", False)
84+
authz_state = _init_authorization_state(
85+
provider_config, db_uri, sub_hash_salt, mirror_public
86+
)
7987

8088
client_db_uri = self.config.get("client_db_uri")
8189
cdb_file = self.config.get("client_db_path")
@@ -460,7 +468,9 @@ def _create_provider(
460468
return provider
461469

462470

463-
def _init_authorization_state(provider_config, db_uri, sub_hash_salt):
471+
def _init_authorization_state(
472+
provider_config, db_uri, sub_hash_salt, mirror_public=False
473+
):
464474
if db_uri:
465475
authz_code_db = StorageBase.from_uri(
466476
db_uri,
@@ -499,8 +509,14 @@ def _init_authorization_state(provider_config, db_uri, sub_hash_salt):
499509
]
500510
if k in provider_config
501511
}
512+
513+
subject_id_factory = (
514+
MirrorPublicSubjectIdentifierFactory(sub_hash_salt)
515+
if mirror_public
516+
else HashBasedSubjectIdentifierFactory(sub_hash_salt)
517+
)
502518
return AuthorizationState(
503-
HashBasedSubjectIdentifierFactory(sub_hash_salt),
519+
subject_id_factory,
504520
authz_code_db,
505521
access_token_db,
506522
refresh_token_db,

tests/satosa/frontends/test_openid_connect.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,26 @@ def test_register_endpoints_dynamic_client_registration_is_configurable(
402402
provider_info = ProviderConfigurationResponse().deserialize(frontend.provider_config(None).message, "json")
403403
assert ("registration_endpoint" in provider_info) == client_registration_enabled
404404

405+
@pytest.mark.parametrize("sub_mirror_public", [
406+
True,
407+
False
408+
])
409+
def test_mirrored_subject(self, context, frontend_config, authn_req, sub_mirror_public):
410+
frontend_config["sub_mirror_public"] = sub_mirror_public
411+
frontend_config["provider"]["subject_types_supported"] = ["public"]
412+
frontend = self.create_frontend(frontend_config)
413+
414+
self.insert_client_in_client_db(frontend, authn_req["redirect_uri"])
415+
internal_response = self.setup_for_authn_response(context, frontend, authn_req)
416+
http_resp = frontend.handle_authn_response(context, internal_response)
417+
418+
resp = AuthorizationResponse().deserialize(urlparse(http_resp.message).fragment)
419+
id_token = IdToken().from_jwt(resp["id_token"], key=[frontend.signing_key])
420+
if sub_mirror_public:
421+
assert id_token["sub"] == OIDC_USERS["testuser1"]["eduPersonTargetedID"][0]
422+
else:
423+
assert id_token["sub"] != OIDC_USERS["testuser1"]["eduPersonTargetedID"][0]
424+
405425
def test_token_endpoint(self, context, frontend_config, authn_req):
406426
token_lifetime = 60 * 60 * 24
407427
frontend_config["provider"]["access_token_lifetime"] = token_lifetime

0 commit comments

Comments
 (0)