Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (24.4 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 31fdafa8 Kostas Papadimitriou
        astakos_settings.SHIBBOLETH_REQUIRE_NAME_INFO = True
153 d2633501 Kostas Papadimitriou
        # shibboleth user info required
154 d2633501 Kostas Papadimitriou
        r = client.get('/im/login/shibboleth?', follow=True)
155 2e90e3ec Kostas Papadimitriou
        self.assertContains(r, messages.SHIBBOLETH_MISSING_NAME)
156 31fdafa8 Kostas Papadimitriou
        astakos_settings.SHIBBOLETH_REQUIRE_NAME_INFO = False
157 d2633501 Kostas Papadimitriou
158 d2633501 Kostas Papadimitriou
        # shibboleth logged us in
159 ba50648c Kostas Papadimitriou
        client.set_tokens(mail="kpap@grnet.gr", eppn="kpapeppn", cn="1",
160 ba50648c Kostas Papadimitriou
                          ep_affiliation="Test Affiliation")
161 d2633501 Kostas Papadimitriou
        r = client.get('/im/login/shibboleth?')
162 ba50648c Kostas Papadimitriou
        self.assertEqual(r.status_code, 200)
163 d2633501 Kostas Papadimitriou
164 d2633501 Kostas Papadimitriou
        # astakos asks if we want to add shibboleth
165 d2633501 Kostas Papadimitriou
        self.assertContains(r, "Already have an account?")
166 d2633501 Kostas Papadimitriou
167 d2633501 Kostas Papadimitriou
        # a new pending user created
168 d2633501 Kostas Papadimitriou
        pending_user = PendingThirdPartyUser.objects.get(
169 d2633501 Kostas Papadimitriou
            third_party_identifier="kpapeppn")
170 d2633501 Kostas Papadimitriou
        self.assertEqual(PendingThirdPartyUser.objects.count(), 1)
171 ba50648c Kostas Papadimitriou
        # keep the token for future use
172 d2633501 Kostas Papadimitriou
        token = pending_user.token
173 d2633501 Kostas Papadimitriou
        # from now on no shibboleth headers are sent to the server
174 d2633501 Kostas Papadimitriou
        client.reset_tokens()
175 d2633501 Kostas Papadimitriou
176 ba50648c Kostas Papadimitriou
        # this is the old way, it should fail, to avoid pending user take over
177 d2633501 Kostas Papadimitriou
        r = client.get('/im/shibboleth/signup/%s' % pending_user.username)
178 d2633501 Kostas Papadimitriou
        self.assertEqual(r.status_code, 404)
179 d2633501 Kostas Papadimitriou
180 ba50648c Kostas Papadimitriou
        # this is the signup unique url associated with the pending user created
181 ba50648c Kostas Papadimitriou
        r = client.get('/im/signup/?third_party_token=%s' % token)
182 d2633501 Kostas Papadimitriou
        form = r.context['form']
183 ba50648c Kostas Papadimitriou
        post_data = {'third_party_identifier': pending_user.third_party_identifier,
184 d2633501 Kostas Papadimitriou
                     'first_name': 'Kostas',
185 d2633501 Kostas Papadimitriou
                     'third_party_token': token,
186 d2633501 Kostas Papadimitriou
                     'last_name': 'Mitroglou',
187 d2633501 Kostas Papadimitriou
                     'provider': 'shibboleth'
188 d2633501 Kostas Papadimitriou
                    }
189 ba50648c Kostas Papadimitriou
190 ba50648c Kostas Papadimitriou
        # invlid email
191 ba50648c Kostas Papadimitriou
        post_data['email'] = 'kpap'
192 d2633501 Kostas Papadimitriou
        r = client.post('/im/signup', post_data)
193 8ab484ea Kostas Papadimitriou
        self.assertContains(r, token)
194 ba50648c Kostas Papadimitriou
195 ba50648c Kostas Papadimitriou
        # existing email
196 ba50648c Kostas Papadimitriou
        existing_user = get_local_user('test@test.com')
197 ba50648c Kostas Papadimitriou
        post_data['email'] = 'test@test.com'
198 ba50648c Kostas Papadimitriou
        r = client.post('/im/signup', post_data)
199 ba50648c Kostas Papadimitriou
        self.assertContains(r, messages.EMAIL_USED)
200 ba50648c Kostas Papadimitriou
        existing_user.delete()
201 ba50648c Kostas Papadimitriou
202 ba50648c Kostas Papadimitriou
        # and finally a valid signup
203 8ab484ea Kostas Papadimitriou
        post_data['email'] = 'kpap@grnet.gr'
204 8ab484ea Kostas Papadimitriou
        r = client.post('/im/signup', post_data)
205 d2633501 Kostas Papadimitriou
        self.assertEqual(r.status_code, 200)
206 ba50648c Kostas Papadimitriou
207 ba50648c Kostas Papadimitriou
        # everything is ok in our db
208 d2633501 Kostas Papadimitriou
        self.assertEqual(AstakosUser.objects.count(), 1)
209 d2633501 Kostas Papadimitriou
        self.assertEqual(AstakosUserAuthProvider.objects.count(), 1)
210 ba50648c Kostas Papadimitriou
        self.assertEqual(PendingThirdPartyUser.objects.count(), 0)
211 d2633501 Kostas Papadimitriou
212 ba50648c Kostas Papadimitriou
        # provider info stored
213 ba50648c Kostas Papadimitriou
        provider = AstakosUserAuthProvider.objects.get(module="shibboleth")
214 ba50648c Kostas Papadimitriou
        self.assertEqual(provider.affiliation, 'Test Affiliation')
215 ba50648c Kostas Papadimitriou
        self.assertEqual(provider.info, {u'email': u'kpap@grnet.gr',
216 ba50648c Kostas Papadimitriou
                                         u'eppn': u'kpapeppn'})
217 d2633501 Kostas Papadimitriou
218 ba50648c Kostas Papadimitriou
        # lets login (not activated yet)
219 d2633501 Kostas Papadimitriou
        client.set_tokens(mail="kpap@grnet.gr", eppn="kpapeppn", cn="1", )
220 d2633501 Kostas Papadimitriou
        r = client.get("/im/login/shibboleth?", follow=True)
221 31fdafa8 Kostas Papadimitriou
        self.assertContains(r, messages.ACCOUNT_PENDING_MODERATION)
222 d2633501 Kostas Papadimitriou
        r = client.get("/im/profile", follow=True)
223 d2633501 Kostas Papadimitriou
        self.assertRedirects(r, 'http://testserver/im/?next=%2Fim%2Fprofile')
224 d2633501 Kostas Papadimitriou
225 ba50648c Kostas Papadimitriou
        # admin activates our user
226 ba50648c Kostas Papadimitriou
        u = AstakosUser.objects.get(username="kpap@grnet.gr")
227 d2633501 Kostas Papadimitriou
        functions.activate(u)
228 d2633501 Kostas Papadimitriou
        self.assertEqual(u.is_active, True)
229 d2633501 Kostas Papadimitriou
230 ba50648c Kostas Papadimitriou
        # we see our profile
231 ba50648c Kostas Papadimitriou
        r = client.get("/im/login/shibboleth?", follow=True)
232 d2633501 Kostas Papadimitriou
        self.assertRedirects(r, '/im/profile')
233 ba50648c Kostas Papadimitriou
        self.assertEqual(r.status_code, 200)
234 d2633501 Kostas Papadimitriou
235 d2633501 Kostas Papadimitriou
    def test_existing(self):
236 ba50648c Kostas Papadimitriou
        """
237 ba50648c Kostas Papadimitriou
        Test adding of third party login to an existing account
238 ba50648c Kostas Papadimitriou
        """
239 ba50648c Kostas Papadimitriou
240 ba50648c Kostas Papadimitriou
        # this is our existing user
241 d2633501 Kostas Papadimitriou
        existing_user = get_local_user('kpap@grnet.gr')
242 d2633501 Kostas Papadimitriou
243 d2633501 Kostas Papadimitriou
        client = ShibbolethClient()
244 d2633501 Kostas Papadimitriou
        # shibboleth logged us in, notice that we use different email
245 d2633501 Kostas Papadimitriou
        client.set_tokens(mail="kpap@shibboleth.gr", eppn="kpapeppn", cn="1", )
246 d2633501 Kostas Papadimitriou
        r = client.get("/im/login/shibboleth?")
247 ba50648c Kostas Papadimitriou
248 d2633501 Kostas Papadimitriou
        # astakos asks if we want to switch a local account to shibboleth
249 d2633501 Kostas Papadimitriou
        self.assertContains(r, "Already have an account?")
250 d2633501 Kostas Papadimitriou
251 d2633501 Kostas Papadimitriou
        # a new pending user created
252 d2633501 Kostas Papadimitriou
        pending_user = PendingThirdPartyUser.objects.get()
253 d2633501 Kostas Papadimitriou
        self.assertEqual(PendingThirdPartyUser.objects.count(), 1)
254 d2633501 Kostas Papadimitriou
        pending_key = pending_user.token
255 d2633501 Kostas Papadimitriou
        client.reset_tokens()
256 d2633501 Kostas Papadimitriou
257 d2633501 Kostas Papadimitriou
        # we choose to add shibboleth to an our existing account
258 d2633501 Kostas Papadimitriou
        # we get redirected to login page with the pending token set
259 d2633501 Kostas Papadimitriou
        r = client.get('/im/login?key=%s' % pending_key)
260 d2633501 Kostas Papadimitriou
        post_data = {'password': 'password',
261 d2633501 Kostas Papadimitriou
                     'username': 'kpap@grnet.gr',
262 d2633501 Kostas Papadimitriou
                     'key': pending_key}
263 d2633501 Kostas Papadimitriou
        r = client.post('/im/local', post_data, follow=True)
264 ba50648c Kostas Papadimitriou
        self.assertRedirects(r, "/im/profile")
265 d2633501 Kostas Papadimitriou
        self.assertContains(r, "Your new login method has been added")
266 d2633501 Kostas Papadimitriou
267 ba50648c Kostas Papadimitriou
        self.assertTrue(existing_user.has_auth_provider('shibboleth'))
268 ba50648c Kostas Papadimitriou
        self.assertTrue(existing_user.has_auth_provider('local',
269 ba50648c Kostas Papadimitriou
                                                        auth_backend='astakos'))
270 d2633501 Kostas Papadimitriou
        client.logout()
271 d2633501 Kostas Papadimitriou
272 ba50648c Kostas Papadimitriou
        # check that we cannot assign same third party provide twice
273 d2633501 Kostas Papadimitriou
        r = client.get('/im/login?key=%s' % pending_key)
274 d2633501 Kostas Papadimitriou
        post_data = {'password': 'password',
275 d2633501 Kostas Papadimitriou
                     'username': 'kpap@grnet.gr',
276 d2633501 Kostas Papadimitriou
                     'key': pending_key}
277 d2633501 Kostas Papadimitriou
        r = self.client.post('/im/local', post_data, follow=True)
278 31fdafa8 Kostas Papadimitriou
        self.assertContains(r, messages.AUTH_PROVIDER_ADD_FAILED)
279 d2633501 Kostas Papadimitriou
        self.client.logout()
280 d2633501 Kostas Papadimitriou
        client.logout()
281 d2633501 Kostas Papadimitriou
282 d2633501 Kostas Papadimitriou
        # look Ma, i can login with both my shibboleth and local account
283 d2633501 Kostas Papadimitriou
        client.set_tokens(mail="kpap@shibboleth.gr", eppn="kpapeppn", cn="1")
284 d2633501 Kostas Papadimitriou
        r = client.get("/im/login/shibboleth?", follow=True)
285 d2633501 Kostas Papadimitriou
        self.assertTrue(r.context['request'].user.is_authenticated())
286 d2633501 Kostas Papadimitriou
        self.assertTrue(r.context['request'].user.email == "kpap@grnet.gr")
287 ba50648c Kostas Papadimitriou
        self.assertRedirects(r, '/im/profile')
288 ba50648c Kostas Papadimitriou
        self.assertEqual(r.status_code, 200)
289 d2633501 Kostas Papadimitriou
        client.logout()
290 d2633501 Kostas Papadimitriou
        client.reset_tokens()
291 ba50648c Kostas Papadimitriou
292 ba50648c Kostas Papadimitriou
        # logged out
293 d2633501 Kostas Papadimitriou
        r = client.get("/im/profile", follow=True)
294 d2633501 Kostas Papadimitriou
        self.assertFalse(r.context['request'].user.is_authenticated())
295 d2633501 Kostas Papadimitriou
296 ba50648c Kostas Papadimitriou
        # login with local account also works
297 d2633501 Kostas Papadimitriou
        post_data = {'password': 'password',
298 d2633501 Kostas Papadimitriou
                     'username': 'kpap@grnet.gr'}
299 d2633501 Kostas Papadimitriou
        r = self.client.post('/im/local', post_data, follow=True)
300 d2633501 Kostas Papadimitriou
        self.assertTrue(r.context['request'].user.is_authenticated())
301 ba50648c Kostas Papadimitriou
        self.assertTrue(r.context['request'].user.email == "kpap@grnet.gr")
302 ba50648c Kostas Papadimitriou
        self.assertRedirects(r, '/im/profile')
303 ba50648c Kostas Papadimitriou
        self.assertEqual(r.status_code, 200)
304 d2633501 Kostas Papadimitriou
305 ba50648c Kostas Papadimitriou
        # cannot add the same eppn
306 d2633501 Kostas Papadimitriou
        client.set_tokens(mail="secondary@shibboleth.gr", eppn="kpapeppn", cn="1", )
307 d2633501 Kostas Papadimitriou
        r = client.get("/im/login/shibboleth?", follow=True)
308 ba50648c Kostas Papadimitriou
        self.assertRedirects(r, '/im/profile')
309 ba50648c Kostas Papadimitriou
        self.assertTrue(r.status_code, 200)
310 ba50648c Kostas Papadimitriou
        self.assertEquals(existing_user.auth_providers.count(), 2)
311 d2633501 Kostas Papadimitriou
312 ba50648c Kostas Papadimitriou
        # but can add additional eppn
313 ba50648c Kostas Papadimitriou
        client.set_tokens(mail="secondary@shibboleth.gr", eppn="kpapeppn2",
314 ba50648c Kostas Papadimitriou
                          cn="1", ep_affiliation="affil2")
315 ba50648c Kostas Papadimitriou
        r = client.get("/im/login/shibboleth?", follow=True)
316 ba50648c Kostas Papadimitriou
        new_provider = existing_user.auth_providers.get(identifier="kpapeppn2")
317 ba50648c Kostas Papadimitriou
        self.assertRedirects(r, '/im/profile')
318 ba50648c Kostas Papadimitriou
        self.assertTrue(r.status_code, 200)
319 ba50648c Kostas Papadimitriou
        self.assertEquals(existing_user.auth_providers.count(), 3)
320 ba50648c Kostas Papadimitriou
        self.assertEqual(new_provider.affiliation, 'affil2')
321 d2633501 Kostas Papadimitriou
        client.logout()
322 ba50648c Kostas Papadimitriou
        client.reset_tokens()
323 ba50648c Kostas Papadimitriou
324 ba50648c Kostas Papadimitriou
        # cannot login with another eppn
325 d2633501 Kostas Papadimitriou
        client.set_tokens(mail="kpap@grnet.gr", eppn="kpapeppninvalid", cn="1")
326 d2633501 Kostas Papadimitriou
        r = client.get("/im/login/shibboleth?", follow=True)
327 d2633501 Kostas Papadimitriou
        self.assertFalse(r.context['request'].user.is_authenticated())
328 d2633501 Kostas Papadimitriou
329 f432088a Kostas Papadimitriou
        # lets remove local password
330 f432088a Kostas Papadimitriou
        user = AstakosUser.objects.get(username="kpap@grnet.gr",
331 f432088a Kostas Papadimitriou
                                       email="kpap@grnet.gr")
332 ba50648c Kostas Papadimitriou
        remove_local_url = user.get_provider_remove_url('local')
333 ba50648c Kostas Papadimitriou
        remove_shibbo_url = user.get_provider_remove_url('shibboleth',
334 ba50648c Kostas Papadimitriou
                                                         identifier='kpapeppn')
335 ba50648c Kostas Papadimitriou
        remove_shibbo2_url = user.get_provider_remove_url('shibboleth',
336 ba50648c Kostas Papadimitriou
                                                         identifier='kpapeppn2')
337 f432088a Kostas Papadimitriou
        client.set_tokens(mail="kpap@shibboleth.gr", eppn="kpapeppn", cn="1")
338 f432088a Kostas Papadimitriou
        r = client.get("/im/login/shibboleth?", follow=True)
339 f432088a Kostas Papadimitriou
        client.reset_tokens()
340 ba50648c Kostas Papadimitriou
341 ba50648c Kostas Papadimitriou
        # TODO: this view should use POST
342 ba50648c Kostas Papadimitriou
        r = client.get(remove_local_url)
343 ba50648c Kostas Papadimitriou
        # 2 providers left
344 ba50648c Kostas Papadimitriou
        self.assertEqual(user.auth_providers.count(), 2)
345 ba50648c Kostas Papadimitriou
        r = client.get(remove_shibbo2_url)
346 ba50648c Kostas Papadimitriou
        # 1 provider left
347 f432088a Kostas Papadimitriou
        self.assertEqual(user.auth_providers.count(), 1)
348 ba50648c Kostas Papadimitriou
        # cannot remove last provider
349 ba50648c Kostas Papadimitriou
        r = client.get(remove_shibbo_url)
350 f432088a Kostas Papadimitriou
        self.assertEqual(r.status_code, 403)
351 f432088a Kostas Papadimitriou
        self.client.logout()
352 ba50648c Kostas Papadimitriou
353 ba50648c Kostas Papadimitriou
        # cannot login using local credentials (notice we use another client)
354 f432088a Kostas Papadimitriou
        post_data = {'password': 'password',
355 f432088a Kostas Papadimitriou
                     'username': 'kpap@grnet.gr'}
356 f432088a Kostas Papadimitriou
        r = self.client.post('/im/local', post_data, follow=True)
357 f432088a Kostas Papadimitriou
        self.assertFalse(r.context['request'].user.is_authenticated())
358 f432088a Kostas Papadimitriou
359 ba50648c Kostas Papadimitriou
        # we can reenable the local provider by setting a password
360 f432088a Kostas Papadimitriou
        r = client.get("/im/password_change", follow=True)
361 f432088a Kostas Papadimitriou
        r = client.post("/im/password_change", {'new_password1':'111',
362 f432088a Kostas Papadimitriou
                                                'new_password2': '111'},
363 f432088a Kostas Papadimitriou
                        follow=True)
364 f432088a Kostas Papadimitriou
        user = r.context['request'].user
365 f432088a Kostas Papadimitriou
        self.assertTrue(user.has_auth_provider('local'))
366 f432088a Kostas Papadimitriou
        self.assertTrue(user.has_auth_provider('shibboleth'))
367 f432088a Kostas Papadimitriou
        self.assertTrue(user.check_password('111'))
368 f432088a Kostas Papadimitriou
        self.assertTrue(user.has_usable_password())
369 f432088a Kostas Papadimitriou
        self.client.logout()
370 ba50648c Kostas Papadimitriou
371 ba50648c Kostas Papadimitriou
        # now we can login
372 f432088a Kostas Papadimitriou
        post_data = {'password': '111',
373 f432088a Kostas Papadimitriou
                     'username': 'kpap@grnet.gr'}
374 f432088a Kostas Papadimitriou
        r = self.client.post('/im/local', post_data, follow=True)
375 f432088a Kostas Papadimitriou
        self.assertTrue(r.context['request'].user.is_authenticated())
376 f432088a Kostas Papadimitriou
377 ba50648c Kostas Papadimitriou
        client.reset_tokens()
378 f432088a Kostas Papadimitriou
379 ba50648c Kostas Papadimitriou
        # we cannot take over another shibboleth identifier
380 f432088a Kostas Papadimitriou
        user2 = get_local_user('another@grnet.gr')
381 f432088a Kostas Papadimitriou
        user2.add_auth_provider('shibboleth', identifier='existingeppn')
382 ba50648c Kostas Papadimitriou
        # login
383 ba50648c Kostas Papadimitriou
        client.set_tokens(mail="kpap@shibboleth.gr", eppn="kpapeppn", cn="1")
384 f432088a Kostas Papadimitriou
        r = client.get("/im/login/shibboleth?", follow=True)
385 ba50648c Kostas Papadimitriou
        # try to assign existing shibboleth identifier of another user
386 f432088a Kostas Papadimitriou
        client.set_tokens(mail="kpap_second@shibboleth.gr", eppn="existingeppn", cn="1")
387 f432088a Kostas Papadimitriou
        r = client.get("/im/login/shibboleth?", follow=True)
388 f432088a Kostas Papadimitriou
        self.assertContains(r, 'Account already exists')
389 2e90e3ec Kostas Papadimitriou
390 d2633501 Kostas Papadimitriou
391 d2633501 Kostas Papadimitriou
class LocalUserTests(TestCase):
392 d2633501 Kostas Papadimitriou
393 d2633501 Kostas Papadimitriou
    fixtures = ['groups']
394 d2633501 Kostas Papadimitriou
395 2e90e3ec Kostas Papadimitriou
    def setUp(self):
396 2e90e3ec Kostas Papadimitriou
        from django.conf import settings
397 2e90e3ec Kostas Papadimitriou
        settings.ADMINS = (('admin', 'support@cloud.grnet.gr'),)
398 2e90e3ec Kostas Papadimitriou
        settings.SERVER_EMAIL = 'no-reply@grnet.gr'
399 2e90e3ec Kostas Papadimitriou
400 7233d542 Kostas Papadimitriou
    def test_no_moderation(self):
401 ba50648c Kostas Papadimitriou
        # disable moderation
402 7233d542 Kostas Papadimitriou
        astakos_settings.MODERATION_ENABLED = False
403 ba50648c Kostas Papadimitriou
404 ba50648c Kostas Papadimitriou
        # create a new user
405 7233d542 Kostas Papadimitriou
        r = self.client.get("/im/signup")
406 7233d542 Kostas Papadimitriou
        self.assertEqual(r.status_code, 200)
407 7233d542 Kostas Papadimitriou
        data = {'email':'kpap@grnet.gr', 'password1':'password',
408 7233d542 Kostas Papadimitriou
                'password2':'password', 'first_name': 'Kostas',
409 7233d542 Kostas Papadimitriou
                'last_name': 'Mitroglou', 'provider': 'local'}
410 7233d542 Kostas Papadimitriou
        r = self.client.post("/im/signup", data)
411 ba50648c Kostas Papadimitriou
412 ba50648c Kostas Papadimitriou
        # user created
413 7233d542 Kostas Papadimitriou
        self.assertEqual(AstakosUser.objects.count(), 1)
414 7233d542 Kostas Papadimitriou
        user = AstakosUser.objects.get(username="kpap@grnet.gr",
415 7233d542 Kostas Papadimitriou
                                       email="kpap@grnet.gr")
416 7233d542 Kostas Papadimitriou
        self.assertEqual(user.username, 'kpap@grnet.gr')
417 7233d542 Kostas Papadimitriou
        self.assertEqual(user.has_auth_provider('local'), True)
418 7233d542 Kostas Papadimitriou
        self.assertFalse(user.is_active)
419 7233d542 Kostas Papadimitriou
420 ba50648c Kostas Papadimitriou
        # user (but not admin) gets notified
421 7233d542 Kostas Papadimitriou
        self.assertEqual(len(get_mailbox('support@cloud.grnet.gr')), 0)
422 7233d542 Kostas Papadimitriou
        self.assertEqual(len(get_mailbox('kpap@grnet.gr')), 1)
423 7233d542 Kostas Papadimitriou
        astakos_settings.MODERATION_ENABLED = True
424 d2633501 Kostas Papadimitriou
425 d2633501 Kostas Papadimitriou
    def test_local_provider(self):
426 ba50648c Kostas Papadimitriou
        # enable moderation
427 7233d542 Kostas Papadimitriou
        astakos_settings.MODERATION_ENABLED = True
428 ba50648c Kostas Papadimitriou
429 ba50648c Kostas Papadimitriou
        # create a user
430 d2633501 Kostas Papadimitriou
        r = self.client.get("/im/signup")
431 d2633501 Kostas Papadimitriou
        self.assertEqual(r.status_code, 200)
432 d2633501 Kostas Papadimitriou
        data = {'email':'kpap@grnet.gr', 'password1':'password',
433 d2633501 Kostas Papadimitriou
                'password2':'password', 'first_name': 'Kostas',
434 d2633501 Kostas Papadimitriou
                'last_name': 'Mitroglou', 'provider': 'local'}
435 d2633501 Kostas Papadimitriou
        r = self.client.post("/im/signup", data)
436 ba50648c Kostas Papadimitriou
437 ba50648c Kostas Papadimitriou
        # user created
438 d2633501 Kostas Papadimitriou
        self.assertEqual(AstakosUser.objects.count(), 1)
439 d2633501 Kostas Papadimitriou
        user = AstakosUser.objects.get(username="kpap@grnet.gr",
440 d2633501 Kostas Papadimitriou
                                       email="kpap@grnet.gr")
441 d2633501 Kostas Papadimitriou
        self.assertEqual(user.username, 'kpap@grnet.gr')
442 d2633501 Kostas Papadimitriou
        self.assertEqual(user.has_auth_provider('local'), True)
443 ba50648c Kostas Papadimitriou
        self.assertFalse(user.is_active) # not activated
444 ba50648c Kostas Papadimitriou
        self.assertFalse(user.email_verified) # not verified
445 ba50648c Kostas Papadimitriou
        self.assertFalse(user.activation_sent) # activation automatically sent
446 d2633501 Kostas Papadimitriou
447 ba50648c Kostas Papadimitriou
        # admin gets notified and activates the user from the command line
448 d2633501 Kostas Papadimitriou
        self.assertEqual(len(get_mailbox('support@cloud.grnet.gr')), 1)
449 31fdafa8 Kostas Papadimitriou
        r = self.client.post('/im/local', {'username': 'kpap@grnet.gr',
450 31fdafa8 Kostas Papadimitriou
                                                 'password': 'password'})
451 31fdafa8 Kostas Papadimitriou
        self.assertContains(r, messages.ACCOUNT_PENDING_MODERATION)
452 d2633501 Kostas Papadimitriou
        functions.send_activation(user)
453 d2633501 Kostas Papadimitriou
454 ba50648c Kostas Papadimitriou
        # user activation fields updated and user gets notified via email
455 d2633501 Kostas Papadimitriou
        user = AstakosUser.objects.get(pk=user.pk)
456 d2633501 Kostas Papadimitriou
        self.assertTrue(user.activation_sent)
457 d2633501 Kostas Papadimitriou
        self.assertFalse(user.email_verified)
458 d2633501 Kostas Papadimitriou
        self.assertEqual(len(get_mailbox('kpap@grnet.gr')), 1)
459 d2633501 Kostas Papadimitriou
460 d2633501 Kostas Papadimitriou
        # user forgot she got registered and tries to submit registration
461 d2633501 Kostas Papadimitriou
        # form. Notice the upper case in email
462 d2633501 Kostas Papadimitriou
        data = {'email':'KPAP@grnet.gr', 'password1':'password',
463 d2633501 Kostas Papadimitriou
                'password2':'password', 'first_name': 'Kostas',
464 d2633501 Kostas Papadimitriou
                'last_name': 'Mitroglou', 'provider': 'local'}
465 d2633501 Kostas Papadimitriou
        r = self.client.post("/im/signup", data)
466 2e90e3ec Kostas Papadimitriou
        self.assertContains(r, messages.EMAIL_USED)
467 d2633501 Kostas Papadimitriou
468 d2633501 Kostas Papadimitriou
        # hmmm, email exists; lets get the password
469 d2633501 Kostas Papadimitriou
        r = self.client.get('/im/local/password_reset')
470 d2633501 Kostas Papadimitriou
        self.assertEqual(r.status_code, 200)
471 d2633501 Kostas Papadimitriou
        r = self.client.post('/im/local/password_reset', {'email':
472 d2633501 Kostas Papadimitriou
                                                          'kpap@grnet.gr'})
473 d2633501 Kostas Papadimitriou
        # she can't because account is not active yet
474 d2633501 Kostas Papadimitriou
        self.assertContains(r, "doesn't have an associated user account")
475 d2633501 Kostas Papadimitriou
476 d2633501 Kostas Papadimitriou
        # moderation is enabled so no automatic activation can be send
477 d2633501 Kostas Papadimitriou
        r = self.client.get('/im/send/activation/%d' % user.pk)
478 d2633501 Kostas Papadimitriou
        self.assertEqual(r.status_code, 403)
479 d2633501 Kostas Papadimitriou
        self.assertEqual(len(get_mailbox('kpap@grnet.gr')), 1)
480 ba50648c Kostas Papadimitriou
481 d2633501 Kostas Papadimitriou
        # also she cannot login
482 d2633501 Kostas Papadimitriou
        r = self.client.post('/im/local', {'username': 'kpap@grnet.gr',
483 d2633501 Kostas Papadimitriou
                                                 'password': 'password'})
484 31fdafa8 Kostas Papadimitriou
        self.assertContains(r, messages.ACCOUNT_PENDING_ACTIVATION_HELP)
485 31fdafa8 Kostas Papadimitriou
        self.assertContains(r, messages.ACCOUNT_PENDING_ACTIVATION)
486 d2633501 Kostas Papadimitriou
        self.assertNotContains(r, 'Resend activation')
487 d2633501 Kostas Papadimitriou
        self.assertFalse(r.context['request'].user.is_authenticated())
488 d2633501 Kostas Papadimitriou
        self.assertFalse('_pithos2_a' in self.client.cookies)
489 d2633501 Kostas Papadimitriou
490 ba50648c Kostas Papadimitriou
        # same with disabled moderation
491 d2633501 Kostas Papadimitriou
        astakos_settings.MODERATION_ENABLED = False
492 d2633501 Kostas Papadimitriou
        r = self.client.post('/im/local/password_reset', {'email':
493 d2633501 Kostas Papadimitriou
                                                          'kpap@grnet.gr'})
494 d2633501 Kostas Papadimitriou
        self.assertContains(r, "doesn't have an associated user account")
495 d2633501 Kostas Papadimitriou
        r = self.client.post('/im/local', {'username': 'kpap@grnet.gr',
496 d2633501 Kostas Papadimitriou
                                                 'password': 'password'})
497 31fdafa8 Kostas Papadimitriou
        print r
498 31fdafa8 Kostas Papadimitriou
        self.assertContains(r, messages.ACCOUNT_PENDING_ACTIVATION)
499 d2633501 Kostas Papadimitriou
        self.assertContains(r, 'Resend activation')
500 d2633501 Kostas Papadimitriou
        self.assertFalse(r.context['request'].user.is_authenticated())
501 d2633501 Kostas Papadimitriou
        self.assertFalse('_pithos2_a' in self.client.cookies)
502 ba50648c Kostas Papadimitriou
503 d2633501 Kostas Papadimitriou
        # user sees the message and resends activation
504 d2633501 Kostas Papadimitriou
        r = self.client.get('/im/send/activation/%d' % user.pk)
505 d2633501 Kostas Papadimitriou
        self.assertEqual(len(get_mailbox('kpap@grnet.gr')), 2)
506 d2633501 Kostas Papadimitriou
507 d2633501 Kostas Papadimitriou
        # switch back moderation setting
508 d2633501 Kostas Papadimitriou
        astakos_settings.MODERATION_ENABLED = True
509 d2633501 Kostas Papadimitriou
        r = self.client.get(user.get_activation_url(), follow=True)
510 d2633501 Kostas Papadimitriou
        self.assertRedirects(r, "/im/profile")
511 d2633501 Kostas Papadimitriou
        self.assertContains(r, "kpap@grnet.gr")
512 d2633501 Kostas Papadimitriou
        self.assertEqual(len(get_mailbox('kpap@grnet.gr')), 3)
513 d2633501 Kostas Papadimitriou
514 d2633501 Kostas Papadimitriou
        user = AstakosUser.objects.get(pk=user.pk)
515 d2633501 Kostas Papadimitriou
        # user activated and logged in, token cookie set
516 d2633501 Kostas Papadimitriou
        self.assertTrue(r.context['request'].user.is_authenticated())
517 d2633501 Kostas Papadimitriou
        self.assertTrue('_pithos2_a' in self.client.cookies)
518 d2633501 Kostas Papadimitriou
        cookies = self.client.cookies
519 d2633501 Kostas Papadimitriou
        self.assertTrue(quote(user.auth_token) in cookies.get('_pithos2_a').value)
520 d2633501 Kostas Papadimitriou
        r = self.client.get('/im/logout', follow=True)
521 d2633501 Kostas Papadimitriou
        r = self.client.get('/im/')
522 d2633501 Kostas Papadimitriou
        # user logged out, token cookie removed
523 d2633501 Kostas Papadimitriou
        self.assertFalse(r.context['request'].user.is_authenticated())
524 d2633501 Kostas Papadimitriou
        self.assertFalse(self.client.cookies.get('_pithos2_a').value)
525 d2633501 Kostas Papadimitriou
        # https://docs.djangoproject.com/en/dev/topics/testing/#persistent-state
526 d2633501 Kostas Papadimitriou
        del self.client.cookies['_pithos2_a']
527 d2633501 Kostas Papadimitriou
528 d2633501 Kostas Papadimitriou
        # user can login
529 d2633501 Kostas Papadimitriou
        r = self.client.post('/im/local', {'username': 'kpap@grnet.gr',
530 d2633501 Kostas Papadimitriou
                                           'password': 'password'},
531 d2633501 Kostas Papadimitriou
                                          follow=True)
532 d2633501 Kostas Papadimitriou
        self.assertTrue(r.context['request'].user.is_authenticated())
533 d2633501 Kostas Papadimitriou
        self.assertTrue('_pithos2_a' in self.client.cookies)
534 d2633501 Kostas Papadimitriou
        cookies = self.client.cookies
535 d2633501 Kostas Papadimitriou
        self.assertTrue(quote(user.auth_token) in cookies.get('_pithos2_a').value)
536 d2633501 Kostas Papadimitriou
        self.client.get('/im/logout', follow=True)
537 d2633501 Kostas Papadimitriou
538 d2633501 Kostas Papadimitriou
        # user forgot password
539 d2633501 Kostas Papadimitriou
        old_pass = user.password
540 d2633501 Kostas Papadimitriou
        r = self.client.get('/im/local/password_reset')
541 d2633501 Kostas Papadimitriou
        self.assertEqual(r.status_code, 200)
542 d2633501 Kostas Papadimitriou
        r = self.client.post('/im/local/password_reset', {'email':
543 d2633501 Kostas Papadimitriou
                                                          'kpap@grnet.gr'})
544 d2633501 Kostas Papadimitriou
        self.assertEqual(r.status_code, 302)
545 d2633501 Kostas Papadimitriou
        # email sent
546 d2633501 Kostas Papadimitriou
        self.assertEqual(len(get_mailbox('kpap@grnet.gr')), 4)
547 d2633501 Kostas Papadimitriou
548 d2633501 Kostas Papadimitriou
        # user visits change password link
549 d2633501 Kostas Papadimitriou
        r = self.client.get(user.get_password_reset_url())
550 d2633501 Kostas Papadimitriou
        r = self.client.post(user.get_password_reset_url(),
551 d2633501 Kostas Papadimitriou
                            {'new_password1':'newpass',
552 d2633501 Kostas Papadimitriou
                             'new_password2':'newpass'})
553 d2633501 Kostas Papadimitriou
554 d2633501 Kostas Papadimitriou
        user = AstakosUser.objects.get(pk=user.pk)
555 d2633501 Kostas Papadimitriou
        self.assertNotEqual(old_pass, user.password)
556 d2633501 Kostas Papadimitriou
557 d2633501 Kostas Papadimitriou
        # old pass is not usable
558 d2633501 Kostas Papadimitriou
        r = self.client.post('/im/local', {'username': 'kpap@grnet.gr',
559 d2633501 Kostas Papadimitriou
                                           'password': 'password'})
560 31fdafa8 Kostas Papadimitriou
        self.assertContains(r, 'Please enter a correct username and password')
561 d2633501 Kostas Papadimitriou
        r = self.client.post('/im/local', {'username': 'kpap@grnet.gr',
562 d2633501 Kostas Papadimitriou
                                           'password': 'newpass'},
563 d2633501 Kostas Papadimitriou
                                           follow=True)
564 d2633501 Kostas Papadimitriou
        self.assertTrue(r.context['request'].user.is_authenticated())
565 d2633501 Kostas Papadimitriou
        self.client.logout()
566 d2633501 Kostas Papadimitriou
567 d2633501 Kostas Papadimitriou
        # tests of special local backends
568 d2633501 Kostas Papadimitriou
        user = AstakosUser.objects.get(pk=user.pk)
569 d2633501 Kostas Papadimitriou
        user.auth_providers.filter(module='local').update(auth_backend='ldap')
570 d2633501 Kostas Papadimitriou
        user.save()
571 d2633501 Kostas Papadimitriou
572 d2633501 Kostas Papadimitriou
        # non astakos local backends do not support password reset
573 d2633501 Kostas Papadimitriou
        r = self.client.get('/im/local/password_reset')
574 d2633501 Kostas Papadimitriou
        self.assertEqual(r.status_code, 200)
575 d2633501 Kostas Papadimitriou
        r = self.client.post('/im/local/password_reset', {'email':
576 d2633501 Kostas Papadimitriou
                                                          'kpap@grnet.gr'})
577 d2633501 Kostas Papadimitriou
        # she can't because account is not active yet
578 d2633501 Kostas Papadimitriou
        self.assertContains(r, "Password change for this account is not"
579 d2633501 Kostas Papadimitriou
                                " supported")