Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / tests.py @ e5966bd9

History | View | Annotate | Download (26 kB)

1
# Copyright 2011 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

    
34
import datetime
35

    
36
from django.test import TestCase, Client
37
from django.conf import settings
38
from django.core import mail
39

    
40
from astakos.im.target.shibboleth import Tokens as ShibbolethTokens
41
from astakos.im.models import *
42
from astakos.im import functions
43
from astakos.im import settings as astakos_settings
44
from astakos.im import forms
45

    
46
from urllib import quote
47

    
48
from astakos.im import messages
49

    
50
class ShibbolethClient(Client):
51
    """
52
    A shibboleth agnostic client.
53
    """
54
    VALID_TOKENS = filter(lambda x: not x.startswith("_"), dir(ShibbolethTokens))
55

    
56
    def __init__(self, *args, **kwargs):
57
        self.tokens = kwargs.pop('tokens', {})
58
        super(ShibbolethClient, self).__init__(*args, **kwargs)
59

    
60
    def set_tokens(self, **kwargs):
61
        for key, value in kwargs.iteritems():
62
            key = 'SHIB_%s' % key.upper()
63
            if not key in self.VALID_TOKENS:
64
                raise Exception('Invalid shibboleth token')
65

    
66
            self.tokens[key] = value
67

    
68
    def unset_tokens(self, *keys):
69
        for key in keys:
70
            key = 'SHIB_%s' % param.upper()
71
            if key in self.tokens:
72
                del self.tokens[key]
73

    
74
    def reset_tokens(self):
75
        self.tokens = {}
76

    
77
    def get_http_token(self, key):
78
        http_header = getattr(ShibbolethTokens, key)
79
        return http_header
80

    
81
    def request(self, **request):
82
        """
83
        Transform valid shibboleth tokens to http headers
84
        """
85
        for token, value in self.tokens.iteritems():
86
            request[self.get_http_token(token)] = value
87

    
88
        for param in request.keys():
89
            key = 'SHIB_%s' % param.upper()
90
            if key in self.VALID_TOKENS:
91
                request[self.get_http_token(key)] = request[param]
92
                del request[param]
93

    
94
        return super(ShibbolethClient, self).request(**request)
95

    
96

    
97
def get_local_user(username, **kwargs):
98
        try:
99
            return AstakosUser.objects.get(email=username)
100
        except:
101
            user_params = {
102
                'username': username,
103
                'email': username,
104
                'is_active': True,
105
                'activation_sent': datetime.now(),
106
                'email_verified': True,
107
                'provider': 'local'
108
            }
109
            user_params.update(kwargs)
110
            user = AstakosUser(**user_params)
111
            user.set_password(kwargs.get('password', 'password'))
112
            user.save()
113
            user.add_auth_provider('local', auth_backend='astakos')
114
            if kwargs.get('is_active', True):
115
                user.is_active = True
116
            else:
117
                user.is_active = False
118
            user.save()
119
            return user
120

    
121

    
122
def get_mailbox(email):
123
    mails = []
124
    for sent_email in mail.outbox:
125
        for recipient in sent_email.recipients():
126
            if email in recipient:
127
                mails.append(sent_email)
128
    return mails
129

    
130

    
131
class ShibbolethTests(TestCase):
132
    """
133
    Testing shibboleth authentication.
134
    """
135

    
136
    fixtures = ['groups']
137

    
138
    def setUp(self):
139
        self.client = ShibbolethClient()
140
        settings.ASTAKOS_IM_MODULES = ['local', 'shibboleth']
141
        settings.ASTAKOS_MODERATION_ENABLED = True
142

    
143
    def test_create_account(self):
144

    
145
        client = ShibbolethClient()
146

    
147
        # shibboleth views validation
148
        # eepn required
149
        r = client.get('/im/login/shibboleth?', follow=True)
150
        self.assertContains(r, messages.SHIBBOLETH_MISSING_EPPN)
151
        client.set_tokens(eppn="kpapeppn")
152

    
153
        astakos_settings.SHIBBOLETH_REQUIRE_NAME_INFO = True
154
        # shibboleth user info required
155
        r = client.get('/im/login/shibboleth?', follow=True)
156
        self.assertContains(r, messages.SHIBBOLETH_MISSING_NAME)
157
        astakos_settings.SHIBBOLETH_REQUIRE_NAME_INFO = False
158

    
159
        # shibboleth logged us in
160
        client.set_tokens(mail="kpap@grnet.gr", eppn="kpapeppn",
161
                          cn="Kostas Papadimitriou",
162
                          ep_affiliation="Test Affiliation")
