Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / tests.py @ 1d59653f

History | View | Annotate | Download (24 kB)

1 d2633501 Kostas Papadimitriou
# Copyright 2011 GRNET S.A. All rights reserved.
2 d2633501 Kostas Papadimitriou
#
3 d2633501 Kostas Papadimitriou
# Redistribution and use in source and binary forms, with or
4 d2633501 Kostas Papadimitriou
# without modification, are permitted provided that the following
5 d2633501 Kostas Papadimitriou
# conditions are met:
6 d2633501 Kostas Papadimitriou
#
7 d2633501 Kostas Papadimitriou
#   1. Redistributions of source code must retain the above
8 d2633501 Kostas Papadimitriou
#      copyright notice, this list of conditions and the following
9 d2633501 Kostas Papadimitriou
#      disclaimer.
10 d2633501 Kostas Papadimitriou
#
11 d2633501 Kostas Papadimitriou
#   2. Redistributions in binary form must reproduce the above
12 d2633501 Kostas Papadimitriou
#      copyright notice, this list of conditions and the following
13 d2633501 Kostas Papadimitriou
#      disclaimer in the documentation and/or other materials
14 d2633501 Kostas Papadimitriou
#      provided with the distribution.
15 d2633501 Kostas Papadimitriou
#
16 d2633501 Kostas Papadimitriou
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 d2633501 Kostas Papadimitriou
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 d2633501 Kostas Papadimitriou
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 d2633501 Kostas Papadimitriou
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 d2633501 Kostas Papadimitriou
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 d2633501 Kostas Papadimitriou
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 d2633501 Kostas Papadimitriou
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 d2633501 Kostas Papadimitriou
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 d2633501 Kostas Papadimitriou
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 d2633501 Kostas Papadimitriou
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 d2633501 Kostas Papadimitriou
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 d2633501 Kostas Papadimitriou
# POSSIBILITY OF SUCH DAMAGE.
28 d2633501 Kostas Papadimitriou
#
29 d2633501 Kostas Papadimitriou
# The views and conclusions contained in the software and
30 d2633501 Kostas Papadimitriou
# documentation are those of the authors and should not be
31 d2633501 Kostas Papadimitriou
# interpreted as representing official policies, either expressed
32 d2633501 Kostas Papadimitriou
# or implied, of GRNET S.A.
33 d2633501 Kostas Papadimitriou
34 d2633501 Kostas Papadimitriou
import datetime
35 d2633501 Kostas Papadimitriou
36 d2633501 Kostas Papadimitriou
from django.test import TestCase, Client
37 d2633501 Kostas Papadimitriou
from django.conf import settings
38 d2633501 Kostas Papadimitriou
from django.core import mail
39 d2633501 Kostas Papadimitriou
40 d2633501 Kostas Papadimitriou
from astakos.im.target.shibboleth import Tokens as ShibbolethTokens
41 d2633501 Kostas Papadimitriou
from astakos.im.models import *
42 d2633501 Kostas Papadimitriou
from astakos.im import functions
43 d2633501 Kostas Papadimitriou
from astakos.im import settings as astakos_settings
44 d2633501 Kostas Papadimitriou
45 d2633501 Kostas Papadimitriou
from urllib import quote
46 d2633501 Kostas Papadimitriou
47 2e90e3ec Kostas Papadimitriou
from astakos.im import messages
48 2e90e3ec Kostas Papadimitriou
49 d2633501 Kostas Papadimitriou
class ShibbolethClient(Client):
50 d2633501 Kostas Papadimitriou
    """
51 d2633501 Kostas Papadimitriou
    A shibboleth agnostic client.
52 d2633501 Kostas Papadimitriou
    """
53 d2633501 Kostas Papadimitriou
    VALID_TOKENS = filter(lambda x: not x.startswith("_"), dir(ShibbolethTokens))
54 d2633501 Kostas Papadimitriou
55 d2633501 Kostas Papadimitriou
    def __init__(self, *args, **kwargs):
56 d2633501 Kostas Papadimitriou
        self.tokens = kwargs.pop('tokens', {})
57 d2633501 Kostas Papadimitriou
        super(ShibbolethClient, self).__init__(*args, **kwargs)
58 d2633501 Kostas Papadimitriou
59 d2633501 Kostas Papadimitriou
    def set_tokens(self, **kwargs):
60 d2633501 Kostas Papadimitriou
        for key, value in kwargs.iteritems():
61 d2633501 Kostas Papadimitriou
            key = 'SHIB_%s' % key.upper()
62 d2633501 Kostas Papadimitriou
            if not key in self.VALID_TOKENS:
63 d2633501 Kostas Papadimitriou
                raise Exception('Invalid shibboleth token')
64 d2633501 Kostas Papadimitriou
65 d2633501 Kostas Papadimitriou
            self.tokens[key] = value
66 d2633501 Kostas Papadimitriou
67 d2633501 Kostas Papadimitriou
    def unset_tokens(self, *keys):
68 d2633501 Kostas Papadimitriou
        for key in keys:
69 d2633501 Kostas Papadimitriou
            key = 'SHIB_%s' % param.upper()
70 d2633501 Kostas Papadimitriou
            if key in self.tokens:
71 d2633501 Kostas Papadimitriou
                del self.tokens[key]
72 d2633501 Kostas Papadimitriou
73 d2633501 Kostas Papadimitriou
    def reset_tokens(self):
74 d2633501 Kostas Papadimitriou
        self.tokens = {}
75 d2633501 Kostas Papadimitriou
76 d2633501 Kostas Papadimitriou
    def get_http_token(self, key):
77 d2633501 Kostas Papadimitriou
        http_header = getattr(ShibbolethTokens, key)
78 d2633501 Kostas Papadimitriou
        return http_header
79 d2633501 Kostas Papadimitriou
80 d2633501 Kostas Papadimitriou
    def request(self, **request):
81 d2633501 Kostas Papadimitriou
        """
82 d2633501 Kostas Papadimitriou
        Transform valid shibboleth tokens to http headers
83 d2633501 Kostas Papadimitriou
        """
84 d2633501 Kostas Papadimitriou
        for token, value in self.tokens.iteritems():
