Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / views / im.py @ b6496f0c

History | View | Annotate | Download (33.8 kB)

1
# Copyright 2011-2012 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 logging
35
import inflect
36

    
37
engine = inflect.engine()
38

    
39
from urllib import quote
40

    
41
from django.shortcuts import get_object_or_404
42
from django.contrib import messages
43
from django.contrib.auth.models import User
44
from django.core.urlresolvers import reverse
45
from django.db import transaction
46
from django.http import HttpResponse, HttpResponseRedirect, Http404
47
from django.shortcuts import redirect
48
from django.utils.translation import ugettext as _
49
from django.core.exceptions import PermissionDenied
50
from django.views.decorators.http import require_http_methods
51
from django.utils import simplejson as json
52

    
53
from synnefo.lib import join_urls
54

    
55
import astakos.im.messages as astakos_messages
56

    
57
from astakos.im import activation_backends
58
from astakos.im.models import AstakosUser, ApprovalTerms, EmailChange, \
59
    AstakosUserAuthProvider, PendingThirdPartyUser, Component
60
from astakos.im.util import get_context, prepare_response, get_query, \
61
    restrict_next
62
from astakos.im.forms import LoginForm, InvitationForm, FeedbackForm, \
63
    SignApprovalTermsForm, EmailChangeForm
64
from astakos.im.forms import ExtendedProfileForm as ProfileForm
65
from astakos.im.functions import send_feedback, logout as auth_logout, \
66
    invite as invite_func
67
from astakos.im import settings
68
from astakos.im import presentation
69
from astakos.im import auth_providers as auth
70
from astakos.im import quotas
71
from astakos.im.views.util import render_response, _resources_catalog
72
from astakos.im.views.decorators import cookie_fix, signed_terms_required,\
73
    required_auth_methods_assigned, valid_astakos_user_required, login_required
74

    
75
logger = logging.getLogger(__name__)
76

    
77

    
78
@require_http_methods(["GET", "POST"])
79
@cookie_fix
80
@signed_terms_required
81
def login(request, template_name='im/login.html', extra_context=None):
82
    """
83
    Renders login page.
84

85
    **Arguments**
86

87
    ``template_name``
88
        A custom login template to use. This is optional; if not specified,
89
        this will default to ``im/login.html``.
90

91
    ``extra_context``
92
        An dictionary of variables to add to the template context.
93
    """
94

    
95
    extra_context = extra_context or {}
96

    
97
    third_party_token = request.GET.get('key', False)
98
    if third_party_token:
99
        messages.info(request, astakos_messages.AUTH_PROVIDER_LOGIN_TO_ADD)
100

    
101
    if request.user.is_authenticated():
102
        return HttpResponseRedirect(reverse('landing'))
103

    
104
    return render_response(
105
        template_name,
106
        login_form=LoginForm(request=request),
107
        context_instance=get_context(request, extra_context)
108
    )
109

    
110

    
111
@require_http_methods(["GET", "POST"])
112
@cookie_fix
113
@signed_terms_required
114
def index(request, authenticated_redirect='landing',
115
          anonymous_redirect='login', extra_context=None):
116
    """
117
    If user is authenticated redirect to ``authenticated_redirect`` url.
118
    Otherwise redirects to ``anonymous_redirect`` url.
119

120
    """
121
    if request.user.is_authenticated():
122
        return HttpResponseRedirect(reverse(authenticated_redirect))
123
    return HttpResponseRedirect(reverse(anonymous_redirect))
124

    
125

    
126
@require_http_methods(["POST"])
127
@cookie_fix
128
@valid_astakos_user_required
129
def update_token(request):
130
    """
131
    Update api token view.
132
    """
133
    user = request.user
134
    user.renew_token()
135
    user.save()
136
    messages.success(request, astakos_messages.TOKEN_UPDATED)
137
    return HttpResponseRedirect(reverse('api_access'))
