From: Rodney Ewing Date: Tue, 25 Jun 2013 20:37:21 +0000 (-0700) Subject: Merge remote-tracking branch 'upstream/master' into auth X-Git-Url: https://vcs.fsf.org/?a=commitdiff_plain;h=af4414a85f3354a432e29ecf8cf39ffd926371e3;p=mediagoblin.git Merge remote-tracking branch 'upstream/master' into auth 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 --- af4414a85f3354a432e29ecf8cf39ffd926371e3 diff --cc mediagoblin/app.py index f44504a7,58058360..96461711 --- a/mediagoblin/app.py +++ b/mediagoblin/app.py @@@ -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 diff --cc mediagoblin/auth/forms.py index dad5dd86,ec395d60..866caa13 --- a/mediagoblin/auth/forms.py +++ b/mediagoblin/auth/forms.py @@@ -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()]) diff --cc mediagoblin/auth/tools.py index 71f824de,c45944d3..877da14f --- a/mediagoblin/auth/tools.py +++ b/mediagoblin/auth/tools.py @@@ -14,10 -14,15 +14,11 @@@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -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) diff --cc mediagoblin/auth/views.py index d7535ef0,45cb3a54..34500f91 --- a/mediagoblin/auth/views.py +++ b/mediagoblin/auth/views.py @@@ -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( @@@ -314,12 -308,22 +332,22 @@@ 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): diff --cc mediagoblin/db/migrations.py index 129c1998,7074ffec..fef353af --- a/mediagoblin/db/migrations.py +++ b/mediagoblin/db/migrations.py @@@ -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() ++ diff --cc mediagoblin/db/models.py index 323ed4d7,4c24bfe8..826d47ba --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@@ -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. diff --cc mediagoblin/edit/views.py index 161285a2,4eda61a2..429eb584 --- a/mediagoblin/edit/views.py +++ b/mediagoblin/edit/views.py @@@ -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 diff --cc mediagoblin/plugins/basic_auth/__init__.py index 71e96d73,00000000..a2efae92 mode 100644,000000..100644 --- a/mediagoblin/plugins/basic_auth/__init__.py +++ b/mediagoblin/plugins/basic_auth/__init__.py @@@ -1,98 -1,0 +1,95 @@@ +# 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 . - import uuid - +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.verification_key = unicode(uuid.uuid4()) + 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, +} diff --cc mediagoblin/tests/test_edit.py index b6ec7a29,2afc519a..acc638d9 --- a/mediagoblin/tests/test_edit.py +++ b/mediagoblin/tests/test_edit.py @@@ -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):