163
        r = client.get('/im/login/shibboleth?')
164
        self.assertEqual(r.status_code, 200)
165

    
166
        # astakos asks if we want to add shibboleth
167
        self.assertContains(r, "Already have an account?")
168

    
169
        # a new pending user created
170
        pending_user = PendingThirdPartyUser.objects.get(
171
            third_party_identifier="kpapeppn")
172
        self.assertEqual(PendingThirdPartyUser.objects.count(), 1)
173
        # keep the token for future use
174
        token = pending_user.token
175
        # from now on no shibboleth headers are sent to the server
176
        client.reset_tokens()
177

    
178
        # this is the old way, it should fail, to avoid pending user take over
179
        r = client.get('/im/shibboleth/signup/%s' % pending_user.username)
180
        self.assertEqual(r.status_code, 404)
181

    
182
        # this is the signup unique url associated with the pending user created
183
        r = client.get('/im/signup/?third_party_token=%s' % token)
184
        form = r.context['form']
185
        post_data = {'third_party_identifier': pending_user.third_party_identifier,
186
                     'first_name': 'Kostas',
187
                     'third_party_token': token,
188
                     'last_name': 'Mitroglou',
189
                     'provider': 'shibboleth'
190
                    }
191

    
192
        # invlid email
193
        post_data['email'] = 'kpap'
194
        r = client.post('/im/signup', post_data)
195
        self.assertContains(r, token)
196

    
197
        # existing email
198
        existing_user = get_local_user('test@test.com')
199
        post_data['email'] = 'test@test.com'
200
        r = client.post('/im/signup', post_data)
201
        self.assertContains(r, messages.EMAIL_USED)
202
        existing_user.delete()
203

    
204
        # and finally a valid signup
205
        post_data['email'] = 'kpap@grnet.gr'
206
        r = client.post('/im/signup', post_data, follow=True)
207
        self.assertContains(r, messages.NOTIFICATION_SENT)
208

    
209
        # everything is ok in our db
210
        self.assertEqual(AstakosUser.objects.count(), 1)
211
        self.assertEqual(AstakosUserAuthProvider.objects.count(), 1)
212
        self.assertEqual(PendingThirdPartyUser.objects.count(), 0)
213

    
214
        # provider info stored
215
        provider = AstakosUserAuthProvider.objects.get(module="shibboleth")
216
        self.assertEqual(provider.affiliation, 'Test Affiliation')
217
        self.assertEqual(provider.info, {u'email': u'kpap@grnet.gr',
218
                                         u'eppn': u'kpapeppn',
219
                                         u'name': u'Kostas Papadimitriou'})
220

    
221
        # lets login (not activated yet)
222
        client.set_tokens(mail="kpap@grnet.gr", eppn="kpapeppn",
223
                          cn="Kostas Papadimitriou", )
224
        r = client.get("/im/login/shibboleth?", follow=True)
225
        self.assertContains(r, messages.ACCOUNT_PENDING_MODERATION)
226
        r = client.get("/im/profile", follow=True)
227
        self.assertRedirects(r, 'http://testserver/im/?next=%2Fim%2Fprofile')
228

    
229
        # admin activates our user
230
        u = AstakosUser.objects.get(username="kpap@grnet.gr")
231
        functions.activate(u)
232
        self.assertEqual(u.is_active, True)
233

    
234
        # we see our profile
235
        r = client.get("/im/login/shibboleth?", follow=True)
236
        self.assertRedirects(r, '/im/profile')
237
        self.assertEqual(r.status_code, 200)
238

    
239
    def test_existing(self):
240
        """
241
        Test adding of third party login to an existing account
242
        """
243

    
244
        # this is our existing user
245
        existing_user = get_local_user('kpap@grnet.gr')
246

    
247
        client = ShibbolethClient()
248
        # shibboleth logged us in, notice that we use different email
249
        client.set_tokens(mail="kpap@shibboleth.gr", eppn="kpapeppn",
250
                          cn="Kostas Papadimitriou", )
251
        r = client.get("/im/login/shibboleth?")
252

    
253
        # astakos asks if we want to switch a local account to shibboleth
254
        self.assertContains(r, "Already have an account?")
255

    
256
        # a new pending user created
257
        pending_user = PendingThirdPartyUser.objects.get()
258
        self.assertEqual(PendingThirdPartyUser.objects.count(), 1)
259
        pending_key = pending_user.token
