Skip to content
This repository was archived by the owner on Jun 12, 2021. It is now read-only.

Commit 2a02313

Browse files
committed
First attempt at supporting custom scopes.
1 parent 148d6b7 commit 2a02313

11 files changed

Lines changed: 208 additions & 81 deletions

src/oidcendpoint/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
except ImportError:
77
import random as rnd
88

9-
__version__ = "0.10.0"
9+
__version__ = "0.10.1"
1010

1111

1212
DEF_SIGN_ALG = {

src/oidcendpoint/endpoint_context.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
from jinja2 import Environment
1212
from jinja2 import FileSystemLoader
1313
from oidcmsg.oidc import IdToken
14-
from oidcmsg.oidc import SCOPE2CLAIMS
1514

1615
from oidcendpoint import authz
1716
from oidcendpoint import rndstr
@@ -23,6 +22,7 @@
2322
from oidcendpoint.sso_db import SSODb
2423
from oidcendpoint.template_handler import Jinja2TemplateHandler
2524
from oidcendpoint.user_authn.authn_context import populate_authn_broker
25+
from oidcendpoint.user_info import SCOPE2CLAIMS
2626
from oidcendpoint.util import build_endpoints
2727
from oidcendpoint.util import importer
2828

@@ -167,6 +167,7 @@ def __init__(
167167
self.args = {}
168168

169169
self._sub_func = None
170+
self.scope2claims = SCOPE2CLAIMS
170171

171172
if cookie_name:
172173
self.cookie_name = cookie_name
@@ -180,12 +181,12 @@ def __init__(
180181
}
181182

182183
for param in [
183-
"verify_ssl",
184-
"issuer",
185-
"sso_ttl",
186-
"symkey",
187-
"client_authn",
188-
"id_token_schema",
184+
"verify_ssl",
185+
"issuer",
186+
"sso_ttl",
187+
"symkey",
188+
"client_authn",
189+
"id_token_schema",
189190
]:
190191
try:
191192
setattr(self, param, conf[param])
@@ -223,7 +224,7 @@ def __init__(
223224
self.keyjar = init_key_jar(**args)
224225

225226
for item in ['cookie_dealer', "sub_func", "authz", "authentication",
226-
"id_token"]:
227+
"id_token", "scope2claims"]:
227228
_func = getattr(self, "do_{}".format(item), None)
228229
if _func:
229230
_func(self.conf)
@@ -393,11 +394,11 @@ def package_capabilities(self):
393394
_provider_info["version"] = "3.0"
394395

395396
_claims = []
396-
for _cl in SCOPE2CLAIMS.values():
397+
for _cl in self.scope2claims.values():
397398
_claims.extend(_cl)
398399
_provider_info["claims_supported"] = list(set(_claims))
399400

400-
_scopes = list(SCOPE2CLAIMS.keys())
401+
_scopes = list(self.scope2claims.keys())
401402
_provider_info["scopes_supported"] = _scopes
402403

403404
# Sort order RS, ES, HS, PS

src/oidcendpoint/id_token.py

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
logger = logging.getLogger(__name__)
1010

11-
1211
DEF_SIGN_ALG = {
1312
"id_token": "RS256",
1413
"userinfo": "RS256",
@@ -20,7 +19,7 @@
2019

2120

2221
def get_sign_and_encrypt_algorithms(
23-
endpoint_context, client_info, payload_type, sign=False, encrypt=False
22+
endpoint_context, client_info, payload_type, sign=False, encrypt=False
2423
):
2524
args = {"sign": sign, "encrypt": encrypt}
2625
if sign:
@@ -76,18 +75,19 @@ class IDToken(object):
7675
def __init__(self, endpoint_context, **kwargs):
7776
self.endpoint_context = endpoint_context
7877
self.kwargs = kwargs
78+
self.scope_to_claims = None
7979

8080
def payload(
81-
self,
82-
session,
83-
acr="",
84-
alg="RS256",
85-
code=None,
86-
access_token=None,
87-
user_info=None,
88-
auth_time=0,
89-
lifetime=None,
90-
extra_claims=None,
81+
self,
82+
session,
83+
acr="",
84+
alg="RS256",
85+
code=None,
86+
access_token=None,
87+
user_info=None,
88+
auth_time=0,
89+
lifetime=None,
90+
extra_claims=None,
9191
):
9292
"""
9393
@@ -148,16 +148,16 @@ def payload(
148148
return {"payload": _args, "lifetime": lifetime}
149149

150150
def sign_encrypt(
151-
self,
152-
session_info,
153-
client_id,
154-
code=None,
155-
access_token=None,
156-
user_info=None,
157-
sign=True,
158-
encrypt=False,
159-
lifetime=None,
160-
extra_claims=None,
151+
self,
152+
session_info,
153+
client_id,
154+
code=None,
155+
access_token=None,
156+
user_info=None,
157+
sign=True,
158+
encrypt=False,
159+
lifetime=None,
160+
extra_claims=None,
161161
):
162162
"""
163163
Signed and or encrypt a IDToken
@@ -222,7 +222,8 @@ def make(self, req, sess_info, authn_req=None, user_claims=False, **kwargs):
222222
)
223223

224224
if user_claims:
225-
info = collect_user_info(_context, sess_info)
225+
info = collect_user_info(_context, sess_info,
226+
scope_to_claims=self.scope_to_claims)
226227
if userinfo is None:
227228
userinfo = info
228229
else:

src/oidcendpoint/jwt_token.py

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,28 @@
11
from typing import Any
22
from typing import Dict
3-
from typing import List
43
from typing import Optional
54

65
from cryptojwt import JWT
7-
from oidcmsg.oidc import scope2claims
86

97
from oidcendpoint.exception import ToOld
108
from oidcendpoint.token_handler import Token
119
from oidcendpoint.token_handler import is_expired
10+
from oidcendpoint.user_info import scope2claims
1211

1312

1413
class JWTToken(Token):
1514
def __init__(
16-
self,
17-
typ,
18-
black_list=None,
19-
keyjar=None,
20-
issuer=None,
21-
aud=None,
22-
alg="ES256",
23-
lifetime=300,
24-
ec=None,
25-
token_type="Bearer",
26-
**kwargs
15+
self,
16+
typ,
17+
black_list=None,
18+
keyjar=None,
19+
issuer=None,
20+
aud=None,
21+
alg="ES256",
22+
lifetime=300,
23+
ec=None,
24+
token_type="Bearer",
25+
**kwargs
2726
):
2827
Token.__init__(self, typ, black_list, **kwargs)
2928
self.token_type = token_type
@@ -35,6 +34,10 @@ def __init__(
3534

3635
self.def_aud = aud or []
3736
self.alg = alg
37+
if 'scope_claims_map' in kwargs:
38+
self.scope_claims_map = kwargs['scope_claims_map']
39+
else:
40+
self.scope_claims_map = None
3841

3942
def add_claims(self, payload, uinfo, claims):
4043
for attr in claims:
@@ -46,13 +49,13 @@ def add_claims(self, payload, uinfo, claims):
4649
pass
4750

4851
def __call__(
49-
self,
50-
sid: str,
51-
uinfo: Dict,
52-
sinfo: Dict,
53-
*args,
54-
aud: Optional[Any],
55-
**kwargs
52+
self,
53+
sid: str,
54+
uinfo: Dict,
55+
sinfo: Dict,
56+
*args,
57+
aud: Optional[Any],
58+
**kwargs
5659
):
5760
"""
5861
Return a token.
@@ -69,7 +72,9 @@ def __call__(
6972
self.add_claims(payload, uinfo, self.args["add_claims"])
7073
if "add_claims_by_scope":
7174
self.add_claims(
72-
payload, uinfo, scope2claims(sinfo["authn_req"]["scope"]).keys()
75+
payload, uinfo,
76+
scope2claims(sinfo["authn_req"]["scope"],
77+
map=self.scope_claims_map).keys()
7378
)
7479

7580
payload.update(kwargs)

src/oidcendpoint/oidc/userinfo.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
import logging
33

44
from cryptojwt.exception import MissingValue
5-
from oidcmsg import oidc
65
from cryptojwt.jwt import JWT
6+
from oidcmsg import oidc
77
from oidcmsg.message import Message
88
from oidcmsg.oauth2 import ResponseMessage
99
from oidcmsg.time_util import time_sans_frac
@@ -24,6 +24,10 @@ class UserInfo(Endpoint):
2424
endpoint_name = "userinfo_endpoint"
2525
name = "userinfo"
2626

27+
def __init__(self, endpoint_context, **kwargs):
28+
Endpoint.__init__(self, endpoint_context, **kwargs)
29+
self.scope_to_claims = None
30+
2731
def get_client_id_from_token(self, endpoint_context, token, request=None):
2832
sinfo = self.endpoint_context.sdb[token]
2933
return sinfo["authn_req"]["client_id"]
@@ -104,7 +108,8 @@ def process_request(self, request=None, **kwargs):
104108

105109
if allowed:
106110
# Scope can translate to userinfo_claims
107-
info = collect_user_info(self.endpoint_context, session)
111+
info = collect_user_info(self.endpoint_context, session,
112+
scope_to_claims=self.scope_to_claims)
108113
else:
109114
info = {
110115
"error": "invalid_request",

src/oidcendpoint/user_info/__init__.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,30 @@ def search(self, **kwargs):
7777
return uid
7878

7979
raise KeyError("No matching user")
80+
81+
82+
SCOPE2CLAIMS = {
83+
"openid": ["sub"],
84+
"profile": ["name", "given_name", "family_name", "middle_name",
85+
"nickname", "profile", "picture", "website", "gender",
86+
"birthdate", "zoneinfo", "locale", "updated_at",
87+
"preferred_username"],
88+
"email": ["email", "email_verified"],
89+
"address": ["address"],
90+
"phone": ["phone_number", "phone_number_verified"],
91+
"offline_access": []
92+
}
93+
94+
95+
def scope2claims(scopes, map=None):
96+
if map is None:
97+
map = SCOPE2CLAIMS
98+
99+
res = {}
100+
for scope in scopes:
101+
try:
102+
claims = dict([(name, None) for name in map[scope]])
103+
res.update(claims)
104+
except KeyError:
105+
continue
106+
return res

src/oidcendpoint/userinfo.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
from oidcservice import sanitize
44
from oidcmsg.oidc import Claims
5-
from oidcmsg.oidc import scope2claims
65

76
from oidcendpoint.exception import FailedAuthentication
7+
from oidcendpoint.user_info import scope2claims
88

99
logger = logging.getLogger(__name__)
1010

@@ -102,7 +102,8 @@ def by_schema(cls, **kwa):
102102
return dict([(key, val) for key, val in kwa.items() if key in cls.c_param])
103103

104104

105-
def collect_user_info(endpoint_context, session, userinfo_claims=None):
105+
def collect_user_info(endpoint_context, session, userinfo_claims=None,
106+
scope_to_claims=None):
106107
"""
107108
Collect information about a user.
108109
This can happen in two cases, either when constructing an IdToken or
@@ -115,7 +116,7 @@ def collect_user_info(endpoint_context, session, userinfo_claims=None):
115116
authn_req = session["authn_req"]
116117

117118
if userinfo_claims is None:
118-
uic = scope2claims(authn_req["scope"])
119+
uic = scope2claims(authn_req["scope"], map=scope_to_claims)
119120

120121
# Get only keys allowed by user and update the dict if such info
121122
# is stored in session
@@ -155,7 +156,8 @@ def collect_user_info(endpoint_context, session, userinfo_claims=None):
155156
return info
156157

157158

158-
def userinfo_in_id_token_claims(endpoint_context, session, def_itc=None):
159+
def userinfo_in_id_token_claims(endpoint_context, session, def_itc=None,
160+
scope_to_claims=None):
159161
"""
160162
Collect user info claims that are to be placed in the id token.
161163
@@ -177,6 +179,7 @@ def userinfo_in_id_token_claims(endpoint_context, session, def_itc=None):
177179
_claims = by_schema(endpoint_context.id_token_schema, **itc)
178180

179181
if _claims:
180-
return collect_user_info(endpoint_context, session, _claims)
182+
return collect_user_info(endpoint_context, session, _claims,
183+
scope_to_claims=scope_to_claims)
181184
else:
182185
return None

0 commit comments

Comments
 (0)