fix AnonymousUser logout
[astakos] / snf-astakos-app / astakos / im / views.py
index b247f9c..1936d9f 100644 (file)
@@ -39,7 +39,7 @@ from urllib import quote
 from functools import wraps
 
 from django.core.mail import send_mail
-from django.http import HttpResponse
+from django.http import HttpResponse, HttpResponseBadRequest
 from django.shortcuts import redirect
 from django.template.loader import render_to_string
 from django.utils.translation import ugettext as _
@@ -47,19 +47,20 @@ from django.core.urlresolvers import reverse
 from django.contrib.auth.decorators import login_required
 from django.contrib import messages
 from django.db import transaction
-from django.contrib.auth import logout as auth_logout
 from django.utils.http import urlencode
 from django.http import HttpResponseRedirect, HttpResponseBadRequest
 from django.db.utils import IntegrityError
 from django.contrib.auth.views import password_change
+from django.core.exceptions import ValidationError
+from django.db.models import Q
 
 from astakos.im.models import AstakosUser, Invitation, ApprovalTerms
-from astakos.im.backends import get_backend
-from astakos.im.util import get_context, prepare_response, set_cookie, has_signed_terms
+from astakos.im.activation_backends import get_backend, SimpleBackend
+from astakos.im.util import get_context, prepare_response, set_cookie, get_query
 from astakos.im.forms import *
-from astakos.im.functions import send_greeting
-from astakos.im.settings import DEFAULT_CONTACT_EMAIL, DEFAULT_FROM_EMAIL, COOKIE_NAME, COOKIE_DOMAIN, IM_MODULES, SITENAME, BASEURL, LOGOUT_NEXT
-from astakos.im.functions import invite as invite_func
+from astakos.im.functions import send_greeting, send_feedback, SendMailError, \
+    invite as invite_func, logout as auth_logout, activate as activate_func, switch_account_to_shibboleth
+from astakos.im.settings import DEFAULT_CONTACT_EMAIL, DEFAULT_FROM_EMAIL, COOKIE_NAME, COOKIE_DOMAIN, IM_MODULES, SITENAME, LOGOUT_NEXT, LOGGING_LEVEL
 
 logger = logging.getLogger(__name__)
 
@@ -100,7 +101,7 @@ def signed_terms_required(func):
     """
     @wraps(func)
     def wrapper(request, *args, **kwargs):
-        if request.user.is_authenticated() and not has_signed_terms(request.user):
+        if request.user.is_authenticated() and not request.user.signed_terms():
             params = urlencode({'next': request.build_absolute_uri(),
                               'show_form':''})
             terms_uri = reverse('latest_terms') + '?' + params
@@ -132,12 +133,10 @@ def index(request, login_template_name='im/login.html', profile_template_name='i
 
     """
     template_name = login_template_name
-    formclass = 'LoginForm'
-    kwargs = {}
     if request.user.is_authenticated():
         return HttpResponseRedirect(reverse('astakos.im.views.edit_profile'))
     return render_response(template_name,
-                           form = globals()[formclass](**kwargs),
+                           login_form = LoginForm(request=request),
                            context_instance = get_context(request, extra_context))
 
 @login_required
@@ -179,26 +178,29 @@ def invite(request, template_name='im/invitations.html', extra_context={}):
     """
     status = None
     message = None
-    inviter = AstakosUser.objects.get(username = request.user.username)
-
+    form = InvitationForm()
+    
+    inviter = request.user
     if request.method == 'POST':
-        username = request.POST.get('uniq')
-        realname = request.POST.get('realname')
-
+        form = InvitationForm(request.POST)
         if inviter.invitations > 0:
-            try:
-                invite_func(inviter, username, realname)
-                status = messages.SUCCESS
-                message = _('Invitation sent to %s' % username)
-                transaction.commit()
-            except (SMTPException, socket.error) as e:
-                status = messages.ERROR
-                message = getattr(e, 'strerror', '')
-                transaction.rollback()
-            except IntegrityError, e:
-                status = messages.ERROR
-                message = _('There is already invitation for %s' % username)
-                transaction.rollback()
+            if form.is_valid():
+                try:
+                    invitation = form.save()
+                    invite_func(invitation, inviter)
+                    status = messages.SUCCESS
+                    message = _('Invitation sent to %s' % invitation.username)
+                except SendMailError, e:
+                    status = messages.ERROR
+                    message = e.message
+                    transaction.rollback()
+                except BaseException, e:
+                    status = messages.ERROR
+                    message = _('Something went wrong.')
+                    logger.exception(e)
+                    transaction.rollback()
+                else:
+                    transaction.commit()
         else:
             status = messages.ERROR
             message = _('No invitations left')
@@ -207,11 +209,12 @@ def invite(request, template_name='im/invitations.html', extra_context={}):
     sent = [{'email': inv.username,
              'realname': inv.realname,
              'is_consumed': inv.is_consumed}
-             for inv in inviter.invitations_sent.all()]
+             for inv in request.user.invitations_sent.all()]
     kwargs = {'inviter': inviter,
               'sent':sent}
     context = get_context(request, extra_context, **kwargs)
     return render_response(template_name,
+                           invitation_form = form,
                            context_instance = context)
 
 @login_required
@@ -259,40 +262,42 @@ def edit_profile(request, template_name='im/profile.html', extra_context={}):
                 next = request.POST.get('next')
                 if next:
                     return redirect(next)
-                msg = _('Profile has been updated successfully')
+                msg = _('<p>Profile has been updated successfully</p>')
                 messages.add_message(request, messages.SUCCESS, msg)
             except ValueError, ve:
                 messages.add_message(request, messages.ERROR, ve)
+    elif request.method == "GET":
+        request.user.is_verified = True
+        request.user.save()
     return render_response(template_name,
                            reset_cookie = reset_cookie,
-                           form = form,
+                           profile_form = form,
                            context_instance = get_context(request,
                                                           extra_context))
 
-def signup(request, on_failure='im/signup.html', on_success='im/signup_complete.html', extra_context={}, backend=None):
+def signup(request, template_name='im/signup.html', on_success='im/signup_complete.html', extra_context={}, backend=None):
     """
     Allows a user to create a local account.
 
