From 956057ef8fcf9557fdae265afeee79819ebb4bc6 Mon Sep 17 00:00:00 2001 From: Nikos Sklikas Date: Wed, 18 Mar 2020 10:52:06 +0200 Subject: [PATCH 1/3] Add claims per client in id token --- src/oidcendpoint/id_token.py | 8 +-- tests/test_03_id_token.py | 101 ++++++++++++++++++++++++++++++++++- 2 files changed, 105 insertions(+), 4 deletions(-) diff --git a/src/oidcendpoint/id_token.py b/src/oidcendpoint/id_token.py index f9e3391..fb68de5 100755 --- a/src/oidcendpoint/id_token.py +++ b/src/oidcendpoint/id_token.py @@ -242,7 +242,6 @@ def sign_encrypt( def make(self, req, sess_info, authn_req=None, user_claims=False, **kwargs): _context = self.endpoint_context - _sdb = _context.sdb if authn_req: _client_id = authn_req["client_id"] @@ -251,11 +250,14 @@ def make(self, req, sess_info, authn_req=None, user_claims=False, **kwargs): _cinfo = _context.cdb[_client_id] - default_idtoken_claims = dict(self.kwargs.get("default_claims", {})) + idtoken_claims = dict( + self.kwargs.get("default_claims", {}), + **_cinfo.get("id_token_claims", {}) + ) lifetime = self.kwargs.get("lifetime") userinfo = userinfo_in_id_token_claims( - _context, sess_info, default_idtoken_claims + _context, sess_info, idtoken_claims ) if user_claims: diff --git a/tests/test_03_id_token.py b/tests/test_03_id_token.py index 7e85414..da92976 100644 --- a/tests/test_03_id_token.py +++ b/tests/test_03_id_token.py @@ -30,7 +30,8 @@ def full_path(local_file): return os.path.join(BASEDIR, local_file) -USERINFO = UserInfo(json.loads(open(full_path("users.json")).read())) +USERS = json.loads(open(full_path("users.json")).read()) +USERINFO = UserInfo(USERS) AREQN = AuthorizationRequest( response_type="code", @@ -70,6 +71,10 @@ def full_path(local_file): "kwargs": {"user": "diana"}, } }, + "userinfo": { + "class": "oidcendpoint.user_info.UserInfo", + "kwargs": {"db": USERS}, + }, "client_authn": verify_client, "template_dir": "template", "id_token": {"class": IDToken, "kwargs": {"foo": "bar"}}, @@ -252,3 +257,97 @@ def test_get_sign_algorithm_4(self): ) # default signing alg assert algs == {"sign": True, "encrypt": False, "sign_alg": "RS512"} + + def test_default_claims(self): + session_info = { + "authn_req": AREQN, + "sub": "sub", + "authn_event": { + "authn_info": "loa2", + "authn_time": time.time(), + "uid": "diana" + }, + } + self.endpoint_context.idtoken.kwargs['default_claims'] = { + "nickname": {"essential": True} + } + req = {"client_id": "client_1"} + _token = self.endpoint_context.idtoken.make(req, session_info) + assert _token + client_keyjar = KeyJar() + _jwks = self.endpoint_context.keyjar.export_jwks() + client_keyjar.import_jwks(_jwks, self.endpoint_context.issuer) + _jwt = JWT(key_jar=client_keyjar, iss="client_1") + res = _jwt.unpack(_token) + assert "nickname" in res + + def test_no_default_claims(self): + session_info = { + "authn_req": AREQN, + "sub": "sub", + "authn_event": { + "authn_info": "loa2", + "authn_time": time.time(), + "uid": "diana" + }, + } + req = {"client_id": "client_1"} + _token = self.endpoint_context.idtoken.make(req, session_info) + assert _token + client_keyjar = KeyJar() + _jwks = self.endpoint_context.keyjar.export_jwks() + client_keyjar.import_jwks(_jwks, self.endpoint_context.issuer) + _jwt = JWT(key_jar=client_keyjar, iss="client_1") + res = _jwt.unpack(_token) + assert "nickname" not in res + + def test_client_claims(self): + session_info = { + "authn_req": AREQN, + "sub": "sub", + "authn_event": { + "authn_info": "loa2", + "authn_time": time.time(), + "uid": "diana" + }, + } + self.endpoint_context.cdb["client_1"]['id_token_claims'] = { + "address": None + } + req = {"client_id": "client_1"} + _token = self.endpoint_context.idtoken.make(req, session_info) + assert _token + client_keyjar = KeyJar() + _jwks = self.endpoint_context.keyjar.export_jwks() + client_keyjar.import_jwks(_jwks, self.endpoint_context.issuer) + _jwt = JWT(key_jar=client_keyjar, iss="client_1") + res = _jwt.unpack(_token) + assert "address" in res + assert "nickname" not in res + + def test_client_claims_with_default(self): + session_info = { + "authn_req": AREQN, + "sub": "sub", + "authn_event": { + "authn_info": "loa2", + "authn_time": time.time(), + "uid": "diana" + }, + } + self.endpoint_context.cdb["client_1"]['id_token_claims'] = { + "address": None + } + self.endpoint_context.idtoken.kwargs['default_claims'] = { + "nickname": {"essential": True} + } + req = {"client_id": "client_1"} + _token = self.endpoint_context.idtoken.make(req, session_info) + assert _token + client_keyjar = KeyJar() + _jwks = self.endpoint_context.keyjar.export_jwks() + client_keyjar.import_jwks(_jwks, self.endpoint_context.issuer) + _jwt = JWT(key_jar=client_keyjar, iss="client_1") + res = _jwt.unpack(_token) + assert "address" in res + assert "nickname" in res From ffd419c34f16c0bb56371ca33956169e5da15144 Mon Sep 17 00:00:00 2001 From: Nikos Sklikas Date: Tue, 24 Mar 2020 12:28:57 +0200 Subject: [PATCH 2/3] Add option to disable claims per client --- src/oidcendpoint/id_token.py | 8 ++++---- tests/test_03_id_token.py | 26 ++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/oidcendpoint/id_token.py b/src/oidcendpoint/id_token.py index fb68de5..a80b538 100755 --- a/src/oidcendpoint/id_token.py +++ b/src/oidcendpoint/id_token.py @@ -112,6 +112,7 @@ class IDToken(object): def __init__(self, endpoint_context, **kwargs): self.endpoint_context = endpoint_context self.kwargs = kwargs + self.enable_claims_per_client = kwargs.get('enable_claims_per_client') self.scope_to_claims = None self.provider_info = construct_endpoint_info( self.default_capabilities, **kwargs @@ -250,10 +251,9 @@ def make(self, req, sess_info, authn_req=None, user_claims=False, **kwargs): _cinfo = _context.cdb[_client_id] - idtoken_claims = dict( - self.kwargs.get("default_claims", {}), - **_cinfo.get("id_token_claims", {}) - ) + idtoken_claims = dict(self.kwargs.get("default_claims", {})) + if self.enable_claims_per_client: + idtoken_claims.update(_cinfo.get("id_token_claims", {})) lifetime = self.kwargs.get("lifetime") userinfo = userinfo_in_id_token_claims( diff --git a/tests/test_03_id_token.py b/tests/test_03_id_token.py index da92976..373d3ec 100644 --- a/tests/test_03_id_token.py +++ b/tests/test_03_id_token.py @@ -311,6 +311,7 @@ def test_client_claims(self): "uid": "diana" }, } + self.endpoint_context.idtoken.enable_claims_per_client = True self.endpoint_context.cdb["client_1"]['id_token_claims'] = { "address": None } @@ -341,6 +342,7 @@ def test_client_claims_with_default(self): self.endpoint_context.idtoken.kwargs['default_claims'] = { "nickname": {"essential": True} } + self.endpoint_context.idtoken.enable_claims_per_client = True req = {"client_id": "client_1"} _token = self.endpoint_context.idtoken.make(req, session_info) assert _token @@ -351,3 +353,27 @@ def test_client_claims_with_default(self): res = _jwt.unpack(_token) assert "address" in res assert "nickname" in res + + def test_client_claims_disabled(self): + session_info = { + "authn_req": AREQN, + "sub": "sub", + "authn_event": { + "authn_info": "loa2", + "authn_time": time.time(), + "uid": "diana" + }, + } + self.endpoint_context.cdb["client_1"]['id_token_claims'] = { + "address": None + } + req = {"client_id": "client_1"} + _token = self.endpoint_context.idtoken.make(req, session_info) + assert _token + client_keyjar = KeyJar() + _jwks = self.endpoint_context.keyjar.export_jwks() + client_keyjar.import_jwks(_jwks, self.endpoint_context.issuer) + _jwt = JWT(key_jar=client_keyjar, iss="client_1") + res = _jwt.unpack(_token) + assert "address" not in res + assert "nickname" not in res From 60c3287c6a8476a5a99c43751deb76731f4d6653 Mon Sep 17 00:00:00 2001 From: Nikos Sklikas Date: Tue, 24 Mar 2020 13:57:17 +0200 Subject: [PATCH 3/3] Add default value to enable_claims_per_client --- src/oidcendpoint/id_token.py | 4 +++- tests/test_03_id_token.py | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/oidcendpoint/id_token.py b/src/oidcendpoint/id_token.py index a80b538..5df1ec6 100755 --- a/src/oidcendpoint/id_token.py +++ b/src/oidcendpoint/id_token.py @@ -112,7 +112,9 @@ class IDToken(object): def __init__(self, endpoint_context, **kwargs): self.endpoint_context = endpoint_context self.kwargs = kwargs - self.enable_claims_per_client = kwargs.get('enable_claims_per_client') + self.enable_claims_per_client = kwargs.get( + 'enable_claims_per_client', False + ) self.scope_to_claims = None self.provider_info = construct_endpoint_info( self.default_capabilities, **kwargs diff --git a/tests/test_03_id_token.py b/tests/test_03_id_token.py index 373d3ec..9f53d83 100644 --- a/tests/test_03_id_token.py +++ b/tests/test_03_id_token.py @@ -355,6 +355,7 @@ def test_client_claims_with_default(self): assert "nickname" in res def test_client_claims_disabled(self): + # enable_claims_per_client defaults to False session_info = { "authn_req": AREQN, "sub": "sub",