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

Commit 4a0ad18

Browse files
committed
Split out oauth2 functionality into a module of its own.
Separated out functions common to oauth2 and oidc. Bumped version Allow some class parameters to be changed with configuration. Added a place to keep pushed authorization requests. For now just a dictionary. Must be something more substantial in a service. Renamed tests so distinguish between oauth2 and oidc endpoint tests.
1 parent ca2a917 commit 4a0ad18

25 files changed

Lines changed: 2117 additions & 239 deletions

setup.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ def run_tests(self):
5151
url='https://github.com/IdentityPython/oicsrv',
5252
packages=["oidcendpoint", 'oidcendpoint/oidc', 'oidcendpoint/authz',
5353
'oidcendpoint/user_authn', 'oidcendpoint/user_info',
54-
'oidcendpoint/oauth2', 'oidcendpoint/oidc/add_on'],
54+
'oidcendpoint/oauth2', 'oidcendpoint/oidc/add_on',
55+
'oidcendpoint/common'],
5556
package_dir={"": "src"},
5657
classifiers=[
5758
"Development Status :: 4 - Beta",

src/oidcendpoint/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
except ImportError:
77
import random as rnd
88

9-
__version__ = "0.11.3"
9+
__version__ = "0.12.0"
1010

1111

1212
DEF_SIGN_ALG = {

src/oidcendpoint/common/__init__.py

Whitespace-only changes.
Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
import logging
2+
from urllib.parse import parse_qs
3+
from urllib.parse import splitquery
4+
from urllib.parse import unquote
5+
from urllib.parse import urlencode
6+
from urllib.parse import urlparse
7+
8+
from oidcmsg.exception import ParameterError
9+
from oidcmsg.exception import URIError
10+
from oidcmsg.oauth2 import AuthorizationErrorResponse
11+
from oidcmsg.oidc import AuthorizationResponse
12+
from oidcmsg.oidc import verified_claim_name
13+
14+
from oidcendpoint import sanitize
15+
from oidcendpoint.exception import RedirectURIError
16+
from oidcendpoint.exception import UnknownClient
17+
from oidcendpoint.user_info import SCOPE2CLAIMS
18+
19+
logger = logging.getLogger(__name__)
20+
21+
FORM_POST = """<html>
22+
<head>
23+
<title>Submit This Form</title>
24+
</head>
25+
<body onload="javascript:document.forms[0].submit()">
26+
<form method="post" action="{action}">
27+
{inputs}
28+
</form>
29+
</body>
30+
</html>"""
31+
32+
DEFAULT_SCOPES = list(SCOPE2CLAIMS.keys())
33+
34+
35+
def inputs(form_args):
36+
"""
37+
Creates list of input elements
38+
"""
39+
element = []
40+
html_field = '<input type="hidden" name="{}" value="{}"/>'
41+
for name, value in form_args.items():
42+
element.append(
43+
html_field.format(name, value)
44+
)
45+
return "\n".join(element)
46+
47+
48+
def max_age(request):
49+
verified_request = verified_claim_name("request")
50+
return request.get(verified_request, {}).get("max_age") or request.get("max_age", 0)
51+
52+
53+
def verify_uri(endpoint_context, request, uri_type, client_id=None):
54+
"""
55+
A redirect URI
56+
MUST NOT contain a fragment
57+
MAY contain query component
58+
59+
:param endpoint_context: An EndpointContext instance
60+
:param request: The authorization request
61+
:param uri_type: redirect_uri or post_logout_redirect_uri
62+
:return: An error response if the redirect URI is faulty otherwise
63+
None
64+
"""
65+
_cid = request.get("client_id", client_id)
66+
67+
if not _cid:
68+
logger.error("No client id found")
69+
raise UnknownClient("No client_id provided")
70+
71+
_redirect_uri = unquote(request[uri_type])
72+
73+
part = urlparse(_redirect_uri)
74+
if part.fragment:
75+
raise URIError("Contains fragment")
76+
77+
(_base, _query) = splitquery(_redirect_uri)
78+
if _query:
79+
_query = parse_qs(_query)
80+
81+
match = False
82+
# Get the clients registered redirect uris
83+
redirect_uris = endpoint_context.cdb.get(_cid, {}).get("{}s".format(uri_type))
84+
if not redirect_uris:
85+
raise ValueError("No registered {}".format(uri_type))
86+
else:
87+
for regbase, rquery in redirect_uris:
88+
# The URI MUST exactly match one of the Redirection URI
89+
if _base == regbase:
90+
# every registered query component must exist in the uri
91+
if rquery:
92+
if not _query:
93+
raise ValueError("Missing query part")
94+
95+
for key, vals in rquery.items():
96+
if key not in _query:
97+
raise ValueError('"{}" not in query part'.format(key))
98+
99+
for val in vals:
100+
if val not in _query[key]:
101+
raise ValueError(
102+
"{}={} value not in query part".format(key, val)
103+
)
104+
105+
# and vice versa, every query component in the uri
106+
# must be registered
107+
if _query:
108+
if not rquery:
109+
raise ValueError("No registered query part")
110+
111+
for key, vals in _query.items():
112+
if key not in rquery:
113+
raise ValueError('"{}" extra in query part'.format(key))
114+
for val in vals:
115+
if val not in rquery[key]:
116+
raise ValueError(
117+
"Extra {}={} value in query part".format(key, val)
118+
)
119+
match = True
120+
break
121+
if not match:
122+
raise RedirectURIError("Doesn't match any registered uris")
123+
124+
125+
def join_query(base, query):
126+
"""
127+
128+
:param base: URL base
129+
:param query: query part as a dictionary
130+
:return:
131+
"""
132+
if query:
133+
return "{}?{}".format(base, urlencode(query, doseq=True))
134+
else:
135+
return base
136+
137+
138+
def get_uri(endpoint_context, request, uri_type):
139+
""" verify that the redirect URI is reasonable.
140+
141+
:param endpoint_context: An EndpointContext instance
142+
:param request: The Authorization request
143+
:param uri_type: 'redirect_uri' or 'post_logout_redirect_uri'
144+
:return: redirect_uri
145+
"""
146+
uri = ""
147+
148+
if uri_type in request:
149+
verify_uri(endpoint_context, request, uri_type)
150+
uri = request[uri_type]
151+
else:
152+
uris = "{}s".format(uri_type)
153+
client_id = str(request["client_id"])
154+
if client_id in endpoint_context.cdb:
155+
_specs = endpoint_context.cdb[client_id].get(uris)
156+
if not _specs:
157+
raise ParameterError("Missing {} and none registered".format(uri_type))
158+
159+
if len(_specs) > 1:
160+
raise ParameterError(
161+
"Missing {} and more than one registered".format(uri_type)
162+
)
163+
164+
uri = join_query(*_specs[0])
165+
166+
return uri
167+
168+
169+
def authn_args_gather(request, authn_class_ref, cinfo, **kwargs):
170+
"""
171+
Gather information to be used by the authentication method
172+
"""
173+
authn_args = {
174+
"authn_class_ref": authn_class_ref,
175+
"query": request.to_urlencoded(),
176+
"return_uri": request["redirect_uri"],
177+
}
178+
179+
if "req_user" in kwargs:
180+
authn_args["as_user"] = (kwargs["req_user"],)
181+
182+
# Below are OIDC specific. Just ignore if OAuth2
183+
for attr in ["policy_uri", "logo_uri", "tos_uri"]:
184+
if cinfo.get(attr):
185+
authn_args[attr] = cinfo[attr]
186+
187+
for attr in ["ui_locales", "acr_values", "login_hint"]:
188+
if request.get(attr):
189+
authn_args[attr] = request[attr]
190+
191+
return authn_args
192+
193+
194+
def create_authn_response(endpoint, request, sid):
195+
"""
196+
197+
:param endpoint:
198+
:param request:
199+
:param sid:
200+
:return:
201+
"""
202+
# create the response
203+
aresp = AuthorizationResponse()
204+
if request.get("state"):
205+
aresp["state"] = request["state"]
206+
207+
if "response_type" in request and request["response_type"] == ["none"]:
208+
fragment_enc = False
209+
else:
210+
_context = endpoint.endpoint_context
211+
_sinfo = _context.sdb[sid]
212+
213+
if request.get("scope"):
214+
aresp["scope"] = request["scope"]
215+
216+
rtype = set(request["response_type"][:])
217+
handled_response_type = []
218+
219+
fragment_enc = True
220+
if len(rtype) == 1 and "code" in rtype:
221+
fragment_enc = False
222+
223+
if "code" in request["response_type"]:
224+
_code = aresp["code"] = _context.sdb[sid]["code"]
225+
handled_response_type.append("code")
226+
else:
227+
_context.sdb.update(sid, code=None)
228+
_code = None
229+
230+
if "token" in rtype:
231+
_dic = _context.sdb.upgrade_to_token(issue_refresh=False, key=sid)
232+
233+
logger.debug("_dic: %s" % sanitize(_dic))
234+
for key, val in _dic.items():
235+
if key in aresp.parameters() and val is not None:
236+
aresp[key] = val
237+
238+
handled_response_type.append("token")
239+
240+
_access_token = aresp.get("access_token", None)
241+
242+
not_handled = rtype.difference(handled_response_type)
243+
if not_handled:
244+
resp = AuthorizationErrorResponse(
245+
error="invalid_request", error_description="unsupported_response_type"
246+
)
247+
return {"response_args": resp, "fragment_enc": fragment_enc}
248+
249+
return {"response_args": aresp, "fragment_enc": fragment_enc}
250+
251+
252+
class AllowedAlgorithms:
253+
def __init__(self, algorithm_parameters):
254+
self.algorithm_parameters = algorithm_parameters
255+
256+
def __call__(self, client_id, endpoint_context, alg, alg_type):
257+
_cinfo = endpoint_context.cdb[client_id]
258+
_pinfo = endpoint_context.provider_info
259+
260+
_reg, _sup = self.algorithm_parameters[alg_type]
261+
_allowed = _cinfo.get(_reg)
262+
if _allowed is None:
263+
_allowed = _pinfo.get(_sup)
264+
265+
if alg not in _allowed:
266+
logger.error("Signing alg user: {} not among allowed: {}".format(alg, _allowed))
267+
raise ValueError("Not allowed '%s' algorithm used", alg)
268+
269+
270+
def re_authenticate(request, authn):
271+
return False

src/oidcendpoint/endpoint.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,12 @@ def __init__(self, endpoint_context, **kwargs):
142142
self.kwargs = kwargs
143143
self.full_path = ""
144144

145+
for param in ["request_cls", "response_cls", "request_format", "request_placement",
146+
"response_format", "response_placement"]:
147+
_val = kwargs.get(param)
148+
if _val:
149+
setattr(self, param, _val)
150+
145151
if "client_authn_method" in kwargs:
146152
self.client_authn_method = kwargs["client_authn_method"]
147153
elif self.default_capabilities is not None:

src/oidcendpoint/endpoint_context.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ def __init__(
125125
self.scope2claims = SCOPE2CLAIMS
126126
# arguments for endpoints add-ons
127127
self.args = {}
128+
self.par_db = {}
128129

129130
self.th_args = get_token_handlers(conf)
130131

src/oidcendpoint/exception.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,7 @@ class OnlyForTestingWarning(Warning):
6969

7070
class ProcessError(OidcEndpointError):
7171
pass
72+
73+
74+
class ServiceError(OidcEndpointError):
75+
pass

0 commit comments

Comments
 (0)