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

Commit f4b217a

Browse files
committed
Extended introspection to also deal with non-jws access tokens.
1 parent 79fd7f8 commit f4b217a

2 files changed

Lines changed: 106 additions & 15 deletions

File tree

src/oidcendpoint/oauth2/introspection.py

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

44
from cryptojwt import JWT
5+
from cryptojwt.jws.jws import factory
56
from oidcmsg import oauth2
67
from oidcmsg.time_util import utc_time_sans_frac
78

@@ -32,6 +33,38 @@ def get_client_id_from_token(self, endpoint_context, token, request=None):
3233
sinfo = endpoint_context.sdb[token]
3334
return sinfo["authn_req"]["client_id"]
3435

36+
def do_jws(self, token, default_response):
37+
_jwt = JWT(key_jar=self.endpoint_context.keyjar)
38+
39+
try:
40+
_jwt_info = _jwt.unpack(token)
41+
except Exception:
42+
return {"response_args": default_response}
43+
44+
return _jwt_info
45+
46+
def do_access_token(self, token):
47+
_info = self.endpoint_context.sdb[token]
48+
if _info: # Now what can be returned ?
49+
_ret = {
50+
"sub": _info["sub"],
51+
"client_id": _info["client_id"],
52+
"token_type": "bearer",
53+
"iss": self.endpoint_context.issuer
54+
}
55+
_authn = _info.get("authn_event")
56+
if _authn:
57+
_ret["authn_info"] = _authn["authn_info"]
58+
_ret["authn_time"] = _authn["authn_time"]
59+
60+
_scope = _info.get("scope")
61+
if not _scope:
62+
_ret["scope"] = " ".join(_info["authn_req"]["scope"])
63+
64+
return _ret
65+
else:
66+
return _info
67+
3568
def process_request(self, request=None, **kwargs):
3669
"""
3770
@@ -43,31 +76,33 @@ def process_request(self, request=None, **kwargs):
4376
if "error" in _introspect_request:
4477
return _introspect_request
4578

79+
_token = _introspect_request["token"]
4680
_resp = self.response_cls(active=False)
4781

48-
_jwt = JWT(key_jar=self.endpoint_context.keyjar)
82+
if factory(_token):
83+
_info = self.do_jws(_token, _resp)
84+
# expired ?
85+
if "exp" in _info:
86+
now = utc_time_sans_frac()
87+
if _info["exp"] < now:
88+
return {"response_args": _resp}
89+
else:
90+
# A non-jws access token
91+
_info = self.do_access_token(_token)
4992

50-
try:
51-
_jwt_info = _jwt.unpack(_introspect_request["token"])
52-
except Exception:
93+
if not _info:
5394
return {"response_args": _resp}
5495

55-
# expired ?
56-
if "exp" in _jwt_info:
57-
now = utc_time_sans_frac()
58-
if _jwt_info["exp"] < now:
59-
return {"response_args": _resp}
60-
6196
if "release" in self.kwargs:
6297
if "username" in self.kwargs["release"]:
6398
try:
64-
_jwt_info["username"] = self.endpoint_context.userinfo.search(
65-
sub=_jwt_info["sub"]
99+
_info["username"] = self.endpoint_context.userinfo.search(
100+
sub=_info["sub"]
66101
)
67102
except KeyError:
68-
return {"response_args": _resp}
103+
pass
69104

70-
_resp.update(_jwt_info)
105+
_resp.update(_info)
71106
_resp.weed()
72107
_resp["active"] = True
73108

tests/test_31_introspection.py

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
from cryptojwt import JWT
77
from cryptojwt import as_unicode
88
from cryptojwt.utils import as_bytes
9+
from oidcmsg.oidc import AccessTokenRequest
10+
11+
from oidcendpoint.oidc.token_coop import TokenCoop
12+
913
from oidcendpoint.client_authn import ClientSecretPost
1014
from oidcendpoint.client_authn import UnknownOrNoAuthnMethod
1115
from oidcendpoint.client_authn import WrongAuthnMethod
@@ -65,6 +69,16 @@
6569
response_type="code id_token",
6670
)
6771

72+
TOKEN_REQ = AccessTokenRequest(
73+
client_id="client_1",
74+
redirect_uri="https://example.com/cb",
75+
state="STATE",
76+
grant_type="authorization_code",
77+
client_secret="hemligt",
78+
)
79+
80+
TOKEN_REQ_DICT = TOKEN_REQ.to_dict()
81+
6882
BASEDIR = os.path.abspath(os.path.dirname(__file__))
6983

7084

@@ -98,6 +112,18 @@ def create_endpoint(self):
98112
"client_authn_method": {"client_secret_post": ClientSecretPost},
99113
},
100114
},
115+
"token": {
116+
"path": "token",
117+
"class": TokenCoop,
118+
"kwargs": {
119+
"client_authn_method": [
120+
"client_secret_basic",
121+
"client_secret_post",
122+
"client_secret_jwt",
123+
"private_key_jwt",
124+
]
125+
},
126+
},
101127
},
102128
"authentication": {
103129
"anon": {
@@ -127,6 +153,7 @@ def create_endpoint(self):
127153
endpoint_context.issuer,
128154
)
129155
self.introspection_endpoint = endpoint_context.endpoint["introspection"]
156+
self.token_endpoint = endpoint_context.endpoint["token"]
130157

131158
def _create_jwt(self, uid, lifetime=0, with_jti=False):
132159
_jwt = JWT(
@@ -237,4 +264,33 @@ def test_do_response_no_token(self):
237264
}
238265
)
239266
_resp = self.introspection_endpoint.process_request(_req)
240-
assert "error" in _resp
267+
assert "error" in _resp
268+
269+
def test_access_token(self):
270+
_context = self.introspection_endpoint.endpoint_context
271+
272+
session_id = setup_session(
273+
_context,
274+
AUTH_REQ,
275+
uid="user",
276+
acr=INTERNETPROTOCOLPASSWORD,
277+
)
278+
_token_request = TOKEN_REQ_DICT.copy()
279+
_token_request["code"] = _context.sdb[session_id]["code"]
280+
_context.sdb.update(session_id, user="diana")
281+
282+
_req = self.token_endpoint.parse_request(_token_request)
283+
_resp = self.token_endpoint.process_request(request=_req)
284+
285+
_req = self.introspection_endpoint.parse_request(
286+
{
287+
"token": _resp["response_args"]["access_token"],
288+
"client_id": "client_1",
289+
"client_secret": _context.cdb["client_1"]["client_secret"],
290+
}
291+
)
292+
_resp = self.introspection_endpoint.process_request(_req)
293+
_resp_args = _resp["response_args"]
294+
assert "sub" in _resp_args
295+
assert _resp_args["active"]
296+
assert _resp_args["scope"] == "openid"

0 commit comments

Comments
 (0)