Merge remote-tracking branch 'upstream/master' into auth
authorRodney Ewing <ewing.rj@gmail.com>
Tue, 25 Jun 2013 20:37:21 +0000 (13:37 -0700)
committerRodney Ewing <ewing.rj@gmail.com>
Tue, 25 Jun 2013 20:37:21 +0000 (13:37 -0700)
Conflicts:
mediagoblin/app.py
mediagoblin/auth/forms.py
mediagoblin/auth/tools.py
mediagoblin/db/migrations.py
mediagoblin/db/models.py
mediagoblin/edit/views.py
mediagoblin/plugins/basic_auth/tools.py
mediagoblin/tests/test_edit.py

13 files changed:
1  2 
mediagoblin.ini
mediagoblin/app.py
mediagoblin/auth/forms.py
mediagoblin/auth/tools.py
mediagoblin/auth/views.py
mediagoblin/db/migrations.py
mediagoblin/db/models.py
mediagoblin/edit/views.py
mediagoblin/plugins/basic_auth/__init__.py
mediagoblin/templates/mediagoblin/base.html
mediagoblin/tests/test_auth.py
mediagoblin/tests/test_edit.py
mediagoblin/tests/tools.py

diff --cc mediagoblin.ini
Simple merge
index f44504a7aa48a714696783f81577523182baf636,580583607f32f2ca3bd7558993ec5900f469ebf2..9646171107af66c697e4bcd187d2432c95dfe3d6
@@@ -37,7 -37,7 +37,8 @@@ from mediagoblin.init import (get_jinja
      setup_storage)
  from mediagoblin.tools.pluginapi import PluginManager, hook_transform
  from mediagoblin.tools.crypto import setup_crypto
 +from mediagoblin.auth.tools import check_auth_enabled, no_auth_logout
+ from mediagoblin import notifications
  
  
  _log = logging.getLogger(__name__)
@@@ -192,9 -187,8 +193,11 @@@ class MediaGoblinApp(object)
  
          request.urlgen = build_proxy
  
 +        # Log user out if authentication_disabled
 +        no_auth_logout(request)
 +
+         request.notifications = notifications
          mg_request.setup_user_in_request(request)
  
          request.controller_name = None
index dad5dd86baf628ecce69fe894cc33010470de64c,ec395d60b2eb64ad1f4566ced6cbbba6ca6787c2..866caa13897c0cb67f33bda7e661fb352cd5dc0b
@@@ -29,10 -55,9 +29,7 @@@ class ForgotPassForm(wtforms.Form)
  
  class ChangePassForm(wtforms.Form):
      password = wtforms.PasswordField(
 -        'Password',
 -        [wtforms.validators.Required(),
 -         wtforms.validators.Length(min=5, max=1024)])
 +        'Password')
-     userid = wtforms.HiddenField(
-         '',
-         [wtforms.validators.Required()])
      token = wtforms.HiddenField(
          '',
          [wtforms.validators.Required()])
index 71f824dea7d69980383768160565dccf160c1d8f,c45944d3c007fdcab39a3501c1f9392171ffe851..877da14f03e4a22ed29110b27c56e9c46ec53669
  # You should have received a copy of the GNU Affero General Public License
  # along with this program.  If not, see <http://www.gnu.org/licenses/>.
  
 -import uuid
  import logging
 -
  import wtforms
 -from sqlalchemy import or_
  
  from mediagoblin import mg_globals
 -from mediagoblin.auth import lib as auth_lib
+ from mediagoblin.tools.crypto import get_timed_signer_url
  from mediagoblin.db.models import User
  from mediagoblin.tools.mail import (normalize_email, send_email,
                                      email_debug_message)
