Skip to content

Commit 2f756ba

Browse files
committed
Merge branch 'fix-signed-authnreq-w-redirect-binding'
2 parents fc42b2a + 1994002 commit 2f756ba

10 files changed

+419
-250
lines changed

src/saml2/client.py

Lines changed: 145 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from saml2 import BINDING_HTTP_POST
1515
from saml2 import BINDING_SOAP
1616

17-
import saml2.xmldsig as ds
17+
from saml2.xmldsig import DefaultSignature
1818

1919
from saml2.ident import decode, code
2020
from saml2.httpbase import HTTPError
@@ -40,10 +40,19 @@ class Saml2Client(Base):
4040
""" The basic pySAML2 service provider class """
4141

4242
def prepare_for_authenticate(
43-
self, entityid=None, relay_state="",
44-
binding=saml2.BINDING_HTTP_REDIRECT, vorg="", nameid_format=None,
45-
scoping=None, consent=None, extensions=None, sign=None,
46-
response_binding=saml2.BINDING_HTTP_POST, **kwargs):
43+
self,
44+
entityid=None,
45+
relay_state="",
46+
binding=saml2.BINDING_HTTP_REDIRECT,
47+
vorg="",
48+
nameid_format=None,
49+
scoping=None,
50+
consent=None, extensions=None,
51+
sign=None,
52+
sigalg=None,
53+
response_binding=saml2.BINDING_HTTP_POST,
54+
**kwargs,
55+
):
4756
""" Makes all necessary preparations for an authentication request.
4857
4958
:param entityid: The entity ID of the IdP to send the request to
@@ -61,19 +70,20 @@ def prepare_for_authenticate(
6170
:return: session id and AuthnRequest info
6271
"""
6372

64-
reqid, negotiated_binding, info = \
65-
self.prepare_for_negotiated_authenticate(
66-
entityid=entityid,
67-
relay_state=relay_state,
68-
binding=binding,
69-
vorg=vorg,
70-
nameid_format=nameid_format,
71-
scoping=scoping,
72-
consent=consent,
73-
extensions=extensions,
74-
sign=sign,
75-
response_binding=response_binding,
76-
**kwargs)
73+
reqid, negotiated_binding, info = self.prepare_for_negotiated_authenticate(
74+
entityid=entityid,
75+
relay_state=relay_state,
76+
binding=binding,
77+
vorg=vorg,
78+
nameid_format=nameid_format,
79+
scoping=scoping,
80+
consent=consent,
81+
extensions=extensions,
82+
sign=sign,
83+
sigalg=sigalg,
84+
response_binding=response_binding,
85+
**kwargs,
86+
)
7787