138

    
139

    
140
@require_http_methods(["GET", "POST"])
141
@cookie_fix
142
@valid_astakos_user_required
143
@transaction.commit_manually
144
def invite(request, template_name='im/invitations.html', extra_context=None):
145
    """
146
    Allows a user to invite somebody else.
147

148
    In case of GET request renders a form for providing the invitee information.
149
    In case of POST checks whether the user has not run out of invitations and then
150
    sends an invitation email to singup to the service.
151

152
    The view uses commit_manually decorator in order to ensure the number of the
153
    user invitations is going to be updated only if the email has been successfully sent.
154

155
    If the user isn't logged in, redirects to settings.LOGIN_URL.
156

157
    **Arguments**
158

159
    ``template_name``
160
        A custom template to use. This is optional; if not specified,
161
        this will default to ``im/invitations.html``.
162

163
    ``extra_context``
164
        An dictionary of variables to add to the template context.
165

166
    **Template:**
167

168
    im/invitations.html or ``template_name`` keyword argument.
169

170
    **Settings:**
171

172
    The view expectes the following settings are defined:
173

174
    * LOGIN_URL: login uri
175
    """
176
    extra_context = extra_context or {}
177
    status = None
178
    message = None
179
    form = InvitationForm()
180

    
181
    inviter = request.user
182
    if request.method == 'POST':
183
        form = InvitationForm(request.POST)
184
        if inviter.invitations > 0:
185
            if form.is_valid():
186
                try:
187
                    email = form.cleaned_data.get('username')
188
                    realname = form.cleaned_data.get('realname')
189
                    invite_func(inviter, email, realname)
190
                    message = _(astakos_messages.INVITATION_SENT) % locals()
191
                    messages.success(request, message)
192
                except Exception, e:
193
                    transaction.rollback()
194
                    raise
195
                else:
196
                    transaction.commit()
197
        else:
198
            message = _(astakos_messages.MAX_INVITATION_NUMBER_REACHED)
199
            messages.error(request, message)
200

    
201
    sent = [{'email': inv.username,
202
             'realname': inv.realname,
203
             'is_consumed': inv.is_consumed}
204
            for inv in request.user.invitations_sent.all()]
205
    kwargs = {'inviter': inviter,
206
              'sent': sent}
207
    context = get_context(request, extra_context, **kwargs)
208
    return render_response(template_name,
209
                           invitation_form=form,
210
                           context_instance=context)
211

    
212

    
213
@require_http_methods(["GET", "POST"])
214
@required_auth_methods_assigned()
215
@login_required
216
@cookie_fix
217
@signed_terms_required
218
def api_access(request, template_name='im/api_access.html',
219
               extra_context=None):
220
    """
221
    API access view.
222
    """
223
    context = {}
224

    
225
    token_url = join_urls(settings.BASE_HOST, reverse('tokens_authenticate'))
226
    context['services'] = Component.catalog()
227
    context['token_url'] = token_url
228
    context['client_url'] = settings.API_CLIENT_URL
229

    
230
    if extra_context:
231
        context.update(extra_context)
232
    context_instance = get_context(request, context)
233
    return render_response(template_name,
234
                           context_instance=context_instance)
235

    
236

    
237
@require_http_methods(["GET", "POST"])
238
@required_auth_methods_assigned(allow_access=True)
239
@login_required
240
@cookie_fix
241
@signed_terms_required
242
def edit_profile(request, template_name='im/profile.html', extra_context=None):
243
    """
244
    Allows a user to edit his/her profile.
245

246
    In case of GET request renders a form for displaying the user information.
247
    In case of POST updates the user informantion and redirects to ``next``
248
    url parameter if exists.
249

250
    If the user isn't logged in, redirects to settings.LOGIN_URL.
251

252
    **Arguments**
253

254
    ``template_name``
255
        A custom template to use. This is optional; if not specified,
256
        this will default to ``im/profile.html``.
257

258
    ``extra_context``
259
        An dictionary of variables to add to the template context.
260

261
    **Template:**
262

263
    im/profile.html or ``template_name`` keyword argument.
264

265
    **Settings:**
266

267
    The view expectes the following settings are defined:
268

269
    * LOGIN_URL: login uri
270
    """
271
    extra_context = extra_context or {}
