diff --git a/cas.py b/cas.py index d28c3cc..40fb6ce 100644 --- a/cas.py +++ b/cas.py @@ -1,6 +1,5 @@ +import requests from six.moves.urllib import parse as urllib_parse -from six.moves.urllib import request as urllib_request -from six.moves.urllib.request import Request from uuid import uuid4 import datetime @@ -82,10 +81,10 @@ def get_proxy_url(self, pgt): def get_proxy_ticket(self, pgt): """Returns proxy ticket given the proxy granting ticket""" - response = urllib_request.urlopen(self.get_proxy_url(pgt)) - if response.code == 200: + response = requests.get(self.get_proxy_url(pgt)) + if response.status_code == 200: from lxml import etree - root = etree.fromstring(response.read()) + root = etree.fromstring(response.content) tickets = root.xpath( "//cas:proxyTicket", namespaces={"cas": "http://www.yale.edu/tp/cas"} @@ -98,7 +97,7 @@ def get_proxy_ticket(self, pgt): ) if len(errors) == 1: raise CASError(errors[0].attrib['code'], errors[0].text) - raise CASError("Bad http code %s" % response.code) + raise CASError("Bad http code %s" % response.status_code) class CASClientV1(CASClientBase): @@ -114,11 +113,12 @@ def verify_ticket(self, ticket): params = [('ticket', ticket), ('service', self.service_url)] url = (urllib_parse.urljoin(self.server_url, 'validate') + '?' + urllib_parse.urlencode(params)) - page = urllib_request.urlopen(url) + page = requests.get(url, stream=True) try: - verified = page.readline().strip() + page_iterator = page.iter_lines(chunk_size=8192) + verified = next(page_iterator).strip() if verified == 'yes': - return page.readline().strip(), None, None + return next(page_iterator).strip(), None, None else: return None, None, None finally: @@ -142,14 +142,16 @@ def verify_ticket(self, ticket): return self.verify_response(response) def get_verification_response(self, ticket): - params = [('ticket', ticket), ('service', self.service_url)] + params = { + 'ticket': ticket, + 'service': self.service_url + } if self.proxy_callback: - params.append(('pgtUrl', self.proxy_callback)) + params.update({'pgtUrl': self.proxy_callback}) base_url = urllib_parse.urljoin(self.server_url, self.url_suffix) - url = base_url + '?' + urllib_parse.urlencode(params) - page = urllib_request.urlopen(url) + page = requests.get(base_url, params=params) try: - return page.read() + return page.content finally: page.close() @@ -191,10 +193,11 @@ def parse_response_xml(cls, response): tree = ElementTree.fromstring(response) if tree[0].tag.endswith('authenticationSuccess'): + """ Get namespace for looking for elements by tagname """ + namespace = tree.tag[0:tree.tag.index('}')+1] + user = tree[0].find('.//' + namespace + 'user').text for element in tree[0]: - if element.tag.endswith('user'): - user = element.text - elif element.tag.endswith('proxyGrantingTicket'): + if element.tag.endswith('proxyGrantingTicket'): pgtiou = element.text elif element.tag.endswith('attributes'): attributes = cls.parse_attributes_xml_element(element) @@ -265,7 +268,7 @@ def verify_ticket(self, ticket, **kwargs): try: user = None attributes = {} - response = page.read() + response = page.content tree = ElementTree.fromstring(response) # Find the authentication status success = tree.find('.//' + SAML_1_0_PROTOCOL_NS + 'StatusCode') @@ -302,16 +305,15 @@ def fetch_saml_validation(self, ticket): 'connection': 'keep-alive', 'content-type': 'text/xml; charset=utf-8', } - params = [('TARGET', self.service_url)] + params = {'TARGET': self.service_url} saml_validate_url = urllib_parse.urljoin( self.server_url, 'samlValidate', ) - request = Request( - saml_validate_url + '?' + urllib_parse.urlencode(params), + return requests.post( + saml_validate_url, self.get_saml_assertion(ticket), - headers, - ) - return urllib_request.urlopen(request) + params=params, + headers=headers) @classmethod def get_saml_assertion(cls, ticket): diff --git a/docs/changelog.rst b/docs/changelog.rst index 15e4d17..271aafd 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -21,6 +21,12 @@ here. For additional detail, read the complete `commit history`_. * allowed to customize name of logout redirect url parameter * base cas client with SAML on CASClientV2 + +**python-cas 1.2.0** ``[2016-11-06]`` + +* Replace urllib2 calls with requests to avoid SNI issues +* In SAMLV1.1, the user username is withing NameIdentifier tags + .. _commit history: https://github.com/python-cas/python-cas/commits .. _django-cas-ng: https://github.com/mingchen/django-cas-ng diff --git a/docs/conf.py b/docs/conf.py index 3fde35a..3cba0af 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -52,9 +52,9 @@ # built documents. # # The short X.Y version. -version = '1.1' +version = '1.2' # The full version, including alpha/beta/rc tags. -release = '1.1.0' +release = '1.2.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/requirements-dev.txt b/requirements-dev.txt index c960f77..8d18e28 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -3,3 +3,4 @@ pytest-pythonpath==0.3 pytest==2.6.4 six tox==1.8.1 +requests diff --git a/requirements.txt b/requirements.txt index 469bdad..0caa1f1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ lxml>=3.4 six +requests diff --git a/setup.py b/setup.py index 7a340a1..d21c20b 100755 --- a/setup.py +++ b/setup.py @@ -24,6 +24,7 @@ ], install_requires=[ 'six', + 'requests' ], description='Python CAS client library', keywords=['cas', 'cas2', 'cas3', 'client', 'sso', 'single sign-on', 'authentication', 'auth'], @@ -33,6 +34,6 @@ packages=['.'], url='https://github.com/python-cas/python-cas', download_url ='https://github.com/python-cas/python-cas/releases', - version='1.1.0', + version='1.2.0', ) diff --git a/tests/test_cas.py b/tests/test_cas.py index 4930535..3d92aff 100644 --- a/tests/test_cas.py +++ b/tests/test_cas.py @@ -193,3 +193,10 @@ def test_cas2_jasig_attributes(client_v2): 'nombroj': ['unu', 'du', 'tri', 'kvar'], } assert attributes == expected_attributes + +SUCCESS_RESPONSE_WITH_NON_STANDARD_USER_NODE = """ +John Doesomeusersomeuser +""" +def test_cas2_non_standard_user_node(client_v2): + user, attributes, pgtiou = client_v2.verify_response(SUCCESS_RESPONSE_WITH_NON_STANDARD_USER_NODE) + assert user == 'someuser'