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

Commit ab7fdf9

Browse files
committed
Improved token validity checking.
1 parent ecb4663 commit ab7fdf9

File tree

2 files changed

+140
-14
lines changed

2 files changed

+140
-14
lines changed

src/oidcendpoint/oauth2/introspection.py

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,18 @@
1111
LOGGER = logging.getLogger(__name__)
1212

1313

14+
def before(t1, t2, range):
15+
if t1 < (t2 - range):
16+
return True
17+
return False
18+
19+
20+
def after(t1, t2, range):
21+
if t1 > (t2 + range):
22+
return True
23+
return False
24+
25+
1426
class Introspection(Endpoint):
1527
"""Implements RFC 7662"""
1628

@@ -21,6 +33,10 @@ class Introspection(Endpoint):
2133
endpoint_name = "introspection_endpoint"
2234
name = "introspection"
2335

36+
def __init__(self, **kwargs):
37+
Endpoint.__init__(self, **kwargs)
38+
self.offset = kwargs.get("offset", 0)
39+
2440
def get_client_id_from_token(self, endpoint_context, token, request=None):
2541
"""
2642
Will try to match tokens against information in the session DB.
@@ -33,18 +49,30 @@ def get_client_id_from_token(self, endpoint_context, token, request=None):
3349
sinfo = endpoint_context.sdb[token]
3450
return sinfo["authn_req"]["client_id"]
3551

36-
def do_jws(self, token, default_response):
52+
def do_jws(self, token):
3753
_jwt = JWT(key_jar=self.endpoint_context.keyjar)
3854

3955
try:
4056
_jwt_info = _jwt.unpack(token)
41-
except Exception:
42-
return {"response_args": default_response}
57+
except Exception as err:
58+
return None
4359

4460
return _jwt_info
4561

4662
def do_access_token(self, token):
47-
_info = self.endpoint_context.sdb[token]
63+
try:
64+
_info = self.endpoint_context.sdb[token]
65+
except KeyError:
66+
return None
67+
68+
_revoked = _info.get("revoked", False)
69+
if _revoked:
70+
return None
71+
72+
_eat = _info.get("expires_at")
73+
if _eat < utc_time_sans_frac():
74+
return None
75+
4876
if _info: # Now what can be returned ?
4977
_ret = {
5078
"sub": _info["sub"],
@@ -80,15 +108,26 @@ def process_request(self, request=None, **kwargs):
80108
_resp = self.response_cls(active=False)
81109

82110
if factory(_token):
83-
_info = self.do_jws(_token, _resp)
84-
# expired ?
111+
_info = self.do_jws(_token)
112+
if _info is None:
113+
return {"response_args": _resp}
114+
_now = utc_time_sans_frac()
115+
116+
# Time checks
85117
if "exp" in _info:
86-
now = utc_time_sans_frac()
87-
if _info["exp"] < now:
118+
if after(_now, _info["exp"], self.offset):
119+
return {"response_args": _resp}
120+
if 'iat' in _info:
121+
if after(_info["iat"], _now, self.offset):
122+
return {"response_args": _resp}
123+
if 'nbf' in _info:
124+
if before(_now, _info["nbf"], self.offset):
88125
return {"response_args": _resp}
89126
else:
90127
# A non-jws access token
91128
_info = self.do_access_token(_token)
129+
if _info is None:
130+
return {"response_args": _resp}
92131

93132
if not _info:
94133
return {"response_args": _resp}

tests/test_31_introspection.py

Lines changed: 93 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,24 @@
55
import pytest
66
from cryptojwt import JWT
77
from cryptojwt import as_unicode
8+
from cryptojwt.key_jar import build_keyjar
89
from cryptojwt.utils import as_bytes
10+
from oidcmsg.oauth2 import TokenIntrospectionRequest
911
from oidcmsg.oidc import AccessTokenRequest
10-
11-
from oidcendpoint.oidc.token_coop import TokenCoop
12+
from oidcmsg.oidc import AuthorizationRequest
13+
from oidcmsg.time_util import utc_time_sans_frac
1214

1315
from oidcendpoint.client_authn import ClientSecretPost
1416
from oidcendpoint.client_authn import UnknownOrNoAuthnMethod
1517
from oidcendpoint.client_authn import WrongAuthnMethod
1618
from oidcendpoint.client_authn import verify_client
1719
from oidcendpoint.endpoint_context import EndpointContext
18-
from oidcendpoint.oauth2.introspection import Introspection
1920
from oidcendpoint.oauth2.authorization import Authorization
21+
from oidcendpoint.oauth2.introspection import Introspection
22+
from oidcendpoint.oidc.token_coop import TokenCoop
2023
from oidcendpoint.session import setup_session
2124
from oidcendpoint.user_authn.authn_context import INTERNETPROTOCOLPASSWORD
2225
from oidcendpoint.user_info import UserInfo
23-
from oidcmsg.oauth2 import TokenIntrospectionRequest
24-
from oidcmsg.oidc import AuthorizationRequest
2526

2627
KEYDEFS = [
2728
{"type": "RSA", "key": "", "use": ["sig"]},
@@ -252,7 +253,7 @@ def test_do_response(self):
252253
"iss",
253254
"jti",
254255
}
255-
assert _payload["active"] == True
256+
assert _payload["active"] is True
256257

257258
def test_do_response_no_token(self):
258259
_context = self.introspection_endpoint.endpoint_context
@@ -294,3 +295,89 @@ def test_access_token(self):
294295
assert "sub" in _resp_args
295296
assert _resp_args["active"]
296297
assert _resp_args["scope"] == "openid"
298+
299+
def test_jwt_unknown_key(self):
300+
_keyjar = build_keyjar(KEYDEFS)
301+
302+
_jwt = JWT(
303+
_keyjar,
304+
iss=self.introspection_endpoint.endpoint_context.issuer,
305+
lifetime=3600
306+
)
307+
308+
_jwt.with_jti = True
309+
310+
_payload = {"sub": "subject_id"}
311+
_token = _jwt.pack(_payload, aud="client_1")
312+
_context = self.introspection_endpoint.endpoint_context
313+
314+
_req = self.introspection_endpoint.parse_request(
315+
{
316+
"token": _token,
317+
"client_id": "client_1",
318+
"client_secret": _context.cdb["client_1"]["client_secret"],
319+
}
320+
)
321+
322+
_req = self.introspection_endpoint.parse_request(_req)
323+
_resp = self.introspection_endpoint.process_request(_req)
324+
assert _resp["response_args"]["active"] is False
325+
326+
def test_expired_access_token(self):
327+
_context = self.introspection_endpoint.endpoint_context
328+
329+
session_id = setup_session(
330+
_context,
331+
AUTH_REQ,
332+
uid="user",
333+
acr=INTERNETPROTOCOLPASSWORD,
334+
)
335+
_token_request = TOKEN_REQ_DICT.copy()
336+
_token_request["code"] = _context.sdb[session_id]["code"]
337+
_context.sdb.update(session_id, user="diana")
338+
339+
_req = self.token_endpoint.parse_request(_token_request)
340+
_resp = self.token_endpoint.process_request(request=_req)
341+
342+
_info = self.token_endpoint.endpoint_context.sdb[_resp["response_args"]["access_token"]]
343+
_info['expires_at'] = utc_time_sans_frac() - 1000
344+
self.token_endpoint.endpoint_context.sdb[_resp["response_args"]["access_token"]] = _info
345+
346+
_req = self.introspection_endpoint.parse_request(
347+
{
348+
"token": _resp["response_args"]["access_token"],
349+
"client_id": "client_1",
350+
"client_secret": _context.cdb["client_1"]["client_secret"],
351+
}
352+
)
353+
_resp = self.introspection_endpoint.process_request(_req)
354+
assert _resp["response_args"]["active"] is False
355+
356+
def test_revoked_access_token(self):
357+
_context = self.introspection_endpoint.endpoint_context
358+
359+
session_id = setup_session(
360+
_context,
361+
AUTH_REQ,
362+
uid="user",
363+
acr=INTERNETPROTOCOLPASSWORD,
364+
)
365+
_token_request = TOKEN_REQ_DICT.copy()
366+
_token_request["code"] = _context.sdb[session_id]["code"]
367+
_context.sdb.update(session_id, user="diana")
368+
369+
_req = self.token_endpoint.parse_request(_token_request)
370+
_resp = self.token_endpoint.process_request(request=_req)
371+
372+
self.token_endpoint.endpoint_context.sdb.revoke_session(
373+
token=_resp["response_args"]["access_token"])
374+
375+
_req = self.introspection_endpoint.parse_request(
376+
{
377+
"token": _resp["response_args"]["access_token"],
378+
"client_id": "client_1",
379+
"client_secret": _context.cdb["client_1"]["client_secret"],
380+
}
381+
)
382+
_resp = self.introspection_endpoint.process_request(_req)
383+
assert _resp["response_args"]["active"] is False

0 commit comments

Comments
 (0)