index d7535ef07ba8619da03bf03fe22d7b1ecff6ab83,45cb3a5409877e5e82437967ed0a4d864983aa34..34500f91173687affcb3241d816c26948b6568a2
@@@ -299,10 -295,8 +319,8 @@@ def verify_forgot_password(request)
          cp_form = auth_forms.ChangePassForm(formdata_vars)
  
          if request.method == 'POST' and cp_form.validate():
 -            user.pw_hash = auth_lib.bcrypt_gen_password_hash(
 +            user.pw_hash = auth.gen_password_hash(
                  cp_form.password.data)
-             user.fp_verification_key = None
-             user.fp_token_expire = None
              user.save()
  
              messages.add_message(
              return render_to_response(
                  request,
                  'mediagoblin/auth/change_fp.html',
 -                {'cp_form': cp_form})
 +                {'cp_form': cp_form,})
  
-     # in case there is a valid id but no user with that id in the db
-     # or the token expired
-     else:
-         return render_404(request)
+     if not user.email_verified:
+         messages.add_message(
+             request, messages.ERROR,
+             _('You need to verify your email before you can reset your'
+               ' password.'))
+     if not user.status == 'active':
+         messages.add_message(
+             request, messages.ERROR,
+             _('You are no longer an active user. Please contact the system'
+               ' admin to reactivate your accoutn.'))
+     return redirect(
+         request, 'index')
  
  
  def _process_for_token(request):
index 129c19989263c4791c219d2bcf376f0f1dcda371,7074ffec11800d2c9bc239039ee9cbfef4a47327..fef353af5d507674d1366c7bc1dfa5cd8c0aa272
@@@ -288,17 -288,76 +288,92 @@@ def unique_collections_slug(db)
  
      db.commit()
  
  @RegisterMigration(11, MIGRATIONS)
+ def drop_token_related_User_columns(db):
+     """
+     Drop unneeded columns from the User table after switching to using
+     itsdangerous tokens for email and forgot password verification.
+     """
+     metadata = MetaData(bind=db.bind)
+     user_table = inspect_table(metadata, 'core__users')
+     verification_key = user_table.columns['verification_key']
+     fp_verification_key = user_table.columns['fp_verification_key']
+     fp_token_expire = user_table.columns['fp_token_expire']
+     verification_key.drop()
+     fp_verification_key.drop()
+     fp_token_expire.drop()
+     db.commit()
+ class CommentSubscription_v0(declarative_base()):
+     __tablename__ = 'core__comment_subscriptions'
+     id = Column(Integer, primary_key=True)
+     created = Column(DateTime, nullable=False, default=datetime.datetime.now)
+     media_entry_id = Column(Integer, ForeignKey(MediaEntry.id), nullable=False)
+     user_id = Column(Integer, ForeignKey(User.id), nullable=False)
+     notify = Column(Boolean, nullable=False, default=True)
+     send_email = Column(Boolean, nullable=False, default=True)
+ class Notification_v0(declarative_base()):
+     __tablename__ = 'core__notifications'
+     id = Column(Integer, primary_key=True)
+     type = Column(Unicode)
+     created = Column(DateTime, nullable=False, default=datetime.datetime.now)
+     user_id = Column(Integer, ForeignKey(User.id), nullable=False,
+                      index=True)
+     seen = Column(Boolean, default=lambda: False, index=True)
+ class CommentNotification_v0(Notification_v0):
+     __tablename__ = 'core__comment_notifications'
+     id = Column(Integer, ForeignKey(Notification_v0.id), primary_key=True)
+     subject_id = Column(Integer, ForeignKey(MediaComment.id))
+ class ProcessingNotification_v0(Notification_v0):
+     __tablename__ = 'core__processing_notifications'
+     id = Column(Integer, ForeignKey(Notification_v0.id), primary_key=True)
+     subject_id = Column(Integer, ForeignKey(MediaEntry.id))
+ @RegisterMigration(12, MIGRATIONS)
+ def add_new_notification_tables(db):
+     metadata = MetaData(bind=db.bind)
+     user_table = inspect_table(metadata, 'core__users')
+     mediaentry_table = inspect_table(metadata, 'core__media_entries')
+     mediacomment_table = inspect_table(metadata, 'core__media_comments')
+     CommentSubscription_v0.__table__.create(db.bind)
+     Notification_v0.__table__.create(db.bind)
+     CommentNotification_v0.__table__.create(db.bind)
+     ProcessingNotification_v0.__table__.create(db.bind)
++
++
++@RegisterMigration(13, MIGRATIONS)
 +def pw_hash_nullable(db):
 +    """Make pw_hash column nullable"""
 +    metadata = MetaData(bind=db.bind)
 +    user_table = inspect_table(metadata, "core__users")
 +
 +    user_table.c.pw_hash.alter(nullable=True)
 +
 +    if db.bind.url.drivername == 'sqlite':
 +        constraint = UniqueConstraint('username', table=user_table)
 +        constraint.create()
 +
 +    db.commit()
++
index 323ed4d7cbb690ed89737b16a93b6932135a87fd,4c24bfe8b0b09eab0054c681ccd1b7bf13a28f15..826d47baa0a36b47aaf8699c23027d4b3afa9d4d
@@@ -60,9 -62,9 +62,9 @@@ class User(Base, UserMixin)
      # the RFC) and because it would be a mess to implement at this
      # point.
      email = Column(Unicode, nullable=False)
-     created = Column(DateTime, nullable=False, default=datetime.datetime.now)
 -    pw_hash = Column(Unicode, nullable=False)
 +    pw_hash = Column(Unicode)
      email_verified = Column(Boolean, default=False)
+     created = Column(DateTime, nullable=False, default=datetime.datetime.now)
      status = Column(Unicode, default=u"needs_email_verification", nullable=False)
      # Intented to be nullable=False, but migrations would not work for it
      # set to nullable=True implicitly.
index 161285a2392039d4d81fab164d395e02b73c461d,4eda61a26554b6bb39e3c5e74e31d75c45780aa1..429eb5849fea51ef606c3aa458b793776d156c09
@@@ -26,11 -29,13 +27,14 @@@ from mediagoblin import aut
  from mediagoblin.edit import forms
  from mediagoblin.edit.lib import may_edit_media
  from mediagoblin.decorators import (require_active_login, active_user_from_url,
-      get_media_entry_by_id,
-      user_may_alter_collection, get_user_collection)
- from mediagoblin.tools.response import render_to_response, \
-     redirect, redirect_obj
+                             get_media_entry_by_id, user_may_alter_collection,
+                             get_user_collection)
+ from mediagoblin.tools.crypto import get_timed_signer_url
++from mediagoblin.tools.mail import email_debug_message
+ from mediagoblin.tools.response import (render_to_response,
+                                         redirect, redirect_obj, render_404)
  from mediagoblin.tools.translate import pass_to_ugettext as _