272
    form = ProfileForm(
273
        instance=request.user,
274
        session_key=request.session.session_key
275
    )
276
    extra_context['next'] = request.GET.get('next')
277
    if request.method == 'POST':
278
        form = ProfileForm(
279
            request.POST,
280
            instance=request.user,
281
            session_key=request.session.session_key
282
        )
283
        if form.is_valid():
284
            try:
285
                prev_token = request.user.auth_token
286
                user = form.save(request=request)
287
                next = restrict_next(
288
                    request.POST.get('next'),
289
                    domain=settings.COOKIE_DOMAIN
290
                )
291
                msg = _(astakos_messages.PROFILE_UPDATED)
292
                messages.success(request, msg)
293

    
294
                if form.email_changed:
295
                    msg = _(astakos_messages.EMAIL_CHANGE_REGISTERED)
296
                    messages.success(request, msg)
297
                if form.password_changed:
298
                    msg = _(astakos_messages.PASSWORD_CHANGED)
299
                    messages.success(request, msg)
300

    
301
                if next:
302
                    return redirect(next)
303
                else:
304
                    return redirect(reverse('edit_profile'))
305
            except ValueError, ve:
306
                messages.success(request, ve)
307
    elif request.method == "GET":
308
        request.user.is_verified = True
309
        request.user.save()
310

    
311
    # existing providers
312
    user_providers = request.user.get_enabled_auth_providers()
313
    user_disabled_providers = request.user.get_disabled_auth_providers()
314

    
315
    # providers that user can add
316
    user_available_providers = request.user.get_available_auth_providers()
317

    
318
    extra_context['services'] = Component.catalog().values()
319
    return render_response(template_name,
320
                           profile_form=form,
321
                           user_providers=user_providers,
322
                           user_disabled_providers=user_disabled_providers,
323
                           user_available_providers=user_available_providers,
324
                           context_instance=get_context(request,
325
                                                          extra_context))
326

    
327

    
328
@transaction.commit_manually
329
@require_http_methods(["GET", "POST"])
330
@cookie_fix
331
def signup(request, template_name='im/signup.html', on_success='index',
332
           extra_context=None, activation_backend=None):
333
    """
334
    Allows a user to create a local account.
335

336
    In case of GET request renders a form for entering the user information.
337
    In case of POST handles the signup.
338

339
    The user activation will be delegated to the backend specified by the
340
    ``activation_backend`` keyword argument if present, otherwise to the
341
    ``astakos.im.activation_backends.InvitationBackend`` if
342
    settings.ASTAKOS_INVITATIONS_ENABLED is True or
343
    ``astakos.im.activation_backends.SimpleBackend`` if not (see
344
    activation_backends);
345

346
    Upon successful user creation, if ``next`` url parameter is present the
347
    user is redirected there otherwise renders the same page with a success
348
    message.
349

350
    On unsuccessful creation, renders ``template_name`` with an error message.
351

352
    **Arguments**
353

354
    ``template_name``
355
        A custom template to render. This is optional;
356
        if not specified, this will default to ``im/signup.html``.
357

358
    ``extra_context``
359
        An dictionary of variables to add to the template context.
360

361
    ``on_success``
362
        Resolvable view name to redirect on registration success.
363

364
    **Template:**
365

366
    im/signup.html or ``template_name`` keyword argument.
367
    """
368
    extra_context = extra_context or {}
369
    if request.user.is_authenticated():
370
        logger.info("%s already signed in, redirect to index",
371
                    request.user.log_display)
372
        return HttpResponseRedirect(reverse('index'))
373

    
374
    provider = get_query(request).get('provider', 'local')
375
    if not auth.get_provider(provider).get_create_policy:
376
        logger.error("%s provider not available for signup", provider)
377
        raise PermissionDenied
378

    
379
    instance = None
380

    
381
    # user registered using third party provider
382
    third_party_token = request.REQUEST.get('third_party_token', None)
383
    unverified = None
384
    if third_party_token:
385
        # retreive third party entry. This was created right after the initial
386
        # third party provider handshake.
387
        pending = get_object_or_404(PendingThirdPartyUser,
388
                                    token=third_party_token)
