Merge remote-tracking branch 'remotes/lorochka85/bug852_use_media_slug_instead_of_id'
[mediagoblin.git] / mediagoblin / auth / views.py
index a5112299f2fd831fd9a1ec1b0285a4c9c3945413..d01861d1221137b383066950e088b6a97dccf20d 100644 (file)
@@ -1,5 +1,5 @@
 # GNU MediaGoblin -- federated, autonomous media hosting
-# Copyright (C) 2011 Free Software Foundation, Inc
+# Copyright (C) 2011 MediaGoblin contributors.  See AUTHORS.
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Affero General Public License as published by
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import uuid
+import datetime
 
 from webob import exc
 
-from mediagoblin.util import render_to_response
-from mediagoblin.db.util import ObjectId
+from mediagoblin import messages
+from mediagoblin import mg_globals
+from mediagoblin.tools.response import render_to_response, redirect, render_404
+from mediagoblin.tools.translate import pass_to_ugettext as _
+from mediagoblin.db.util import ObjectId, InvalidId
 from mediagoblin.auth import lib as auth_lib
 from mediagoblin.auth import forms as auth_forms
-from mediagoblin.auth.lib import send_verification_email
+from mediagoblin.auth.lib import send_verification_email, \
+                                 send_fp_verification_email
+
+
+def email_debug_message(request):
+    """
+    If the server is running in email debug mode (which is
+    the current default), give a debug message to the user
+    so that they have an idea where to find their email.
+    """
+    if mg_globals.app_config['email_debug_mode']:
+        # DEBUG message, no need to translate
+        messages.add_message(request, messages.DEBUG,
+            u"This instance is running in email debug mode. "
+            u"The email will be on the console of the server process.")
 
 
 def register(request):
     """
     Your classic registration view!
     """
+    # Redirects to indexpage if registrations are disabled
+    if not mg_globals.app_config["allow_registration"]:
+        messages.add_message(
+            request,
+            messages.WARNING,
+            _('Sorry, registration is disabled on this instance.'))
+        return redirect(request, "index")
+
     register_form = auth_forms.RegistrationForm(request.POST)
 
     if request.method == 'POST' and register_form.validate():
         # TODO: Make sure the user doesn't exist already
+        username = unicode(request.POST['username'].lower())
+        email = unicode(request.POST['email'].lower())
+        users_with_username = request.db.User.find(
+            {'username': username}).count()
+        users_with_email = request.db.User.find(
+            {'email': email}).count()
 
-        users_with_username = \
-            request.db.User.find({
-                'username': request.POST['username'].lower()
-            }).count()
+        extra_validation_passes = True
 
         if users_with_username:
             register_form.username.errors.append(
-                u'Sorry, a user with that name already exists.')
-
-        else:
+                _(u'Sorry, a user with that name already exists.'))
+            extra_validation_passes = False
+        if users_with_email:
+            register_form.email.errors.append(
+                _(u'Sorry, a user with that email address already exists.'))
+            extra_validation_passes = False
+
+        if extra_validation_passes:
             # Create the user
-            entry = request.db.User()
-            entry['username'] = request.POST['username'].lower()
-            entry['email'] = request.POST['email']
-            entry['pw_hash'] = auth_lib.bcrypt_gen_password_hash(
+            user = request.db.User()
+            user['username'] = username
+            user['email'] = email
+            user['pw_hash'] = auth_lib.bcrypt_gen_password_hash(
                 request.POST['password'])
-            entry.save(validate=True)
-            
-            send_verification_email(entry, request)
+            user.save(validate=True)
 
-            # Redirect to register_success
-            return exc.HTTPFound(
-                location=request.urlgen("mediagoblin.auth.register_success"))
+            # log the user in
+            request.session['user_id'] = unicode(user._id)
+            request.session.save()
 
-    # render
-    return render_to_response(
-            request, 'mediagoblin/auth/register.html',
-            {'register_form': register_form})
+            # send verification email
+            email_debug_message(request)
+            send_verification_email(user, request)
 
+            # redirect the user to their homepage... there will be a
+            # message waiting for them to verify their email
+            return redirect(
+                request, 'mediagoblin.user_pages.user_home',
+                user=user['username'])
 
-def register_success(request):
     return render_to_response(
-            request, 'mediagoblin/auth/register_success.html', {})
+        request,
+        'mediagoblin/auth/register.html',
+        {'register_form': register_form})
 
 
 def login(request):
