Skip to content

Commit d51f570

Browse files
committed
Fix encode ADFS issue. Merge branch 'm6a-UdS-feature/bugfix-adfs'
2 parents 957b587 + f527bc1 commit d51f570

6 files changed

Lines changed: 30 additions & 10 deletions

File tree

demo-bottle/index.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,15 @@ def init_saml_auth(req):
3030
def prepare_bottle_request(req):
3131
url_data = urlparse(req.url)
3232
return {
33+
'https': 'on' if req.urlparts.scheme == 'https' else 'off',
3334
'http_host': req.get_header('host'),
3435
'server_port': url_data.port,
3536
'script_name': req.fullpath,
3637
'get_data': req.query,
3738
'post_data': req.forms,
3839
'query_string': req.query_string,
39-
'https': 'on' if req.urlparts.scheme == 'https' else 'off'
40+
# Uncomment if using ADFS as IdP, https://github.com/onelogin/python-saml/pull/144
41+
# 'lowercase_urlencoding': True
4042
}
4143

4244

demo-django/demo/views.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ def prepare_django_request(request):
2424
'server_port': request.META['SERVER_PORT'],
2525
'get_data': request.GET.copy(),
2626
'post_data': request.POST.copy(),
27+
# Uncomment if using ADFS as IdP, https://github.com/onelogin/python-saml/pull/144
28+
# 'lowercase_urlencoding': True,
2729
'query_string': request.META['QUERY_STRING']
2830
}
2931
return result

demo-flask/index.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ def prepare_flask_request(request):
2929
'script_name': request.path,
3030
'get_data': request.args.copy(),
3131
'post_data': request.form.copy(),
32+
# Uncomment if using ADFS as IdP, https://github.com/onelogin/python-saml/pull/144
33+
# 'lowercase_urlencoding': True,
3234
'query_string': request.query_string
3335
}
3436

src/onelogin/saml2/logout_request.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,7 @@ def is_valid(self, request_data):
261261
:rtype: boolean
262262
"""
263263
self.__error = None
264+
lowercase_urlencoding = False
264265
try:
265266
dom = fromstring(self.__logout_request)
266267

@@ -272,6 +273,9 @@ def is_valid(self, request_data):
272273
else:
273274
get_data = {}
274275

276+
if 'lowercase_urlencoding' in request_data.keys():
277+
lowercase_urlencoding = request_data['lowercase_urlencoding']
278+
275279
if self.__settings.is_strict():
276280
res = OneLogin_Saml2_Utils.validate_xml(dom, 'saml-schema-protocol-2.0.xsd', self.__settings.is_debug_active())
277281
if not isinstance(res, Document):
@@ -316,10 +320,10 @@ def is_valid(self, request_data):
316320
else:
317321
sign_alg = get_data['SigAlg']
318322

319-
signed_query = 'SAMLRequest=%s' % OneLogin_Saml2_Utils.get_encoded_parameter(get_data, 'SAMLRequest')
323+
signed_query = 'SAMLRequest=%s' % OneLogin_Saml2_Utils.get_encoded_parameter(get_data, 'SAMLRequest', lowercase_urlencoding=lowercase_urlencoding)
320324
if 'RelayState' in get_data:
321-
signed_query = '%s&RelayState=%s' % (signed_query, OneLogin_Saml2_Utils.get_encoded_parameter(get_data, 'RelayState'))
322-
signed_query = '%s&SigAlg=%s' % (signed_query, OneLogin_Saml2_Utils.get_encoded_parameter(get_data, 'SigAlg', OneLogin_Saml2_Constants.RSA_SHA1))
325+
signed_query = '%s&RelayState=%s' % (signed_query, OneLogin_Saml2_Utils.get_encoded_parameter(get_data, 'RelayState', lowercase_urlencoding=lowercase_urlencoding))
326+
signed_query = '%s&SigAlg=%s' % (signed_query, OneLogin_Saml2_Utils.get_encoded_parameter(get_data, 'SigAlg', OneLogin_Saml2_Constants.RSA_SHA1, lowercase_urlencoding=lowercase_urlencoding))
323327

324328
if 'x509cert' not in idp_data or idp_data['x509cert'] is None:
325329
raise Exception('In order to validate the sign on the Logout Request, the x509cert of the IdP is required')

src/onelogin/saml2/logout_response.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,15 @@ def is_valid(self, request_data, request_id=None):
7777
:rtype: boolean
7878
"""
7979
self.__error = None
80+
lowercase_urlencoding = False
8081
try:
8182
idp_data = self.__settings.get_idp_data()
8283
idp_entity_id = idp_data['entityId']
8384
get_data = request_data['get_data']
8485