389

    
390
        provider = pending.provider
391

    
392
        # clone third party instance into the corresponding AstakosUser
393
        instance = pending.get_user_instance()
394
        get_unverified = AstakosUserAuthProvider.objects.unverified
395

    
396
        # check existing unverified entries
397
        unverified = get_unverified(pending.provider,
398
                                    identifier=pending.third_party_identifier)
399

    
400
        if unverified and request.method == 'GET':
401
            messages.warning(request, unverified.get_pending_registration_msg)
402
            if unverified.user.moderated:
403
                messages.warning(request,
404
                                 unverified.get_pending_resend_activation_msg)
405
            else:
406
                messages.warning(request,
407
                                 unverified.get_pending_moderation_msg)
408

    
409
    # prepare activation backend based on current request
410
    if not activation_backend:
411
        activation_backend = activation_backends.get_backend()
412

    
413
    form_kwargs = {'instance': instance}
414
    if third_party_token:
415
        form_kwargs['third_party_token'] = third_party_token
416

    
417
    form = activation_backend.get_signup_form(
418
        provider, None, **form_kwargs)
419

    
420
    if request.method == 'POST':
421
        form = activation_backend.get_signup_form(
422
            provider,
423
            request.POST,
424
            **form_kwargs)
425

    
426
        if form.is_valid():
427
            commited = False
428
            try:
429
                user = form.save(commit=False)
430

    
431
                # delete previously unverified accounts
432
                if AstakosUser.objects.user_exists(user.email):
433
                    AstakosUser.objects.get_by_identifier(user.email).delete()
434

    
435
                # store_user so that user auth providers get initialized
436
                form.store_user(user, request)
437
                result = activation_backend.handle_registration(user)
438
                if result.status == \
439
                        activation_backend.Result.PENDING_MODERATION:
440
                    # user should be warned that his account is not active yet
441
                    status = messages.WARNING
442
                else:
443
                    status = messages.SUCCESS
444
                message = result.message
445
                activation_backend.send_result_notifications(result, user)
446

    
447
                # commit user entry
448
                transaction.commit()
449
                # commited flag
450
                # in case an exception get raised from this point
451
                commited = True
452

    
453
                if user and user.is_active:
454
                    # activation backend directly activated the user
455
                    # log him in
456
                    next = request.POST.get('next', '')
457
                    response = prepare_response(request, user, next=next)
458
                    return response
459

    
460
                messages.add_message(request, status, message)
461
                return HttpResponseRedirect(reverse(on_success))
462
            except Exception, e:
463
                if not commited:
464
                    transaction.rollback()
465
                raise
466

    
467
    return render_response(template_name,
468
                           signup_form=form,
469
                           third_party_token=third_party_token,
470
                           provider=provider,
471
                           context_instance=get_context(request, extra_context))
472

    
473

    
474
@require_http_methods(["GET", "POST"])
475
@required_auth_methods_assigned(allow_access=True)
476
@login_required
477
@cookie_fix
478
@signed_terms_required
479
def feedback(request, template_name='im/feedback.html', email_template_name='im/feedback_mail.txt', extra_context=None):
480
    """
481
    Allows a user to send feedback.
482

483
    In case of GET request renders a form for providing the feedback information.
484
    In case of POST sends an email to support team.
485

486
    If the user isn't logged in, redirects to settings.LOGIN_URL.
487

488
    **Arguments**
489

490
    ``template_name``
491
        A custom template to use. This is optional; if not specified,
492
        this will default to ``im/feedback.html``.
493

494
    ``extra_context``
495
        An dictionary of variables to add to the template context.
496

497
    **Template:**
498

499
    im/signup.html or ``template_name`` keyword argument.
500

501
    **Settings:**
502

503
    * LOGIN_URL: login uri
504
    """
505
    extra_context = extra_context or {}
506
    if request.method == 'GET':
507
        form = FeedbackForm()
508
    if request.method == 'POST':
509
        if not request.user:
510
            return HttpResponse('Unauthorized', status=401)
511

    
512
        form = FeedbackForm(request.POST)
513
        if form.is_valid():