85 d2633501 Kostas Papadimitriou
            request[self.get_http_token(token)] = value
86 d2633501 Kostas Papadimitriou
87 d2633501 Kostas Papadimitriou
        for param in request.keys():
88 d2633501 Kostas Papadimitriou
            key = 'SHIB_%s' % param.upper()
89 d2633501 Kostas Papadimitriou
            if key in self.VALID_TOKENS:
90 d2633501 Kostas Papadimitriou
                request[self.get_http_token(key)] = request[param]
91 d2633501 Kostas Papadimitriou
                del request[param]
92 d2633501 Kostas Papadimitriou
93 d2633501 Kostas Papadimitriou
        return super(ShibbolethClient, self).request(**request)
94 d2633501 Kostas Papadimitriou
95 d2633501 Kostas Papadimitriou
96 d2633501 Kostas Papadimitriou
def get_local_user(username, **kwargs):
97 d2633501 Kostas Papadimitriou
        try:
98 d2633501 Kostas Papadimitriou
            return AstakosUser.objects.get(email=username)
99 d2633501 Kostas Papadimitriou
        except:
100 d2633501 Kostas Papadimitriou
            user_params = {
101 d2633501 Kostas Papadimitriou
                'username': username,
102 d2633501 Kostas Papadimitriou
                'email': username,
103 d2633501 Kostas Papadimitriou
                'is_active': True,
104 d2633501 Kostas Papadimitriou
                'activation_sent': datetime.now(),
105 d2633501 Kostas Papadimitriou
                'email_verified': True,
106 d2633501 Kostas Papadimitriou
                'provider': 'local'
107 d2633501 Kostas Papadimitriou
            }
108 d2633501 Kostas Papadimitriou
            user_params.update(kwargs)
109 d2633501 Kostas Papadimitriou
            user = AstakosUser(**user_params)
110 d2633501 Kostas Papadimitriou
            user.set_password(kwargs.get('password', 'password'))
111 d2633501 Kostas Papadimitriou
            user.save()
112 d2633501 Kostas Papadimitriou
            user.add_auth_provider('local', auth_backend='astakos')
113 d2633501 Kostas Papadimitriou
            if kwargs.get('is_active', True):
114 d2633501 Kostas Papadimitriou
                user.is_active = True
115 d2633501 Kostas Papadimitriou
            else:
116 d2633501 Kostas Papadimitriou
                user.is_active = False
117 d2633501 Kostas Papadimitriou
            user.save()
118 d2633501 Kostas Papadimitriou
            return user
119 d2633501 Kostas Papadimitriou
120 d2633501 Kostas Papadimitriou
121 d2633501 Kostas Papadimitriou
def get_mailbox(email):
122 d2633501 Kostas Papadimitriou
    mails = []
123 d2633501 Kostas Papadimitriou
    for sent_email in mail.outbox:
124 d2633501 Kostas Papadimitriou
        for recipient in sent_email.recipients():
125 d2633501 Kostas Papadimitriou
            if email in recipient:
126 d2633501 Kostas Papadimitriou
                mails.append(sent_email)
127 d2633501 Kostas Papadimitriou
    return mails
128 d2633501 Kostas Papadimitriou
129 d2633501 Kostas Papadimitriou
130 d2633501 Kostas Papadimitriou
class ShibbolethTests(TestCase):
131 d2633501 Kostas Papadimitriou
    """
132 d2633501 Kostas Papadimitriou
    Testing shibboleth authentication.
133 d2633501 Kostas Papadimitriou
    """
134 d2633501 Kostas Papadimitriou
135 d2633501 Kostas Papadimitriou
    fixtures = ['groups']
136 d2633501 Kostas Papadimitriou
137 d2633501 Kostas Papadimitriou
    def setUp(self):
138 d2633501 Kostas Papadimitriou
        self.client = ShibbolethClient()
139 d2633501 Kostas Papadimitriou
        settings.ASTAKOS_IM_MODULES = ['local', 'shibboleth']
140 ba50648c Kostas Papadimitriou
        settings.ASTAKOS_MODERATION_ENABLED = True
141 d2633501 Kostas Papadimitriou
142 d2633501 Kostas Papadimitriou
    def test_create_account(self):
143 ba50648c Kostas Papadimitriou
144 d2633501 Kostas Papadimitriou
        client = ShibbolethClient()
145 d2633501 Kostas Papadimitriou
146 d2633501 Kostas Papadimitriou
        # shibboleth views validation
147 d2633501 Kostas Papadimitriou
        # eepn required
148 d2633501 Kostas Papadimitriou
        r = client.get('/im/login/shibboleth?', follow=True)
149 2e90e3ec Kostas Papadimitriou
        self.assertContains(r, messages.SHIBBOLETH_MISSING_EPPN)
150 d2633501 Kostas Papadimitriou
        client.set_tokens(eppn="kpapeppn")
151 ba50648c Kostas Papadimitriou
152 d2633501 Kostas Papadimitriou
        # shibboleth user info required
153 d2633501 Kostas Papadimitriou
        r = client.get('/im/login/shibboleth?', follow=True)
154 2e90e3ec Kostas Papadimitriou
        self.assertContains(r, messages.SHIBBOLETH_MISSING_NAME)
155 d2633501 Kostas Papadimitriou
156 d2633501 Kostas Papadimitriou
        # shibboleth logged us in
157 ba50648c Kostas Papadimitriou
        client.set_tokens(mail="kpap@grnet.gr", eppn="kpapeppn", cn="1",
158 ba50648c Kostas Papadimitriou
                          ep_affiliation="Test Affiliation")
159 d2633501 Kostas Papadimitriou
        r = client.get('/im/login/shibboleth?')
160 ba50648c Kostas Papadimitriou
        self.assertEqual(r.status_code, 200)
161 d2633501 Kostas Papadimitriou
162 d2633501 Kostas Papadimitriou
        # astakos asks if we want to add shibboleth
163 d2633501 Kostas Papadimitriou
        self.assertContains(r, "Already have an account?")
164 d2633501 Kostas Papadimitriou
165 d2633501 Kostas Papadimitriou
        # a new pending user created
