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

Commit b3605a0

Browse files
authored
Merge pull request #103 from IdentityPython/develop
v2.0.1
2 parents 2506b8f + c46919f commit b3605a0

26 files changed

+694
-180
lines changed

docs/source/contents/conf.rst

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -156,28 +156,33 @@ An example::
156156
backchannel_logout_session_supported: True
157157
check_session_iframe: https://127.0.0.1:5000/check_session_iframe
158158

159-
--------------
159+
-------------
160160
cookie_handler
161-
--------------
161+
-------------
162162

163163
An example::
164164

165-
"cookie_handler": {
165+
"cookie_handler": {
166166
"class": "oidcop.cookie_handler.CookieHandler",
167-
"kwargs": {
168-
"keys": {
169-
"private_path": f"{OIDC_JWKS_PRIVATE_PATH}/cookie_jwks.json",
170-
"key_defs": [
171-
{"type": "OCT", "use": ["enc"], "kid": "enc"},
172-
{"type": "OCT", "use": ["sig"], "kid": "sig"}
173-
],
174-
"read_only": False
175-
},
176-
"name": {
177-
"session": "oidc_op",
178-
"register": "oidc_op_rp",
179-
"session_management": "sman"
180-
}
167+
"kwargs": {
168+
"keys": {
169+
"private_path": f"{OIDC_JWKS_PRIVATE_PATH}/cookie_jwks.json",
170+
"key_defs": [
171+
{"type": "OCT", "use": ["enc"], "kid": "enc"},
172+
{"type": "OCT", "use": ["sig"], "kid": "sig"}
173+
],
174+
"read_only": False
175+
},
176+
"flags": {
177+
"samesite": "None",
178+
"httponly": True,
179+
"secure": True,
180+
},
181+
"name": {
182+
"session": "oidc_op",
183+
"register": "oidc_op_rp",
184+
"session_management": "sman"
185+
}
181186
}
182187
},
183188

docs/source/contents/usage.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,41 @@ The identity representation with the information fetched from the user info endp
4949
We can even test the single logout
5050

5151

52+
Refresh token
53+
-------------
54+
55+
Here an example about how to refresh a token.
56+
It is important to consider that only scope=offline_access will get a usable refresh token.
57+
58+
59+
import requests
60+
61+
CLIENT_ID = "DBP60x3KUQfCYWZlqFaS_Q"
62+
CLIENT_SECRET="8526270403788522b2444e87ea90c53bcafb984119cec92eeccc12f1"
63+
REFRESH_TOKEN = "Z0FBQUFBQ ... lN2JNODYtZThjMnFsZUNDcg=="
64+
65+
data = {
66+
"grant_type" : "refresh_token",
67+
"client_id" : f"{CLIENT_ID}",
68+
"client_secret" : f"{CLIENT_SECRET}",
69+
"refresh_token" : f"{REFRESH_TOKEN}"
70+
}
71+
headers = {'Content-Type': "application/x-www-form-urlencoded" }
72+
response = requests.post(
73+
'https://127.0.0.1:8000/oidcop/token', verify=False, data=data, headers=headers
74+
)
75+
76+
oidc-op will return a json response like this::
77+
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+
}
84+
85+
86+
5287
Introspection endpoint
5388
----------------------
5489

example/flask_op/config.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@
9191
"acr": "urn:oasis:names:tc:SAML:2.0:ac:classes:InternetProtocolPassword",
9292
"class": "oidcop.user_authn.user.UserPassJinja2",
9393
"kwargs": {
94-
"verify_endpoint": "verify/user",
94+
"verify_endpoint": "/verify/user",
9595
"template": "user_pass.jinja2",
9696
"db": {
9797
"class": "oidcop.util.JSONDictDB",

example/flask_op/views.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,9 @@
2828

2929

3030
def _add_cookie(resp, cookie_spec):
31-
kwargs = {'value': cookie_spec["value"]}
32-
for param in ['expires', 'max-age']:
33-
if param in cookie_spec:
34-
kwargs[param] = cookie_spec[param]
31+
kwargs = {k:v
32+
for k,v in cookie_spec.items()
33+
if k not in ('name',)}
3534
kwargs["path"] = "/"
3635
resp.set_cookie(cookie_spec["name"], **kwargs)
3736

setup.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ def run_tests(self):
4747
with open(os.path.join(os.path.dirname(__file__), 'README.md')) as readme:
4848
README = readme.read()
4949

50+
5051
setup(
5152
name="oidcop",
5253
version=version,
@@ -71,7 +72,7 @@ def run_tests(self):
7172
"Programming Language :: Python :: 3.9",
7273
"Topic :: Software Development :: Libraries :: Python Modules"],
7374
install_requires=[
74-
"oidcmsg==1.3.2-1",
75+
"oidcmsg==1.3.3-1",
7576
"cryptojwt==1.5.2",
7677
"pyyaml",
7778
"jinja2>=2.11.3",

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.0.0-2"
3+
__version__ = "2.0.1"
44

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

src/oidcop/configure.py

Lines changed: 81 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
}
6666
},
6767
},
68-
"httpc_params": {"verify": False},
68+
"httpc_params": {"verify": False, "timeout": 4},
6969
"issuer": "https://{domain}:{port}",
7070
"template_dir": "templates",
7171
"token_handler_args": {
@@ -105,9 +105,6 @@ def add_base_path(conf: Union[dict, str], base_path: str, file_attributes: List[
105105
return conf
106106

107107

108-
URIS = ["issuer", "base_url"]
109-
110-
111108
def set_domain_and_port(conf: dict, uris: List[str], domain: str, port: int):
112109
for key, val in conf.items():
113110
if key in uris:
@@ -154,42 +151,59 @@ def create_from_config_file(
154151
)
155152

156153

157-
class Base:
154+
class Base(dict):
158155
""" Configuration base class """
159156

160157
parameter = {}
161158

162159
def __init__(
163160
self, conf: Dict, base_path: str = "", file_attributes: Optional[List[str]] = None,
164161
):
162+
dict.__init__(self)
163+
165164
if file_attributes is None:
166165
file_attributes = DEFAULT_FILE_ATTRIBUTE_NAMES
167166

168167
if base_path and file_attributes:
169168
# this adds a base path to all paths in the configuration
170169
add_base_path(conf, base_path, file_attributes)
171170

172-
def __getitem__(self, item):
173-
if item in self.__dict__:
174-
return self.__dict__[item]
175-
else:
176-
raise KeyError
177-
178-
def get(self, item, default=None):
179-
return getattr(self, item, default)
171+
def __getattr__(self, item):
172+
return self[item]
180173

181-
def __contains__(self, item):
182-
return item in self.__dict__
174+
def __setattr__(self, key, value):
175+
if key in self:
176+
raise KeyError('{} has already been set'.format(key))
177+
super(Base, self).__setitem__(key, value)
183178

184-
def items(self):
185-
for key in self.__dict__:
186-
if key.startswith("__") and key.endswith("__"):
187-
continue
188-
yield key, getattr(self, key)
179+
def __setitem__(self, key, value):
180+
if key in self:
181+
raise KeyError('{} has already been set'.format(key))
182+
super(Base, self).__setitem__(key, value)
189183

190184

191185
class EntityConfiguration(Base):
192186
default_config = AS_DEFAULT_CONFIG
187+
uris = ["issuer", "base_url"]
188+
parameter = {
189+
"add_on": None,
190+
"authz": None,
191+
"authentication": None,
192+
"base_url": "",
193+
"capabilities": None,
194+
"claims_interface": None,
195+
"cookie_handler": None,
196+
"endpoint": {},
197+
"httpc_params": {},
198+
"issuer": "",
199+
"keys": None,
200+
"session_key": None,
201+
"template_dir": None,
202+
"token_handler_args": {},
203+
"userinfo": None,
204+
"password": None,
205+
"salt": None,
206+
}
193207

194208
def __init__(
195209
self,
@@ -204,72 +218,64 @@ def __init__(
204218
conf = copy.deepcopy(conf)
205219
Base.__init__(self, conf, base_path, file_attributes)
206220

207-
self.add_on = None
208-
self.authz = None
209-
self.authentication = None
210-
self.base_url = ""
211-
self.capabilities = None
212-
self.claims_interface = None
213-
self.cookie_handler = None
214-
self.endpoint = {}
215-
self.httpc_params = {}
216-
self.issuer = ""
217-
self.keys = None
218-
self.template_dir = None
219-
self.token_handler_args = {}
220-
self.userinfo = None
221-
self.session_params = None
222-
223221
if file_attributes is None:
224222
file_attributes = DEFAULT_FILE_ATTRIBUTE_NAMES
225223

226-
for key in self.__dict__.keys():
224+
if not domain:
225+
domain = conf.get("domain", "127.0.0.1")
226+
227+
if not port:
228+
port = conf.get("port", 80)
229+
230+
for key in self.parameter.keys():
227231
_val = conf.get(key)
228232
if not _val:
229233
if key in self.default_config:
230-
_dc = copy.deepcopy(self.default_config[key])
231-
add_base_path(_dc, base_path, file_attributes)
232-
_val = _dc
234+
_val = copy.deepcopy(self.default_config[key])
235+
self.format(_val, base_path=base_path, file_attributes=file_attributes,
236+
domain=domain, port=port)
233237
else:
234238
continue
235-
setattr(self, key, _val)
236239

237-
if self.template_dir is None:
238-
self.template_dir = os.path.abspath("templates")
239-
else:
240-
self.template_dir = os.path.abspath(self.template_dir)
240+
if key == "template_dir":
241+
_val = os.path.abspath(_val)
241242

242-
if not domain:
243-
domain = conf.get("domain", "127.0.0.1")
244-
245-
if not port:
246-
port = conf.get("port", 80)
243+
setattr(self, key, _val)
247244

248-
set_domain_and_port(conf, URIS, domain=domain, port=port)
245+
# try:
246+
# _dir = self.template_dir
247+
# except AttributeError:
248+
# self.template_dir = os.path.abspath("templates")
249+
# else:
250+
# self.template_dir =
251+
252+
def format(self, conf, base_path, file_attributes, domain, port):
253+
"""
254+
Formats parts of the configuration. That includes replacing the strings {domain} and {port}
255+
with the used domain and port and making references to files and directories absolute
256+
rather then relative. The formatting is done in place.
257+
258+
:param conf: The configuration part
259+
:param base_path: The base path used to make file/directory refrences absolute
260+
:param file_attributes: Attribute names that refer to files or directories.
261+
:param domain: The domain name
262+
:param port: The port used
263+
"""
264+
add_base_path(conf, base_path, file_attributes)
265+
if isinstance(conf, dict):
266+
set_domain_and_port(conf, self.uris, domain=domain, port=port)
249267

250268

251269
class OPConfiguration(EntityConfiguration):
252270
"Provider configuration"
253271
default_config = OP_DEFAULT_CONFIG
254-
255-
def __init__(
256-
self,
257-
conf: Dict,
258-
base_path: Optional[str] = "",
259-
entity_conf: Optional[List[dict]] = None,
260-
domain: Optional[str] = "",
261-
port: Optional[int] = 0,
262-
file_attributes: Optional[List[str]] = None,
263-
):
264-
# OP special
265-
self.id_token = None
266-
self.login_hint2acrs = {}
267-
self.login_hint_lookup = None
268-
269-
EntityConfiguration.__init__(self, conf=conf, base_path=base_path,
270-
entity_conf=entity_conf, domain=domain, port=port,
271-
file_attributes=file_attributes)
272-
272+
parameter = EntityConfiguration.parameter.copy()
273+
parameter.update({
274+
"id_token": None,
275+
"login_hint2acrs": {},
276+
"login_hint_lookup": None,
277+
"sub_func": {}
278+
})
273279

274280
class ASConfiguration(EntityConfiguration):
275281
"Authorization server configuration"
@@ -290,6 +296,7 @@ def __init__(
290296

291297
class Configuration(Base):
292298
"""Server Configuration"""
299+
uris = ["issuer", "base_url"]
293300

294301
def __init__(
295302
self,
@@ -316,7 +323,7 @@ def __init__(
316323
if not port:
317324
port = conf.get("port", 80)
318325

319-
set_domain_and_port(conf, URIS, domain=domain, port=port)
326+
set_domain_and_port(conf, self.uris, domain=domain, port=port)
320327

321328
if entity_conf:
322329
for econf in entity_conf:
@@ -496,7 +503,7 @@ def __init__(
496503
},
497504
},
498505
},
499-
"httpc_params": {"verify": False},
506+
"httpc_params": {"verify": False, "timeout": 4},
500507
"issuer": "https://{domain}:{port}",
501508
"keys": {
502509
"private_path": "private/jwks.json",
@@ -511,7 +518,8 @@ def __init__(
511518
"login_hint2acrs": {
512519
"class": "oidcop.login_hint.LoginHint2Acrs",
513520
"kwargs": {
514-
"scheme_map": {"email": ["urn:oasis:names:tc:SAML:2.0:ac:classes:InternetProtocolPassword"]}
521+
"scheme_map": {
522+
"email": ["urn:oasis:names:tc:SAML:2.0:ac:classes:InternetProtocolPassword"]}
515523
},
516524
},
517525
"template_dir": "templates",

0 commit comments

Comments
 (0)