@@ -85,14 +122,13 @@ def login(request):
 
         if user and user.check_login(request.POST['password']):
             # set up login in session
-            request.session['user_id'] = unicode(user['_id'])
+            request.session['user_id'] = unicode(user._id)
             request.session.save()
 
             if request.POST.get('next'):
                 return exc.HTTPFound(location=request.POST['next'])
             else:
-                return exc.HTTPFound(
-                    location=request.urlgen("index"))
+                return redirect(request, "index")
 
         else:
             # Prevent detecting who's on this system by testing login
@@ -100,20 +136,20 @@ def login(request):
             auth_lib.fake_login_attempt()
             login_failed = True
 
-    # render
     return render_to_response(
-            request, 'mediagoblin/auth/login.html',
-            {'login_form': login_form,
-             'next': request.GET.get('next') or request.POST.get('next'),
-             'login_failed': login_failed})
+        request,
+        'mediagoblin/auth/login.html',
+        {'login_form': login_form,
+         'next': request.GET.get('next') or request.POST.get('next'),
+         'login_failed': login_failed,
+         'allow_registration': mg_globals.app_config["allow_registration"]})
 
 
 def logout(request):
     # Maybe deleting the user_id parameter would be enough?
     request.session.delete()
-    
-    return exc.HTTPFound(
-        location=request.urlgen("index"))
+
+    return redirect(request, "index")
 
 
 def verify_email(request):
@@ -124,34 +160,33 @@ def verify_email(request):
     you are lucky :)
     """
     # If we don't have userid and token parameters, we can't do anything; 404
-    if not request.GET.has_key('userid') or not request.GET.has_key('token'):
-        return exc.HTTPNotFound()
+    if not 'userid' in request.GET or not 'token' in request.GET:
+        return render_404(request)
 
     user = request.db.User.find_one(
         {'_id': ObjectId(unicode(request.GET['userid']))})
 
     if user and user['verification_key'] == unicode(request.GET['token']):
-        user['status'] = u'active'
-        user['email_verified'] = True
-        verification_successful = True
+        user[u'status'] = u'active'
+        user[u'email_verified'] = True
+        user[u'verification_key'] = None
+
         user.save()
-    else:
-        verification_successful = False
-        
-    return render_to_response(
-            request, 'mediagoblin/auth/verify_email.html',
-            {'user': user,
-             'verification_successful': verification_successful})
 
-def verify_email_notice(request):
-    """
-    Verify warning view.
+        messages.add_message(
+            request,
+            messages.SUCCESS,
+            _("Your email address has been verified. "
+              "You may now login, edit your profile, and submit images!"))
+    else:
+        messages.add_message(
+            request,
+            messages.ERROR,
+            _('The verification key or user id is incorrect'))
 
-    When the user tries to do some action that requires their account
-    to be verified beforehand, this view is called upon!
-    """
-    return render_to_response(
-            request, 'mediagoblin/auth/verification_needed.html', {})
+    return redirect(
+        request, 'mediagoblin.user_pages.user_home',
+        user=user['username'])
 
 
 def resend_activation(request):
@@ -161,15 +196,154 @@ def resend_activation(request):
     Resend the activation email.
     """
 
-    request.user['verification_key'] = unicode(uuid.uuid4())
-    request.user.save()
+    if request.user is None:
+        messages.add_message(
+            request,
+            messages.ERROR,
+            _('You must be logged in so we know who to send the email to!'))
+        
+        return redirect(request, 'mediagoblin.auth.login')
+
+    if request.user["email_verified"]:
+        messages.add_message(
+            request,
+            messages.ERROR,
+            _("You've already verified your email address!"))
+        
+        return redirect(request, "mediagoblin.user_pages.user_home", user=request.user['username'])
 
+    request.user[u'verification_key'] = unicode(uuid.uuid4())
+    request.user.save()
+    
+    email_debug_message(request)
     send_verification_email(request.user, request)
 