260
        client.reset_tokens()
261

    
262
        # we choose to add shibboleth to an our existing account
263
        # we get redirected to login page with the pending token set
264
        r = client.get('/im/login?key=%s' % pending_key)
265
        post_data = {'password': 'password',
266
                     'username': 'kpap@grnet.gr',
267
                     'key': pending_key}
268
        r = client.post('/im/local', post_data, follow=True)
269
        self.assertRedirects(r, "/im/profile")
270
        self.assertContains(r, messages.AUTH_PROVIDER_ADDED)
271

    
272
        self.assertTrue(existing_user.has_auth_provider('shibboleth'))
273
        self.assertTrue(existing_user.has_auth_provider('local',
274
                                                        auth_backend='astakos'))
275
        client.logout()
276

    
277
        # check that we cannot assign same third party provide twice
278
        r = client.get('/im/login?key=%s' % pending_key)
279
        post_data = {'password': 'password',
280
                     'username': 'kpap@grnet.gr',
281
                     'key': pending_key}
282
        r = self.client.post('/im/local', post_data, follow=True)
283
        self.assertContains(r, messages.AUTH_PROVIDER_ADD_FAILED)
284
        self.client.logout()
285
        client.logout()
286

    
287
        # look Ma, i can login with both my shibboleth and local account
288
        client.set_tokens(mail="kpap@shibboleth.gr", eppn="kpapeppn",
289
                          cn="Kostas Papadimitriou")
290
        r = client.get("/im/login/shibboleth?", follow=True)
291
        self.assertTrue(r.context['request'].user.is_authenticated())
292
        self.assertTrue(r.context['request'].user.email == "kpap@grnet.gr")
293
        self.assertRedirects(r, '/im/profile')
294
        self.assertEqual(r.status_code, 200)
295
        client.logout()
296
        client.reset_tokens()
297

    
298
        # logged out
299
        r = client.get("/im/profile", follow=True)
300
        self.assertFalse(r.context['request'].user.is_authenticated())
301

    
302
        # login with local account also works
303
        post_data = {'password': 'password',
304
                     'username': 'kpap@grnet.gr'}
305
        r = self.client.post('/im/local', post_data, follow=True)
306
        self.assertTrue(r.context['request'].user.is_authenticated())
307
        self.assertTrue(r.context['request'].user.email == "kpap@grnet.gr")
308
        self.assertRedirects(r, '/im/profile')
309
        self.assertEqual(r.status_code, 200)
310

    
311
        # cannot add the same eppn
312
        client.set_tokens(mail="secondary@shibboleth.gr", eppn="kpapeppn",
313
                          cn="Kostas Papadimitriou", )
314
        r = client.get("/im/login/shibboleth?", follow=True)
315
        self.assertRedirects(r, '/im/profile')
316
        self.assertTrue(r.status_code, 200)
317
        self.assertEquals(existing_user.auth_providers.count(), 2)
318

    
319
        # but can add additional eppn
320
        client.set_tokens(mail="secondary@shibboleth.gr", eppn="kpapeppn2",
321
                          cn="Kostas Papadimitriou", ep_affiliation="affil2")
322
        r = client.get("/im/login/shibboleth?", follow=True)
323
        new_provider = existing_user.auth_providers.get(identifier="kpapeppn2")
324
        self.assertRedirects(r, '/im/profile')
325
        self.assertTrue(r.status_code, 200)
326
        self.assertEquals(existing_user.auth_providers.count(), 3)
327
        self.assertEqual(new_provider.affiliation, 'affil2')
328
        client.logout()
329
        client.reset_tokens()
330

    
331
        # cannot login with another eppn
332
        client.set_tokens(mail="kpap@grnet.gr", eppn="kpapeppninvalid",
333
                          cn="Kostas Papadimitriou")
334
        r = client.get("/im/login/shibboleth?", follow=True)
335
        self.assertFalse(r.context['request'].user.is_authenticated())
336

    
337
        # lets remove local password
338
        user = AstakosUser.objects.get(username="kpap@grnet.gr",
339
                                       email="kpap@grnet.gr")
340
        remove_local_url = user.get_provider_remove_url('local')
341
        remove_shibbo_url = user.get_provider_remove_url('shibboleth',
342
                                                         identifier='kpapeppn')
343
        remove_shibbo2_url = user.get_provider_remove_url('shibboleth',
344
                                                         identifier='kpapeppn2')
345
        client.set_tokens(mail="kpap@shibboleth.gr", eppn="kpapeppn",
346
                          cn="Kostas Papadimtriou")