514
            msg = form.cleaned_data['feedback_msg']
515
            data = form.cleaned_data['feedback_data']
516
            send_feedback(msg, data, request.user, email_template_name)
517
            message = _(astakos_messages.FEEDBACK_SENT)
518
            messages.success(request, message)
519
            return HttpResponseRedirect(reverse('feedback'))
520

    
521
    return render_response(template_name,
522
                           feedback_form=form,
523
                           context_instance=get_context(request,
524
                                                        extra_context))
525

    
526

    
527
@require_http_methods(["GET"])
528
@cookie_fix
529
def logout(request, template='registration/logged_out.html',
530
           extra_context=None):
531
    """
532
    Wraps `django.contrib.auth.logout`.
533
    """
534
    extra_context = extra_context or {}
535
    response = HttpResponse()
536
    if request.user.is_authenticated():
537
        email = request.user.email
538
        auth_logout(request)
539
    else:
540
        response['Location'] = reverse('index')
541
        response.status_code = 301
542
        return response
543

    
544
    next = restrict_next(
545
        request.GET.get('next'),
546
        domain=settings.COOKIE_DOMAIN
547
    )
548

    
549
    if next:
550
        response['Location'] = next
551
        response.status_code = 302
552
    elif settings.LOGOUT_NEXT:
553
        response['Location'] = settings.LOGOUT_NEXT
554
        response.status_code = 301
555
    else:
556
        last_provider = request.COOKIES.get('astakos_last_login_method', 'local')
557
        provider = auth.get_provider(last_provider)
558
        message = provider.get_logout_success_msg
559
        extra = provider.get_logout_success_extra_msg
560
        if extra:
561
            message += "<br />"  + extra
562
        messages.success(request, message)
563
        response['Location'] = reverse('index')
564
        response.status_code = 301
565
    return response
566

    
567

    
568
@require_http_methods(["GET", "POST"])
569
@cookie_fix
570
@transaction.commit_manually
571
def activate(request, greeting_email_template_name='im/welcome_email.txt',
572
             helpdesk_email_template_name='im/helpdesk_notification.txt'):
573
    """
574
    Activates the user identified by the ``auth`` request parameter, sends a
575
    welcome email and renews the user token.
576

577
    The view uses commit_manually decorator in order to ensure the user state
578
    will be updated only if the email will be send successfully.
579
    """
580
    token = request.GET.get('auth')
581
    next = request.GET.get('next')
582

    
583
    if request.user.is_authenticated():
584
        message = _(astakos_messages.LOGGED_IN_WARNING)
585
        messages.error(request, message)
586
        return HttpResponseRedirect(reverse('index'))
587

    
588
    try:
589
        user = AstakosUser.objects.get(verification_code=token)
590
    except AstakosUser.DoesNotExist:
591
        raise Http404
592

    
593
    if user.email_verified:
594
        message = _(astakos_messages.ACCOUNT_ALREADY_VERIFIED)
595
        messages.error(request, message)
596
        return HttpResponseRedirect(reverse('index'))
597

    
598
    try:
599
        backend = activation_backends.get_backend()
600
        result = backend.handle_verification(user, token)
601
        backend.send_result_notifications(result, user)
602
        next = settings.ACTIVATION_REDIRECT_URL or next
603
        response = HttpResponseRedirect(reverse('index'))
604
        if user.is_active:
605
            response = prepare_response(request, user, next, renew=True)
606
            messages.success(request, _(result.message))
607
        else:
608
            messages.warning(request, _(result.message))
609
    except Exception:
610
        transaction.rollback()
611
        raise
612
    else:
613
        transaction.commit()
614
        return response
615

    
616

    
617
@require_http_methods(["GET", "POST"])
618
@cookie_fix
619
def approval_terms(request, term_id=None,
620
                   template_name='im/approval_terms.html', extra_context=None):
621
    extra_context = extra_context or {}
622
    term = None
623
    terms = None
624
    if not term_id:
625
        try:
626
            term = ApprovalTerms.objects.order_by('-id')[0]
627
        except IndexError:
628
            pass
629
    else:
