Skip to content

Commit ed7924f

Browse files
committed
Merge branch 'dev'
2 parents 035aa3c + a0cfb5c commit ed7924f

File tree

5 files changed

+52
-31
lines changed

5 files changed

+52
-31
lines changed

djangosaml2/backends.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ def authenticate(self, request, session_info=None, attribute_mapping=None, creat
140140
if user is not None:
141141
user = self._update_user(
142142
user, attributes, attribute_mapping, force_save=created)
143-
143+
144144
if self.user_can_authenticate(user):
145145
return user
146146

djangosaml2/conf.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,12 @@ def config_settings_loader(request: Optional[HttpRequest] = None) -> SPConfig:
5151
return conf
5252

5353

54-
def get_config(config_loader_path: Optional[Union[Callable, str]] = None, request: Optional[HttpRequest] = None) -> SPConfig:
55-
""" Load a config_loader function if necessary, and call that function with the request as argument.
56-
If the config_loader_path is a callable instead of a string, no importing is necessary and it will be used directly.
54+
def get_config(config_loader_path: Optional[Union[Callable, str]] = None,
55+
request: Optional[HttpRequest] = None) -> SPConfig:
56+
""" Load a config_loader function if necessary, and call that
57+
function with the request as argument.
58+
If the config_loader_path is a callable instead of a string,
59+
no importing is necessary and it will be used directly.
5760
Return the resulting SPConfig.
5861
"""
5962
config_loader_path = config_loader_path or get_custom_setting(

djangosaml2/utils.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,23 +104,25 @@ def saml2_from_httpredirect_request(url):
104104

105105

106106
def get_session_id_from_saml2(saml2_xml):
107-
saml2_xml = saml2_xml.encode() if isinstance(saml2_xml, str) else saml2_xml
108-
return re.findall(b'ID="([a-z0-9\-]*)"', saml2_xml, re.I)[0].decode()
107+
saml2_xml = saml2_xml.decode() if isinstance(saml2_xml, bytes) else saml2_xml
108+
return re.findall(r'ID="([a-z0-9\-]*)"', saml2_xml, re.I)[0]
109109

110110

111111
def get_subject_id_from_saml2(saml2_xml):
112112
saml2_xml = saml2_xml if isinstance(saml2_xml, str) else saml2_xml.decode()
113113
re.findall('">([a-z0-9]+)</saml:NameID>', saml2_xml)[0]
114114

115-
def add_param_in_url(url:str, param_key:str, param_value:str):
115+
116+
def add_param_in_url(url: str, param_key: str, param_value: str):
116117
params = list(url.split('?'))
117118
params.append(f'{param_key}={param_value}')
118-
new_url = params[0] + '?' +''.join(params[1:])
119+
new_url = params[0] + '?' + ''.join(params[1:])
119120
return new_url
120121

122+
121123
def add_idp_hinting(request, http_response) -> bool:
122124
idphin_param = getattr(settings, 'SAML2_IDPHINT_PARAM', 'idphint')
123-
params = urllib.parse.urlencode(request.GET)
125+
urllib.parse.urlencode(request.GET)
124126

125127
if idphin_param not in request.GET.keys():
126128
return False

djangosaml2/views.py

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,6 @@
4646
from saml2.samlp import AuthnRequest, IDPEntry, IDPList, Scoping
4747
from saml2.sigver import MissingKey
4848
from saml2.validate import ResponseLifetimeExceed, ToEarly
49-
from saml2.xmldsig import ( # support for SHA1 is required by spec
50-
SIG_RSA_SHA1, SIG_RSA_SHA256)
5149

5250
from .cache import IdentityCache, OutstandingQueriesCache, StateCache
5351
from .conf import get_config
@@ -160,6 +158,8 @@ def get(self, request, *args, **kwargs):
160158
configured_idps = available_idps(conf)
161159
selected_idp = request.GET.get('idp', None)
162160

161+
sso_kwargs = {}
162+
163163
# Do we have a Discovery Service?
164164
if not selected_idp:
165165
discovery_service = getattr(settings, 'SAML2_DISCO_URL', None)
@@ -193,14 +193,19 @@ def get(self, request, *args, **kwargs):
193193
selected_idp = list(configured_idps.keys())[0]
194194

195195
# perform IdP Scoping if scoping param is present
196-
idp_scoping = Scoping()
197196
idp_scoping_param = request.GET.get('scoping', None)
198197
if idp_scoping_param:
198+
idp_scoping = Scoping()
199199
idp_scoping.idp_list = IDPList()
200-
idp_scoping.idp_list.idp_entry.append(IDPEntry(provider_id = idp_scoping_param))
200+
idp_scoping.idp_list.idp_entry.append(
201+
IDPEntry(provider_id = idp_scoping_param)
202+
)
203+
sso_kwargs['scoping'] = idp_scoping
204+
201205

202206
# choose a binding to try first
203-
binding = getattr(settings, 'SAML_DEFAULT_BINDING', saml2.BINDING_HTTP_POST)
207+
binding = getattr(settings, 'SAML_DEFAULT_BINDING',
208+
saml2.BINDING_HTTP_POST)
204209
logger.debug(f'Trying binding {binding} for IDP {selected_idp}')
205210

206211
# ensure our selected binding is supported by the IDP
@@ -232,18 +237,16 @@ def get(self, request, *args, **kwargs):
232237
)
233238

234239
client = Saml2Client(conf)
235-
http_response = None
236240

237241
# SSO options
238242
sign_requests = getattr(conf, '_sp_authn_requests_signed', False)
239-
sso_kwargs = {}
243+
240244
if sign_requests:
241-
sso_kwargs["sigalg"] = settings.SAML_CONFIG['service']['sp']\
242-
.get('signing_algorithm',
243-
saml2.xmldsig.SIG_RSA_SHA256)
244-
sso_kwargs["digest_alg"] = settings.SAML_CONFIG['service']['sp']\
245-
.get('digest_algorithm',
246-
saml2.xmldsig.DIGEST_SHA256)
245+
csc = settings.SAML_CONFIG['service']['sp']
246+
sso_kwargs["sigalg"] = csc.get('signing_algorithm',
247+
saml2.xmldsig.SIG_RSA_SHA256)
248+
sso_kwargs["digest_alg"] = csc.get('digest_algorithm',
249+
saml2.xmldsig.DIGEST_SHA256)
247250

248251
# pysaml needs a string otherwise: "cannot serialize True (type bool)"
249252
if getattr(conf, '_sp_force_authn', False):
@@ -256,17 +259,20 @@ def get(self, request, *args, **kwargs):
256259

257260
logger.debug(f'Redirecting user to the IdP via {binding} binding.')
258261
_msg = 'Unable to know which IdP to use'
262+
http_response = None
263+
259264
if binding == saml2.BINDING_HTTP_REDIRECT:
260265
try:
261266
session_id, result = client.prepare_for_authenticate(
262267
entityid=selected_idp, relay_state=next_path,
263-
binding=binding, sign=sign_requests, scoping=idp_scoping,
268+
binding=binding, sign=sign_requests,
264269
**sso_kwargs)
265270
except TypeError as e:
266271
logger.error(f'{_msg}: {e}')
267272
return HttpResponse(_msg)
268273
else:
269274
http_response = HttpResponseRedirect(get_location(result))
275+
270276
elif binding == saml2.BINDING_HTTP_POST:
271277
if self.post_binding_form_template:
272278
# get request XML to build our own html based on the template
@@ -275,10 +281,12 @@ def get(self, request, *args, **kwargs):
275281
except TypeError as e:
276282
logger.error(f'{_msg}: {e}')
277283
return HttpResponse(_msg)
284+
278285
session_id, request_xml = client.create_authn_request(
279286
location,
280287
binding=binding,
281-
**sso_kwargs)
288+
**sso_kwargs
289+
)
282290
try:
283291
if isinstance(request_xml, AuthnRequest):
284292
# request_xml will be an instance of AuthnRequest if the message is not signed
@@ -294,14 +302,16 @@ def get(self, request, *args, **kwargs):
294302
},
295303
})
296304
except TemplateDoesNotExist as e:
297-
logger.error(f'TemplateDoesNotExist: {e}')
305+
logger.error(
306+
f'TemplateDoesNotExist: [{self.post_binding_form_template}] - {e}'
307+
)
298308

299309
if not http_response:
300310
# use the html provided by pysaml2 if no template was specified or it doesn't exist
301311
try:
302312
session_id, result = client.prepare_for_authenticate(
303313
entityid=selected_idp, relay_state=next_path,
304-
binding=binding, scoping=idp_scoping)
314+
binding=binding, **sso_kwargs)
305315
except TypeError as e:
306316
_msg = f"Can't prepare the authentication for {selected_idp}"
307317
logger.error(f'{_msg}: {e}')
@@ -380,7 +390,8 @@ def post(self, request, attribute_mapping=None, create_unknown_user=None):
380390
except ResponseLifetimeExceed as e:
381391
_exception = e
382392
logger.info(
383-
("SAML Assertion is no longer valid. Possibly caused by network delay or replay attack."), exc_info=True)
393+
("SAML Assertion is no longer valid. Possibly caused "
394+
"by network delay or replay attack."), exc_info=True)
384395
except SignatureError as e:
385396
_exception = e
386397
logger.info("Invalid or malformed SAML Assertion.", exc_info=True)
@@ -435,7 +446,8 @@ def post(self, request, attribute_mapping=None, create_unknown_user=None):
435446
for sc in assertion.subject.subject_confirmation:
436447
if sc.method == SCM_BEARER:
437448
assertion_not_on_or_after = sc.subject_confirmation_data.not_on_or_after
438-
assertion_info = {'assertion_id': assertion.id, 'not_on_or_after': assertion_not_on_or_after}
449+
assertion_info = {'assertion_id': assertion.id,
450+
'not_on_or_after': assertion_not_on_or_after}
439451
break
440452

441453
if callable(attribute_mapping):
@@ -607,10 +619,14 @@ class LogoutView(SPConfigMixin, View):
607619
logout_error_template = 'djangosaml2/logout_error.html'
608620

609621
def get(self, request, *args, **kwargs):
610-
return self.do_logout_service(request, request.GET, saml2.BINDING_HTTP_REDIRECT, *args, **kwargs)
622+
return self.do_logout_service(
623+
request, request.GET, saml2.BINDING_HTTP_REDIRECT, *args, **kwargs
624+
)
611625

612626
def post(self, request, *args, **kwargs):
613-
return self.do_logout_service(request, request.POST, saml2.BINDING_HTTP_POST, *args, **kwargs)
627+
return self.do_logout_service(
628+
request, request.POST, saml2.BINDING_HTTP_POST, *args, **kwargs
629+
)
614630

615631
def do_logout_service(self, request, data, binding):
616632
logger.debug('Logout service started')

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def read(*rnames):
2424

2525
setup(
2626
name='djangosaml2',
27-
version='1.1.5',
27+
version='1.2.0',
2828
description='pysaml2 integration for Django',
2929
long_description=read('README.md'),
3030
long_description_content_type='text/markdown',

0 commit comments

Comments
 (0)