347
        r = client.get("/im/login/shibboleth?", follow=True)
348
        client.reset_tokens()
349

    
350
        # TODO: this view should use POST
351
        r = client.get(remove_local_url)
352
        # 2 providers left
353
        self.assertEqual(user.auth_providers.count(), 2)
354
        r = client.get(remove_shibbo2_url)
355
        # 1 provider left
356
        self.assertEqual(user.auth_providers.count(), 1)
357
        # cannot remove last provider
358
        r = client.get(remove_shibbo_url)
359
        self.assertEqual(r.status_code, 403)
360
        self.client.logout()
361

    
362
        # cannot login using local credentials (notice we use another client)
363
        post_data = {'password': 'password',
364
                     'username': 'kpap@grnet.gr'}
365
        r = self.client.post('/im/local', post_data, follow=True)
366
        self.assertFalse(r.context['request'].user.is_authenticated())
367

    
368
        # we can reenable the local provider by setting a password
369
        r = client.get("/im/password_change", follow=True)
370
        r = client.post("/im/password_change", {'new_password1':'111',
371
                                                'new_password2': '111'},
372
                        follow=True)
373
        user = r.context['request'].user
374
        self.assertTrue(user.has_auth_provider('local'))
375
        self.assertTrue(user.has_auth_provider('shibboleth'))
376
        self.assertTrue(user.check_password('111'))
377
        self.assertTrue(user.has_usable_password())
378
        self.client.logout()
379

    
380
        # now we can login
381
        post_data = {'password': '111',
382
                     'username': 'kpap@grnet.gr'}
383
        r = self.client.post('/im/local', post_data, follow=True)
384
        self.assertTrue(r.context['request'].user.is_authenticated())
385

    
386
        client.reset_tokens()
387

    
388
        # we cannot take over another shibboleth identifier
389
        user2 = get_local_user('another@grnet.gr')
390
        user2.add_auth_provider('shibboleth', identifier='existingeppn')
391
        # login
392
        client.set_tokens(mail="kpap@shibboleth.gr", eppn="kpapeppn",
393
                          cn="Kostas Papadimitriou")
394
        r = client.get("/im/login/shibboleth?", follow=True)
395
        # try to assign existing shibboleth identifier of another user
396
        client.set_tokens(mail="kpap_second@shibboleth.gr", eppn="existingeppn",
397
                          cn="Kostas Papadimitriou")
398
        r = client.get("/im/login/shibboleth?", follow=True)
399
        self.assertContains(r, messages.AUTH_PROVIDER_ADD_FAILED)
400
        self.assertContains(r, messages.AUTH_PROVIDER_ADD_EXISTS)
401

    
402

    
403
class LocalUserTests(TestCase):
404

    
405
    fixtures = ['groups']
406

    
407
    def setUp(self):
408
        from django.conf import settings
409
        settings.ADMINS = (('admin', 'support@cloud.grnet.gr'),)
410
        settings.SERVER_EMAIL = 'no-reply@grnet.gr'
411

    
412
    def test_no_moderation(self):
413
        # disable moderation
414
        astakos_settings.MODERATION_ENABLED = False
415

    
416
        # create a new user
417
        r = self.client.get("/im/signup")
418
        self.assertEqual(r.status_code, 200)
419
        data = {'email':'kpap@grnet.gr', 'password1':'password',
420
                'password2':'password', 'first_name': 'Kostas',
421
                'last_name': 'Mitroglou', 'provider': 'local'}
422
        r = self.client.post("/im/signup", data)
423

    
424
        # user created
425
        self.assertEqual(AstakosUser.objects.count(), 1)
426
        user = AstakosUser.objects.get(username="kpap@grnet.gr",
427
                                       email="kpap@grnet.gr")
428
        self.assertEqual(user.username, 'kpap@grnet.gr')
429
        self.assertEqual(user.has_auth_provider('local'), True)
430
        self.assertFalse(user.is_active)
431

    
432
        # user (but not admin) gets notified
433
        self.assertEqual(len(get_mailbox('support@cloud.grnet.gr')), 0)
434
        self.assertEqual(len(get_mailbox('kpap@grnet.gr')), 1)
435
        astakos_settings.MODERATION_ENABLED = True
436

    
437
    def test_email_case(self):
438
        data = {
439
          'email': 'kPap@grnet.gr',
440
          'password1': '1234',
441
          'password2': '1234'
442
        }
443

    
444
        form = forms.LocalUserCreationForm(data)
