def url_replace_param(url, name, value):
"""
Replace a GET parameter in an URL
"""
url_components = urlparse(force_str(url))
params = parse_qs(url_components.query)
if value is None:
del params[name]
else:
params[name] = value
return mark_safe(urlunparse([
url_components.scheme,
url_components.netloc,
url_components.path,
url_components.params,
urlencode(params, doseq=True),
url_components.fragment,
]))
python类parse_qs()的实例源码
def decode_cursor(self, request):
"""
Given a request with a cursor, return a `Cursor` instance.
"""
# Determine if we have a cursor, and if so then decode it.
encoded = request.query_params.get(self.cursor_query_param)
if encoded is None:
return None
try:
querystring = b64decode(encoded.encode('ascii')).decode('ascii')
tokens = urlparse.parse_qs(querystring, keep_blank_values=True)
offset = tokens.get('o', ['0'])[0]
offset = _positive_int(offset, cutoff=self.offset_cutoff)
reverse = tokens.get('r', ['0'])[0]
reverse = bool(int(reverse))
position = tokens.get('p', [None])[0]
except (TypeError, ValueError):
raise NotFound(self.invalid_cursor_message)
return Cursor(offset=offset, reverse=reverse, position=position)
def test_login_evil_redirect(self):
"""
Make sure that if we give an URL other than our own host as the next
parameter, it is replaced with the default LOGIN_REDIRECT_URL.
"""
# monkey patch SAML configuration
settings.SAML_CONFIG = conf.create_conf(
sp_host='sp.example.com',
idp_hosts=['idp.example.com'],
metadata_file='remote_metadata_one_idp.xml',
)
response = self.client.get(reverse('saml2_login') + '?next=http://evil.com')
url = urlparse(response['Location'])
params = parse_qs(url.query)
self.assertEqual(params['RelayState'], [settings.LOGIN_REDIRECT_URL, ])
def test_login_evil_redirect(self):
"""
Make sure that if we give an URL other than our own host as the next
parameter, it is replaced with the default LOGIN_REDIRECT_URL.
"""
# monkey patch SAML configuration
settings.SAML_CONFIG = conf.create_conf(
sp_host='sp.example.com',
idp_hosts=['idp.example.com'],
metadata_file='remote_metadata_one_idp.xml',
)
response = self.client.get(reverse('saml2_login') + '?next=http://evil.com')
url = urlparse(response['Location'])
params = parse_qs(url.query)
self.assertEquals(params['RelayState'], [settings.LOGIN_REDIRECT_URL, ])
def replace_query_param(url, key, val):
"""
Given a URL and a key/val pair, set or replace an item in the query
parameters of the URL, and return the new URL.
"""
(scheme, netloc, path, query, fragment) = urlparse.urlsplit(url)
query_dict = urlparse.parse_qs(query, keep_blank_values=True)
query_dict[key] = [val]
query = urlparse.urlencode(sorted(list(query_dict.items())), doseq=True)
return urlparse.urlunsplit((scheme, netloc, path, query, fragment))
def remove_query_param(url, key):
"""
Given a URL and a key/val pair, remove an item in the query
parameters of the URL, and return the new URL.
"""
(scheme, netloc, path, query, fragment) = urlparse.urlsplit(url)
query_dict = urlparse.parse_qs(query, keep_blank_values=True)
query_dict.pop(key, None)
query = urlparse.urlencode(sorted(list(query_dict.items())), doseq=True)
return urlparse.urlunsplit((scheme, netloc, path, query, fragment))
def decode_cursor(self, request):
"""
Given a request with a cursor, return a `Cursor` instance.
Differs from the standard CursorPagination to handle a tuple in the
position field.
"""
# Determine if we have a cursor, and if so then decode it.
encoded = request.query_params.get(self.cursor_query_param)
if encoded is None:
return None
try:
querystring = b64decode(encoded.encode('ascii')).decode('ascii')
tokens = urlparse.parse_qs(querystring, keep_blank_values=True)
offset = tokens.get('o', ['0'])[0]
# This was hard-coded until Django REST Framework 3.4.0.
try:
offset_cutoff = self.offset_cutoff
except AttributeError:
offset_cutoff = 1000
offset = _positive_int(offset, cutoff=offset_cutoff)
reverse = tokens.get('r', ['0'])[0]
reverse = bool(int(reverse))
# The difference. Don't get just the 0th entry: get all entries.
position = tokens.get('p', None)
except (TypeError, ValueError):
raise NotFound(self.invalid_cursor_message)
return Cursor(offset=offset, reverse=reverse, position=position)
def replace_query_param(url, key, val):
"""
Given a URL and a key/val pair, set or replace an item in the query
parameters of the URL, and return the new URL.
"""
(scheme, netloc, path, query, fragment) = urlparse.urlsplit(url)
query_dict = urlparse.parse_qs(query)
query_dict[key] = [val]
query = urlparse.urlencode(sorted(list(query_dict.items())), doseq=True)
return urlparse.urlunsplit((scheme, netloc, path, query, fragment))
def remove_query_param(url, key):
"""
Given a URL and a key/val pair, remove an item in the query
parameters of the URL, and return the new URL.
"""
(scheme, netloc, path, query, fragment) = urlparse.urlsplit(url)
query_dict = urlparse.parse_qs(query)
query_dict.pop(key, None)
query = urlparse.urlencode(sorted(list(query_dict.items())), doseq=True)
return urlparse.urlunsplit((scheme, netloc, path, query, fragment))
def decode_cursor(self, request):
"""
Given a request with a cursor, return a `Cursor` instance.
"""
# Determine if we have a cursor, and if so then decode it.
encoded = request.query_params.get(self.cursor_query_param)
if encoded is None:
return None
# The offset in the cursor is used in situations where we have a
# nearly-unique index. (Eg millisecond precision creation timestamps)
# We guard against malicious users attempting to cause expensive database
# queries, by having a hard cap on the maximum possible size of the offset.
OFFSET_CUTOFF = 1000
try:
querystring = b64decode(encoded.encode('ascii')).decode('ascii')
tokens = urlparse.parse_qs(querystring, keep_blank_values=True)
offset = tokens.get('o', ['0'])[0]
offset = _positive_int(offset, cutoff=OFFSET_CUTOFF)
reverse = tokens.get('r', ['0'])[0]
reverse = bool(int(reverse))
position = tokens.get('p', [None])[0]
except (TypeError, ValueError):
raise NotFound(self.invalid_cursor_message)
return Cursor(offset=offset, reverse=reverse, position=position)
def test_login_several_idps(self):
settings.SAML_CONFIG = conf.create_conf(
sp_host='sp.example.com',
idp_hosts=['idp1.example.com',
'idp2.example.com',
'idp3.example.com'],
metadata_file='remote_metadata_three_idps.xml',
)
response = self.client.get(reverse('saml2_login'))
# a WAYF page should be displayed
self.assertContains(response, 'Where are you from?', status_code=200)
for i in range(1, 4):
link = '/login/?idp=https://idp%d.example.com/simplesaml/saml2/idp/metadata.php&next=/'
self.assertContains(response, link % i)
# click on the second idp
response = self.client.get(reverse('saml2_login'), {
'idp': 'https://idp2.example.com/simplesaml/saml2/idp/metadata.php',
'next': '/',
})
self.assertEqual(response.status_code, 302)
location = response['Location']
url = urlparse(location)
self.assertEqual(url.hostname, 'idp2.example.com')
self.assertEqual(url.path, '/simplesaml/saml2/idp/SSOService.php')
params = parse_qs(url.query)
self.assertIn('SAMLRequest', params)
self.assertIn('RelayState', params)
saml_request = params['SAMLRequest'][0]
if PY_VERSION < (3,):
expected_request = """<?xml version='1.0' encoding='UTF-8'?>
<samlp:AuthnRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" AssertionConsumerServiceURL="http://sp.example.com/saml2/acs/" Destination="https://idp2.example.com/simplesaml/saml2/idp/SSOService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://sp.example.com/saml2/metadata/</saml:Issuer><samlp:NameIDPolicy AllowCreate="false" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" /></samlp:AuthnRequest>"""
else:
expected_request = """<samlp:AuthnRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" AssertionConsumerServiceURL="http://sp.example.com/saml2/acs/" Destination="https://idp2.example.com/simplesaml/saml2/idp/SSOService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://sp.example.com/saml2/metadata/</saml:Issuer><samlp:NameIDPolicy AllowCreate="false" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" /></samlp:AuthnRequest>"""
self.assertSAMLRequestsEquals(decode_base64_and_inflate(saml_request).decode('utf-8'),
expected_request)
def test_logout(self):
settings.SAML_CONFIG = conf.create_conf(
sp_host='sp.example.com',
idp_hosts=['idp.example.com'],
metadata_file='remote_metadata_one_idp.xml',
)
self.do_login()
response = self.client.get(reverse('saml2_logout'))
self.assertEqual(response.status_code, 302)
location = response['Location']
url = urlparse(location)
self.assertEqual(url.hostname, 'idp.example.com')
self.assertEqual(url.path,
'/simplesaml/saml2/idp/SingleLogoutService.php')
params = parse_qs(url.query)
self.assertIn('SAMLRequest', params)
saml_request = params['SAMLRequest'][0]
if PY_VERSION < (3,):
expected_request = """<?xml version='1.0' encoding='UTF-8'?>
<samlp:LogoutRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" Destination="https://idp.example.com/simplesaml/saml2/idp/SingleLogoutService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" Reason="" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://sp.example.com/saml2/metadata/</saml:Issuer><saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient" SPNameQualifier="http://sp.example.com/saml2/metadata/">58bcc81ea14700f66aeb707a0eff1360</saml:NameID><samlp:SessionIndex>a0123456789abcdef0123456789abcdef</samlp:SessionIndex></samlp:LogoutRequest>"""
else:
expected_request = """<samlp:LogoutRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" Destination="https://idp.example.com/simplesaml/saml2/idp/SingleLogoutService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" Reason="" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://sp.example.com/saml2/metadata/</saml:Issuer><saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient" SPNameQualifier="http://sp.example.com/saml2/metadata/">58bcc81ea14700f66aeb707a0eff1360</saml:NameID><samlp:SessionIndex>a0123456789abcdef0123456789abcdef</samlp:SessionIndex></samlp:LogoutRequest>"""
self.assertSAMLRequestsEquals(decode_base64_and_inflate(saml_request).decode('utf-8'),
expected_request)
def replace_query_param(url, key, val):
"""
Given a URL and a key/val pair, set or replace an item in the query
parameters of the URL, and return the new URL.
"""
(scheme, netloc, path, query, fragment) = urlparse.urlsplit(url)
query_dict = urlparse.parse_qs(query)
query_dict[key] = [val]
query = urlparse.urlencode(sorted(list(query_dict.items())), doseq=True)
return urlparse.urlunsplit((scheme, netloc, path, query, fragment))
def remove_query_param(url, key):
"""
Given a URL and a key/val pair, remove an item in the query
parameters of the URL, and return the new URL.
"""
(scheme, netloc, path, query, fragment) = urlparse.urlsplit(url)
query_dict = urlparse.parse_qs(query)
query_dict.pop(key, None)
query = urlparse.urlencode(sorted(list(query_dict.items())), doseq=True)
return urlparse.urlunsplit((scheme, netloc, path, query, fragment))
def test_logout(self):
settings.SAML_CONFIG = conf.create_conf(
sp_host='sp.example.com',
idp_hosts=['idp.example.com'],
metadata_file='remote_metadata_one_idp.xml',
)
self.do_login()
response = self.client.get(reverse('saml2_logout'))
self.assertEquals(response.status_code, 302)
location = response['Location']
url = urlparse(location)
self.assertEquals(url.hostname, 'idp.example.com')
self.assertEquals(url.path,
'/simplesaml/saml2/idp/SingleLogoutService.php')
params = parse_qs(url.query)
self.assert_('SAMLRequest' in params)
saml_request = params['SAMLRequest'][0]
if PY_VERSION < (2, 7):
expected_request = """<?xml version='1.0' encoding='UTF-8'?>
<samlp:LogoutRequest Destination="https://idp.example.com/simplesaml/saml2/idp/SingleLogoutService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" Reason="" Version="2.0" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">http://sp.example.com/saml2/metadata/</saml:Issuer><saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient" SPNameQualifier="http://sp.example.com/saml2/metadata/" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">58bcc81ea14700f66aeb707a0eff1360</saml:NameID></samlp:LogoutRequest>"""
elif PY_VERSION < (3,):
expected_request = """<?xml version='1.0' encoding='UTF-8'?>
<samlp:LogoutRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" Destination="https://idp.example.com/simplesaml/saml2/idp/SingleLogoutService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" Reason="" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://sp.example.com/saml2/metadata/</saml:Issuer><saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient" SPNameQualifier="http://sp.example.com/saml2/metadata/">58bcc81ea14700f66aeb707a0eff1360</saml:NameID><samlp:SessionIndex>a0123456789abcdef0123456789abcdef</samlp:SessionIndex></samlp:LogoutRequest>"""
else:
expected_request = """<samlp:LogoutRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" Destination="https://idp.example.com/simplesaml/saml2/idp/SingleLogoutService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" Reason="" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://sp.example.com/saml2/metadata/</saml:Issuer><saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient" SPNameQualifier="http://sp.example.com/saml2/metadata/">58bcc81ea14700f66aeb707a0eff1360</saml:NameID><samlp:SessionIndex>a0123456789abcdef0123456789abcdef</samlp:SessionIndex></samlp:LogoutRequest>"""
self.assertSAMLRequestsEquals(decode_base64_and_inflate(saml_request).decode('utf-8'),
expected_request)
def test_login_one_idp(self):
# monkey patch SAML configuration
settings.SAML_CONFIG = conf.create_conf(
sp_host='sp.example.com',
idp_hosts=['idp.example.com'],
metadata_file='remote_metadata_one_idp.xml',
)
response = self.client.get(reverse('saml2_login'))
self.assertEqual(response.status_code, 302)
location = response['Location']
url = urlparse(location)
self.assertEqual(url.hostname, 'idp.example.com')
self.assertEqual(url.path, '/simplesaml/saml2/idp/SSOService.php')
params = parse_qs(url.query)
self.assertIn('SAMLRequest', params)
self.assertIn('RelayState', params)
saml_request = params['SAMLRequest'][0]
if PY_VERSION < (3,):
expected_request = """<?xml version='1.0' encoding='UTF-8'?>
<samlp:AuthnRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" AssertionConsumerServiceURL="http://sp.example.com/saml2/acs/" Destination="https://idp.example.com/simplesaml/saml2/idp/SSOService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://sp.example.com/saml2/metadata/</saml:Issuer><samlp:NameIDPolicy AllowCreate="false" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" /></samlp:AuthnRequest>"""
else:
expected_request = """<samlp:AuthnRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" AssertionConsumerServiceURL="http://sp.example.com/saml2/acs/" Destination="https://idp.example.com/simplesaml/saml2/idp/SSOService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://sp.example.com/saml2/metadata/</saml:Issuer><samlp:NameIDPolicy AllowCreate="false" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" /></samlp:AuthnRequest>"""
self.assertSAMLRequestsEquals(
decode_base64_and_inflate(saml_request).decode('utf-8'),
expected_request)
# if we set a next arg in the login view, it is preserverd
# in the RelayState argument
next = '/another-view/'
response = self.client.get(reverse('saml2_login'), {'next': next})
self.assertEqual(response.status_code, 302)
location = response['Location']
url = urlparse(location)
self.assertEqual(url.hostname, 'idp.example.com')
self.assertEqual(url.path, '/simplesaml/saml2/idp/SSOService.php')
params = parse_qs(url.query)
self.assertIn('SAMLRequest', params)
self.assertIn('RelayState', params)
self.assertEqual(params['RelayState'][0], next)
def test_logout_service_local(self):
settings.SAML_CONFIG = conf.create_conf(
sp_host='sp.example.com',
idp_hosts=['idp.example.com'],
metadata_file='remote_metadata_one_idp.xml',
)
self.do_login()
response = self.client.get(reverse('saml2_logout'))
self.assertEqual(response.status_code, 302)
location = response['Location']
url = urlparse(location)
self.assertEqual(url.hostname, 'idp.example.com')
self.assertEqual(url.path,
'/simplesaml/saml2/idp/SingleLogoutService.php')
params = parse_qs(url.query)
self.assertIn('SAMLRequest', params)
saml_request = params['SAMLRequest'][0]
if PY_VERSION < (3,):
expected_request = """<?xml version='1.0' encoding='UTF-8'?>
<samlp:LogoutRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" Destination="https://idp.example.com/simplesaml/saml2/idp/SingleLogoutService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" Reason="" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://sp.example.com/saml2/metadata/</saml:Issuer><saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient" SPNameQualifier="http://sp.example.com/saml2/metadata/">58bcc81ea14700f66aeb707a0eff1360</saml:NameID><samlp:SessionIndex>a0123456789abcdef0123456789abcdef</samlp:SessionIndex></samlp:LogoutRequest>"""
else:
expected_request = """<samlp:LogoutRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" Destination="https://idp.example.com/simplesaml/saml2/idp/SingleLogoutService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" Reason="" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://sp.example.com/saml2/metadata/</saml:Issuer><saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient" SPNameQualifier="http://sp.example.com/saml2/metadata/">58bcc81ea14700f66aeb707a0eff1360</saml:NameID><samlp:SessionIndex>a0123456789abcdef0123456789abcdef</samlp:SessionIndex></samlp:LogoutRequest>"""
self.assertSAMLRequestsEquals(decode_base64_and_inflate(saml_request).decode('utf-8'),
expected_request)
# now simulate a logout response sent by the idp
request_id = re.findall(r' ID="(.*?)" ', expected_request)[0]
instant = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ')
saml_response = """<?xml version='1.0' encoding='UTF-8'?>
<samlp:LogoutResponse xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" Destination="http://sp.example.com/saml2/ls/" ID="a140848e7ce2bce834d7264ecdde0151" InResponseTo="%s" IssueInstant="%s" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">https://idp.example.com/simplesaml/saml2/idp/metadata.php</saml:Issuer><samlp:Status><samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success" /></samlp:Status></samlp:LogoutResponse>""" % (
request_id, instant)
response = self.client.get(reverse('saml2_ls'), {
'SAMLResponse': deflate_and_base64_encode(saml_response),
})
self.assertContains(response, "Logged out", status_code=200)
self.assertListEqual(list(self.client.session.keys()), [])
def test_login_one_idp(self):
# monkey patch SAML configuration
settings.SAML_CONFIG = conf.create_conf(
sp_host='sp.example.com',
idp_hosts=['idp.example.com'],
metadata_file='remote_metadata_one_idp.xml',
)
response = self.client.get(reverse('saml2_login'))
self.assertEquals(response.status_code, 302)
location = response['Location']
url = urlparse(location)
self.assertEquals(url.hostname, 'idp.example.com')
self.assertEquals(url.path, '/simplesaml/saml2/idp/SSOService.php')
params = parse_qs(url.query)
self.assert_('SAMLRequest' in params)
self.assert_('RelayState' in params)
saml_request = params['SAMLRequest'][0]
if PY_VERSION < (2, 7):
expected_request = """<?xml version='1.0' encoding='UTF-8'?>
<samlp:AuthnRequest AssertionConsumerServiceURL="http://sp.example.com/saml2/acs/" Destination="https://idp.example.com/simplesaml/saml2/idp/SSOService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Version="2.0" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">http://sp.example.com/saml2/metadata/</saml:Issuer><samlp:NameIDPolicy AllowCreate="false" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" /></samlp:AuthnRequest>"""
elif PY_VERSION < (3,):
expected_request = """<?xml version='1.0' encoding='UTF-8'?>
<samlp:AuthnRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" AssertionConsumerServiceURL="http://sp.example.com/saml2/acs/" Destination="https://idp.example.com/simplesaml/saml2/idp/SSOService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://sp.example.com/saml2/metadata/</saml:Issuer><samlp:NameIDPolicy AllowCreate="false" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" /></samlp:AuthnRequest>"""
else:
expected_request = """<samlp:AuthnRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" AssertionConsumerServiceURL="http://sp.example.com/saml2/acs/" Destination="https://idp.example.com/simplesaml/saml2/idp/SSOService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://sp.example.com/saml2/metadata/</saml:Issuer><samlp:NameIDPolicy AllowCreate="false" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" /></samlp:AuthnRequest>"""
self.assertSAMLRequestsEquals(
decode_base64_and_inflate(saml_request).decode('utf-8'),
expected_request)
# if we set a next arg in the login view, it is preserverd
# in the RelayState argument
next = '/another-view/'
response = self.client.get(reverse('saml2_login'), {'next': next})
self.assertEquals(response.status_code, 302)
location = response['Location']
url = urlparse(location)
self.assertEquals(url.hostname, 'idp.example.com')
self.assertEquals(url.path, '/simplesaml/saml2/idp/SSOService.php')
params = parse_qs(url.query)
self.assert_('SAMLRequest' in params)
self.assert_('RelayState' in params)
self.assertEquals(params['RelayState'][0], next)
def test_login_several_idps(self):
settings.SAML_CONFIG = conf.create_conf(
sp_host='sp.example.com',
idp_hosts=['idp1.example.com',
'idp2.example.com',
'idp3.example.com'],
metadata_file='remote_metadata_three_idps.xml',
)
response = self.client.get(reverse('saml2_login'))
# a WAYF page should be displayed
self.assertContains(response, 'Where are you from?', status_code=200)
for i in range(1, 4):
link = '/login/?idp=https://idp%d.example.com/simplesaml/saml2/idp/metadata.php&next=/'
self.assertContains(response, link % i)
# click on the second idp
response = self.client.get(reverse('saml2_login'), {
'idp': 'https://idp2.example.com/simplesaml/saml2/idp/metadata.php',
'next': '/',
})
self.assertEquals(response.status_code, 302)
location = response['Location']
url = urlparse(location)
self.assertEquals(url.hostname, 'idp2.example.com')
self.assertEquals(url.path, '/simplesaml/saml2/idp/SSOService.php')
params = parse_qs(url.query)
self.assert_('SAMLRequest' in params)
self.assert_('RelayState' in params)
saml_request = params['SAMLRequest'][0]
if PY_VERSION < (2, 7):
expected_request = """<?xml version='1.0' encoding='UTF-8'?>
<samlp:AuthnRequest AssertionConsumerServiceURL="http://sp.example.com/saml2/acs/" Destination="https://idp2.example.com/simplesaml/saml2/idp/SSOService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Version="2.0" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">http://sp.example.com/saml2/metadata/</saml:Issuer><samlp:NameIDPolicy AllowCreate="false" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" /></samlp:AuthnRequest>"""
elif PY_VERSION < (3,):
expected_request = """<?xml version='1.0' encoding='UTF-8'?>
<samlp:AuthnRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" AssertionConsumerServiceURL="http://sp.example.com/saml2/acs/" Destination="https://idp2.example.com/simplesaml/saml2/idp/SSOService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://sp.example.com/saml2/metadata/</saml:Issuer><samlp:NameIDPolicy AllowCreate="false" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" /></samlp:AuthnRequest>"""
else:
expected_request = """<samlp:AuthnRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" AssertionConsumerServiceURL="http://sp.example.com/saml2/acs/" Destination="https://idp2.example.com/simplesaml/saml2/idp/SSOService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://sp.example.com/saml2/metadata/</saml:Issuer><samlp:NameIDPolicy AllowCreate="false" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" /></samlp:AuthnRequest>"""
self.assertSAMLRequestsEquals(decode_base64_and_inflate(saml_request).decode('utf-8'),
expected_request)
def test_logout_service_local(self):
settings.SAML_CONFIG = conf.create_conf(
sp_host='sp.example.com',
idp_hosts=['idp.example.com'],
metadata_file='remote_metadata_one_idp.xml',
)
self.do_login()
response = self.client.get(reverse('saml2_logout'))
self.assertEquals(response.status_code, 302)
location = response['Location']
url = urlparse(location)
self.assertEquals(url.hostname, 'idp.example.com')
self.assertEquals(url.path,
'/simplesaml/saml2/idp/SingleLogoutService.php')
params = parse_qs(url.query)
self.assert_('SAMLRequest' in params)
saml_request = params['SAMLRequest'][0]
if PY_VERSION < (2, 7):
expected_request = """<?xml version='1.0' encoing='UTF-8'?>
<samlp:LogoutRequest Destination="https://idp.example.com/simplesaml/saml2/idp/SingleLogoutService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" Reason="" Version="2.0" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">http://sp.example.com/saml2/metadata/</saml:Issuer><saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient" SPNameQualifier="http://sp.example.com/saml2/metadata/" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">58bcc81ea14700f66aeb707a0eff1360</saml:NameID></samlp:LogoutRequest>"""
elif PY_VERSION < (3,):
expected_request = """<?xml version='1.0' encoding='UTF-8'?>
<samlp:LogoutRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" Destination="https://idp.example.com/simplesaml/saml2/idp/SingleLogoutService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" Reason="" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://sp.example.com/saml2/metadata/</saml:Issuer><saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient" SPNameQualifier="http://sp.example.com/saml2/metadata/">58bcc81ea14700f66aeb707a0eff1360</saml:NameID><samlp:SessionIndex>a0123456789abcdef0123456789abcdef</samlp:SessionIndex></samlp:LogoutRequest>"""
else:
expected_request = """<samlp:LogoutRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" Destination="https://idp.example.com/simplesaml/saml2/idp/SingleLogoutService.php" ID="XXXXXXXXXXXXXXXXXXXXXX" IssueInstant="2010-01-01T00:00:00Z" Reason="" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://sp.example.com/saml2/metadata/</saml:Issuer><saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient" SPNameQualifier="http://sp.example.com/saml2/metadata/">58bcc81ea14700f66aeb707a0eff1360</saml:NameID><samlp:SessionIndex>a0123456789abcdef0123456789abcdef</samlp:SessionIndex></samlp:LogoutRequest>"""
self.assertSAMLRequestsEquals(decode_base64_and_inflate(saml_request).decode('utf-8'),
expected_request)
# now simulate a logout response sent by the idp
request_id = re.findall(r' ID="(.*?)" ', xml)[0]
instant = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ')
saml_response = """<?xml version='1.0' encoding='UTF-8'?>
<samlp:LogoutResponse xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" Destination="http://sp.example.com/saml2/ls/" ID="a140848e7ce2bce834d7264ecdde0151" InResponseTo="%s" IssueInstant="%s" Version="2.0"><saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">https://idp.example.com/simplesaml/saml2/idp/metadata.php</saml:Issuer><samlp:Status><samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success" /></samlp:Status></samlp:LogoutResponse>""" % (
request_id, instant)
response = self.client.get(reverse('saml2_ls'), {
'SAMLResponse': deflate_and_base64_encode(saml_response),
})
self.assertContains(response, "Logged out", status_code=200)
self.assertEquals(self.client.session.keys(), [])