Skip to content

Commit 444bd68

Browse files
authored
Merge pull request CZ-NIC#854 from gbip/853_session_state
Use session_state to backup consumer state if available
2 parents 3b1a9c3 + 433017c commit 444bd68

3 files changed

Lines changed: 70 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ The format is based on the [KeepAChangeLog] project.
66
[KeepAChangeLog]: https://keepachangelog.com/
77

88
## Unreleased
9+
10+
- [#854] Improve OIDC Session Management support by using the `session_state` parameter from an *Authentication Response* (if available) as a key to store `Consumer` data.
11+
912
### Changed
1013
- [#847] Using pydantic for settings instead of custom class
1114
- [#851], [#852] Add `authn_method` to `Consumer.complete`

src/oic/oic/consumer.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -461,10 +461,19 @@ def parse_authz(
461461
self.verify_id_token(idt, self.authz_req.get(_state or atr["state"]))
462462
return aresp, atr, idt
463463

464-
def complete(self, state, authn_method: str = "client_secret_basic"):
464+
def complete(
465+
self,
466+
state,
467+
authn_method: str = "client_secret_basic",
468+
session_state: Optional[str] = None,
469+
):
465470
"""
466471
Do the access token request, the last step in a code flow.
467472
473+
'session_state' is an optional parameter that can be provided if the Authorization Server support OIDC Session
474+
Management.
475+
If provided, it is used as the key in the session database to store the consumer data.
476+
468477
If Implicit flow was used then this method is never used.
469478
"""
470479
args = {"redirect_uri": self.redirect_uris[0]}
@@ -496,7 +505,12 @@ def complete(self, state, authn_method: str = "client_secret_basic"):
496505
if resp.type() == "ErrorResponse":
497506
raise TokenError(resp.error, resp)
498507

499-
self._backup(state)
508+
if session_state is not None:
509+
# Use session_state from Authorization server, as per §2
510+
# from https://openid.net/specs/openid-connect-session-1_0.html
511+
self._backup(session_state)
512+
else:
513+
self._backup(state)
500514

501515
return resp
502516

tests/test_oic_consumer.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,8 +306,59 @@ def test_complete(self):
306306
resp = self.consumer.complete(_state)
307307
assert isinstance(resp, AccessTokenResponse)
308308
assert _eq(resp.keys(), ["token_type", "state", "access_token", "scope"])
309+
assert _state in self.consumer.sdb # Consumer has been saved using 'state'
310+
assert resp["state"] == _state
309311

312+
def test_complete_with_session_management(self):
313+
_state = "state0"
314+
_session_state = "o75rvk4#5#JDYD`w"
315+
args = {
316+
"client_id": self.consumer.client_id,
317+
"response_type": "code",
318+
"scope": ["openid"],
319+
}
320+
321+
location = "https://example.com/cb?code=code&state=state0"
322+
with responses.RequestsMock() as rsps:
323+
rsps.add(
324+
responses.GET,
325+
"https://example.com/authorization",
326+
status=302,
327+
headers={"location": location},
328+
)
329+
rsps.add(
330+
responses.POST,
331+
"https://example.com/token",
332+
content_type="application/json",
333+
json={
334+
"access_token": "some_token",
335+
"token_type": "bearer",
336+
"state": "state0",
337+
"scope": "openid",
338+
"session_state": _session_state,
339+
},
340+
)
341+
result = self.consumer.do_authorization_request(
342+
state=_state, request_args=args
343+
)
344+
parsed = urlparse(result.headers["location"])
345+
346+
self.consumer.parse_response(
347+
AuthorizationResponse, info=parsed.query, sformat="urlencoded"
348+
)
349+
350+
resp = self.consumer.complete(_state, session_state=_session_state)
351+
352+
assert isinstance(resp, AccessTokenResponse)
353+
assert _eq(
354+
resp.keys(),
355+
["token_type", "state", "access_token", "scope", "session_state"],
356+
)
357+
assert (
358+
_session_state in self.consumer.sdb
359+
) # Consumer has been saved using 'session_state'
310360
assert resp["state"] == _state
361+
assert resp["session_state"] == _session_state
311362

312363
def test_parse_authz(self):
313364
_state = "state0"

0 commit comments

Comments
 (0)