445
        self.assertTrue(form.is_valid())
446
        user = form.save()
447
        form.store_user(user, {})
448

    
449
        u = AstakosUser.objects.get(pk=1)
450
        self.assertEqual(u.email, 'kPap@grnet.gr')
451
        self.assertEqual(u.username, 'kpap@grnet.gr')
452
        u.is_active = True
453
        u.email_verified = True
454
        u.save()
455

    
456
        data = {'username': 'kpap@grnet.gr', 'password': '1234'}
457
        login = forms.LoginForm(data=data)
458
        self.assertTrue(login.is_valid())
459

    
460
        data = {'username': 'KpaP@grnet.gr', 'password': '1234'}
461
        login = forms.LoginForm(data=data)
462
        self.assertTrue(login.is_valid())
463

    
464
        data = {
465
          'email': 'kpap@grnet.gr',
466
          'password1': '1234',
467
          'password2': '1234'
468
        }
469
        form = forms.LocalUserCreationForm(data)
470
        self.assertFalse(form.is_valid())
471

    
472
    def test_local_provider(self):
473
        # enable moderation
474
        astakos_settings.MODERATION_ENABLED = True
475

    
476
        # create a user
477
        r = self.client.get("/im/signup")
478
        self.assertEqual(r.status_code, 200)
479
        data = {'email':'kpap@grnet.gr', 'password1':'password',
480
                'password2':'password', 'first_name': 'Kostas',
481
                'last_name': 'Mitroglou', 'provider': 'local'}
482
        r = self.client.post("/im/signup", data)
483

    
484
        # user created
485
        self.assertEqual(AstakosUser.objects.count(), 1)
486
        user = AstakosUser.objects.get(username="kpap@grnet.gr",
487
                                       email="kpap@grnet.gr")
488
        self.assertEqual(user.username, 'kpap@grnet.gr')
489
        self.assertEqual(user.has_auth_provider('local'), True)
490
        self.assertFalse(user.is_active) # not activated
491
        self.assertFalse(user.email_verified) # not verified
492
        self.assertFalse(user.activation_sent) # activation automatically sent
493

    
494
        # admin gets notified and activates the user from the command line
495
        self.assertEqual(len(get_mailbox('support@cloud.grnet.gr')), 1)
496
        r = self.client.post('/im/local', {'username': 'kpap@grnet.gr',
497
                                                 'password': 'password'})
498
        self.assertContains(r, messages.ACCOUNT_PENDING_MODERATION)
499
        functions.send_activation(user)
500

    
501
        # user activation fields updated and user gets notified via email
502
        user = AstakosUser.objects.get(pk=user.pk)
503
        self.assertTrue(user.activation_sent)
504
        self.assertFalse(user.email_verified)
505
        self.assertEqual(len(get_mailbox('kpap@grnet.gr')), 1)
506

    
507
        # user forgot she got registered and tries to submit registration
508
        # form. Notice the upper case in email
509
        data = {'email':'KPAP@grnet.gr', 'password1':'password',
510
                'password2':'password', 'first_name': 'Kostas',
511
                'last_name': 'Mitroglou', 'provider': 'local'}
512
        r = self.client.post("/im/signup", data)
513
        self.assertContains(r, messages.EMAIL_USED)
514

    
515
        # hmmm, email exists; lets get the password
516
        r = self.client.get('/im/local/password_reset')
517
        self.assertEqual(r.status_code, 200)
518
        r = self.client.post('/im/local/password_reset', {'email':
519
                                                          'kpap@grnet.gr'})
520
        # she can't because account is not active yet
521
        self.assertContains(r, "doesn't have an associated user account")
522

    
523
        # moderation is enabled so no automatic activation can be send
524
        r = self.client.get('/im/send/activation/%d' % user.pk)
525
        self.assertEqual(r.status_code, 403)
526
        self.assertEqual(len(get_mailbox('kpap@grnet.gr')), 1)
527

    
528
        # also she cannot login
529
        r = self.client.post('/im/local', {'username': 'kpap@grnet.gr',
530
                                                 'password': 'password'})
531
        self.assertContains(r, messages.ACCOUNT_PENDING_ACTIVATION_HELP)
532
        self.assertContains(r, messages.ACCOUNT_PENDING_ACTIVATION)
533
        self.assertNotContains(r, 'Resend activation')
534
        self.assertFalse(r.context['request'].user.is_authenticated())
535
        self.assertFalse('_pithos2_a' in self.client.cookies)
536

    
537
        # same with disabled moderation