86+
if 'lowercase_urlencoding' in request_data.keys():
87+
lowercase_urlencoding = request_data['lowercase_urlencoding']
88+
8589
if self.__settings.is_strict():
8690
res = OneLogin_Saml2_Utils.validate_xml(self.document, 'saml-schema-protocol-2.0.xsd', self.__settings.is_debug_active())
8791
if not isinstance(res, Document):
@@ -119,10 +123,10 @@ def is_valid(self, request_data, request_id=None):
119123
else:
120124
sign_alg = get_data['SigAlg']
121125

122-
signed_query = 'SAMLResponse=%s' % OneLogin_Saml2_Utils.get_encoded_parameter(get_data, 'SAMLResponse')
126+
signed_query = 'SAMLResponse=%s' % OneLogin_Saml2_Utils.get_encoded_parameter(get_data, 'SAMLResponse', lowercase_urlencoding=lowercase_urlencoding)
123127
if 'RelayState' in get_data:
124-
signed_query = '%s&RelayState=%s' % (signed_query, OneLogin_Saml2_Utils.get_encoded_parameter(get_data, 'RelayState'))
125-
signed_query = '%s&SigAlg=%s' % (signed_query, OneLogin_Saml2_Utils.get_encoded_parameter(get_data, 'SigAlg', OneLogin_Saml2_Constants.RSA_SHA1))
128+
signed_query = '%s&RelayState=%s' % (signed_query, OneLogin_Saml2_Utils.get_encoded_parameter(get_data, 'RelayState', lowercase_urlencoding=lowercase_urlencoding))
129+
signed_query = '%s&SigAlg=%s' % (signed_query, OneLogin_Saml2_Utils.get_encoded_parameter(get_data, 'SigAlg', OneLogin_Saml2_Constants.RSA_SHA1, lowercase_urlencoding=lowercase_urlencoding))
126130

127131
if 'x509cert' not in idp_data or idp_data['x509cert'] is None:
128132
raise Exception('In order to validate the sign on the Logout Response, the x509cert of the IdP is required')

src/onelogin/saml2/utils.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1127,18 +1127,19 @@ def validate_binary_sign(signed_query, signature, cert=None, algorithm=OneLogin_
11271127
return False
11281128

11291129
@staticmethod
1130-
def get_encoded_parameter(get_data, name, default=None):
1130+
def get_encoded_parameter(get_data, name, default=None, lowercase_urlencoding=False):
11311131
"""Return an url encoded get parameter value
11321132
Prefer to extract the original encoded value directly from query_string since url
11331133
encoding is not canonical. The encoding used by ADFS 3.0 is not compatible with
11341134
python's quote_plus (ADFS produces lower case hex numbers and quote_plus produces
11351135
upper case hex numbers)
11361136
"""
1137+
11371138
if name not in get_data:
1138-
return quote_plus(default)
1139+
return OneLogin_Saml2_Utils.case_sensitive_urlencode(default, lowercase_urlencoding)
11391140
if 'query_string' in get_data:
11401141
return OneLogin_Saml2_Utils.extract_raw_query_parameter(get_data['query_string'], name)
1141-
return quote_plus(get_data[name])
1142+
return OneLogin_Saml2_Utils.case_sensitive_urlencode(get_data[name], lowercase_urlencoding)
11421143

11431144
@staticmethod
11441145
def extract_raw_query_parameter(query_string, parameter, default=''):
@@ -1147,3 +1148,8 @@ def extract_raw_query_parameter(query_string, parameter, default=''):
11471148
return m.group(1)
11481149
else:
11491150
return default
1151+
1152+
@staticmethod
1153+
def case_sensitive_urlencode(to_encode, lowercase=False):
1154+
encoded = quote_plus(to_encode)
1155+
return re.sub(r"%[A-F0-9]{2}", lambda m: m.group(0).lower(), encoded) if lowercase else encoded

0 commit comments

Comments
 (0)