Skip to content

Commit 332cabd

Browse files
authored
Merge pull request #13 from IdentityPython/develop
Verifying client configuration
2 parents cf741aa + ad43829 commit 332cabd

39 files changed

+421
-119
lines changed

doc/move.rst

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
.. _move:
2+
3+
********************************************************
4+
How to move from oidcmsg,oidcrp and oidc-op to idpy-oidc
5+
********************************************************
6+
7+
Since you are here the chance that you are using OidcRP and/or oidc-op is very
8+
high. Hopefully you are happy with what the packages has provided you with
9+
so far. But somehow you have now learned that idpy-oidc is where the
10+
action will be in the future so you want to know what it takes to move
11+
from OidcRP and/or oidc-op to idpy-oidc.
12+
13+
idpy-oidc is collecting the three original packages into one:
14+
15+
* OidcMsg -> idpy-oidc/message
16+
* OidcRP -> idpy-oidc/client
17+
* oidc-op -> idpy-oidc/server
18+
19+
Some of the functionality that was in OidcMsg because it was needed by
20+
both OidcRP and oidc-op is now placed at the root of idpy-oidc.
21+
We will do them one by one
22+
23+
OidcRP
24+
------
25+
26+
If you have kept yourself to always using the high level api moving
27+
28+
You probably can get away only doing what I descibe below.
29+
These are the steps I had to take to get the example/flask_rp RP working.
30+
31+
1) Created a file, named script_py.sed with this content::
32+
33+
s/from oidcop.server import Server/from idpyoidc.server import Server/g
34+
s/oidcop/idpyoidc.server/g
35+
s/oidcrp/idpyoidc.client/g
36+
s/oidcmsg/idpyoidc.message/g
37+
s/idpyoidc.message.configure/idpyoidc.configure/g
38+
s/idpyoidc.message.client/idpyoidc.client/g
39+
s/idpyoidc.message.ssl_context/idpyoidc.ssl_context/g
40+
s/from idpyoidc.client.util import create_context/from idpyoidc.ssl_context import create_context/g
41+
42+
2) Create another file, named script_json.sed with this content::
43+
44+
s/oidcop/idpyoidc.server/g
45+
s/oidcrp/idpyoidc.client/g
46+
s/oidcmsg/idpyoidc.message/g
47+
48+
3) Ran the commands::
49+
50+
find . -name "*.py" -exec sed -i '' -f script_py.sed {} \;
51+
find . -name "*.json" -exec sed -i '' -f script_json.sed {} \;
52+
53+
And I was able to successfully launch the RP.
54+
This worked for me, it might be enough for you too. If not you can probably
55+
figure out what needs changing. If you do I'd appreciate letting me know
56+
so I can add those steps to this document.
57+
58+
oidc-op
59+
-------
60+
61+
Getting oidc-op/example/flask_op running was a bit trickier but not a lot.
62+
63+
Started of with creating the sed script files:
64+
65+
1) Created a file, named script_py.sed with this content::
66+
67+
s/from oidcop.server import Server/from idpyoidc.server import Server/g
68+
s/oidcop/idpyoidc.server/g
69+
s/oidcrp/idpyoidc.client/g
70+
s/oidcmsg/idpyoidc.message/g
71+
s/idpyoidc.message.configure/idpyoidc.configure/g
72+
s/idpyoidc.message.client/idpyoidc.client/g
73+
s/idpyoidc.message.ssl_context/idpyoidc.ssl_context/g
74+
s/from idpyoidc.server.utils import create_context/from idpyoidc.ssl_context import create_context/g
75+
76+
2) Create another file, named script_json.sed with this content::
77+
78+
s/oidcop/idpyoidc.server/g
79+
s/oidcrp/idpyoidc.client/g
80+
s/oidcmsg/idpyoidc.message/g
81+
82+
3) Ran the commands::
83+
84+
find . -name "*.py" -exec sed -i '' -f script_py.sed {} \;
85+
find . -name "*.json" -exec sed -i '' -f script_json.sed {} \;
86+
87+
Now, I had to edit 2 files.
88+
89+
views.py
90+
++++++++
91+
92+
Removed the single line (22)::
93+
94+
from oidcop.exception import TokenAuthenticationError
95+
96+
and the lines (233-238::
97+
98+
except TokenAuthenticationError as err:
99+
_log.error(err)
100+
return make_response(json.dumps({
101+
'error': 'invalid_token',
102+
'error_description': str(err)
103+
}), 401)
104+
105+
106+
config.json
107+
+++++++++++
108+
109+
Removed the line (312) ::
110+
111+
"jwks_file": "private/token_jwks.json",
112+
113+
And that was it.

example/flask_op/views.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ def service_endpoint(endpoint):
221221
# name is not unique
222222
"cookie": [{"name": k, "value": v} for k, v in request.cookies.items()]
223223
}
224+
_log.info(f"http_info: {http_info}")
224225