538
        astakos_settings.MODERATION_ENABLED = False
539
        r = self.client.post('/im/local/password_reset', {'email':
540
                                                          'kpap@grnet.gr'})
541
        self.assertContains(r, "doesn't have an associated user account")
542
        r = self.client.post('/im/local', {'username': 'kpap@grnet.gr',
543
                                                 'password': 'password'})
544
        self.assertContains(r, messages.ACCOUNT_PENDING_ACTIVATION)
545
        self.assertContains(r, 'Resend activation')
546
        self.assertFalse(r.context['request'].user.is_authenticated())
547
        self.assertFalse('_pithos2_a' in self.client.cookies)
548

    
549
        # user sees the message and resends activation
550
        r = self.client.get('/im/send/activation/%d' % user.pk)
551
        self.assertEqual(len(get_mailbox('kpap@grnet.gr')), 2)
552

    
553
        # switch back moderation setting
554
        astakos_settings.MODERATION_ENABLED = True
555
        r = self.client.get(user.get_activation_url(), follow=True)
556
        self.assertRedirects(r, "/im/profile")
557
        self.assertContains(r, "kpap@grnet.gr")
558
        self.assertEqual(len(get_mailbox('kpap@grnet.gr')), 3)
559

    
560
        user = AstakosUser.objects.get(pk=user.pk)
561
        # user activated and logged in, token cookie set
562
        self.assertTrue(r.context['request'].user.is_authenticated())
563
        self.assertTrue('_pithos2_a' in self.client.cookies)
564
        cookies = self.client.cookies
565
        self.assertTrue(quote(user.auth_token) in cookies.get('_pithos2_a').value)
566
        r = self.client.get('/im/logout', follow=True)
567
        r = self.client.get('/im/')
568
        # user logged out, token cookie removed
569
        self.assertFalse(r.context['request'].user.is_authenticated())
570
        self.assertFalse(self.client.cookies.get('_pithos2_a').value)
571
        # https://docs.djangoproject.com/en/dev/topics/testing/#persistent-state
572
        del self.client.cookies['_pithos2_a']
573

    
574
        # user can login
575
        r = self.client.post('/im/local', {'username': 'kpap@grnet.gr',
576
                                           'password': 'password'},
577
                                          follow=True)
578
        self.assertTrue(r.context['request'].user.is_authenticated())
579
        self.assertTrue('_pithos2_a' in self.client.cookies)
580
        cookies = self.client.cookies
581
        self.assertTrue(quote(user.auth_token) in cookies.get('_pithos2_a').value)
582
        self.client.get('/im/logout', follow=True)
583

    
584
        # user forgot password
585
        old_pass = user.password
586
        r = self.client.get('/im/local/password_reset')
587
        self.assertEqual(r.status_code, 200)
588
        r = self.client.post('/im/local/password_reset', {'email':
589
                                                          'kpap@grnet.gr'})
590
        self.assertEqual(r.status_code, 302)
591
        # email sent
592
        self.assertEqual(len(get_mailbox('kpap@grnet.gr')), 4)
593

    
594
        # user visits change password link
595
        r = self.client.get(user.get_password_reset_url())
596
        r = self.client.post(user.get_password_reset_url(),
597
                            {'new_password1':'newpass',
598
                             'new_password2':'newpass'})
599

    
600
        user = AstakosUser.objects.get(pk=user.pk)
601
        self.assertNotEqual(old_pass, user.password)
602

    
603
        # old pass is not usable
604
        r = self.client.post('/im/local', {'username': 'kpap@grnet.gr',
605
                                           'password': 'password'})
606
        self.assertContains(r, 'Please enter a correct username and password')
607
        r = self.client.post('/im/local', {'username': 'kpap@grnet.gr',
608
                                           'password': 'newpass'},
609
                                           follow=True)
610
        self.assertTrue(r.context['request'].user.is_authenticated())
611
        self.client.logout()
612

    
613
        # tests of special local backends
614
        user = AstakosUser.objects.get(pk=user.pk)
615
        user.auth_providers.filter(module='local').update(auth_backend='ldap')
616
        user.save()
617

    
618
        # non astakos local backends do not support password reset
619
        r = self.client.get('/im/local/password_reset')
620
        self.assertEqual(r.status_code, 200)
621
        r = self.client.post('/im/local/password_reset', {'email':
622
                                                          'kpap@grnet.gr'})
623
        # she can't because account is not active yet
624
        self.assertContains(r, "Password change for this account is not"
625
                                " supported")
626