166 d2633501 Kostas Papadimitriou
        pending_user = PendingThirdPartyUser.objects.get(
167 d2633501 Kostas Papadimitriou
            third_party_identifier="kpapeppn")
168 d2633501 Kostas Papadimitriou
        self.assertEqual(PendingThirdPartyUser.objects.count(), 1)
169 ba50648c Kostas Papadimitriou
        # keep the token for future use
170 d2633501 Kostas Papadimitriou
        token = pending_user.token
171 d2633501 Kostas Papadimitriou
        # from now on no shibboleth headers are sent to the server
172 d2633501 Kostas Papadimitriou
        client.reset_tokens()
173 d2633501 Kostas Papadimitriou
174 ba50648c Kostas Papadimitriou
        # this is the old way, it should fail, to avoid pending user take over
175 d2633501 Kostas Papadimitriou
        r = client.get('/im/shibboleth/signup/%s' % pending_user.username)
176 d2633501 Kostas Papadimitriou
        self.assertEqual(r.status_code, 404)
177 d2633501 Kostas Papadimitriou
178 ba50648c Kostas Papadimitriou
        # this is the signup unique url associated with the pending user created
179 ba50648c Kostas Papadimitriou
        r = client.get('/im/signup/?third_party_token=%s' % token)
180 d2633501 Kostas Papadimitriou
        form = r.context['form']
181 ba50648c Kostas Papadimitriou
        post_data = {'third_party_identifier': pending_user.third_party_identifier,
182 d2633501 Kostas Papadimitriou
                     'first_name': 'Kostas',
183 d2633501 Kostas Papadimitriou
                     'third_party_token': token,
184 d2633501 Kostas Papadimitriou
                     'last_name': 'Mitroglou',
185 d2633501 Kostas Papadimitriou
                     'provider': 'shibboleth'
186 d2633501 Kostas Papadimitriou
                    }
187 ba50648c Kostas Papadimitriou
188 ba50648c Kostas Papadimitriou
        # invlid email
189 ba50648c Kostas Papadimitriou
        post_data['email'] = 'kpap'
190 d2633501 Kostas Papadimitriou
        r = client.post('/im/signup', post_data)
191 8ab484ea Kostas Papadimitriou
        self.assertContains(r, token)
192 ba50648c Kostas Papadimitriou
193 ba50648c Kostas Papadimitriou
        # existing email
194 ba50648c Kostas Papadimitriou
        existing_user = get_local_user('test@test.com')
195 ba50648c Kostas Papadimitriou
        post_data['email'] = 'test@test.com'
196 ba50648c Kostas Papadimitriou
        r = client.post('/im/signup', post_data)
197 ba50648c Kostas Papadimitriou
        self.assertContains(r, messages.EMAIL_USED)
198 ba50648c Kostas Papadimitriou
        existing_user.delete()
199 ba50648c Kostas Papadimitriou
200 ba50648c Kostas Papadimitriou
        # and finally a valid signup
201 8ab484ea Kostas Papadimitriou
        post_data['email'] = 'kpap@grnet.gr'
202 8ab484ea Kostas Papadimitriou
        r = client.post('/im/signup', post_data)
203 d2633501 Kostas Papadimitriou
        self.assertEqual(r.status_code, 200)
204 ba50648c Kostas Papadimitriou
205 ba50648c Kostas Papadimitriou
        # everything is ok in our db
206 d2633501 Kostas Papadimitriou
        self.assertEqual(AstakosUser.objects.count(), 1)
207 d2633501 Kostas Papadimitriou
        self.assertEqual(AstakosUserAuthProvider.objects.count(), 1)
208 ba50648c Kostas Papadimitriou
        self.assertEqual(PendingThirdPartyUser.objects.count(), 0)
209 d2633501 Kostas Papadimitriou
210 ba50648c Kostas Papadimitriou
        # provider info stored
211 ba50648c Kostas Papadimitriou
        provider = AstakosUserAuthProvider.objects.get(module="shibboleth")
212 ba50648c Kostas Papadimitriou
        self.assertEqual(provider.affiliation, 'Test Affiliation')
213 ba50648c Kostas Papadimitriou
        self.assertEqual(provider.info, {u'email': u'kpap@grnet.gr',
214 ba50648c Kostas Papadimitriou
                                         u'eppn': u'kpapeppn'})
215 d2633501 Kostas Papadimitriou
216 ba50648c Kostas Papadimitriou
        # lets login (not activated yet)
217 d2633501 Kostas Papadimitriou
        client.set_tokens(mail="kpap@grnet.gr", eppn="kpapeppn", cn="1", )
218 d2633501 Kostas Papadimitriou
        r = client.get("/im/login/shibboleth?", follow=True)
219 d2633501 Kostas Papadimitriou
        self.assertContains(r, "Your request is pending activation")
220 d2633501 Kostas Papadimitriou
        r = client.get("/im/profile", follow=True)
221 d2633501 Kostas Papadimitriou
        self.assertRedirects(r, 'http://testserver/im/?next=%2Fim%2Fprofile')
222 d2633501 Kostas Papadimitriou
223 ba50648c Kostas Papadimitriou
        # admin activates our user
224 ba50648c Kostas Papadimitriou
        u = AstakosUser.objects.get(username="kpap@grnet.gr")
225 d2633501 Kostas Papadimitriou
        functions.activate(u)
226 d2633501 Kostas Papadimitriou
        self.assertEqual(u.is_active, True)
227 d2633501 Kostas Papadimitriou
228 ba50648c Kostas Papadimitriou
        # we see our profile
229 ba50648c Kostas Papadimitriou
        r = client.get("/im/login/shibboleth?", follow=True)
230 d2633501 Kostas Papadimitriou
        self.assertRedirects(r, '/im/profile')
231 ba50648c Kostas Papadimitriou
        self.assertEqual(r.status_code, 200)
232 d2633501 Kostas Papadimitriou
233 d2633501 Kostas Papadimitriou
    def test_existing(self):
234 ba50648c Kostas Papadimitriou
        """
235 ba50648c Kostas Papadimitriou
        Test adding of third party login to an existing account
236 ba50648c Kostas Papadimitriou
        """