630
        try:
631
            term = ApprovalTerms.objects.get(id=term_id)
632
        except ApprovalTerms.DoesNotExist, e:
633
            pass
634

    
635
    if not term:
636
        messages.error(request, _(astakos_messages.NO_APPROVAL_TERMS))
637
        return HttpResponseRedirect(reverse('index'))
638
    try:
639
        f = open(term.location, 'r')
640
    except IOError:
641
        messages.error(request, _(astakos_messages.GENERIC_ERROR))
642
        return render_response(
643
            template_name, context_instance=get_context(request,
644
                                                        extra_context))
645

    
646
    terms = f.read()
647

    
648
    if request.method == 'POST':
649
        next = restrict_next(
650
            request.POST.get('next'),
651
            domain=settings.COOKIE_DOMAIN
652
        )
653
        if not next:
654
            next = reverse('index')
655
        form = SignApprovalTermsForm(request.POST, instance=request.user)
656
        if not form.is_valid():
657
            return render_response(template_name,
658
                                   terms=terms,
659
                                   approval_terms_form=form,
660
                                   context_instance=get_context(request,
661
                                                                extra_context))
662
        user = form.save()
663
        return HttpResponseRedirect(next)
664
    else:
665
        form = None
666
        if request.user.is_authenticated() and not request.user.signed_terms:
667
            form = SignApprovalTermsForm(instance=request.user)
668
        return render_response(template_name,
669
                               terms=terms,
670
                               approval_terms_form=form,
671
                               context_instance=get_context(request,
672
                                                            extra_context))
673

    
674

    
675
@require_http_methods(["GET", "POST"])
676
@cookie_fix
677
@transaction.commit_manually
678
def change_email(request, activation_key=None,
679
                 email_template_name='registration/email_change_email.txt',
680
                 form_template_name='registration/email_change_form.html',
681
                 confirm_template_name='registration/email_change_done.html',
682
                 extra_context=None):
683
    extra_context = extra_context or {}
684

    
685
    if not settings.EMAILCHANGE_ENABLED:
686
        raise PermissionDenied
687

    
688
    if activation_key:
689
        try:
690
            try:
691
                email_change = EmailChange.objects.get(
692
                    activation_key=activation_key)
693
            except EmailChange.DoesNotExist:
694
                transaction.rollback()
695
                logger.error("[change-email] Invalid or used activation "
696
                             "code, %s", activation_key)
697
                raise Http404
698

    
699
            if (request.user.is_authenticated() and \
700
                request.user == email_change.user) or not \
701
                    request.user.is_authenticated():
702
                user = EmailChange.objects.change_email(activation_key)
703
                msg = _(astakos_messages.EMAIL_CHANGED)
704
                messages.success(request, msg)
705
                transaction.commit()
706
                return HttpResponseRedirect(reverse('edit_profile'))
707
            else:
708
                logger.error("[change-email] Access from invalid user, %s %s",
709
                             email_change.user, request.user.log_display)
710
                transaction.rollback()
711
                raise PermissionDenied
712
        except ValueError, e:
713
            messages.error(request, e)
714
            transaction.rollback()
715
            return HttpResponseRedirect(reverse('index'))
716

    
717
        return render_response(confirm_template_name,
718
                               modified_user=user if 'user' in locals()
719
                               else None, context_instance=get_context(request,
720
                               extra_context))
721

    
722
    if not request.user.is_authenticated():
723
        path = quote(request.get_full_path())
724
        url = request.build_absolute_uri(reverse('index'))
725
        return HttpResponseRedirect(url + '?next=' + path)
726

    
727
    # clean up expired email changes
728
    if request.user.email_change_is_pending():
729
        change = request.user.emailchanges.get()
730
        if change.activation_key_expired():
731
            change.delete()
732
            transaction.commit()
733
            return HttpResponseRedirect(reverse('email_change'))
734

    
735
    form = EmailChangeForm(request.POST or None)
736
    if request.method == 'POST' and form.is_valid():
737
        try:
738
            ec = form.save(request, email_template_name, request)
739
        except Exception, e:
740
            transaction.rollback()