7888
if negotiated_binding != binding:
7989
raise ValueError(
@@ -85,9 +95,20 @@ def prepare_for_authenticate(
8595
return reqid, info
8696

8797
def prepare_for_negotiated_authenticate(
88-
self, entityid=None, relay_state="", binding=None, vorg="",
89-
nameid_format=None, scoping=None, consent=None, extensions=None,
90-
sign=None, response_binding=saml2.BINDING_HTTP_POST, **kwargs):
98+
self,
99+
entityid=None,
100+
relay_state="",
101+
binding=None,
102+
vorg="",
103+
nameid_format=None,
104+
scoping=None,
105+
consent=None,
106+
extensions=None,
107+
sign=None,
108+
response_binding=saml2.BINDING_HTTP_POST,
109+
sigalg=None,
110+
**kwargs,
111+
):
91112
""" Makes all necessary preparations for an authentication request
92113
that negotiates which binding to use for authentication.
93114
@@ -115,27 +136,41 @@ def prepare_for_negotiated_authenticate(
115136
destination = self._sso_location(entityid, binding)
116137
logger.info("destination to provider: %s", destination)
117138

139+
# XXX - sign_post will embed the signature to the xml doc
140+
# XXX ^through self.create_authn_request(...)
141+
# XXX - sign_redirect will add the signature to the query params
142+
# XXX ^through self.apply_binding(...)
143+
sign_post = (binding == BINDING_HTTP_POST and sign)
144+
sign_redirect = (binding == BINDING_HTTP_REDIRECT and sign)
145+
118146
reqid, request = self.create_authn_request(
119-
destination, vorg, scoping, response_binding, nameid_format,
120-
consent=consent, extensions=extensions, sign=sign,
121-
**kwargs)
147+
destination,
148+
vorg,
149+
scoping,
150+
response_binding,
151+
nameid_format,
152+
consent=consent,
153+
extensions=extensions,
154+
sign=sign_post,
155+
sign_alg=sigalg,
156+
**kwargs,
157+
)
122158

123159
_req_str = str(request)
124-
125160
logger.info("AuthNReq: %s", _req_str)
126161

127-
try:
128-
args = {'sigalg': kwargs["sigalg"]}
129-
except KeyError:
130-
args = {}
131-
132-
http_info = self.apply_binding(binding, _req_str, destination,
133-
relay_state, sign=sign, **args)
162+
http_info = self.apply_binding(
163+
binding,
164+
_req_str,
165+
destination,
166+
relay_state,
167+
sign=sign_redirect,
168+
sigalg=sigalg,
169+
)
134170

135171
return reqid, binding, http_info
136172
else:
137-
raise SignOnError(
138-
"No supported bindings available for authentication")
173+
raise SignOnError("No supported bindings available for authentication")
139174

140175
def global_logout(self, name_id, reason="", expire=None, sign=None,
141176
sign_alg=None, digest_alg=None):
@@ -194,14 +229,13 @@ def do_logout(self, name_id, entity_ids, reason, expire, sign=None,
194229
for entity_id in entity_ids:
195230
logger.debug("Logout from '%s'", entity_id)
196231
# for all where I can use the SOAP binding, do those first
197-
for binding in [BINDING_SOAP, BINDING_HTTP_POST,
198-
BINDING_HTTP_REDIRECT]:
232+
for binding in [BINDING_SOAP, BINDING_HTTP_POST, BINDING_HTTP_REDIRECT]:
199233
if expected_binding and binding != expected_binding:
200234
continue
201235
try:
202-
srvs = self.metadata.single_logout_service(entity_id,
203-
binding,
204-
"idpsso")
236+
srvs = self.metadata.single_logout_service(
237+
entity_id, binding, "idpsso"
238+
)
205239
except:
206240
srvs = None
207241

@@ -212,63 +246,66 @@ def do_logout(self, name_id, entity_ids, reason, expire, sign=None,
212246
destination = next(locations(srvs), None)
213247
logger.info("destination to provider: %s", destination)
214248
try:
215-
session_info = self.users.get_info_from(name_id,
216-
entity_id,
217-
False)
249+
session_info = self.users.get_info_from(
250+
name_id, entity_id, False
251+
)
218252
session_indexes = [session_info['session_index']]
219253
except KeyError:
220254
session_indexes = None
221255
req_id, request = self.create_logout_request(
222256
destination, entity_id, name_id=name_id, reason=reason,
223257
expire=expire, session_indexes=session_indexes)
224258

225-
# to_sign = []
226-
if binding.startswith("http://"):
227-
sign = True
228-
229-
if sign is None:
230-
sign = self.logout_requests_signed
259+
sign = sign if sign is not None else self.logout_requests_signed
260+
def_sig = DefaultSignature()
261+
sign_alg = def_sig.get_sign_alg() if sign_alg is None else sign_alg
262+
digest_alg = (
263+
def_sig.get_digest_alg()
264+
if digest_alg is None
265+
else digest_alg
266+
)
231267

232-
sigalg = None
233268
if sign:
234269
if binding == BINDING_HTTP_REDIRECT:
235-
sigalg = kwargs.get(
236-
"sigalg", ds.DefaultSignature().get_sign_alg())
237-
# key = kwargs.get("key", self.signkey)
238270
srequest = str(request)
239271
else:
240-
srequest = self.sign(request, sign_alg=sign_alg,
241-
digest_alg=digest_alg)
272+
srequest = self.sign(
273+
request, sign_alg=sign_alg, digest_alg=digest_alg
274+
)
242275
else:
243276
srequest = str(request)
244277

245278
relay_state = self._relay_state(req_id)
246279

247-
http_info = self.apply_binding(binding, srequest, destination,
248-
relay_state, sign=sign, sigalg=sigalg)
280+
http_info = self.apply_binding(
281+
binding,
282+
srequest,
283+
destination,
284+
relay_state,
285+
sign=sign,
286+
sigalg=sign_alg,
287+
)
249288

250289
if binding == BINDING_SOAP:
251290
response = self.send(**http_info)
252-
253291
if response and response.status_code == 200:
254292
not_done.remove(entity_id)
255293
response = response.text
256294
logger.info("Response: %s", response)
257-
res = self.parse_logout_request_response(response,
258-
binding)
295+
res = self.parse_logout_request_response(response, binding)
259296
responses[entity_id] = res
260297
else:
261298
logger.info("NOT OK response from %s", destination)
262-
263299
else:
264-
self.state[req_id] = {"entity_id": entity_id,
265-
"operation": "SLO",
266-
"entity_ids": entity_ids,
267-
"name_id": code(name_id),
268-
"reason": reason,
269-
"not_on_or_after": expire,
270-
"sign": sign}
271-
300+
self.state[req_id] = {
301+
"entity_id": entity_id,
302+
"operation": "SLO",
303+
"entity_ids": entity_ids,
304+
"name_id": code(name_id),
305+
"reason": reason,
306+
"not_on_or_after": expire,
307+
"sign": sign,
308+
}
272309
responses[entity_id] = (binding, http_info)
273310
not_done.remove(entity_id)
274311

@@ -419,11 +456,22 @@ def do_authn_query(self, entity_id,
419456

420457
return None
421458

422-
def do_attribute_query(self, entityid, subject_id,
423-
attribute=None, sp_name_qualifier=None,
424-
name_qualifier=None, nameid_format=None,
425-
real_id=None, consent=None, extensions=None,
426-
sign=False, binding=BINDING_SOAP, nsprefix=None):
459+
def do_attribute_query(
460+
self,
461+
entityid,
462+
subject_id,
463+
attribute=None,
464+
sp_name_qualifier=None,
465+
name_qualifier=None,
466+
nameid_format=None,
467+
real_id=None,
468+
consent=None,
469+
extensions=None,
470+
sign=False,
471+
binding=BINDING_SOAP,
472+
nsprefix=None,
473+
sign_alg=None,
474+
):
427475
""" Does a attribute request to an attribute authority, this is
428476
by default done over SOAP.
429477
@@ -482,13 +530,20 @@ def do_attribute_query(self, entityid, subject_id,
482530
"subject_id": subject_id,
483531
"sign": sign}
484532
relay_state = self._relay_state(query.id)
485-
return self.apply_binding(binding, "%s" % query, destination,
486-
relay_state, sign=sign)
533+
return self.apply_binding(
534+
binding,
535+
str(query),
536+
destination,
537+
relay_state,
538+
sign=sign,
539+
sigalg=sign_alg,
540+
)
487541
else:
488542
raise SAMLError("Unsupported binding")
489543

490-
def handle_logout_request(self, request, name_id, binding, sign=None,
491-
sign_alg=None, relay_state=""):
544+
def handle_logout_request(
545+
self, request, name_id, binding, sign=None, sign_alg=None, relay_state=""
546+
):
492547
"""
493548
Deal with a LogoutRequest
494549
@@ -531,16 +586,22 @@ def handle_logout_request(self, request, name_id, binding, sign=None,
531586
elif binding in [BINDING_HTTP_POST, BINDING_HTTP_REDIRECT]:
532587
response_bindings = [BINDING_HTTP_POST, BINDING_HTTP_REDIRECT]
533588
else:
534-
response_bindings = self.config.preferred_binding[
535-
"single_logout_service"]
589+
response_bindings = self.config.preferred_binding["single_logout_service"]
536590

537591
if sign is None:
538592
sign = self.logout_responses_signed
539593

540-
response = self.create_logout_response(_req.message, response_bindings,
541-
status, sign, sign_alg=sign_alg)
594+
response = self.create_logout_response(
595+
_req.message, response_bindings, status, sign, sign_alg=sign_alg
596+
)
542597
rinfo = self.response_args(_req.message, response_bindings)
543598

544-
return self.apply_binding(rinfo["binding"], response,
545-
rinfo["destination"], relay_state,
546-
response=True, sign=sign)
599+
return self.apply_binding(
600+
rinfo["binding"],
601+
response,
602+
rinfo["destination"],
603+
relay_state,
604+
response=True,
605+
sign=sign,
606+
sigalg=sign_alg,
607+
)

0 commit comments

Comments
 (0)