237 ba50648c Kostas Papadimitriou
238 ba50648c Kostas Papadimitriou
        # this is our existing user
239 d2633501 Kostas Papadimitriou
        existing_user = get_local_user('kpap@grnet.gr')
240 d2633501 Kostas Papadimitriou
241 d2633501 Kostas Papadimitriou
        client = ShibbolethClient()
242 d2633501 Kostas Papadimitriou
        # shibboleth logged us in, notice that we use different email
243 d2633501 Kostas Papadimitriou
        client.set_tokens(mail="kpap@shibboleth.gr", eppn="kpapeppn", cn="1", )
244 d2633501 Kostas Papadimitriou
        r = client.get("/im/login/shibboleth?")
245 ba50648c Kostas Papadimitriou
246 d2633501 Kostas Papadimitriou
        # astakos asks if we want to switch a local account to shibboleth
247 d2633501 Kostas Papadimitriou
        self.assertContains(r, "Already have an account?")
248 d2633501 Kostas Papadimitriou
249 d2633501 Kostas Papadimitriou
        # a new pending user created
250 d2633501 Kostas Papadimitriou
        pending_user = PendingThirdPartyUser.objects.get()
251 d2633501 Kostas Papadimitriou
        self.assertEqual(PendingThirdPartyUser.objects.count(), 1)
252 d2633501 Kostas Papadimitriou
        pending_key = pending_user.token
253 d2633501 Kostas Papadimitriou
        client.reset_tokens()
254 d2633501 Kostas Papadimitriou
255 d2633501 Kostas Papadimitriou
        # we choose to add shibboleth to an our existing account
256 d2633501 Kostas Papadimitriou
        # we get redirected to login page with the pending token set
257 d2633501 Kostas Papadimitriou
        r = client.get('/im/login?key=%s' % pending_key)
258 d2633501 Kostas Papadimitriou
        post_data = {'password': 'password',
259 d2633501 Kostas Papadimitriou
                     'username': 'kpap@grnet.gr',
260 d2633501 Kostas Papadimitriou
                     'key': pending_key}
261 d2633501 Kostas Papadimitriou
        r = client.post('/im/local', post_data, follow=True)
262 ba50648c Kostas Papadimitriou
        self.assertRedirects(r, "/im/profile")
263 d2633501 Kostas Papadimitriou
        self.assertContains(r, "Your new login method has been added")
264 d2633501 Kostas Papadimitriou
265 ba50648c Kostas Papadimitriou
        self.assertTrue(existing_user.has_auth_provider('shibboleth'))
266 ba50648c Kostas Papadimitriou
        self.assertTrue(existing_user.has_auth_provider('local',
267 ba50648c Kostas Papadimitriou
                                                        auth_backend='astakos'))
268 d2633501 Kostas Papadimitriou
        client.logout()
269 d2633501 Kostas Papadimitriou
270 ba50648c Kostas Papadimitriou
        # check that we cannot assign same third party provide twice
271 d2633501 Kostas Papadimitriou
        r = client.get('/im/login?key=%s' % pending_key)
272 d2633501 Kostas Papadimitriou
        post_data = {'password': 'password',
273 d2633501 Kostas Papadimitriou
                     'username': 'kpap@grnet.gr',
274 d2633501 Kostas Papadimitriou
                     'key': pending_key}
275 d2633501 Kostas Papadimitriou
        r = self.client.post('/im/local', post_data, follow=True)
276 ba50648c Kostas Papadimitriou
        self.assertContains(r, "Failed to assign new login method")
277 d2633501 Kostas Papadimitriou
        self.client.logout()
278 d2633501 Kostas Papadimitriou
        client.logout()
279 d2633501 Kostas Papadimitriou
280 d2633501 Kostas Papadimitriou
        # look Ma, i can login with both my shibboleth and local account
281 d2633501 Kostas Papadimitriou
        client.set_tokens(mail="kpap@shibboleth.gr", eppn="kpapeppn", cn="1")
282 d2633501 Kostas Papadimitriou
        r = client.get("/im/login/shibboleth?", follow=True)
283 d2633501 Kostas Papadimitriou
        self.assertTrue(r.context['request'].user.is_authenticated())
284 d2633501 Kostas Papadimitriou
        self.assertTrue(r.context['request'].user.email == "kpap@grnet.gr")
285 ba50648c Kostas Papadimitriou
        self.assertRedirects(r, '/im/profile')
286 ba50648c Kostas Papadimitriou
        self.assertEqual(r.status_code, 200)
287 d2633501 Kostas Papadimitriou
        client.logout()
288 d2633501 Kostas Papadimitriou
        client.reset_tokens()
289 ba50648c Kostas Papadimitriou
290 ba50648c Kostas Papadimitriou
        # logged out
291 d2633501 Kostas Papadimitriou
        r = client.get("/im/profile", follow=True)
292 d2633501 Kostas Papadimitriou
        self.assertFalse(r.context['request'].user.is_authenticated())
293 d2633501 Kostas Papadimitriou
294 ba50648c Kostas Papadimitriou
        # login with local account also works
295 d2633501 Kostas Papadimitriou
        post_data = {'password': 'password',
296 d2633501 Kostas Papadimitriou
                     'username': 'kpap@grnet.gr'}
297 d2633501 Kostas Papadimitriou
        r = self.client.post('/im/local', post_data, follow=True)
298 d2633501 Kostas Papadimitriou
        self.assertTrue(r.context['request'].user.is_authenticated())
299 ba50648c Kostas Papadimitriou
        self.assertTrue(r.context['request'].user.email == "kpap@grnet.gr")
300 ba50648c Kostas Papadimitriou
        self.assertRedirects(r, '/im/profile')
301 ba50648c Kostas Papadimitriou
        self.assertEqual(r.status_code, 200)
302 d2633501 Kostas Papadimitriou
303 ba50648c Kostas Papadimitriou
        # cannot add the same eppn
304 d2633501 Kostas Papadimitriou
        client.set_tokens(mail="secondary@shibboleth.gr", eppn="kpapeppn", cn="1", )
