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

Commit 66eb57b

Browse files
authored
Merge pull request #122 from IdentityPython/develop
v2.1.1
2 parents 41d7f5e + 906a1be commit 66eb57b

29 files changed

+577
-102
lines changed

.github/workflows/pypi.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: Publish Python distribution to PyPI
2+
on:
3+
release:
4+
types:
5+
- created
6+
7+
jobs:
8+
build-n-publish:
9+
name: Publish Python distribution to PyPI
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@master
13+
- name: Setup Python 3.8
14+
uses: actions/setup-python@v1
15+
with:
16+
python-version: 3.8
17+
- name: Install pypa/build
18+
run: >-
19+
python -m
20+
pip install
21+
build
22+
--user
23+
- name: Build a binary wheel and a source tarball
24+
run: >-
25+
python -m
26+
build
27+
--sdist
28+
--wheel
29+
--outdir dist/
30+
.
31+
- name: Publish distribution to PyPI
32+
uses: pypa/gh-action-pypi-publish@master
33+
with:
34+
user: __token__
35+
password: ${{ secrets.PYPI_API_TOKEN }}

docs/source/contents/conf.rst

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,27 @@ An example::
8484
}
8585
}
8686

87+
The provided add-ons can be seen in the following sections.
88+
89+
pkce
90+
####
91+
92+
The pkce add on is activated using the ``oidcop.oidc.add_on.pkce.add_pkce_support``
93+
function. The possible configuration options can be found below.
94+
95+
essential
96+
---------
97+
98+
Whether pkce is mandatory, authentication requests without a ``code_challenge``
99+
will fail if this is True. This option can be overridden per client by defining
100+
``pkce_essential`` in the client metadata.
101+
102+
code_challenge_method
103+
---------------------
104+
105+
The allowed code_challenge methods. The supported code challenge methods are:
106+
``plain, S256, S384, S512``
107+
87108
--------------
88109
authentication
89110
--------------
@@ -622,3 +643,72 @@ the following::
622643
}
623644
}
624645
}
646+
647+
648+
=======
649+
Clients
650+
=======
651+
652+
In this section there are some client configuration examples.
653+
654+
A common configuration::
655+
656+
endpoint_context.cdb['jbxedfmfyc'] = {
657+
client_id: 'jbxedfmfyc',
658+
client_salt: '6flfsj0Z',
659+
registration_access_token: 'z3PCMmC1HZ1QmXeXGOQMJpWQNQynM4xY',
660+
registration_client_uri: 'https://127.0.0.1:8000/registration_api?client_id=jbxedfmfyc',
661+
client_id_issued_at: 1630256902,
662+
client_secret: '19cc69b70d0108f630e52f72f7a3bd37ba4e11678ad1a7434e9818e1',
663+
client_secret_expires_at: 1929727754,
664+
application_type: 'web',
665+
contacts: [
666+
667+
],
668+
token_endpoint_auth_method: 'client_secret_basic',
669+
redirect_uris: [
670+
[
671+
'https://127.0.0.1:8090/authz_cb/satosa',
672+
{}
673+
]
674+
],
675+
post_logout_redirect_uris: [
676+
[
677+
'https://127.0.0.1:8090/session_logout/satosa',
678+
null
679+
]
680+
],
681+
response_types: [
682+
'code'
683+
],
684+
grant_types: [
685+
'authorization_code'
686+
],
687+
allowed_scopes: [
688+
'openid',
689+
'profile',
690+
'email',
691+
'offline_access'
692+
]
693+
}
694+
695+
696+
How to configure the release of the user claims per clients::
697+
698+
endpoint_context.cdb["client_1"] = {
699+
"client_secret": "hemligt",
700+
"redirect_uris": [("https://example.com/cb", None)],
701+
"client_salt": "salted",
702+
"token_endpoint_auth_method": "client_secret_post",
703+
"response_types": ["code", "token", "code id_token", "id_token"],
704+
"add_claims": {
705+
"always": {
706+
"introspection": ["nickname", "eduperson_scoped_affiliation"],
707+
"userinfo": ["picture", "phone_number"],
708+
},
709+
# this overload the general endpoint configuration for this client
710+
# self.server.server_get("endpoint", "id_token").kwargs = {"add_claims_by_scope": True}
711+
"by_scope": {
712+
"id_token": False,
713+
},
714+
},

docs/source/contents/usage.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Usage
22
-----
33

4-
Some examples, how to run flask_op and django_op, but also some typical configuration in relation to common use cases.
4+
Some examples, how to run [flask_op](https://github.com/IdentityPython/oidc-op/tree/master/example/flask_op) and [django_op](https://github.com/peppelinux/django-oidc-op) but also some typical configuration in relation to common use cases.
55

66

77

@@ -34,7 +34,7 @@ Get to the RP landing page to choose your authentication endpoint. The first opt
3434

3535
![OP Auth](../_images/2.png)
3636

37-
AS/OP accepted our authentication request and prompt to us the login form. Read passwd.json file to get credentials.
37+
The AS/OP supports dynamic client registration, it accepts the authentication request and prompt to us the login form. Read [passwd.json](https://github.com/IdentityPython/oidc-op/blob/master/example/flask_op/passwd.json) file to get credentials.
3838

3939
----------------------------------
4040

@@ -75,12 +75,12 @@ It is important to consider that only scope=offline_access will get a usable ref
7575

7676
oidc-op will return a json response like this::
7777

78-
{
79-
'access_token': 'eyJhbGc ... CIOH_09tT_YVa_gyTqg',
80-
'token_type': 'Bearer',
81-
'scope': 'openid profile email address phone offline_access',
82-
'refresh_token': 'Z0FBQ ... 1TE16cm1Tdg=='
83-
}
78+
{
79+
'access_token': 'eyJhbGc ... CIOH_09tT_YVa_gyTqg',
80+
'token_type': 'Bearer',
81+
'scope': 'openid profile email address phone offline_access',
82+
'refresh_token': 'Z0FBQ ... 1TE16cm1Tdg=='
83+
}
8484

8585

8686

example/flask_op/views.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ def _add_cookie(resp, cookie_spec):
3232
for k,v in cookie_spec.items()
3333
if k not in ('name',)}
3434
kwargs["path"] = "/"
35+
kwargs["samesite"] = "Lax"
3536
resp.set_cookie(cookie_spec["name"], **kwargs)
3637

3738

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
oidcmsg>=1.3.0
1+
oidcmsg>=1.4.0
22
pyyaml
33
jinja2>=2.11.3
44
responses>=0.13.0

src/oidcop/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import secrets
22

3-
__version__ = "2.1.0"
3+
__version__ = "2.1.1"
44

55
DEF_SIGN_ALG = {
66
"id_token": "RS256",

src/oidcop/client_authn.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from oidcop.exception import InvalidClient
2323
from oidcop.exception import MultipleUsage
2424
from oidcop.exception import NotForMe
25+
from oidcop.exception import ToOld
2526
from oidcop.exception import UnknownClient
2627
from oidcop.util import importer
2728

@@ -409,6 +410,8 @@ def verify_client(
409410
try:
410411
# get_client_id_from_token is a callback... Do not abuse for code readability.
411412
auth_info["client_id"] = get_client_id_from_token(endpoint_context, _token, request)
413+
except ToOld:
414+
raise ValueError("Expired token")
412415
except KeyError:
413416
raise ValueError("Unknown token")
414417

src/oidcop/oauth2/token.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ def process_request(self, req: Union[Message, dict], **kwargs):
253253
_resp = {
254254
"access_token": access_token.value,
255255
"token_type": access_token.token_type,
256-
"scope": _grant.scope,
256+
"scope": scope,
257257
}
258258

259259
if access_token.expires_at:
@@ -318,7 +318,7 @@ def post_parse_request(
318318
if "scope" in request:
319319
req_scopes = set(request["scope"])
320320
scopes = set(grant.find_scope(token.based_on))
321-
if scopes < req_scopes:
321+
if not req_scopes.issubset(scopes):
322322
return self.error_cls(
323323
error="invalid_request",
324324
error_description="Invalid refresh scopes",

src/oidcop/oidc/add_on/pkce.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,9 @@
33
from typing import Dict
44

55
from cryptojwt.utils import b64e
6-
from oidcmsg.oauth2 import (
7-
AuthorizationErrorResponse,
8-
RefreshAccessTokenRequest,
9-
TokenExchangeRequest,
10-
)
6+
from oidcmsg.oauth2 import AuthorizationErrorResponse
7+
from oidcmsg.oauth2 import RefreshAccessTokenRequest
8+
from oidcmsg.oauth2 import TokenExchangeRequest
119
from oidcmsg.oidc import TokenErrorResponse
1210

1311
from oidcop.endpoint import Endpoint
@@ -41,7 +39,14 @@ def post_authn_parse(request, client_id, endpoint_context, **kwargs):
4139
:param kwargs:
4240
:return:
4341
"""
44-
if endpoint_context.args["pkce"]["essential"] and "code_challenge" not in request:
42+
client = endpoint_context.cdb[client_id]
43+
if "pkce_essential" in client:
44+
essential = client["pkce_essential"]
45+
else:
46+
essential = endpoint_context.args["pkce"].get(
47+
"essential", False
48+
)
49+
if essential and "code_challenge" not in request:
4550
return AuthorizationErrorResponse(
4651
error="invalid_request", error_description="Missing required code_challenge",
4752
)
@@ -131,9 +136,6 @@ def add_pkce_support(endpoint: Dict[str, Endpoint], **kwargs):
131136
authn_endpoint.post_parse_request.append(post_authn_parse)
132137
token_endpoint.post_parse_request.append(post_token_parse)
133138

134-
if "essential" not in kwargs:
135-
kwargs["essential"] = False
136-
137139
code_challenge_methods = kwargs.get("code_challenge_methods", CC_METHOD.keys())
138140

139141
kwargs["code_challenge_methods"] = {}

src/oidcop/oidc/token.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ def process_request(self, req: Union[Message, dict], **kwargs):
218218
_resp = {
219219
"access_token": access_token.value,
220220
"token_type": token_type,
221-
"scope": _grant.scope,
221+
"scope": scope,
222222
}
223223

224224
if access_token.expires_at:
@@ -246,7 +246,7 @@ def process_request(self, req: Union[Message, dict], **kwargs):
246246
if "id_token" in _mints and "openid" in scope:
247247
try:
248248
_idtoken = self._mint_token(
249-
token_class="refresh_token",
249+
token_class="id_token",
250250
grant=_grant,
251251
session_id=_session_info["session_id"],
252252
client_id=_session_info["client_id"],
@@ -307,7 +307,7 @@ def post_parse_request(
307307
if "scope" in request:
308308
req_scopes = set(request["scope"])
309309
scopes = set(grant.find_scope(token.based_on))
310-
if scopes < req_scopes:
310+
if not req_scopes.issubset(scopes):
311311
return self.error_cls(
312312
error="invalid_request",
313313
error_description="Invalid refresh scopes",

0 commit comments

Comments
 (0)