225226
if request.method == 'GET':
226227
try:

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
77

88
[metadata]
99
name = "idpyoidc"
10-
version = "1.0.7"
10+
version = "1.0.9"
1111
author = "Roland Hedberg"
1212
author_email = "[email protected]"
1313
description = "Everything OAuth2 and OIDC"

src/idpyoidc/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
__author__ = "Roland Hedberg"
2-
__version__ = "1.0.7"
2+
__version__ = "1.0.9"
33

44
import os
55
from typing import Dict

src/idpyoidc/client/oidc/registration.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ def add_callbacks(context, ignore: Optional[List[str]] = None):
171171

172172

173173
CALLBACK_URIS = [
174-
"post_logout_redirect_uris",
174+
"post_logout_redirect_uri",
175175
"backchannel_logout_uri",
176176
"frontchannel_logout_uri",
177177
"request_uris",

src/idpyoidc/client/service_context.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ class ServiceContext(OidcContext):
9393
"httpc_params": None,
9494
"issuer": None,
9595
"kid": None,
96-
"post_logout_redirect_uris": None,
96+
"post_logout_redirect_uri": None,
9797
"provider_info": None,
9898
"redirect_uris": None,
9999
"requests_dir": None,
@@ -128,7 +128,7 @@ def __init__(self, base_url="", keyjar=None, config=None, state=None, **kwargs):
128128
self.client_secret_expires_at = 0
129129
self.behaviour = {}
130130
self.provider_info = {}
131-
self.post_logout_redirect_uris = []
131+
self.post_logout_redirect_uri = ""
132132
self.redirect_uris = []
133133
self.register_args = {}
134134
self.registration_response = {}

src/idpyoidc/configure.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,7 @@
55
from typing import Optional
66
from typing import Union
77

8-
from cryptojwt.key_jar import init_key_jar
9-
108
from idpyoidc.logging import configure_logging
11-
from idpyoidc.util import instantiate
129
from idpyoidc.util import load_config_file
1310

1411
DEFAULT_FILE_ATTRIBUTE_NAMES = [

src/idpyoidc/message/oidc/backchannel_authentication.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ class AuthenticationRequest(Message):
3535
}
3636

3737
def verify(self, **kwargs):
38+
super(AuthenticationRequest, self).verify(**kwargs)
3839
if "request" in self:
3940
_vc_name = verified_claim_name("request")
4041
if _vc_name in self:
@@ -102,16 +103,16 @@ class AuthenticationRequestJWT(Message):
102103
}
103104

104105
def verify(self, **kwargs):
105-
def verify(self, **kwargs):
106-
_iss = kwargs.get("issuer")
107-
if _iss:
108-
if _iss not in self["aud"]:
109-
raise ParameterError("Not among audience")
110-
111-
_client_id = kwargs.get("client_id")
112-
if _client_id:
113-
if _client_id != self["iss"]:
114-
raise ParameterError("Issuer mismatch")
106+
Message.verify(self, **kwargs)
107+
_iss = kwargs.get("issuer")
108+
if _iss:
109+
if _iss not in self["aud"]:
110+
raise ParameterError("Not among audience")
111+
112+
_client_id = kwargs.get("client_id")
113+
if _client_id:
114+
if _client_id != self["iss"]:
115+
raise ParameterError("Issuer mismatch")
115116

116117

117118
class AuthenticationResponse(ResponseMessage):

src/idpyoidc/server/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
# Server specific defaults and a basic Server class
2+
import logging
23
from typing import Any
34
from typing import Optional
45
from typing import Union
56

67
from cryptojwt import KeyJar
78