305 d2633501 Kostas Papadimitriou
        r = client.get("/im/login/shibboleth?", follow=True)
306 ba50648c Kostas Papadimitriou
        self.assertRedirects(r, '/im/profile')
307 ba50648c Kostas Papadimitriou
        self.assertTrue(r.status_code, 200)
308 ba50648c Kostas Papadimitriou
        self.assertEquals(existing_user.auth_providers.count(), 2)
309 d2633501 Kostas Papadimitriou
310 ba50648c Kostas Papadimitriou
        # but can add additional eppn
311 ba50648c Kostas Papadimitriou
        client.set_tokens(mail="secondary@shibboleth.gr", eppn="kpapeppn2",
312 ba50648c Kostas Papadimitriou
                          cn="1", ep_affiliation="affil2")
313 ba50648c Kostas Papadimitriou
        r = client.get("/im/login/shibboleth?", follow=True)
314 ba50648c Kostas Papadimitriou
        new_provider = existing_user.auth_providers.get(identifier="kpapeppn2")
315 ba50648c Kostas Papadimitriou
        self.assertRedirects(r, '/im/profile')
316 ba50648c Kostas Papadimitriou
        self.assertTrue(r.status_code, 200)
317 ba50648c Kostas Papadimitriou
        self.assertEquals(existing_user.auth_providers.count(), 3)
318 ba50648c Kostas Papadimitriou
        self.assertEqual(new_provider.affiliation, 'affil2')
319 d2633501 Kostas Papadimitriou
        client.logout()
320 ba50648c Kostas Papadimitriou
        client.reset_tokens()
321 ba50648c Kostas Papadimitriou
322 ba50648c Kostas Papadimitriou
        # cannot login with another eppn
323 d2633501 Kostas Papadimitriou
        client.set_tokens(mail="kpap@grnet.gr", eppn="kpapeppninvalid", cn="1")
324 d2633501 Kostas Papadimitriou
        r = client.get("/im/login/shibboleth?", follow=True)
325 d2633501 Kostas Papadimitriou
        self.assertFalse(r.context['request'].user.is_authenticated())
326 d2633501 Kostas Papadimitriou
327 f432088a Kostas Papadimitriou
        # lets remove local password
328 f432088a Kostas Papadimitriou
        user = AstakosUser.objects.get(username="kpap@grnet.gr",
329 f432088a Kostas Papadimitriou
                                       email="kpap@grnet.gr")
330 ba50648c Kostas Papadimitriou
        remove_local_url = user.get_provider_remove_url('local')
331 ba50648c Kostas Papadimitriou
        remove_shibbo_url = user.get_provider_remove_url('shibboleth',
332 ba50648c Kostas Papadimitriou
                                                         identifier='kpapeppn')
333 ba50648c Kostas Papadimitriou
        remove_shibbo2_url = user.get_provider_remove_url('shibboleth',
334 ba50648c Kostas Papadimitriou
                                                         identifier='kpapeppn2')
335 f432088a Kostas Papadimitriou
        client.set_tokens(mail="kpap@shibboleth.gr", eppn="kpapeppn", cn="1")
336 f432088a Kostas Papadimitriou
        r = client.get("/im/login/shibboleth?", follow=True)
337 f432088a Kostas Papadimitriou
        client.reset_tokens()
338 ba50648c Kostas Papadimitriou
339 ba50648c Kostas Papadimitriou
        # TODO: this view should use POST
340 ba50648c Kostas Papadimitriou
        r = client.get(remove_local_url)
341 ba50648c Kostas Papadimitriou
        # 2 providers left
342 ba50648c Kostas Papadimitriou
        self.assertEqual(user.auth_providers.count(), 2)
343 ba50648c Kostas Papadimitriou
        r = client.get(remove_shibbo2_url)
344 ba50648c Kostas Papadimitriou
        # 1 provider left
345 f432088a Kostas Papadimitriou
        self.assertEqual(user.auth_providers.count(), 1)
346 ba50648c Kostas Papadimitriou
        # cannot remove last provider
347 ba50648c Kostas Papadimitriou
        r = client.get(remove_shibbo_url)
348 f432088a Kostas Papadimitriou
        self.assertEqual(r.status_code, 403)
349 f432088a Kostas Papadimitriou
        self.client.logout()
350 ba50648c Kostas Papadimitriou
351 ba50648c Kostas Papadimitriou
        # cannot login using local credentials (notice we use another client)
352 f432088a Kostas Papadimitriou
        post_data = {'password': 'password',
353 f432088a Kostas Papadimitriou
                     'username': 'kpap@grnet.gr'}
354 f432088a Kostas Papadimitriou
        r = self.client.post('/im/local', post_data, follow=True)
355 f432088a Kostas Papadimitriou
        self.assertFalse(r.context['request'].user.is_authenticated())
356 f432088a Kostas Papadimitriou
357 ba50648c Kostas Papadimitriou
        # we can reenable the local provider by setting a password
358 f432088a Kostas Papadimitriou
        r = client.get("/im/password_change", follow=True)
359 f432088a Kostas Papadimitriou
        r = client.post("/im/password_change", {'new_password1':'111',
360 f432088a Kostas Papadimitriou
                                                'new_password2': '111'},
361 f432088a Kostas Papadimitriou
                        follow=True)
362 f432088a Kostas Papadimitriou
        user = r.context['request'].user
363 f432088a Kostas Papadimitriou
        self.assertTrue(user.has_auth_provider('local'))
364 f432088a Kostas Papadimitriou
        self.assertTrue(user.has_auth_provider('shibboleth'))
365 f432088a Kostas Papadimitriou
        self.assertTrue(user.check_password('111'))
366 f432088a Kostas Papadimitriou
        self.assertTrue(user.has_usable_password())
367 f432088a Kostas Papadimitriou
        self.client.logout()
368 ba50648c Kostas Papadimitriou
369 ba50648c Kostas Papadimitriou
        # now we can login
370 f432088a Kostas Papadimitriou
        post_data = {'password': '111',
371 f432088a Kostas Papadimitriou
                     'username': 'kpap@grnet.gr'}