741
            raise
742
        else:
743
            msg = _(astakos_messages.EMAIL_CHANGE_REGISTERED)
744
            messages.success(request, msg)
745
            transaction.commit()
746
            return HttpResponseRedirect(reverse('edit_profile'))
747

    
748
    if request.user.email_change_is_pending():
749
        messages.warning(request, astakos_messages.PENDING_EMAIL_CHANGE_REQUEST)
750

    
751
    return render_response(
752
        form_template_name,
753
        form=form,
754
        context_instance=get_context(request, extra_context)
755
    )
756

    
757

    
758
@cookie_fix
759
def send_activation(request, user_id, template_name='im/login.html',
760
                    extra_context=None):
761

    
762
    if request.user.is_authenticated():
763
        return HttpResponseRedirect(reverse('index'))
764

    
765
    extra_context = extra_context or {}
766
    try:
767
        u = AstakosUser.objects.get(id=user_id)
768
    except AstakosUser.DoesNotExist:
769
        messages.error(request, _(astakos_messages.ACCOUNT_UNKNOWN))
770
    else:
771
        if u.email_verified:
772
            logger.warning("[resend activation] Account already verified: %s",
773
                           u.log_display)
774

    
775
            messages.error(request,
776
                           _(astakos_messages.ACCOUNT_ALREADY_VERIFIED))
777
        else:
778
            activation_backend = activation_backends.get_backend()
779
            activation_backend.send_user_verification_email(u)
780
            messages.success(request, astakos_messages.ACTIVATION_SENT)
781

    
782
    return HttpResponseRedirect(reverse('index'))
783

    
784

    
785
@require_http_methods(["GET"])
786
@cookie_fix
787
@valid_astakos_user_required
788
def resource_usage(request):
789

    
790
    resources_meta = presentation.RESOURCES
791

    
792
    current_usage = quotas.get_user_quotas(request.user)
793
    current_usage = json.dumps(current_usage['system'])
794
    resource_catalog, resource_groups = _resources_catalog(for_usage=True)
795
    if resource_catalog is False:
796
        # on fail resource_groups contains the result object
797
        result = resource_groups
798
        messages.error(request, 'Unable to retrieve system resources: %s' %
799
                       result.reason)
800

    
801
    resource_catalog = json.dumps(resource_catalog)
802
    resource_groups = json.dumps(resource_groups)
803
    resources_order = json.dumps(resources_meta.get('resources_order'))
804

    
805
    return render_response('im/resource_usage.html',
806
                           context_instance=get_context(request),
807
                           resource_catalog=resource_catalog,
808
                           resource_groups=resource_groups,
809
                           resources_order=resources_order,
810
                           current_usage=current_usage,
811
                           token_cookie_name=settings.COOKIE_NAME,
812
                           usage_update_interval=
813
                           settings.USAGE_UPDATE_INTERVAL)
814

    
815

    
816
# TODO: action only on POST and user should confirm the removal
817
@require_http_methods(["POST"])
818
@cookie_fix
819
@valid_astakos_user_required
820
def remove_auth_provider(request, pk):
821
    try:
822
        provider = request.user.auth_providers.get(pk=int(pk)).settings
823
    except AstakosUserAuthProvider.DoesNotExist:
824
        raise Http404
825

    
826
    if provider.get_remove_policy:
827
        messages.success(request, provider.get_removed_msg)
828
        provider.remove_from_user()
829
        return HttpResponseRedirect(reverse('edit_profile'))
830
    else:
831
        raise PermissionDenied
832

    
833

    
834
@require_http_methods(["GET"])
835
@required_auth_methods_assigned(allow_access=True)
836
@login_required
837
@cookie_fix
838
@signed_terms_required
839
def landing(request):
840
    context = {'services': Component.catalog(orderfor='dashboard')}
841
    return render_response(
842
        'im/landing.html',
843
        context_instance=get_context(request), **context)
844

    
845

    
846
@cookie_fix
847
def get_menu(request, with_extra_links=False, with_signout=True):
848
    user = request.user
849
    index_url = reverse('index')
