4 from xml.etree import ElementTree
5 from cas_provider.attribute_formatters import CAS, NSMAP
6 from cas_provider.models import ServiceTicket
7 from cas_provider.views import _cas2_sucess_response, INVALID_TICKET, _cas2_error_response
8 from django.contrib.auth.models import User
9 from django.core.urlresolvers import reverse
10 from django.test import TestCase
11 from urlparse import urlparse
12 from django.conf import settings
15 def dummy_urlopen(url):
19 class ViewsTest(TestCase):
21 fixtures = ['cas_users', ]
24 self.service = 'http://example.com/'
27 def test_successful_login_with_proxy(self):
28 urllib2.urlopen = dummy_urlopen # monkey patching urllib2.urlopen so that the testcase doesnt really opens a url
29 proxyTarget = "http://my.sweet.service"
31 response = self._login_user('root', '123')
32 response = self._validate_cas2(response, True, proxyTarget )
34 # Test: I'm acting as the service that will call another service
35 # Step 1: Get the proxy granting ticket
36 responseXml = ElementTree.parse(StringIO.StringIO(response.content))
37 auth_success = responseXml.find(CAS + 'authenticationSuccess', namespaces=NSMAP)
38 pgt = auth_success.find(CAS + "proxyGrantingTicket", namespaces=NSMAP)
39 user = auth_success.find(CAS + "user", namespaces=NSMAP)
40 self.assertEqual('root', user.text)
41 self.assertIsNotNone(pgt.text)
42 self.assertTrue(pgt.text.startswith('PGTIOU'))
44 #Step 2: Get the actual proxy ticket
45 proxyTicketResponse = self.client.get(reverse('proxy'), {'targetService': proxyTarget, 'pgt': pgt.text}, follow=False)
46 proxyTicketResponseXml = ElementTree.parse(StringIO.StringIO(proxyTicketResponse.content))
47 self.assertIsNotNone(proxyTicketResponseXml.find(CAS + "proxySuccess", namespaces=NSMAP))
48 self.assertIsNotNone(proxyTicketResponseXml.find(CAS + "proxySuccess/cas:proxyTicket", namespaces=NSMAP))
49 proxyTicket = proxyTicketResponseXml.find(CAS + "proxySuccess/cas:proxyTicket", namespaces=NSMAP);
51 #Step 3: I have the proxy ticket I can talk to some other backend service as the currently logged in user!
52 proxyValidateResponse = self.client.get(reverse('cas_proxy_validate'), {'ticket': proxyTicket.text, 'service': proxyTarget, 'pgtUrl': None})
53 proxyValidateResponseXml = ElementTree.parse(StringIO.StringIO(proxyValidateResponse.content))
55 auth_success_2 = proxyValidateResponseXml.find(CAS + 'authenticationSuccess', namespaces=NSMAP)
56 user_2 = auth_success.find(CAS + "user", namespaces=NSMAP)
57 proxies = auth_success.find(CAS + "proxies")
58 self.assertIsNotNone(auth_success_2)
59 self.assertEqual(user.text, user_2.text)
60 self.assertIsNotNone(proxies)
63 def test_successful_proxy_chaining(self):
64 self.assertFalse(True)
66 def test_successful_service_not_matching_in_request_to_proxy(self):
67 self.assertFalse(True)
70 def test_succeessful_login(self):
71 response = self._login_user('root', '123')
72 self._validate_cas1(response, True)
74 response = self.client.get(reverse('cas_login'), {'service': self.service}, follow=False)
75 self.assertEqual(response.status_code, 302)
76 self.assertTrue(response['location'].startswith('%s?ticket=' % self.service))
78 response = self.client.get(reverse('cas_login'), follow=False)
79 self.assertEqual(response.status_code, 302)
80 self.assertTrue(response['location'].startswith('http://testserver/'))
82 response = self.client.get(response['location'], follow=False)
83 self.assertIn(response.status_code, [302, 200])
85 response = self.client.get(reverse('cas_login'), {'service': self.service, 'warn': True}, follow=False)
86 self.assertEqual(response.status_code, 200)
87 self.assertTemplateUsed(response, 'cas/warn.html')
90 def _cas_logout(self):
91 response = self.client.get(reverse('cas_logout'), follow=False)
92 self.assertEqual(response.status_code, 200)
95 def test_logout(self):
96 response = self._login_user('root', '123')
97 self._validate_cas1(response, True)
101 response = self.client.get(reverse('cas_login'), follow=False)
102 self.assertEqual(response.status_code, 200)
103 self.assertEqual(response.context['user'].is_anonymous(), True)
106 def test_broken_pwd(self):
107 self._fail_login('root', '321')
109 def test_broken_username(self):
110 self._fail_login('notroot', '123')
112 def test_nonactive_user_login(self):
113 self._fail_login('nonactive', '123')
115 def test_cas2_success_validate(self):
116 response = self._login_user('root', '123')
117 response = self._validate_cas2(response, True)
118 user = User.objects.get(username=self.username)
119 self.assertEqual(response.content, _cas2_sucess_response(user).content)
121 def test_cas2_custom_attrs(self):
122 settings.CAS_CUSTOM_ATTRIBUTES_CALLBACK = cas_mapping
123 response = self._login_user('editor', '123')
125 response = self._validate_cas2(response, True)
126 self.assertEqual(response.content, '''<cas:serviceResponse xmlns:cas="http://www.yale.edu/tp/cas">'''
127 '''<cas:authenticationSuccess>'''
128 '''<cas:user>editor</cas:user>'''
129 '''<cas:attributes>'''
130 '''<cas:attraStyle>Jasig</cas:attraStyle>'''
131 '''<cas:group>editor</cas:group>'''
132 '''<cas:is_staff>True</cas:is_staff>'''
133 '''<cas:is_active>True</cas:is_active>'''
134 '''<cas:email>editor@exapmle.com</cas:email>'''
135 '''</cas:attributes>'''
136 '''</cas:authenticationSuccess>'''
137 '''</cas:serviceResponse>''')
140 response = self._login_user('editor', '123')
141 settings.CAS_CUSTOM_ATTRIBUTES_FORMATER = 'cas_provider.attribute_formatters.ruby_cas'
142 response = self._validate_cas2(response, True)
143 self.assertEqual(response.content, '''<cas:serviceResponse xmlns:cas="http://www.yale.edu/tp/cas">'''
144 '''<cas:authenticationSuccess>'''
145 '''<cas:user>editor</cas:user>'''
146 '''<cas:attraStyle>RubyCAS</cas:attraStyle>'''
147 '''<cas:group>editor</cas:group>'''
148 '''<cas:is_staff>True</cas:is_staff>'''
149 '''<cas:is_active>True</cas:is_active>'''
150 '''<cas:email>editor@exapmle.com</cas:email>'''
151 '''</cas:authenticationSuccess>'''
152 '''</cas:serviceResponse>''')
155 response = self._login_user('editor', '123')
156 settings.CAS_CUSTOM_ATTRIBUTES_FORMATER = 'cas_provider.attribute_formatters.name_value'
157 response = self._validate_cas2(response, True)
158 self.assertEqual(response.content, '''<cas:serviceResponse xmlns:cas="http://www.yale.edu/tp/cas">'''
159 '''<cas:authenticationSuccess>'''
160 '''<cas:user>editor</cas:user>'''
161 '''<cas:attribute name="attraStyle" value="Name-Value"/>'''
162 '''<cas:attribute name="group" value="editor"/>'''
163 '''<cas:attribute name="is_staff" value="True"/>'''
164 '''<cas:attribute name="is_active" value="True"/>'''
165 '''<cas:attribute name="email" value="editor@exapmle.com"/>'''
166 '''</cas:authenticationSuccess>'''
167 '''</cas:serviceResponse>''')
170 def test_cas2_fail_validate(self):
171 for user, pwd in (('root', '321'), ('notroot', '123'), ('nonactive', '123')):
172 response = self._login_user(user, pwd)
173 self._validate_cas2(response, False)
176 def _fail_login(self, username, password):
177 response = self._login_user(username, password)
178 self._validate_cas1(response, False)
180 response = self.client.get(reverse('cas_login'), {'service': self.service}, follow=False)
181 self.assertEqual(response.status_code, 200)
182 response = self.client.get(reverse('cas_login'), follow=False)
183 self.assertEqual(response.status_code, 200)
187 def _login_user(self, username, password):
188 self.username = username
189 response = self.client.get(reverse('cas_login'), {'service': self.service})
190 self.assertEqual(response.status_code, 200)
191 self.assertTemplateUsed(response, 'cas/login.html')
192 form = response.context['form']
193 service = form['service'].value()
194 return self.client.post(reverse('cas_login'), {
195 'username': username,
196 'password': password,
197 'lt': form['lt'].value(),
202 def _validate_cas1(self, response, is_correct=True):
204 self.assertEqual(response.status_code, 302)
205 self.assertTrue(response.has_header('location'))
206 location = urlparse(response['location'])
207 ticket = location.query.split('=')[1]
209 response = self.client.get(reverse('cas_validate'), {'ticket': ticket, 'service': self.service}, follow=False)
210 self.assertEqual(response.status_code, 200)
211 self.assertEqual(unicode(response.content), u'yes\n%s\n' % self.username)
213 self.assertEqual(response.status_code, 200)
214 self.assertEqual(len(response.context['form'].errors), 1)
216 response = self.client.get(reverse('cas_validate'), {'ticket': 'ST-12312312312312312312312', 'service': self.service}, follow=False)
217 self.assertEqual(response.status_code, 200)
218 self.assertEqual(response.content, u'no\n\n')
221 def _validate_cas2(self, response, is_correct=True, pgtUrl = None):
223 self.assertEqual(response.status_code, 302)
224 self.assertTrue(response.has_header('location'))
225 location = urlparse(response['location'])
226 ticket = location.query.split('=')[1]
228 response = self.client.get(reverse('cas_service_validate'), {'ticket': ticket, 'service': self.service, 'pgtUrl': pgtUrl}, follow=False)
230 response = self.client.get(reverse('cas_service_validate'), {'ticket': ticket, 'service': self.service}, follow=False)
231 self.assertEqual(response.status_code, 200)
233 self.assertEqual(response.status_code, 200)
234 self.assertEqual(len(response.context['form'].errors), 1)
236 response = self.client.get(reverse('cas_service_validate'), {'ticket': 'ST-12312312312312312312312', 'service': self.service}, follow=False)
237 self.assertEqual(response.status_code, 200)
238 self.assertEqual(response.content, _cas2_error_response(INVALID_TICKET).content)
241 class ModelsTestCase(TestCase):
243 fixtures = ['cas_users.json', ]
246 self.user = User.objects.get(username='root')
248 def test_redirects(self):
249 ticket = ServiceTicket.objects.create(service='http://example.com', user=self.user)
250 self.assertEqual(ticket.get_redirect_url(), '%(service)s?ticket=%(ticket)s' % ticket.__dict__)
253 def cas_mapping(user):
255 'is_staff': unicode(user.is_staff),
256 'is_active': unicode(user.is_active),
258 'group': [g.name for g in user.groups.all()]