372 f432088a Kostas Papadimitriou
        r = self.client.post('/im/local', post_data, follow=True)
373 f432088a Kostas Papadimitriou
        self.assertTrue(r.context['request'].user.is_authenticated())
374 f432088a Kostas Papadimitriou
375 ba50648c Kostas Papadimitriou
        client.reset_tokens()
376 f432088a Kostas Papadimitriou
377 ba50648c Kostas Papadimitriou
        # we cannot take over another shibboleth identifier
378 f432088a Kostas Papadimitriou
        user2 = get_local_user('another@grnet.gr')
379 f432088a Kostas Papadimitriou
        user2.add_auth_provider('shibboleth', identifier='existingeppn')
380 ba50648c Kostas Papadimitriou
        # login
381 ba50648c Kostas Papadimitriou
        client.set_tokens(mail="kpap@shibboleth.gr", eppn="kpapeppn", cn="1")
382 f432088a Kostas Papadimitriou
        r = client.get("/im/login/shibboleth?", follow=True)
383 ba50648c Kostas Papadimitriou
        # try to assign existing shibboleth identifier of another user
384 f432088a Kostas Papadimitriou
        client.set_tokens(mail="kpap_second@shibboleth.gr", eppn="existingeppn", cn="1")
385 f432088a Kostas Papadimitriou
        r = client.get("/im/login/shibboleth?", follow=True)
386 f432088a Kostas Papadimitriou
        self.assertContains(r, 'Account already exists')
387 2e90e3ec Kostas Papadimitriou
388 d2633501 Kostas Papadimitriou
389 d2633501 Kostas Papadimitriou
class LocalUserTests(TestCase):
390 d2633501 Kostas Papadimitriou
391 d2633501 Kostas Papadimitriou
    fixtures = ['groups']
392 d2633501 Kostas Papadimitriou
393 2e90e3ec Kostas Papadimitriou
    def setUp(self):
394 2e90e3ec Kostas Papadimitriou
        from django.conf import settings
395 2e90e3ec Kostas Papadimitriou
        settings.ADMINS = (('admin', 'support@cloud.grnet.gr'),)
396 2e90e3ec Kostas Papadimitriou
        settings.SERVER_EMAIL = 'no-reply@grnet.gr'
397 2e90e3ec Kostas Papadimitriou
398 7233d542 Kostas Papadimitriou
    def test_no_moderation(self):
399 ba50648c Kostas Papadimitriou
        # disable moderation
400 7233d542 Kostas Papadimitriou
        astakos_settings.MODERATION_ENABLED = False
401 ba50648c Kostas Papadimitriou
402 ba50648c Kostas Papadimitriou
        # create a new user
403 7233d542 Kostas Papadimitriou
        r = self.client.get("/im/signup")
404 7233d542 Kostas Papadimitriou
        self.assertEqual(r.status_code, 200)
405 7233d542 Kostas Papadimitriou
        data = {'email':'kpap@grnet.gr', 'password1':'password',
406 7233d542 Kostas Papadimitriou
                'password2':'password', 'first_name': 'Kostas',
407 7233d542 Kostas Papadimitriou
                'last_name': 'Mitroglou', 'provider': 'local'}
408 7233d542 Kostas Papadimitriou
        r = self.client.post("/im/signup", data)
409 ba50648c Kostas Papadimitriou
410 ba50648c Kostas Papadimitriou
        # user created
411 7233d542 Kostas Papadimitriou
        self.assertEqual(AstakosUser.objects.count(), 1)
412 7233d542 Kostas Papadimitriou
        user = AstakosUser.objects.get(username="kpap@grnet.gr",
413 7233d542 Kostas Papadimitriou
                                       email="kpap@grnet.gr")
414 7233d542 Kostas Papadimitriou
        self.assertEqual(user.username, 'kpap@grnet.gr')
415 7233d542 Kostas Papadimitriou
        self.assertEqual(user.has_auth_provider('local'), True)
416 7233d542 Kostas Papadimitriou
        self.assertFalse(user.is_active)
417 7233d542 Kostas Papadimitriou
418 ba50648c Kostas Papadimitriou
        # user (but not admin) gets notified
419 7233d542 Kostas Papadimitriou
        self.assertEqual(len(get_mailbox('support@cloud.grnet.gr')), 0)
420 7233d542 Kostas Papadimitriou
        self.assertEqual(len(get_mailbox('kpap@grnet.gr')), 1)
421 7233d542 Kostas Papadimitriou
        astakos_settings.MODERATION_ENABLED = True
422 d2633501 Kostas Papadimitriou
423 d2633501 Kostas Papadimitriou
    def test_local_provider(self):
424 ba50648c Kostas Papadimitriou
        # enable moderation
425 7233d542 Kostas Papadimitriou
        astakos_settings.MODERATION_ENABLED = True
426 ba50648c Kostas Papadimitriou
427 ba50648c Kostas Papadimitriou
        # create a user
428 d2633501 Kostas Papadimitriou
        r = self.client.get("/im/signup")
429 d2633501 Kostas Papadimitriou
        self.assertEqual(r.status_code, 200)
430 d2633501 Kostas Papadimitriou
        data = {'email':'kpap@grnet.gr', 'password1':'password',
431 d2633501 Kostas Papadimitriou
                'password2':'password', 'first_name': 'Kostas',
432 d2633501 Kostas Papadimitriou
                'last_name': 'Mitroglou', 'provider': 'local'}
433 d2633501 Kostas Papadimitriou
        r = self.client.post("/im/signup", data)
434 ba50648c Kostas Papadimitriou
435 ba50648c Kostas Papadimitriou
        # user created
436 d2633501 Kostas Papadimitriou
        self.assertEqual(AstakosUser.objects.count(), 1)
437 d2633501 Kostas Papadimitriou
        user = AstakosUser.objects.get(username="kpap@grnet.gr",
438 d2633501 Kostas Papadimitriou
                                       email="kpap@grnet.gr")
439 d2633501 Kostas Papadimitriou
        self.assertEqual(user.username, 'kpap@grnet.gr')
440 d2633501 Kostas Papadimitriou
        self.assertEqual(user.has_auth_provider('local'), True)