-    In case of GET request renders a form for providing the user information.
+    In case of GET request renders a form for entering the user information.
     In case of POST handles the signup.
 
     The user activation will be delegated to the backend specified by the ``backend`` keyword argument
-    if present, otherwise to the ``astakos.im.backends.InvitationBackend``
-    if settings.ASTAKOS_INVITATIONS_ENABLED is True or ``astakos.im.backends.SimpleBackend`` if not
-    (see backends);
-
-    Upon successful user creation if ``next`` url parameter is present the user is redirected there
+    if present, otherwise to the ``astakos.im.activation_backends.InvitationBackend``
+    if settings.ASTAKOS_INVITATIONS_ENABLED is True or ``astakos.im.activation_backends.SimpleBackend`` if not
+    (see activation_backends);
+    
+    Upon successful user creation, if ``next`` url parameter is present the user is redirected there
     otherwise renders the same page with a success message.
-
-    On unsuccessful creation, renders ``on_failure`` with an error message.
-
+    
+    On unsuccessful creation, renders ``template_name`` with an error message.
+    
     **Arguments**
-
-    ``on_failure``
-        A custom template to render in case of failure. This is optional;
+    
+    ``template_name``
+        A custom template to render. This is optional;
         if not specified, this will default to ``im/signup.html``.
 
-
     ``on_success``
         A custom template to render in case of success. This is optional;
         if not specified, this will default to ``im/signup_complete.html``.