850

    
851
    if isinstance(user, User) and user.is_authenticated():
852
        l = []
853
        append = l.append
854
        item = MenuItem
855
        item.current_path = request.build_absolute_uri(request.path)
856
        append(item(url=request.build_absolute_uri(reverse('index')),
857
                    name=user.email))
858
        if with_extra_links:
859
            append(item(url=request.build_absolute_uri(reverse('landing')),
860
                        name="Overview"))
861
        if with_signout:
862
            append(item(url=request.build_absolute_uri(reverse('landing')),
863
                        name="Dashboard"))
864
        if with_extra_links:
865
            append(item(url=request.build_absolute_uri(reverse('edit_profile')),
866
                        name="Profile"))
867

    
868
        if with_extra_links:
869
            if settings.INVITATIONS_ENABLED:
870
                append(item(url=request.build_absolute_uri(reverse('invite')),
871
                            name="Invitations"))
872

    
873
            append(item(url=request.build_absolute_uri(reverse('api_access')),
874
                        name="API access"))
875

    
876
            append(item(url=request.build_absolute_uri(reverse('resource_usage')),
877
                        name="Usage"))
878

    
879
            if settings.PROJECTS_VISIBLE:
880
                append(item(url=request.build_absolute_uri(reverse('project_list')),
881
                            name="Projects"))
882

    
883
            append(item(url=request.build_absolute_uri(reverse('feedback')),
884
                        name="Contact"))
885
        if with_signout:
886
            append(item(url=request.build_absolute_uri(reverse('logout')),
887
                        name="Sign out"))
888
    else:
889
        l = [{'url': request.build_absolute_uri(index_url),
890
              'name': _("Sign in")}]
891

    
892
    callback = request.GET.get('callback', None)
893
    data = json.dumps(tuple(l))
894
    mimetype = 'application/json'
895

    
896
    if callback:
897
        mimetype = 'application/javascript'
898
        data = '%s(%s)' % (callback, data)
899

    
900
    return HttpResponse(content=data, mimetype=mimetype)
901

    
902

    
903
class MenuItem(dict):
904
    current_path = ''
905

    
906
    def __init__(self, *args, **kwargs):
907
        super(MenuItem, self).__init__(*args, **kwargs)
908
        if kwargs.get('url') or kwargs.get('submenu'):
909
            self.__set_is_active__()
910

    
911
    def __setitem__(self, key, value):
912
        super(MenuItem, self).__setitem__(key, value)
913
        if key in ('url', 'submenu'):
914
            self.__set_is_active__()
915

    
916
    def __set_is_active__(self):
917
        if self.get('is_active'):
918
            return
919
        if self.current_path.startswith(self.get('url')):
920
            self.__setitem__('is_active', True)
921
        else:
922
            submenu = self.get('submenu', ())
923
            current = (i for i in submenu if i.get('url') == self.current_path)
924
            try:
925
                current_node = current.next()
926
                if not current_node.get('is_active'):
927
                    current_node.__setitem__('is_active', True)
928
                self.__setitem__('is_active', True)
929
            except StopIteration:
930
                return
931

    
932
    def __setattribute__(self, name, value):
933
        super(MenuItem, self).__setattribute__(name, value)
934
        if name == 'current_path':
935
            self.__set_is_active__()
936

    
937

    
938
def get_services(request):
939
    callback = request.GET.get('callback', None)
940
    mimetype = 'application/json'
941
    data = json.dumps(Component.catalog().values())
942

    
943
    if callback:
944
        # Consume session messages. When get_services is loaded from an astakos
945
        # page, messages should have already been consumed in the html
946
        # response. When get_services is loaded from another domain/service we
947
        # consume them here so that no stale messages to appear if user visits
948
        # an astakos view later on.
949
        # TODO: messages could be served to other services/sites in the dict
950
        # response of get_services and/or get_menu. Services could handle those
951
        # messages respectively.
952
        messages_list = list(messages.get_messages(request))
953
        mimetype = 'application/javascript'
954
        data = '%s(%s)' % (callback, data)
955

    
956
    return HttpResponse(content=data, mimetype=mimetype)