441 ba50648c Kostas Papadimitriou
        self.assertFalse(user.is_active) # not activated
442 ba50648c Kostas Papadimitriou
        self.assertFalse(user.email_verified) # not verified
443 ba50648c Kostas Papadimitriou
        self.assertFalse(user.activation_sent) # activation automatically sent
444 d2633501 Kostas Papadimitriou
445 ba50648c Kostas Papadimitriou
        # admin gets notified and activates the user from the command line
446 d2633501 Kostas Papadimitriou
        self.assertEqual(len(get_mailbox('support@cloud.grnet.gr')), 1)
447 d2633501 Kostas Papadimitriou
        functions.send_activation(user)
448 d2633501 Kostas Papadimitriou
449 ba50648c Kostas Papadimitriou
        # user activation fields updated and user gets notified via email
450 d2633501 Kostas Papadimitriou
        user = AstakosUser.objects.get(pk=user.pk)
451 d2633501 Kostas Papadimitriou
        self.assertTrue(user.activation_sent)
452 d2633501 Kostas Papadimitriou
        self.assertFalse(user.email_verified)
453 d2633501 Kostas Papadimitriou
        self.assertEqual(len(get_mailbox('kpap@grnet.gr')), 1)
454 d2633501 Kostas Papadimitriou
455 d2633501 Kostas Papadimitriou
        # user forgot she got registered and tries to submit registration
456 d2633501 Kostas Papadimitriou
        # form. Notice the upper case in email
457 d2633501 Kostas Papadimitriou
        data = {'email':'KPAP@grnet.gr', 'password1':'password',
458 d2633501 Kostas Papadimitriou
                'password2':'password', 'first_name': 'Kostas',
459 d2633501 Kostas Papadimitriou
                'last_name': 'Mitroglou', 'provider': 'local'}
460 d2633501 Kostas Papadimitriou
        r = self.client.post("/im/signup", data)
461 2e90e3ec Kostas Papadimitriou
        self.assertContains(r, messages.EMAIL_USED)
462 d2633501 Kostas Papadimitriou
463 d2633501 Kostas Papadimitriou
        # hmmm, email exists; lets get the password
464 d2633501 Kostas Papadimitriou
        r = self.client.get('/im/local/password_reset')
465 d2633501 Kostas Papadimitriou
        self.assertEqual(r.status_code, 200)
466 d2633501 Kostas Papadimitriou
        r = self.client.post('/im/local/password_reset', {'email':
467 d2633501 Kostas Papadimitriou
                                                          'kpap@grnet.gr'})
468 d2633501 Kostas Papadimitriou
        # she can't because account is not active yet
469 d2633501 Kostas Papadimitriou
        self.assertContains(r, "doesn't have an associated user account")
470 d2633501 Kostas Papadimitriou
471 d2633501 Kostas Papadimitriou
        # moderation is enabled so no automatic activation can be send
472 d2633501 Kostas Papadimitriou
        r = self.client.get('/im/send/activation/%d' % user.pk)
473 d2633501 Kostas Papadimitriou
        self.assertEqual(r.status_code, 403)
474 d2633501 Kostas Papadimitriou
        self.assertEqual(len(get_mailbox('kpap@grnet.gr')), 1)
475 ba50648c Kostas Papadimitriou
476 d2633501 Kostas Papadimitriou
        # also she cannot login
477 d2633501 Kostas Papadimitriou
        r = self.client.post('/im/local', {'username': 'kpap@grnet.gr',
478 d2633501 Kostas Papadimitriou
                                                 'password': 'password'})
479 d2633501 Kostas Papadimitriou
        self.assertContains(r, 'You have not followed the activation link')
480 d2633501 Kostas Papadimitriou
        self.assertNotContains(r, 'Resend activation')
481 d2633501 Kostas Papadimitriou
        self.assertFalse(r.context['request'].user.is_authenticated())
482 d2633501 Kostas Papadimitriou
        self.assertFalse('_pithos2_a' in self.client.cookies)
483 d2633501 Kostas Papadimitriou
484 ba50648c Kostas Papadimitriou
        # same with disabled moderation
485 d2633501 Kostas Papadimitriou
        astakos_settings.MODERATION_ENABLED = False
486 d2633501 Kostas Papadimitriou
        r = self.client.post('/im/local/password_reset', {'email':
487 d2633501 Kostas Papadimitriou
                                                          'kpap@grnet.gr'})
488 d2633501 Kostas Papadimitriou
        self.assertContains(r, "doesn't have an associated user account")
489 d2633501 Kostas Papadimitriou
        r = self.client.post('/im/local', {'username': 'kpap@grnet.gr',
490 d2633501 Kostas Papadimitriou
                                                 'password': 'password'})
491 d2633501 Kostas Papadimitriou
        self.assertContains(r, 'You have not followed the activation link')
492 d2633501 Kostas Papadimitriou
        self.assertContains(r, 'Resend activation')
493 d2633501 Kostas Papadimitriou
        self.assertFalse(r.context['request'].user.is_authenticated())
494 d2633501 Kostas Papadimitriou
        self.assertFalse('_pithos2_a' in self.client.cookies)
495 ba50648c Kostas Papadimitriou
496 d2633501 Kostas Papadimitriou
        # user sees the message and resends activation
497 d2633501 Kostas Papadimitriou
        r = self.client.get('/im/send/activation/%d' % user.pk)
498 d2633501 Kostas Papadimitriou
        self.assertEqual(len(get_mailbox('kpap@grnet.gr')), 2)
499 d2633501 Kostas Papadimitriou
500 d2633501 Kostas Papadimitriou
        # switch back moderation setting
501 d2633501 Kostas Papadimitriou
        astakos_settings.MODERATION_ENABLED = True
502 d2633501 Kostas Papadimitriou
        r = self.client.get(user.get_activation_url(), follow=True)
503 d2633501 Kostas Papadimitriou
        self.assertRedirects(r, "/im/profile")
504 d2633501 Kostas Papadimitriou
        self.assertContains(r, "kpap@grnet.gr")
505 d2633501 Kostas Papadimitriou
        self.assertEqual(len(get_mailbox('kpap@grnet.gr')), 3)