+ from mediagoblin.tools.template import render_template
  from mediagoblin.tools.text import (
      convert_to_tag_list_of_dicts, media_tags_as_string)
  from mediagoblin.tools.url import slugify
index 71e96d73cc1cf6b54d9b6ef862a26ba75318c396,0000000000000000000000000000000000000000..a2efae92d3b713a4e9e752fe99609d5113e78419
mode 100644,000000..100644
--- /dev/null
@@@ -1,98 -1,0 +1,95 @@@
- import uuid
 +# GNU MediaGoblin -- federated, autonomous media hosting
 +# Copyright (C) 2011, 2012 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
 +# the Free Software Foundation, either version 3 of the License, or
 +# (at your option) any later version.
 +#
 +# This program is distributed in the hope that it will be useful,
 +# but WITHOUT ANY WARRANTY; without even the implied warranty of
 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +# GNU Affero General Public License for more details.
 +#
 +# You should have received a copy of the GNU Affero General Public License
 +# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-         user.verification_key = unicode(uuid.uuid4())
 +from mediagoblin.plugins.basic_auth import forms as auth_forms
 +from mediagoblin.plugins.basic_auth import tools as auth_tools
 +from mediagoblin.db.models import User
 +from mediagoblin.tools import pluginapi
 +from sqlalchemy import or_
 +
 +
 +def setup_plugin():
 +    config = pluginapi.get_config('mediagoblin.pluginapi.basic_auth')
 +
 +
 +def get_user(**kwargs):
 +    username = kwargs.pop('username', None)
 +    if username:
 +        user = User.query.filter(
 +            or_(
 +                User.username == username,
 +                User.email == username,
 +            )).first()
 +        return user
 +
 +
 +def create_user(registration_form):
 +    user = get_user(username=registration_form.username.data)
 +    if not user and 'password' in registration_form:
 +        user = User()
 +        user.username = registration_form.username.data
 +        user.email = registration_form.email.data
 +        user.pw_hash = gen_password_hash(
 +            registration_form.password.data)
 +        user.save()
 +    return user
 +
 +
 +def get_login_form(request):
 +    return auth_forms.LoginForm(request.form)
 +
 +
 +def get_registration_form(request):
 +    return auth_forms.RegistrationForm(request.form)
 +
 +
 +def gen_password_hash(raw_pass, extra_salt=None):
 +    return auth_tools.bcrypt_gen_password_hash(raw_pass, extra_salt)
 +
 +
 +def check_password(raw_pass, stored_hash, extra_salt=None):
 +    return auth_tools.bcrypt_check_password(raw_pass, stored_hash, extra_salt)
 +
 +
 +def auth():
 +    return True
 +
 +
 +def append_to_global_context(context):
 +    context['pass_auth'] = True
 +    return context
 +
 +
 +def add_to_form_context(context):
 +    context['pass_auth_link'] = True
 +    return context
 +
 +
 +hooks = {
 +    'setup': setup_plugin,
 +    'authentication': auth,
 +    'auth_get_user': get_user,
 +    'auth_create_user': create_user,
 +    'auth_get_login_form': get_login_form,
 +    'auth_get_registration_form': get_registration_form,
 +    'auth_gen_password_hash': gen_password_hash,
 +    'auth_check_password': check_password,
 +    'auth_fake_login_attempt': auth_tools.fake_login_attempt,
 +    'template_global_context': append_to_global_context,
 +    ('mediagoblin.plugins.openid.register',
 +    'mediagoblin/auth/register.html'): add_to_form_context,
 +    ('mediagoblin.plugins.openid.login',
 +     'mediagoblin/auth/login.html'): add_to_form_context,
 +}
Simple merge
index b6ec7a29b43075bc4e166abee73861bcd6ed3eec,2afc519ad99b571078640471b39db2c38abf2d6b..acc638d9f7afbb33e04191a5d0ec2b91722453e4
@@@ -20,8 -19,8 +19,8 @@@ import urlpars
  from mediagoblin import mg_globals
  from mediagoblin.db.models import User
  from mediagoblin.tests.tools import fixture_add_user
- from mediagoblin.tools import template
 +from mediagoblin import auth
+ from mediagoblin.tools import template, mail
 -from mediagoblin.auth.lib import bcrypt_check_password
  
  
  class TestUserEdit(object):
Simple merge