@@ -301,47 +306,58 @@ def signup(request, on_failure='im/signup.html', on_success='im/signup_complete.
         An dictionary of variables to add to the template context.
 
     **Template:**
-
-    im/signup.html or ``on_failure`` keyword argument.
-    im/signup_complete.html or ``on_success`` keyword argument.
+    
+    im/signup.html or ``template_name`` keyword argument.
+    im/signup_complete.html or ``on_success`` keyword argument. 
     """
     if request.user.is_authenticated():
-        return HttpResponseRedirect(reverse('astakos.im.views.index'))
+        return HttpResponseRedirect(reverse('astakos.im.views.edit_profile'))
+    
+    provider = get_query(request).get('provider', 'local')
     try:
         if not backend:
             backend = get_backend(request)
-        for provider in IM_MODULES:
-            extra_context['%s_form' % provider] = backend.get_signup_form(provider)
-        if request.method == 'POST':
-            provider = request.POST.get('provider')
-            next = request.POST.get('next', '')
-            form = extra_context['%s_form' % provider]
-            if form.is_valid():
-                if provider != 'local':
-                    url = reverse('astakos.im.target.%s.login' % provider)
-                    url = '%s?email=%s&next=%s' % (url, form.data['email'], next)
-                    if backend.invitation:
-                        url = '%s&code=%s' % (url, backend.invitation.code)
-                    return redirect(url)
-                else:
-                    status, message, user = backend.signup(form)
-                    if user and user.is_active:
-                        return prepare_response(request, user, next=next)
-                    messages.add_message(request, status, message)
-                    return render_response(on_success,
-                                           context_instance=get_context(request, extra_context))
-    except (Invitation.DoesNotExist, ValueError), e:
+        form = backend.get_signup_form(provider)
+    except Exception, e:
+        form = SimpleBackend(request).get_signup_form(provider)
         messages.add_message(request, messages.ERROR, e)
-        for provider in IM_MODULES:
-            main = provider.capitalize() if provider == 'local' else 'ThirdParty'
-            formclass = '%sUserCreationForm' % main
-            extra_context['%s_form' % provider] = globals()[formclass]()
-    return render_response(on_failure,
+    if request.method == 'POST':
+        if form.is_valid():
+            user = form.save(commit=False)
+            try:
+                result = backend.handle_activation(user)
+                status = messages.SUCCESS
+                message = result.message
+                user.save()
+                if 'additional_email' in form.cleaned_data:
+                    additional_email = form.cleaned_data['additional_email']
+                    if additional_email != user.email:
+                        user.additionalmail_set.create(email=additional_email)
+                        msg = 'Additional email: %s saved for user %s.' % (additional_email, user.email)
+                        logger._log(LOGGING_LEVEL, msg, [])
+                if user and user.is_active:
+                    next = request.POST.get('next', '')
+                    return prepare_response(request, user, next=next)
+                messages.add_message(request, status, message)
+                return render_response(on_success,
+                                       context_instance=get_context(request, extra_context))
+            except SendMailError, e:
+                status = messages.ERROR
+                message = e.message
+                messages.add_message(request, status, message)
+            except BaseException, e:
+                status = messages.ERROR
+                message = _('Something went wrong.')
+                messages.add_message(request, status, message)
+                logger.exception(e)
+    return render_response(template_name,
+                           signup_form = form,
+                           provider = provider,
                            context_instance=get_context(request, extra_context))
 
 @login_required
 @signed_terms_required
-def send_feedback(request, template_name='im/feedback.html', email_template_name='im/feedback_mail.txt', extra_context={}):
+def feedback(request, template_name='im/feedback.html', email_template_name='im/feedback_mail.txt', extra_context={}):
     """
     Allows a user to send feedback.
 
@@ -376,33 +392,32 @@ def send_feedback(request, template_name='im/feedback.html', email_template_name
 
         form = FeedbackForm(request.POST)
         if form.is_valid():
-            subject = _("Feedback from %s alpha2 testing" % SITENAME)
-            from_email = request.user.email
-            recipient_list = [DEFAULT_CONTACT_EMAIL]
-            content = render_to_string(email_template_name, {
-                        'message': form.cleaned_data['feedback_msg'],
-                        'data': form.cleaned_data['feedback_data'],
-                        'request': request})
-
+            msg = form.cleaned_data['feedback_msg']
+            data = form.cleaned_data['feedback_data']
             try:
-                send_mail(subject, content, from_email, recipient_list)
+                send_feedback(msg, data, request.user, email_template_name)
+            except SendMailError, e:
+                message = e.message
+                status = messages.ERROR
+            else:
                 message = _('Feedback successfully sent')
                 status = messages.SUCCESS
-            except (SMTPException, socket.error) as e:
-                status = messages.ERROR
-                message = getattr(e, 'strerror', '')
             messages.add_message(request, status, message)
     return render_response(template_name,
-                           form = form,
+                           feedback_form = form,
                            context_instance = get_context(request, extra_context))
 
 def logout(request, template='registration/logged_out.html', extra_context={}):
     """
     Wraps `django.contrib.auth.logout` and delete the cookie.
     """
-    auth_logout(request)
     response = HttpResponse()
-    response.delete_cookie(COOKIE_NAME, path='/', domain=COOKIE_DOMAIN)
+    if request.user.is_authenticated():
+        email = request.user.email
+        auth_logout(request)
+        response.delete_cookie(COOKIE_NAME, path='/', domain=COOKIE_DOMAIN)
+        msg = 'Cookie deleted for %s' % email
+        logger._log(LOGGING_LEVEL, msg, [])
     next = request.GET.get('next')
     if next:
         response['Location'] = next
@@ -412,13 +427,13 @@ def logout(request, template='registration/logged_out.html', extra_context={}):
         response['Location'] = LOGOUT_NEXT
         response.status_code = 301
         return response
-    messages.add_message(request, messages.SUCCESS, _('You have successfully logged out.'))
+    messages.add_message(request, messages.SUCCESS, _('<p>You have successfully logged out.</p>'))
     context = get_context(request, extra_context)
     response.write(render_to_string(template, context_instance=context))
     return response
 
 @transaction.commit_manually
-def activate(request, email_template_name='im/welcome_email.txt', on_failure=''):
+def activate(request, greeting_email_template_name='im/welcome_email.txt', helpdesk_email_template_name='im/helpdesk_notification.txt'):
     """
     Activates the user identified by the ``auth`` request parameter, sends a welcome email
     and renews the user token.
@@ -432,20 +447,50 @@ def activate(request, email_template_name='im/welcome_email.txt', on_failure='')
         user = AstakosUser.objects.get(auth_token=token)
     except AstakosUser.DoesNotExist:
         return HttpResponseBadRequest(_('No such user'))
-
-    user.is_active = True
-    user.email_verified = True
-    user.save()
-    try:
-        send_greeting(user, email_template_name)
-        response = prepare_response(request, user, next, renew=True)
-        transaction.commit()
-        return response
-    except (SMTPException, socket.error) as e:
-        message = getattr(e, 'name') if hasattr(e, 'name') else e
+    
+    if user.is_active:
+        message = _('Account already active.')
         messages.add_message(request, messages.ERROR, message)
-        transaction.rollback()
-        return signup(request, on_failure='im/signup.html')
+        return index(request)
+    
+    try:
+        local_user = AstakosUser.objects.get(~Q(id = user.id), email=user.email, is_active=True)
+    except AstakosUser.DoesNotExist:
+        try:
+            activate_func(user, greeting_email_template_name, helpdesk_email_template_name, verify_email=True)
+            response = prepare_response(request, user, next, renew=True)
+            transaction.commit()
+            return response
+        except SendMailError, e:
+            message = e.message
+            messages.add_message(request, messages.ERROR, message)
+            transaction.rollback()
+            return index(request)
+        except BaseException, e:
+            status = messages.ERROR
+            message = _('Something went wrong.')
+            messages.add_message(request, messages.ERROR, message)
+            logger.exception(e)
+            transaction.rollback()
+            return index(request)
+    else:
+        try:
+            user = switch_account_to_shibboleth(user, local_user, greeting_email_template_name)
+            response = prepare_response(request, user, next, renew=True)
+            transaction.commit()
+            return response
+        except SendMailError, e:
+            message = e.message
+            messages.add_message(request, messages.ERROR, message)
+            transaction.rollback()
+            return index(request)
+        except BaseException, e:
+            status = messages.ERROR
+            message = _('Something went wrong.')
+            messages.add_message(request, messages.ERROR, message)
+            logger.exception(e)
+            transaction.rollback()
+            return index(request)
 
 def approval_terms(request, term_id=None, template_name='im/approval_terms.html', extra_context={}):
     term = None
@@ -474,19 +519,70 @@ def approval_terms(request, term_id=None, template_name='im/approval_terms.html'
         if not form.is_valid():
             return render_response(template_name,
                            terms = terms,
-                           form = form,
+                           approval_terms_form = form,
                            context_instance = get_context(request, extra_context))
         user = form.save()
         return HttpResponseRedirect(next)
     else:
         form = None
-        if request.user.is_authenticated() and not has_signed_terms(request.user):
+        if request.user.is_authenticated() and not request.user.signed_terms():
             form = SignApprovalTermsForm(instance=request.user)
         return render_response(template_name,
                                terms = terms,
-                               form = form,
+                               approval_terms_form = form,
                                context_instance = get_context(request, extra_context))
 
 @signed_terms_required
 def change_password(request):
-    return password_change(request, post_change_redirect=reverse('astakos.im.views.edit_profile'))
+    return password_change(request,
+                            post_change_redirect=reverse('astakos.im.views.edit_profile'),
+                            password_change_form=ExtendedPasswordChangeForm)
+
+@transaction.commit_manually
+def change_email(request, activation_key=None,
+                 email_template_name='registration/email_change_email.txt',
+                 form_template_name='registration/email_change_form.html',
+                 confirm_template_name='registration/email_change_done.html',
+                 extra_context={}):
+    if activation_key:
+        try:
+            user = EmailChange.objects.change_email(activation_key)
+            if request.user.is_authenticated() and request.user == user:
+                msg = _('Email changed successfully.')
+                messages.add_message(request, messages.SUCCESS, msg)
+                auth_logout(request)
+                response = prepare_response(request, user)
+                transaction.commit()
+                return response
+        except ValueError, e:
+            messages.add_message(request, messages.ERROR, e)
+        return render_response(confirm_template_name,
+                               modified_user = user if 'user' in locals() else None,
+                               context_instance = get_context(request,
+                                                              extra_context))
+    
+    if not request.user.is_authenticated():
+        path = quote(request.get_full_path())
+        url = request.build_absolute_uri(reverse('astakos.im.views.index'))
+        return HttpResponseRedirect(url + '?next=' + path)
+    form = EmailChangeForm(request.POST or None)
+    if request.method == 'POST' and form.is_valid():
+        try:
+            ec = form.save(email_template_name, request)
+        except SendMailError, e:
+            status = messages.ERROR
+            msg = e
+            transaction.rollback()
+        except IntegrityError, e:
+            status = messages.ERROR
+            msg = _('There is already a pending change email request.')
+        else:
+            status = messages.SUCCESS
+            msg = _('Change email request has been registered succefully.\
+                    You are going to receive a verification email in the new address.')
+            transaction.commit()
+        messages.add_message(request, status, msg)
+    return render_response(form_template_name,
+                           form = form,
+                           context_instance = get_context(request,
+                                                          extra_context))