506 d2633501 Kostas Papadimitriou
507 d2633501 Kostas Papadimitriou
        user = AstakosUser.objects.get(pk=user.pk)
508 d2633501 Kostas Papadimitriou
        # user activated and logged in, token cookie set
509 d2633501 Kostas Papadimitriou
        self.assertTrue(r.context['request'].user.is_authenticated())
510 d2633501 Kostas Papadimitriou
        self.assertTrue('_pithos2_a' in self.client.cookies)
511 d2633501 Kostas Papadimitriou
        cookies = self.client.cookies
512 d2633501 Kostas Papadimitriou
        self.assertTrue(quote(user.auth_token) in cookies.get('_pithos2_a').value)
513 d2633501 Kostas Papadimitriou
        r = self.client.get('/im/logout', follow=True)
514 d2633501 Kostas Papadimitriou
        r = self.client.get('/im/')
515 d2633501 Kostas Papadimitriou
        # user logged out, token cookie removed
516 d2633501 Kostas Papadimitriou
        self.assertFalse(r.context['request'].user.is_authenticated())
517 d2633501 Kostas Papadimitriou
        self.assertFalse(self.client.cookies.get('_pithos2_a').value)
518 d2633501 Kostas Papadimitriou
        # https://docs.djangoproject.com/en/dev/topics/testing/#persistent-state
519 d2633501 Kostas Papadimitriou
        del self.client.cookies['_pithos2_a']
520 d2633501 Kostas Papadimitriou
521 d2633501 Kostas Papadimitriou
        # user can login
522 d2633501 Kostas Papadimitriou
        r = self.client.post('/im/local', {'username': 'kpap@grnet.gr',
523 d2633501 Kostas Papadimitriou
                                           'password': 'password'},
524 d2633501 Kostas Papadimitriou
                                          follow=True)
525 d2633501 Kostas Papadimitriou
        self.assertTrue(r.context['request'].user.is_authenticated())
526 d2633501 Kostas Papadimitriou
        self.assertTrue('_pithos2_a' in self.client.cookies)
527 d2633501 Kostas Papadimitriou
        cookies = self.client.cookies
528 d2633501 Kostas Papadimitriou
        self.assertTrue(quote(user.auth_token) in cookies.get('_pithos2_a').value)
529 d2633501 Kostas Papadimitriou
        self.client.get('/im/logout', follow=True)
530 d2633501 Kostas Papadimitriou
531 d2633501 Kostas Papadimitriou
        # user forgot password
532 d2633501 Kostas Papadimitriou
        old_pass = user.password
533 d2633501 Kostas Papadimitriou
        r = self.client.get('/im/local/password_reset')
534 d2633501 Kostas Papadimitriou
        self.assertEqual(r.status_code, 200)
535 d2633501 Kostas Papadimitriou
        r = self.client.post('/im/local/password_reset', {'email':
536 d2633501 Kostas Papadimitriou
                                                          'kpap@grnet.gr'})
537 d2633501 Kostas Papadimitriou
        self.assertEqual(r.status_code, 302)
538 d2633501 Kostas Papadimitriou
        # email sent
539 d2633501 Kostas Papadimitriou
        self.assertEqual(len(get_mailbox('kpap@grnet.gr')), 4)
540 d2633501 Kostas Papadimitriou
541 d2633501 Kostas Papadimitriou
        # user visits change password link
542 d2633501 Kostas Papadimitriou
        r = self.client.get(user.get_password_reset_url())
543 d2633501 Kostas Papadimitriou
        r = self.client.post(user.get_password_reset_url(),
544 d2633501 Kostas Papadimitriou
                            {'new_password1':'newpass',
545 d2633501 Kostas Papadimitriou
                             'new_password2':'newpass'})
546 d2633501 Kostas Papadimitriou
547 d2633501 Kostas Papadimitriou
        user = AstakosUser.objects.get(pk=user.pk)
548 d2633501 Kostas Papadimitriou
        self.assertNotEqual(old_pass, user.password)
549 d2633501 Kostas Papadimitriou
550 d2633501 Kostas Papadimitriou
        # old pass is not usable
551 d2633501 Kostas Papadimitriou
        r = self.client.post('/im/local', {'username': 'kpap@grnet.gr',
552 d2633501 Kostas Papadimitriou
                                           'password': 'password'})
553 ba50648c Kostas Papadimitriou
        self.assertContains(r, messages.ACCOUNT_AUTHENTICATION_FAILED)
554 d2633501 Kostas Papadimitriou
        r = self.client.post('/im/local', {'username': 'kpap@grnet.gr',
555 d2633501 Kostas Papadimitriou
                                           'password': 'newpass'},
556 d2633501 Kostas Papadimitriou
                                           follow=True)
557 d2633501 Kostas Papadimitriou
        self.assertTrue(r.context['request'].user.is_authenticated())
558 d2633501 Kostas Papadimitriou
        self.client.logout()
559 d2633501 Kostas Papadimitriou
560 d2633501 Kostas Papadimitriou
        # tests of special local backends
561 d2633501 Kostas Papadimitriou
        user = AstakosUser.objects.get(pk=user.pk)
562 d2633501 Kostas Papadimitriou
        user.auth_providers.filter(module='local').update(auth_backend='ldap')
563 d2633501 Kostas Papadimitriou
        user.save()
564 d2633501 Kostas Papadimitriou
565 d2633501 Kostas Papadimitriou
        # non astakos local backends do not support password reset
566 d2633501 Kostas Papadimitriou
        r = self.client.get('/im/local/password_reset')
567 d2633501 Kostas Papadimitriou
        self.assertEqual(r.status_code, 200)
568 d2633501 Kostas Papadimitriou
        r = self.client.post('/im/local/password_reset', {'email':
569 d2633501 Kostas Papadimitriou
                                                          'kpap@grnet.gr'})
570 d2633501 Kostas Papadimitriou
        # she can't because account is not active yet
571 d2633501 Kostas Papadimitriou
        self.assertContains(r, "Password change for this account is not"
572 d2633501 Kostas Papadimitriou
                                " supported")