89
from idpyoidc.impexp import ImpExp
10+
from idpyoidc.message.oidc import RegistrationRequest
911
from idpyoidc.server import authz
1012
from idpyoidc.server.client_authn import client_auth_setup
1113
from idpyoidc.server.configure import ASConfiguration
@@ -20,6 +22,8 @@
2022
from idpyoidc.server.util import allow_refresh_token
2123
from idpyoidc.server.util import build_endpoints
2224

25+
logger = logging.getLogger(__name__)
26+
2327

2428
def do_endpoints(conf, server_get):
2529
_endpoints = conf.get("endpoint")
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import logging
2+
from typing import Callable
3+
from typing import Optional
4+
5+
from idpyoidc.message import OPTIONAL_LIST_OF_STRINGS
6+
from idpyoidc.message.oidc import SINGLE_OPTIONAL_BOOLEAN
7+
from idpyoidc.message.oidc import SINGLE_OPTIONAL_DICT
8+
from idpyoidc.message.oidc import RegistrationResponse
9+
from idpyoidc.server.session.token import TOKEN_MAP
10+
11+
logger = logging.getLogger(__name__)
12+
13+
14+
class ClientConfiguration(RegistrationResponse):
15+
c_param = RegistrationResponse.c_param.copy()
16+
c_param.update(
17+
{
18+
"token_usage_rules": SINGLE_OPTIONAL_DICT,
19+
"token_exchange": SINGLE_OPTIONAL_DICT,
20+
"add_claims": SINGLE_OPTIONAL_DICT,
21+
"pkce_essential": SINGLE_OPTIONAL_BOOLEAN,
22+
"revoke_refresh_on_issue": SINGLE_OPTIONAL_BOOLEAN,
23+
"allowed_scopes": OPTIONAL_LIST_OF_STRINGS,
24+
"scopes_to_claims": SINGLE_OPTIONAL_DICT,
25+
#
26+
# These may be added dynamically at run time
27+
# "dpop_jkt": SINGLE_OPTIONAL_STRING,
28+
# "si_redirects": OPTIONAL_LIST_OF_STRINGS,
29+
# "sector_id": SINGLE_OPTIONAL_STRING,
30+
# "client_secret_expires_at": SINGLE_OPTIONAL_INT,
31+
# "registration_access_token": SINGLE_OPTIONAL_STRING
32+
# "auth_method": SINGLE_OPTIONAL_DICT,
33+
}
34+
)
35+
36+
def verify(self, **kwargs):
37+
RegistrationResponse.verify(self, **kwargs)
38+
_server_get = kwargs.get("server_get")
39+
if _server_get:
40+
_endpoint_context = _server_get("endpoint_context")
41+
else:
42+
_endpoint_context = None
43+
44+
if "add_claims" in self:
45+
if not set(self["add_claims"].keys()).issubset({"always", "by_scope"}):
46+
_diff = set(self["add_claims"].keys()).difference({"always", "by_scope"})
47+
logger.warning(f"Undefined add_claims parameter '{_diff}' used")
48+
49+
if "token_usage_rules" in self:
50+
for _typ, _rule in self["token_usage_rules"].items():
51+
# The allowed rules are: expires_in, supports_minting, max_usage
52+
if _typ not in TOKEN_MAP.keys():
53+
logger.warning(f"Undefined token type '{_typ}' used")
54+
55+
if not set(_rule.keys()).issubset({"expires_in", "supports_minting", "max_usage"}):
56+
_diff = set(_rule.keys()).difference(
57+
{"expires_in", "supports_minting", "max_usage"}
58+
)
59+
logger.warning(f"Undefined token_usage_rules parameter '{_diff}' used")
60+
61+
_supports = _rule.get("supports_minting")
62+
if _supports:
63+
if not set(_supports).issubset(set(TOKEN_MAP.keys())):
64+
_diff = set(_supports).difference(set(TOKEN_MAP.keys()))
65+
logger.warning(f"Unknown supports_minting token '{_diff}' used")
66+
67+
if "token_exchange" in self:
68+
pass
69+
70+
71+
def verify_oidc_client_information(
72+
conf: dict, server_get: Optional[Callable] = None, **kwargs
73+
) -> dict:
74+
res = {}
75+
for key, item in conf.items():
76+
_rr = ClientConfiguration(**item)
77+
_rr.verify(server_get=server_get, **kwargs)
78+
if _rr.extra():
79+
logger.info(f"Extras: {_rr.extra()}")
80+
res[key] = _rr
81+
82+
return res

0 commit comments

Comments
 (0)