Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / views / im.py @ 70e11eaa

History | View | Annotate | Download (30.6 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 calendar
36
import inflect
37

    
38
engine = inflect.engine()
39

    
40
from urllib import quote
41
from functools import wraps
42
from datetime import datetime
43
from synnefo.lib.ordereddict import OrderedDict
44

    
45
from django_tables2 import RequestConfig
46

    
47
from django.shortcuts import get_object_or_404
48
from django.contrib import messages
49
from django.contrib.auth.decorators import login_required
50
from django.core.urlresolvers import reverse
51
from django.db import transaction
52
from django.db.utils import IntegrityError
53
from django.http import (
54
    HttpResponse, HttpResponseBadRequest,
55
    HttpResponseForbidden, HttpResponseRedirect,
56
    HttpResponseBadRequest, Http404)
57
from django.shortcuts import redirect
58
from django.template import RequestContext, loader as template_loader
59
from django.utils.http import urlencode
60
from django.utils.html import escape
61
from django.utils.safestring import mark_safe
62
from django.utils.translation import ugettext as _
63
from django.views.generic.create_update import (
64
    apply_extra_context, lookup_object, delete_object, get_model_and_form_class)
65
from django.views.generic.list_detail import object_list, object_detail
66
from django.core.xheaders import populate_xheaders
67
from django.core.exceptions import ValidationError, PermissionDenied
68
from django.views.decorators.http import require_http_methods
69
from django.db.models import Q
70
from django.utils import simplejson as json
71
from django.contrib.auth.views import redirect_to_login
72

    
73
from synnefo_branding.utils import render_to_string
74

    
75
import astakos.im.messages as astakos_messages
76

    
77
from astakos.im import activation_backends
78
from astakos.im import tables
79
from astakos.im.models import (
80
    AstakosUser, ApprovalTerms,
81
    EmailChange, AstakosUserAuthProvider, PendingThirdPartyUser,
82
    ProjectApplication, ProjectMembership, Project, Service, Resource)
83
from astakos.im.util import (
84
    get_context, prepare_response, get_query, restrict_next, model_to_dict)
85
from astakos.im.forms import (
86
    LoginForm, InvitationForm,
87
    FeedbackForm, SignApprovalTermsForm,
88
    EmailChangeForm,
89
    ProjectApplicationForm, ProjectSortForm,
90
    AddProjectMembersForm, ProjectSearchForm,
91
    ProjectMembersSortForm)
92
from astakos.im.forms import ExtendedProfileForm as ProfileForm
93
from astakos.im.functions import (
94
    send_feedback,
95
    logout as auth_logout,
96
    invite as invite_func,
97
    check_pending_app_quota,
98
    accept_membership, reject_membership, remove_membership, cancel_membership,
99
    leave_project, join_project, enroll_member, can_join_request,
100
    can_leave_request,
101
    get_related_project_id, get_by_chain_or_404,
102
    approve_application, deny_application,
103
    cancel_application, dismiss_application)
104
from astakos.im.settings import (
105
    COOKIE_DOMAIN, LOGOUT_NEXT,
106
    LOGGING_LEVEL, PAGINATE_BY,
107
    PAGINATE_BY_ALL,
108
    ACTIVATION_REDIRECT_URL,
109
    MODERATION_ENABLED)
110
from astakos.im import presentation
111
from astakos.im import settings
112
from astakos.im import auth_providers as auth
113
from snf_django.lib.db.transaction import commit_on_success_strict
114
from astakos.im.ctx import ExceptionHandler
115
from astakos.im import quotas
116
from astakos.im.views.util import render_response, _resources_catalog
117
from astakos.im.views.decorators import cookie_fix, signed_terms_required,\
118
    required_auth_methods_assigned, valid_astakos_user_required
119

    
120
logger = logging.getLogger(__name__)
121

    
122

    
123
@require_http_methods(["GET", "POST"])
124
@cookie_fix
125
@signed_terms_required
126
def index(request, login_template_name='im/login.html', profile_template_name='im/profile.html', extra_context=None):
127
    """
128
    If there is logged on user renders the profile page otherwise renders login page.
129

130
    **Arguments**
131

132
    ``login_template_name``
133
        A custom login template to use. This is optional; if not specified,
134
        this will default to ``im/login.html``.
135

136
    ``profile_template_name``
137
        A custom profile template to use. This is optional; if not specified,
138
        this will default to ``im/profile.html``.
139

140
    ``extra_context``
141
        An dictionary of variables to add to the template context.
142

143
    **Template:**
144

145
    im/profile.html or im/login.html or ``template_name`` keyword argument.
146

147
    """
148
    extra_context = extra_context or {}
149
    template_name = login_template_name
150
    if request.user.is_authenticated():
151
        return HttpResponseRedirect(reverse('astakos.im.views.edit_profile'))
152

    
153
    third_party_token = request.GET.get('key', False)
154
    if third_party_token:
155
        messages.info(request, astakos_messages.AUTH_PROVIDER_LOGIN_TO_ADD)
156

    
157
    return render_response(
158
        template_name,
159
        login_form = LoginForm(request=request),
160
        context_instance = get_context(request, extra_context)
161
    )
162

    
163

    
164
@require_http_methods(["POST"])
165
@cookie_fix
166
@valid_astakos_user_required
167
def update_token(request):
168
    """
169
    Update api token view.
170
    """
171
    user = request.user
172
    user.renew_token()
173
    user.save()
174
    messages.success(request, astakos_messages.TOKEN_UPDATED)
175
    return HttpResponseRedirect(reverse('edit_profile'))
176

    
177

    
178
@require_http_methods(["GET", "POST"])
179
@cookie_fix
180
@valid_astakos_user_required
181
@transaction.commit_manually
182
def invite(request, template_name='im/invitations.html', extra_context=None):
183
    """
184
    Allows a user to invite somebody else.
185

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

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

193
    If the user isn't logged in, redirects to settings.LOGIN_URL.
194

195
    **Arguments**
196

197
    ``template_name``
198
        A custom template to use. This is optional; if not specified,
199
        this will default to ``im/invitations.html``.
200

201
    ``extra_context``
202
        An dictionary of variables to add to the template context.
203

204
    **Template:**
205

206
    im/invitations.html or ``template_name`` keyword argument.
207

208
    **Settings:**
209

210
    The view expectes the following settings are defined:
211

212
    * LOGIN_URL: login uri
213
    """
214
    extra_context = extra_context or {}
215
    status = None
216
    message = None
217
    form = InvitationForm()
218

    
219
    inviter = request.user
220
    if request.method == 'POST':
221
        form = InvitationForm(request.POST)
222
        if inviter.invitations > 0:
223
            if form.is_valid():
224
                try:
225
                    email = form.cleaned_data.get('username')
226
                    realname = form.cleaned_data.get('realname')
227
                    invite_func(inviter, email, realname)
228
                    message = _(astakos_messages.INVITATION_SENT) % locals()
229
                    messages.success(request, message)
230
                except Exception, e:
231
                    transaction.rollback()
232
                    raise
233
                else:
234
                    transaction.commit()
235
        else:
236
            message = _(astakos_messages.MAX_INVITATION_NUMBER_REACHED)
237
            messages.error(request, message)
238

    
239
    sent = [{'email': inv.username,
240
             'realname': inv.realname,
241
             'is_consumed': inv.is_consumed}
242
            for inv in request.user.invitations_sent.all()]
243
    kwargs = {'inviter': inviter,
244
              'sent': sent}
245
    context = get_context(request, extra_context, **kwargs)
246
    return render_response(template_name,
247
                           invitation_form=form,
248
                           context_instance=context)
249

    
250

    
251
@require_http_methods(["GET", "POST"])
252
@required_auth_methods_assigned(allow_access=True)
253
@login_required
254
@cookie_fix
255
@signed_terms_required
256
def edit_profile(request, template_name='im/profile.html', extra_context=None):
257
    """
258
    Allows a user to edit his/her profile.
259

260
    In case of GET request renders a form for displaying the user information.
261
    In case of POST updates the user informantion and redirects to ``next``
262
    url parameter if exists.
263

264
    If the user isn't logged in, redirects to settings.LOGIN_URL.
265

266
    **Arguments**
267

268
    ``template_name``
269
        A custom template to use. This is optional; if not specified,
270
        this will default to ``im/profile.html``.
271

272
    ``extra_context``
273
        An dictionary of variables to add to the template context.
274

275
    **Template:**
276

277
    im/profile.html or ``template_name`` keyword argument.
278

279
    **Settings:**
280

281
    The view expectes the following settings are defined:
282

283
    * LOGIN_URL: login uri
284
    """
285
    extra_context = extra_context or {}
286
    form = ProfileForm(
287
        instance=request.user,
288
        session_key=request.session.session_key
289
    )
290
    extra_context['next'] = request.GET.get('next')
291
    if request.method == 'POST':
292
        form = ProfileForm(
293
            request.POST,
294
            instance=request.user,
295
            session_key=request.session.session_key
296
        )
297
        if form.is_valid():
298
            try:
299
                prev_token = request.user.auth_token
300
                user = form.save(request=request)
301
                next = restrict_next(
302
                    request.POST.get('next'),
303
                    domain=COOKIE_DOMAIN
304
                )
305
                msg = _(astakos_messages.PROFILE_UPDATED)
306
                messages.success(request, msg)
307

    
308
                if form.email_changed:
309
                    msg = _(astakos_messages.EMAIL_CHANGE_REGISTERED)
310
                    messages.success(request, msg)
311
                if form.password_changed:
312
                    msg = _(astakos_messages.PASSWORD_CHANGED)
313
                    messages.success(request, msg)
314

    
315
                if next:
316
                    return redirect(next)
317
                else:
318
                    return redirect(reverse('edit_profile'))
319
            except ValueError, ve:
320
                messages.success(request, ve)
321
    elif request.method == "GET":
322
        request.user.is_verified = True
323
        request.user.save()
324

    
325
    # existing providers
326
    user_providers = request.user.get_enabled_auth_providers()
327
    user_disabled_providers = request.user.get_disabled_auth_providers()
328

    
329
    # providers that user can add
330
    user_available_providers = request.user.get_available_auth_providers()
331

    
332
    extra_context['services'] = Service.catalog().values()
333
    return render_response(template_name,
334
                           profile_form=form,
335
                           user_providers=user_providers,
336
                           user_disabled_providers=user_disabled_providers,
337
                           user_available_providers=user_available_providers,
338
                           context_instance=get_context(request,
339
                                                          extra_context))
340

    
341

    
342
@transaction.commit_manually
343
@require_http_methods(["GET", "POST"])
344
@cookie_fix
345
def signup(request, template_name='im/signup.html', on_success='index',
346
           extra_context=None, activation_backend=None):
347
    """
348
    Allows a user to create a local account.
349

350
    In case of GET request renders a form for entering the user information.
351
    In case of POST handles the signup.
352

353
    The user activation will be delegated to the backend specified by the
354
    ``activation_backend`` keyword argument if present, otherwise to the
355
    ``astakos.im.activation_backends.InvitationBackend`` if
356
    settings.ASTAKOS_INVITATIONS_ENABLED is True or
357
    ``astakos.im.activation_backends.SimpleBackend`` if not (see
358
    activation_backends);
359

360
    Upon successful user creation, if ``next`` url parameter is present the
361
    user is redirected there otherwise renders the same page with a success
362
    message.
363

364
    On unsuccessful creation, renders ``template_name`` with an error message.
365

366
    **Arguments**
367

368
    ``template_name``
369
        A custom template to render. This is optional;
370
        if not specified, this will default to ``im/signup.html``.
371

372
    ``extra_context``
373
        An dictionary of variables to add to the template context.
374

375
    ``on_success``
376
        Resolvable view name to redirect on registration success.
377

378
    **Template:**
379

380
    im/signup.html or ``template_name`` keyword argument.
381
    """
382
    extra_context = extra_context or {}
383
    if request.user.is_authenticated():
384
        logger.info("%s already signed in, redirect to index",
385
                    request.user.log_display)
386
        return HttpResponseRedirect(reverse('index'))
387

    
388
    provider = get_query(request).get('provider', 'local')
389
    if not auth.get_provider(provider).get_create_policy:
390
        logger.error("%s provider not available for signup", provider)
391
        raise PermissionDenied
392

    
393
    instance = None
394

    
395
    # user registered using third party provider
396
    third_party_token = request.REQUEST.get('third_party_token', None)
397
    unverified = None
398
    if third_party_token:
399
        # retreive third party entry. This was created right after the initial
400
        # third party provider handshake.
401
        pending = get_object_or_404(PendingThirdPartyUser,
402
                                    token=third_party_token)
403

    
404
        provider = pending.provider
405

    
406
        # clone third party instance into the corresponding AstakosUser
407
        instance = pending.get_user_instance()
408
        get_unverified = AstakosUserAuthProvider.objects.unverified
409

    
410
        # check existing unverified entries
411
        unverified = get_unverified(pending.provider,
412
                                    identifier=pending.third_party_identifier)
413

    
414
        if unverified and request.method == 'GET':
415
            messages.warning(request, unverified.get_pending_registration_msg)
416
            if unverified.user.moderated:
417
                messages.warning(request,
418
                                 unverified.get_pending_resend_activation_msg)
419
            else:
420
                messages.warning(request,
421
                                 unverified.get_pending_moderation_msg)
422

    
423
    # prepare activation backend based on current request
424
    if not activation_backend:
425
        activation_backend = activation_backends.get_backend()
426

    
427
    form_kwargs = {'instance': instance}
428
    if third_party_token:
429
        form_kwargs['third_party_token'] = third_party_token
430

    
431
    form = activation_backend.get_signup_form(
432
        provider, None, **form_kwargs)
433

    
434
    if request.method == 'POST':
435
        form = activation_backend.get_signup_form(
436
            provider,
437
            request.POST,
438
            **form_kwargs)
439

    
440
        if form.is_valid():
441
            commited = False
442
            try:
443
                user = form.save(commit=False)
444

    
445
                # delete previously unverified accounts
446
                if AstakosUser.objects.user_exists(user.email):
447
                    AstakosUser.objects.get_by_identifier(user.email).delete()
448

    
449
                # store_user so that user auth providers get initialized
450
                form.store_user(user, request)
451
                result = activation_backend.handle_registration(user)
452
                if result.status == \
453
                        activation_backend.Result.PENDING_MODERATION:
454
                    # user should be warned that his account is not active yet
455
                    status = messages.WARNING
456
                else:
457
                    status = messages.SUCCESS
458
                message = result.message
459
                activation_backend.send_result_notifications(result, user)
460

    
461
                # commit user entry
462
                transaction.commit()
463
                # commited flag
464
                # in case an exception get raised from this point
465
                commited = True
466

    
467
                if user and user.is_active:
468
                    # activation backend directly activated the user
469
                    # log him in
470
                    next = request.POST.get('next', '')
471
                    response = prepare_response(request, user, next=next)
472
                    return response
473

    
474
                messages.add_message(request, status, message)
475
                return HttpResponseRedirect(reverse(on_success))
476
            except Exception, e:
477
                if not commited:
478
                    transaction.rollback()
479
                raise
480

    
481
    return render_response(template_name,
482
                           signup_form=form,
483
                           third_party_token=third_party_token,
484
                           provider=provider,
485
                           context_instance=get_context(request, extra_context))
486

    
487

    
488
@require_http_methods(["GET", "POST"])
489
@required_auth_methods_assigned(allow_access=True)
490
@login_required
491
@cookie_fix
492
@signed_terms_required
493
def feedback(request, template_name='im/feedback.html', email_template_name='im/feedback_mail.txt', extra_context=None):
494
    """
495
    Allows a user to send feedback.
496

497
    In case of GET request renders a form for providing the feedback information.
498
    In case of POST sends an email to support team.
499

500
    If the user isn't logged in, redirects to settings.LOGIN_URL.
501

502
    **Arguments**
503

504
    ``template_name``
505
        A custom template to use. This is optional; if not specified,
506
        this will default to ``im/feedback.html``.
507

508
    ``extra_context``
509
        An dictionary of variables to add to the template context.
510

511
    **Template:**
512

513
    im/signup.html or ``template_name`` keyword argument.
514

515
    **Settings:**
516

517
    * LOGIN_URL: login uri
518
    """
519
    extra_context = extra_context or {}
520
    if request.method == 'GET':
521
        form = FeedbackForm()
522
    if request.method == 'POST':
523
        if not request.user:
524
            return HttpResponse('Unauthorized', status=401)
525

    
526
        form = FeedbackForm(request.POST)
527
        if form.is_valid():
528
            msg = form.cleaned_data['feedback_msg']
529
            data = form.cleaned_data['feedback_data']
530
            send_feedback(msg, data, request.user, email_template_name)
531
            message = _(astakos_messages.FEEDBACK_SENT)
532
            messages.success(request, message)
533
            return HttpResponseRedirect(reverse('feedback'))
534

    
535
    return render_response(template_name,
536
                           feedback_form=form,
537
                           context_instance=get_context(request,
538
                                                        extra_context))
539

    
540

    
541
@require_http_methods(["GET"])
542
@cookie_fix
543
def logout(request, template='registration/logged_out.html',
544
           extra_context=None):
545
    """
546
    Wraps `django.contrib.auth.logout`.
547
    """
548
    extra_context = extra_context or {}
549
    response = HttpResponse()
550
    if request.user.is_authenticated():
551
        email = request.user.email
552
        auth_logout(request)
553
    else:
554
        response['Location'] = reverse('index')
555
        response.status_code = 301
556
        return response
557

    
558
    next = restrict_next(
559
        request.GET.get('next'),
560
        domain=COOKIE_DOMAIN
561
    )
562

    
563
    if next:
564
        response['Location'] = next
565
        response.status_code = 302
566
    elif LOGOUT_NEXT:
567
        response['Location'] = LOGOUT_NEXT
568
        response.status_code = 301
569
    else:
570
        last_provider = request.COOKIES.get('astakos_last_login_method', 'local')
571
        provider = auth.get_provider(last_provider)
572
        message = provider.get_logout_success_msg
573
        extra = provider.get_logout_success_extra_msg
574
        if extra:
575
            message += "<br />"  + extra
576
        messages.success(request, message)
577
        response['Location'] = reverse('index')
578
        response.status_code = 301
579
    return response
580

    
581

    
582
@require_http_methods(["GET", "POST"])
583
@cookie_fix
584
@transaction.commit_manually
585
def activate(request, greeting_email_template_name='im/welcome_email.txt',
586
             helpdesk_email_template_name='im/helpdesk_notification.txt'):
587
    """
588
    Activates the user identified by the ``auth`` request parameter, sends a
589
    welcome email and renews the user token.
590

591
    The view uses commit_manually decorator in order to ensure the user state
592
    will be updated only if the email will be send successfully.
593
    """
594
    token = request.GET.get('auth')
595
    next = request.GET.get('next')
596

    
597
    if request.user.is_authenticated():
598
        message = _(astakos_messages.LOGGED_IN_WARNING)
599
        messages.error(request, message)
600
        return HttpResponseRedirect(reverse('index'))
601

    
602
    try:
603
        user = AstakosUser.objects.get(verification_code=token)
604
    except AstakosUser.DoesNotExist:
605
        raise Http404
606

    
607
    if user.email_verified:
608
        message = _(astakos_messages.ACCOUNT_ALREADY_VERIFIED)
609
        messages.error(request, message)
610
        return HttpResponseRedirect(reverse('index'))
611

    
612
    try:
613
        backend = activation_backends.get_backend()
614
        result = backend.handle_verification(user, token)
615
        backend.send_result_notifications(result, user)
616
        next = ACTIVATION_REDIRECT_URL or next
617
        response = HttpResponseRedirect(reverse('index'))
618
        if user.is_active:
619
            response = prepare_response(request, user, next, renew=True)
620
            messages.success(request, _(result.message))
621
        else:
622
            messages.warning(request, _(result.message))
623
    except Exception:
624
        transaction.rollback()
625
        raise
626
    else:
627
        transaction.commit()
628
        return response
629

    
630

    
631
@require_http_methods(["GET", "POST"])
632
@cookie_fix
633
def approval_terms(request, term_id=None,
634
                   template_name='im/approval_terms.html', extra_context=None):
635
    extra_context = extra_context or {}
636
    term = None
637
    terms = None
638
    if not term_id:
639
        try:
640
            term = ApprovalTerms.objects.order_by('-id')[0]
641
        except IndexError:
642
            pass
643
    else:
644
        try:
645
            term = ApprovalTerms.objects.get(id=term_id)
646
        except ApprovalTerms.DoesNotExist, e:
647
            pass
648

    
649
    if not term:
650
        messages.error(request, _(astakos_messages.NO_APPROVAL_TERMS))
651
        return HttpResponseRedirect(reverse('index'))
652
    try:
653
        f = open(term.location, 'r')
654
    except IOError:
655
        messages.error(request, _(astakos_messages.GENERIC_ERROR))
656
        return render_response(
657
            template_name, context_instance=get_context(request,
658
                                                        extra_context))
659

    
660
    terms = f.read()
661

    
662
    if request.method == 'POST':
663
        next = restrict_next(
664
            request.POST.get('next'),
665
            domain=COOKIE_DOMAIN
666
        )
667
        if not next:
668
            next = reverse('index')
669
        form = SignApprovalTermsForm(request.POST, instance=request.user)
670
        if not form.is_valid():
671
            return render_response(template_name,
672
                                   terms=terms,
673
                                   approval_terms_form=form,
674
                                   context_instance=get_context(request,
675
                                                                extra_context))
676
        user = form.save()
677
        return HttpResponseRedirect(next)
678
    else:
679
        form = None
680
        if request.user.is_authenticated() and not request.user.signed_terms:
681
            form = SignApprovalTermsForm(instance=request.user)
682
        return render_response(template_name,
683
                               terms=terms,
684
                               approval_terms_form=form,
685
                               context_instance=get_context(request,
686
                                                            extra_context))
687

    
688

    
689
@require_http_methods(["GET", "POST"])
690
@cookie_fix
691
@transaction.commit_manually
692
def change_email(request, activation_key=None,
693
                 email_template_name='registration/email_change_email.txt',
694
                 form_template_name='registration/email_change_form.html',
695
                 confirm_template_name='registration/email_change_done.html',
696
                 extra_context=None):
697
    extra_context = extra_context or {}
698

    
699
    if not settings.EMAILCHANGE_ENABLED:
700
        raise PermissionDenied
701

    
702
    if activation_key:
703
        try:
704
            try:
705
                email_change = EmailChange.objects.get(
706
                    activation_key=activation_key)
707
            except EmailChange.DoesNotExist:
708
                transaction.rollback()
709
                logger.error("[change-email] Invalid or used activation "
710
                             "code, %s", activation_key)
711
                raise Http404
712

    
713
            if (request.user.is_authenticated() and \
714
                request.user == email_change.user) or not \
715
                    request.user.is_authenticated():
716
                user = EmailChange.objects.change_email(activation_key)
717
                msg = _(astakos_messages.EMAIL_CHANGED)
718
                messages.success(request, msg)
719
                transaction.commit()
720
                return HttpResponseRedirect(reverse('edit_profile'))
721
            else:
722
                logger.error("[change-email] Access from invalid user, %s %s",
723
                             email_change.user, request.user.log_display)
724
                transaction.rollback()
725
                raise PermissionDenied
726
        except ValueError, e:
727
            messages.error(request, e)
728
            transaction.rollback()
729
            return HttpResponseRedirect(reverse('index'))
730

    
731
        return render_response(confirm_template_name,
732
                               modified_user=user if 'user' in locals()
733
                               else None, context_instance=get_context(request,
734
                               extra_context))
735

    
736
    if not request.user.is_authenticated():
737
        path = quote(request.get_full_path())
738
        url = request.build_absolute_uri(reverse('index'))
739
        return HttpResponseRedirect(url + '?next=' + path)
740

    
741
    # clean up expired email changes
742
    if request.user.email_change_is_pending():
743
        change = request.user.emailchanges.get()
744
        if change.activation_key_expired():
745
            change.delete()
746
            transaction.commit()
747
            return HttpResponseRedirect(reverse('email_change'))
748

    
749
    form = EmailChangeForm(request.POST or None)
750
    if request.method == 'POST' and form.is_valid():
751
        try:
752
            ec = form.save(request, email_template_name, request)
753
        except Exception, e:
754
            transaction.rollback()
755
            raise
756
        else:
757
            msg = _(astakos_messages.EMAIL_CHANGE_REGISTERED)
758
            messages.success(request, msg)
759
            transaction.commit()
760
            return HttpResponseRedirect(reverse('edit_profile'))
761

    
762
    if request.user.email_change_is_pending():
763
        messages.warning(request, astakos_messages.PENDING_EMAIL_CHANGE_REQUEST)
764

    
765
    return render_response(
766
        form_template_name,
767
        form=form,
768
        context_instance=get_context(request, extra_context)
769
    )
770

    
771

    
772
@cookie_fix
773
def send_activation(request, user_id, template_name='im/login.html',
774
                    extra_context=None):
775

    
776
    if request.user.is_authenticated():
777
        return HttpResponseRedirect(reverse('index'))
778

    
779
    extra_context = extra_context or {}
780
    try:
781
        u = AstakosUser.objects.get(id=user_id)
782
    except AstakosUser.DoesNotExist:
783
        messages.error(request, _(astakos_messages.ACCOUNT_UNKNOWN))
784
    else:
785
        if u.email_verified:
786
            logger.warning("[resend activation] Account already verified: %s",
787
                           u.log_display)
788

    
789
            messages.error(request,
790
                           _(astakos_messages.ACCOUNT_ALREADY_VERIFIED))
791
        else:
792
            activation_backend = activation_backends.get_backend()
793
            activation_backend.send_user_verification_email(u)
794
            messages.success(request, astakos_messages.ACTIVATION_SENT)
795

    
796
    return HttpResponseRedirect(reverse('index'))
797

    
798

    
799
@require_http_methods(["GET"])
800
@cookie_fix
801
@valid_astakos_user_required
802
def resource_usage(request):
803

    
804
    resources_meta = presentation.RESOURCES
805

    
806
    current_usage = quotas.get_user_quotas(request.user)
807
    current_usage = json.dumps(current_usage['system'])
808
    resource_catalog, resource_groups = _resources_catalog(for_usage=True)
809
    if resource_catalog is False:
810
        # on fail resource_groups contains the result object
811
        result = resource_groups
812
        messages.error(request, 'Unable to retrieve system resources: %s' %
813
                       result.reason)
814

    
815
    resource_catalog = json.dumps(resource_catalog)
816
    resource_groups = json.dumps(resource_groups)
817
    resources_order = json.dumps(resources_meta.get('resources_order'))
818

    
819
    return render_response('im/resource_usage.html',
820
                           context_instance=get_context(request),
821
                           resource_catalog=resource_catalog,
822
                           resource_groups=resource_groups,
823
                           resources_order=resources_order,
824
                           current_usage=current_usage,
825
                           token_cookie_name=settings.COOKIE_NAME,
826
                           usage_update_interval=
827
                           settings.USAGE_UPDATE_INTERVAL)
828

    
829

    
830
# TODO: action only on POST and user should confirm the removal
831
@require_http_methods(["GET", "POST"])
832
@cookie_fix
833
@valid_astakos_user_required
834
def remove_auth_provider(request, pk):
835
    try:
836
        provider = request.user.auth_providers.get(pk=int(pk)).settings
837
    except AstakosUserAuthProvider.DoesNotExist:
838
        raise Http404
839

    
840
    if provider.get_remove_policy:
841
        messages.success(request, provider.get_removed_msg)
842
        provider.remove_from_user()
843
        return HttpResponseRedirect(reverse('edit_profile'))
844
    else:
845
        raise PermissionDenied
846

    
847

    
848
@require_http_methods(["GET"])
849
@required_auth_methods_assigned(allow_access=True)
850
@login_required
851
@cookie_fix
852
@signed_terms_required
853
def landing(request):
854
    context = {'services': Service.catalog(orderfor='dashboard')}
855
    return render_response(
856
        'im/landing.html',
857
        context_instance=get_context(request), **context)
858

    
859

    
860
def api_access(request):
861
    return render_response(
862
        'im/api_access.html',
863
        context_instance=get_context(request))