-    return exc.HTTPFound(
-        location=request.urlgen('mediagoblin.auth.resend_verification_success'))
+    messages.add_message(
+        request,
+        messages.INFO,
+        _('Resent your verification email.'))
+    return redirect(
+        request, 'mediagoblin.user_pages.user_home',
+        user=request.user['username'])
+
 
+def forgot_password(request):
+    """
+    Forgot password view
+
+    Sends an email whit an url to renew forgoten password
+    """
+    fp_form = auth_forms.ForgotPassForm(request.POST)
+
+    if request.method == 'POST' and fp_form.validate():
+
+        # Here, so it doesn't depend on the actual mail being sent
+        # and thus doesn't reveal, wether mail was sent.
+        email_debug_message(request)
+
+        # '$or' not available till mongodb 1.5.3
+        user = request.db.User.find_one(
+            {'username': request.POST['username']})
+        if not user:
+            user = request.db.User.find_one(
+                {'email': request.POST['username']})
+
+        if user:
+            if user['email_verified'] and user['status'] == 'active':
+                user[u'fp_verification_key'] = unicode(uuid.uuid4())
+                user[u'fp_token_expire'] = datetime.datetime.now() + \
+                                          datetime.timedelta(days=10)
+                user.save()
+
+                send_fp_verification_email(user, request)
+            else:
+                # special case... we can't send the email because the
+                # username is inactive / hasn't verified their email
+                messages.add_message(
+                    request,
+                    messages.WARNING,
+                    _("Could not send password recovery email as "
+                      "your username is inactive or your account's "
+                      "email address has not been verified."))
+
+                return redirect(
+                    request, 'mediagoblin.user_pages.user_home',
+                    user=user['username'])
+
+        # do not reveal whether or not there is a matching user
+        return redirect(request, 'mediagoblin.auth.fp_email_sent')
 
-def resend_activation_success(request):
     return render_to_response(
-            request, 'mediagoblin/auth/resent_verification_email.html', {})
+        request,
+        'mediagoblin/auth/forgot_password.html',
+        {'fp_form': fp_form})
+
+
+def verify_forgot_password(request):
+    """
+    Check the forgot-password verification and possibly let the user
+    change their password because of it.
+    """
+    # get form data variables, and specifically check for presence of token
+    formdata = _process_for_token(request)
+    if not formdata['has_userid_and_token']:
+        return render_404(request)
+
+    formdata_token = formdata['vars']['token']
+    formdata_userid = formdata['vars']['userid']
+    formdata_vars = formdata['vars']
+
+    # check if it's a valid Id
+    try:
+        user = request.db.User.find_one(
+            {'_id': ObjectId(unicode(formdata_userid))})
+    except InvalidId:
+        return render_404(request)
+
+    # check if we have a real user and correct token
+    if ((user and user['fp_verification_key'] and
+         user['fp_verification_key'] == unicode(formdata_token) and
+         datetime.datetime.now() < user['fp_token_expire']
+         and user['email_verified'] and user['status'] == 'active')):
+
+        cp_form = auth_forms.ChangePassForm(formdata_vars)
+
+        if request.method == 'POST' and cp_form.validate():
+            user[u'pw_hash'] = auth_lib.bcrypt_gen_password_hash(
+                request.POST['password'])
+            user[u'fp_verification_key'] = None
+            user[u'fp_token_expire'] = None
+            user.save()
+
+            return redirect(request, 'mediagoblin.auth.fp_changed_success')
+        else:
+            return render_to_response(
+                request,
+                'mediagoblin/auth/change_fp.html',
+                {'cp_form': cp_form})
+
+    # in case there is a valid id but no user whit that id in the db
+    # or the token expired
+    else:
+        return render_404(request)
+
+
+def _process_for_token(request):
+    """
+    Checks for tokens in formdata without prior knowledge of request method
+
+    For now, returns whether the userid and token formdata variables exist, and
+    the formdata variables in a hash. Perhaps an object is warranted?
+    """
+    # retrieve the formdata variables
+    if request.method == 'GET':
+        formdata_vars = request.GET
+    else:
+        formdata_vars = request.POST
+
+    formdata = {
+        'vars': formdata_vars,
+        'has_userid_and_token':
+            'userid' in formdata_vars and 'token' in formdata_vars}
+
+    return formdata