aboutsummaryrefslogtreecommitdiffstats
path: root/mediagoblin
diff options
context:
space:
mode:
Diffstat (limited to 'mediagoblin')
-rw-r--r--mediagoblin/app.py9
-rw-r--r--mediagoblin/auth/__init__.py29
-rw-r--r--mediagoblin/auth/forms.py30
-rw-r--r--mediagoblin/auth/tools.py76
-rw-r--r--mediagoblin/auth/views.py50
-rw-r--r--mediagoblin/db/migrations.py16
-rw-r--r--mediagoblin/db/models.py2
-rw-r--r--mediagoblin/edit/views.py9
-rw-r--r--mediagoblin/gmg_commands/users.py6
-rw-r--r--mediagoblin/plugins/basic_auth/__init__.py95
-rw-r--r--mediagoblin/plugins/basic_auth/forms.py43
-rw-r--r--mediagoblin/plugins/basic_auth/tools.py (renamed from mediagoblin/auth/lib.py)41
-rw-r--r--mediagoblin/templates/mediagoblin/auth/change_fp.html3
-rw-r--r--mediagoblin/templates/mediagoblin/auth/forgot_password.html2
-rw-r--r--mediagoblin/templates/mediagoblin/auth/login.html4
-rw-r--r--mediagoblin/templates/mediagoblin/auth/register.html4
-rw-r--r--mediagoblin/templates/mediagoblin/base.html2
-rw-r--r--mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html36
-rw-r--r--mediagoblin/templates/mediagoblin/utils/wtforms.html16
-rw-r--r--mediagoblin/tests/auth_configs/__init__.py0
-rw-r--r--mediagoblin/tests/auth_configs/authentication_disabled_appconfig.ini25
-rw-r--r--mediagoblin/tests/test_auth.py95
-rw-r--r--mediagoblin/tests/test_basic_auth.py59
-rw-r--r--mediagoblin/tests/test_edit.py6
-rw-r--r--mediagoblin/tests/test_mgoblin_app.ini1
-rw-r--r--mediagoblin/tests/tools.py4
-rw-r--r--mediagoblin/tools/template.py1
27 files changed, 474 insertions, 190 deletions
diff --git a/mediagoblin/app.py b/mediagoblin/app.py
index 58058360..96461711 100644
--- a/mediagoblin/app.py
+++ b/mediagoblin/app.py
@@ -37,6 +37,7 @@ from mediagoblin.init import (get_jinja_loader, get_staticdirector,
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
@@ -98,6 +99,11 @@ class MediaGoblinApp(object):
PluginManager().get_template_paths()
)
+ # Check if authentication plugin is enabled and respond accordingly.
+ self.auth = check_auth_enabled()
+ if not self.auth:
+ app_config['allow_comments'] = False
+
# Set up storage systems
self.public_store, self.queue_store = setup_storage()
@@ -187,6 +193,9 @@ 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)
diff --git a/mediagoblin/auth/__init__.py b/mediagoblin/auth/__init__.py
index 621845ba..be5d0eed 100644
--- a/mediagoblin/auth/__init__.py
+++ b/mediagoblin/auth/__init__.py
@@ -13,3 +13,32 @@
#
# 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/>.
+from mediagoblin.tools.pluginapi import hook_handle, hook_runall
+
+
+def get_user(**kwargs):
+ """ Takes a kwarg such as username and returns a user object """
+ return hook_handle("auth_get_user", **kwargs)
+
+
+def create_user(register_form):
+ results = hook_runall("auth_create_user", register_form)
+ return results[0]
+
+
+def extra_validation(register_form):
+ from mediagoblin.auth.tools import basic_extra_validation
+
+ extra_validation_passes = basic_extra_validation(register_form)
+ if False in hook_runall("auth_extra_validation", register_form):
+ extra_validation_passes = False
+ return extra_validation_passes
+
+
+def gen_password_hash(raw_pass, extra_salt=None):
+ return hook_handle("auth_gen_password_hash", raw_pass, extra_salt)
+
+
+def check_password(raw_pass, stored_hash, extra_salt=None):
+ return hook_handle("auth_check_password",
+ raw_pass, stored_hash, extra_salt)
diff --git a/mediagoblin/auth/forms.py b/mediagoblin/auth/forms.py
index ec395d60..866caa13 100644
--- a/mediagoblin/auth/forms.py
+++ b/mediagoblin/auth/forms.py
@@ -20,32 +20,6 @@ from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
from mediagoblin.auth.tools import normalize_user_or_email_field
-class RegistrationForm(wtforms.Form):
- username = wtforms.TextField(
- _('Username'),
- [wtforms.validators.Required(),
- normalize_user_or_email_field(allow_email=False)])
- password = wtforms.PasswordField(
- _('Password'),
- [wtforms.validators.Required(),
- wtforms.validators.Length(min=5, max=1024)])
- email = wtforms.TextField(
- _('Email address'),
- [wtforms.validators.Required(),
- normalize_user_or_email_field(allow_user=False)])
-
-
-class LoginForm(wtforms.Form):
- username = wtforms.TextField(
- _('Username or Email'),
- [wtforms.validators.Required(),
- normalize_user_or_email_field()])
- password = wtforms.PasswordField(
- _('Password'),
- [wtforms.validators.Required(),
- wtforms.validators.Length(min=5, max=1024)])
-
-
class ForgotPassForm(wtforms.Form):
username = wtforms.TextField(
_('Username or email'),
@@ -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')
token = wtforms.HiddenField(
'',
[wtforms.validators.Required()])
diff --git a/mediagoblin/auth/tools.py b/mediagoblin/auth/tools.py
index c45944d3..877da14f 100644
--- a/mediagoblin/auth/tools.py
+++ b/mediagoblin/auth/tools.py
@@ -14,20 +14,18 @@
# 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)
from mediagoblin.tools.template import render_template
from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
+from mediagoblin.tools.pluginapi import hook_handle
+from mediagoblin import auth
_log = logging.getLogger(__name__)
@@ -103,11 +101,41 @@ def send_verification_email(user, request, email=None,
rendered_email)
+EMAIL_FP_VERIFICATION_TEMPLATE = (
+ u"http://{host}{uri}?"
+ u"userid={userid}&token={fp_verification_key}")
+
+
+def send_fp_verification_email(user, request):
+ """
+ Send the verification email to users to change their password.
+
+ Args:
+ - user: a user object
+ - request: the request
+ """
+ rendered_email = render_template(
+ request, 'mediagoblin/auth/fp_verification_email.txt',
+ {'username': user.username,
+ 'verification_url': EMAIL_FP_VERIFICATION_TEMPLATE.format(
+ host=request.host,
+ uri=request.urlgen('mediagoblin.auth.verify_forgot_password'),
+ userid=unicode(user.id),
+ fp_verification_key=user.fp_verification_key)})
+
+ # TODO: There is no error handling in place
+ send_email(
+ mg_globals.app_config['email_sender_address'],
+ [user.email],
+ 'GNU MediaGoblin - Change forgotten password!',
+ rendered_email)
+
+
def basic_extra_validation(register_form, *args):
users_with_username = User.query.filter_by(
- username=register_form.data['username']).count()
+ username=register_form.username.data).count()
users_with_email = User.query.filter_by(
- email=register_form.data['email']).count()
+ email=register_form.email.data).count()
extra_validation_passes = True
@@ -125,17 +153,11 @@ def basic_extra_validation(register_form, *args):
def register_user(request, register_form):
""" Handle user registration """
- extra_validation_passes = basic_extra_validation(register_form)
+ extra_validation_passes = auth.extra_validation(register_form)
if extra_validation_passes:
# Create the user
- user = User()
- user.username = register_form.data['username']
- user.email = register_form.data['email']
- user.pw_hash = auth_lib.bcrypt_gen_password_hash(
- register_form.password.data)
- user.verification_key = unicode(uuid.uuid4())
- user.save()
+ user = auth.create_user(register_form)
# log the user in
request.session['user_id'] = unicode(user.id)
@@ -150,17 +172,29 @@ def register_user(request, register_form):
return None
-def check_login_simple(username, password, username_might_be_email=False):
- search = (User.username == username)
- if username_might_be_email and ('@' in username):
- search = or_(search, User.email == username)
- user = User.query.filter(search).first()
+def check_login_simple(username, password):
+ user = auth.get_user(username=username)
if not user:
_log.info("User %r not found", username)
- auth_lib.fake_login_attempt()
+ hook_handle("auth_fake_login_attempt")
return None
- if not auth_lib.bcrypt_check_password(password, user.pw_hash):
+ if not auth.check_password(password, user.pw_hash):
_log.warn("Wrong password for %r", username)
return None
_log.info("Logging %r in", username)
return user
+
+
+def check_auth_enabled():
+ if not hook_handle('authentication'):
+ _log.warning('No authentication is enabled')
+ return False
+ else:
+ return True
+
+
+def no_auth_logout(request):
+ """Log out the user if authentication_disabled, but don't delete the messages"""
+ if not mg_globals.app.auth and 'user_id' in request.session:
+ del request.session['user_id']
+ request.session.save()
diff --git a/mediagoblin/auth/views.py b/mediagoblin/auth/views.py
index 45cb3a54..34500f91 100644
--- a/mediagoblin/auth/views.py
+++ b/mediagoblin/auth/views.py
@@ -23,11 +23,12 @@ from mediagoblin.tools.crypto import get_timed_signer_url
from mediagoblin.tools.response import render_to_response, redirect, render_404
from mediagoblin.tools.translate import pass_to_ugettext as _
from mediagoblin.tools.mail import email_debug_message
-from mediagoblin.auth import lib as auth_lib
+from mediagoblin.tools.pluginapi import hook_handle
from mediagoblin.auth import forms as auth_forms
-from mediagoblin.auth.lib import send_fp_verification_email
from mediagoblin.auth.tools import (send_verification_email, register_user,
+ send_fp_verification_email,
check_login_simple)
+from mediagoblin import auth
def register(request):
@@ -36,15 +37,21 @@ def register(request):
Note that usernames will always be lowercased. Email domains are lowercased while
the first part remains case-sensitive.
"""
- # Redirects to indexpage if registrations are disabled
- if not mg_globals.app_config["allow_registration"]:
+ # Redirects to indexpage if registrations are disabled or no authentication
+ # is enabled
+ if not mg_globals.app_config["allow_registration"] or not mg_globals.app.auth:
messages.add_message(
request,
messages.WARNING,
_('Sorry, registration is disabled on this instance.'))
return redirect(request, "index")
- register_form = auth_forms.RegistrationForm(request.form)
+ if 'pass_auth' not in request.template_env.globals:
+ redirect_name = hook_handle('auth_no_pass_redirect')
+ return redirect(request, 'mediagoblin.plugins.{0}.register'.format(
+ redirect_name))
+
+ register_form = hook_handle("auth_get_registration_form", request)
if request.method == 'POST' and register_form.validate():
# TODO: Make sure the user doesn't exist already
@@ -60,7 +67,8 @@ def register(request):
return render_to_response(
request,
'mediagoblin/auth/register.html',
- {'register_form': register_form})
+ {'register_form': register_form,
+ 'post_url': request.urlgen('mediagoblin.auth.register')})
def login(request):
@@ -69,16 +77,28 @@ def login(request):
If you provide the POST with 'next', it'll redirect to that view.
"""
- login_form = auth_forms.LoginForm(request.form)
+ # Redirects to index page if no authentication is enabled
+ if not mg_globals.app.auth:
+ messages.add_message(
+ request,
+ messages.WARNING,
+ _('Sorry, authentication is disabled on this instance.'))
+ return redirect(request, 'index')
+
+ if 'pass_auth' not in request.template_env.globals:
+ redirect_name = hook_handle('auth_no_pass_redirect')
+ return redirect(request, 'mediagoblin.plugins.{0}.login'.format(
+ redirect_name))
+
+ login_form = hook_handle("auth_get_login_form", request)
login_failed = False
if request.method == 'POST':
-
- username = login_form.data['username']
+ username = login_form.username.data
if login_form.validate():
- user = check_login_simple(username, login_form.password.data, True)
+ user = check_login_simple(username, login_form.password.data)
if user:
# set up login in session
@@ -98,6 +118,7 @@ def login(request):
{'login_form': login_form,
'next': request.GET.get('next') or request.form.get('next'),
'login_failed': login_failed,
+ 'post_url': request.urlgen('mediagoblin.auth.login'),
'allow_registration': mg_globals.app_config["allow_registration"]})
@@ -198,13 +219,16 @@ def forgot_password(request):
Sends an email with an url to renew forgotten password.
Use GET querystring parameter 'username' to pre-populate the input field
"""
+ if not 'pass_auth' in request.template_env.globals:
+ return redirect(request, 'index')
+
fp_form = auth_forms.ForgotPassForm(request.form,
username=request.args.get('username'))
if not (request.method == 'POST' and fp_form.validate()):
# Either GET request, or invalid form submitted. Display the template
return render_to_response(request,
- 'mediagoblin/auth/forgot_password.html', {'fp_form': fp_form})
+ 'mediagoblin/auth/forgot_password.html', {'fp_form': fp_form,})
# If we are here: method == POST and form is valid. username casing
# has been sanitized. Store if a user was found by email. We should
@@ -295,7 +319,7 @@ 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.save()
@@ -308,7 +332,7 @@ def verify_forgot_password(request):
return render_to_response(
request,
'mediagoblin/auth/change_fp.html',
- {'cp_form': cp_form})
+ {'cp_form': cp_form,})
if not user.email_verified:
messages.add_message(
diff --git a/mediagoblin/db/migrations.py b/mediagoblin/db/migrations.py
index 7074ffec..fef353af 100644
--- a/mediagoblin/db/migrations.py
+++ b/mediagoblin/db/migrations.py
@@ -361,3 +361,19 @@ def add_new_notification_tables(db):
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 --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py
index 4c24bfe8..826d47ba 100644
--- a/mediagoblin/db/models.py
+++ b/mediagoblin/db/models.py
@@ -62,7 +62,7 @@ class User(Base, UserMixin):
# the RFC) and because it would be a mess to implement at this
# point.
email = Column(Unicode, nullable=False)
- 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)
diff --git a/mediagoblin/edit/views.py b/mediagoblin/edit/views.py
index 4eda61a2..429eb584 100644
--- a/mediagoblin/edit/views.py
+++ b/mediagoblin/edit/views.py
@@ -23,15 +23,14 @@ from werkzeug.utils import secure_filename
from mediagoblin import messages
from mediagoblin import mg_globals
-from mediagoblin.auth import lib as auth_lib
-from mediagoblin.auth import tools as auth_tools
-from mediagoblin.auth.views import email_debug_message
+from mediagoblin import auth
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.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 _
@@ -370,7 +369,7 @@ def change_pass(request):
if request.method == 'POST' and form.validate():
- if not auth_lib.bcrypt_check_password(
+ if not auth.check_password(
form.old_password.data, user.pw_hash):
form.old_password.errors.append(
_('Wrong password'))
@@ -382,7 +381,7 @@ def change_pass(request):
'user': user})
# Password matches
- user.pw_hash = auth_lib.bcrypt_gen_password_hash(
+ user.pw_hash = auth.gen_password_hash(
form.new_password.data)
user.save()
diff --git a/mediagoblin/gmg_commands/users.py b/mediagoblin/gmg_commands/users.py
index 024c8498..1f329459 100644
--- a/mediagoblin/gmg_commands/users.py
+++ b/mediagoblin/gmg_commands/users.py
@@ -15,7 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from mediagoblin.gmg_commands import util as commands_util
-from mediagoblin.auth import lib as auth_lib
+from mediagoblin import auth
from mediagoblin import mg_globals
def adduser_parser_setup(subparser):
@@ -52,7 +52,7 @@ def adduser(args):
entry = db.User()
entry.username = unicode(args.username.lower())
entry.email = unicode(args.email)
- entry.pw_hash = auth_lib.bcrypt_gen_password_hash(args.password)
+ entry.pw_hash = auth.gen_password_hash(args.password)
entry.status = u'active'
entry.email_verified = True
entry.save()
@@ -96,7 +96,7 @@ def changepw(args):
user = db.User.one({'username': unicode(args.username.lower())})
if user:
- user.pw_hash = auth_lib.bcrypt_gen_password_hash(args.password)
+ user.pw_hash = auth.gen_password_hash(args.password)
user.save()
print 'Password successfully changed'
else:
diff --git a/mediagoblin/plugins/basic_auth/__init__.py b/mediagoblin/plugins/basic_auth/__init__.py
new file mode 100644
index 00000000..a2efae92
--- /dev/null
+++ b/mediagoblin/plugins/basic_auth/__init__.py
@@ -0,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 <http://www.gnu.org/licenses/>.
+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,
+}
diff --git a/mediagoblin/plugins/basic_auth/forms.py b/mediagoblin/plugins/basic_auth/forms.py
new file mode 100644
index 00000000..72d99dff
--- /dev/null
+++ b/mediagoblin/plugins/basic_auth/forms.py
@@ -0,0 +1,43 @@
+# 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/>.
+import wtforms
+
+from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
+from mediagoblin.auth.tools import normalize_user_or_email_field
+
+
+class RegistrationForm(wtforms.Form):
+ username = wtforms.TextField(
+ _('Username'),
+ [wtforms.validators.Required(),
+ normalize_user_or_email_field(allow_email=False)])
+ password = wtforms.PasswordField(
+ _('Password'),
+ [wtforms.validators.Required(),
+ wtforms.validators.Length(min=5, max=1024)])
+ email = wtforms.TextField(
+ _('Email address'),
+ [wtforms.validators.Required(),
+ normalize_user_or_email_field(allow_user=False)])
+
+
+class LoginForm(wtforms.Form):
+ username = wtforms.TextField(
+ _('Username or Email'),
+ [wtforms.validators.Required(),
+ normalize_user_or_email_field()])
+ password = wtforms.PasswordField(
+ _('Password'))
diff --git a/mediagoblin/auth/lib.py b/mediagoblin/plugins/basic_auth/tools.py
index 0810bd1b..1300bb9a 100644
--- a/mediagoblin/auth/lib.py
+++ b/mediagoblin/plugins/basic_auth/tools.py
@@ -13,15 +13,8 @@
#
# 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 random
-
import bcrypt
-
-from mediagoblin.tools.mail import send_email
-from mediagoblin.tools.template import render_template
-from mediagoblin.tools.crypto import get_timed_signer_url
-from mediagoblin import mg_globals
+import random
def bcrypt_check_password(raw_pass, stored_hash, extra_salt=None):
@@ -89,35 +82,3 @@ def fake_login_attempt():
randplus_hashed_pass = bcrypt.hashpw(hashed_pass, rand_salt)
randplus_stored_hash == randplus_hashed_pass
-
-
-EMAIL_FP_VERIFICATION_TEMPLATE = (
- u"{uri}?"
- u"token={fp_verification_key}")
-
-
-def send_fp_verification_email(user, request):
- """
- Send the verification email to users to change their password.
-
- Args:
- - user: a user object
- - request: the request
- """
- fp_verification_key = get_timed_signer_url('mail_verification_token') \
- .dumps(user.id)
-
- rendered_email = render_template(
- request, 'mediagoblin/auth/fp_verification_email.txt',
- {'username': user.username,
- 'verification_url': EMAIL_FP_VERIFICATION_TEMPLATE.format(
- uri=request.urlgen('mediagoblin.auth.verify_forgot_password',
- qualified=True),
- fp_verification_key=fp_verification_key)})
-
- # TODO: There is no error handling in place
- send_email(
- mg_globals.app_config['email_sender_address'],
- [user.email],
- 'GNU MediaGoblin - Change forgotten password!',
- rendered_email)
diff --git a/mediagoblin/templates/mediagoblin/auth/change_fp.html b/mediagoblin/templates/mediagoblin/auth/change_fp.html
index 1f7d9aca..a3cf9cb9 100644
--- a/mediagoblin/templates/mediagoblin/auth/change_fp.html
+++ b/mediagoblin/templates/mediagoblin/auth/change_fp.html
@@ -34,11 +34,10 @@
{{ csrf_token }}
<div class="form_box">
<h1>{% trans %}Set your new password{% endtrans %}</h1>
- {{ wtforms_util.render_divs(cp_form) }}
+ {{ wtforms_util.render_divs(cp_form, True) }}
<div class="form_submit_buttons">
<input type="submit" value="{% trans %}Set password{% endtrans %}" class="button_form"/>
</div>
</div>
- </form>
{% endblock %}
diff --git a/mediagoblin/templates/mediagoblin/auth/forgot_password.html b/mediagoblin/templates/mediagoblin/auth/forgot_password.html
index 46aeddef..6cfd2c85 100644
--- a/mediagoblin/templates/mediagoblin/auth/forgot_password.html
+++ b/mediagoblin/templates/mediagoblin/auth/forgot_password.html
@@ -29,7 +29,7 @@
{{ csrf_token }}
<div class="form_box">
<h1>{% trans %}Recover password{% endtrans %}</h1>
- {{ wtforms_util.render_divs(fp_form) }}
+ {{ wtforms_util.render_divs(fp_form, True) }}
<div class="form_submit_buttons">
<input type="submit" value="{% trans %}Send instructions{% endtrans %}" class="button_form"/>
</div>
diff --git a/mediagoblin/templates/mediagoblin/auth/login.html b/mediagoblin/templates/mediagoblin/auth/login.html
index 4a39059d..d9f92557 100644
--- a/mediagoblin/templates/mediagoblin/auth/login.html
+++ b/mediagoblin/templates/mediagoblin/auth/login.html
@@ -45,11 +45,13 @@
{%- trans %}Create one here!{% endtrans %}</a>
</p>
{% endif %}
- {{ wtforms_util.render_divs(login_form) }}
+ {{ wtforms_util.render_divs(login_form, True) }}
+ {% if pass_auth %}
<p>
<a href="{{ request.urlgen('mediagoblin.auth.forgot_password') }}" id="forgot_password">
{% trans %}Forgot your password?{% endtrans %}</a>
</p>
+ {% endif %}
<div class="form_submit_buttons">
<input type="submit" value="{% trans %}Log in{% endtrans %}" class="button_form"/>
</div>
diff --git a/mediagoblin/templates/mediagoblin/auth/register.html b/mediagoblin/templates/mediagoblin/auth/register.html
index 6dff0207..b315975c 100644
--- a/mediagoblin/templates/mediagoblin/auth/register.html
+++ b/mediagoblin/templates/mediagoblin/auth/register.html
@@ -34,7 +34,7 @@
method="POST" enctype="multipart/form-data">
<div class="form_box">
<h1>{% trans %}Create an account!{% endtrans %}</h1>
- {{ wtforms_util.render_divs(register_form) }}
+ {{ wtforms_util.render_divs(register_form, True) }}
{{ csrf_token }}
<div class="form_submit_buttons">
<input type="submit" value="{% trans %}Create{% endtrans %}"
@@ -42,6 +42,4 @@
</div>
</div>
</form>
-<!-- Focus the username field by default -->
-<script>$(document).ready(function(){$("#username").focus();});</script>
{% endblock %}
diff --git a/mediagoblin/templates/mediagoblin/base.html b/mediagoblin/templates/mediagoblin/base.html
index 25186b18..1fc4467c 100644
--- a/mediagoblin/templates/mediagoblin/base.html
+++ b/mediagoblin/templates/mediagoblin/base.html
@@ -75,7 +75,7 @@
{% trans %}Verify your email!{% endtrans %}</a>
or <a href="{{ request.urlgen('mediagoblin.auth.logout') }}">{% trans %}log out{% endtrans %}</a>
{% endif %}
- {%- else %}
+ {%- elif auth %}
<a href="{{ request.urlgen('mediagoblin.auth.login') }}?next={{
request.base_url|urlencode }}">
{%- trans %}Log in{% endtrans -%}
diff --git a/mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html b/mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html
index 544ee146..9ef28a4d 100644
--- a/mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html
+++ b/mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html
@@ -17,19 +17,25 @@
#}
{% if request.user %}
- <h1>{% trans %}Explore{% endtrans %}</h1>
-{% else %}
- <h1>{% trans %}Hi there, welcome to this MediaGoblin site!{% endtrans %}</h1>
- <img class="right_align" src="{{ request.staticdirect('/images/frontpage_image.png') }}" />
- <p>{% trans %}This site is running <a href="http://mediagoblin.org">MediaGoblin</a>, an extraordinarily great piece of media hosting software.{% endtrans %}</p>
- <p>{% trans %}To add your own media, place comments, and more, you can log in with your MediaGoblin account.{% endtrans %}</p>
- {% if allow_registration %}
- <p>{% trans %}Don't have one yet? It's easy!{% endtrans %}</p>
- {% trans register_url=request.urlgen('mediagoblin.auth.register') -%}
- <a class="button_action_highlight" href="{{ register_url }}">Create an account at this site</a>
- or
- <a class="button_action" href="http://wiki.mediagoblin.org/HackingHowto">Set up MediaGoblin on your own server</a>
- {%- endtrans %}
+ <h1>{% trans %}Explore{% endtrans %}</h1>
+ {% else %}
+ <h1>{% trans %}Hi there, welcome to this MediaGoblin site!{% endtrans %}</h1>
+ <img class="right_align" src="{{ request.staticdirect('/images/frontpage_image.png') }}" />
+ <p>{% trans %}This site is running <a href="http://mediagoblin.org">MediaGoblin</a>, an extraordinarily great piece of media hosting software.{% endtrans %}</p>
+ {% if auth %}
+ <p>{% trans %}To add your own media, place comments, and more, you can log in with your MediaGoblin account.{% endtrans %}</p>
+ {% if allow_registration %}
+ <p>{% trans %}Don't have one yet? It's easy!{% endtrans %}</p>
+ {% trans register_url=request.urlgen('mediagoblin.auth.register') -%}
+ <a class="button_action_highlight" href="{{ register_url }}">Create an account at this site</a>
+ or
+ {%- endtrans %}
+ {% endif %}
+ {% endif %}
+ {% trans %}
+ <a class="button_action" href="http://wiki.mediagoblin.org/HackingHowto">Set up MediaGoblin on your own server</a>
+ {%- endtrans %}
+
+ <div class="clear"></div>
{% endif %}
- <div class="clear"></div>
-{% endif %}
+
diff --git a/mediagoblin/templates/mediagoblin/utils/wtforms.html b/mediagoblin/templates/mediagoblin/utils/wtforms.html
index be6976c2..90b237ee 100644
--- a/mediagoblin/templates/mediagoblin/utils/wtforms.html
+++ b/mediagoblin/templates/mediagoblin/utils/wtforms.html
@@ -33,10 +33,14 @@
{%- endmacro %}
{# Generically render a field #}
-{% macro render_field_div(field) %}
+{% macro render_field_div(field, autofocus_first=False) %}
{{- render_label_p(field) }}
<div class="form_field_input">
- {{ field }}
+ {% if autofocus_first %}
+ {{ field(autofocus=True) }}
+ {% else %}
+ {{ field }}
+ {% endif %}
{%- if field.errors -%}
{% for error in field.errors %}
<p class="form_field_error">{{ error }}</p>
@@ -49,9 +53,13 @@
{%- endmacro %}
{# Auto-render a form as a series of divs #}
-{% macro render_divs(form) -%}
+{% macro render_divs(form, autofocus_first=False) -%}
{% for field in form %}
- {{ render_field_div(field) }}
+ {% if autofocus_first and loop.first %}
+ {{ render_field_div(field, True) }}
+ {% else %}
+ {{ render_field_div(field) }}
+ {% endif %}
{% endfor %}
{%- endmacro %}
diff --git a/mediagoblin/tests/auth_configs/__init__.py b/mediagoblin/tests/auth_configs/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/mediagoblin/tests/auth_configs/__init__.py
diff --git a/mediagoblin/tests/auth_configs/authentication_disabled_appconfig.ini b/mediagoblin/tests/auth_configs/authentication_disabled_appconfig.ini
new file mode 100644
index 00000000..a64e9e40
--- /dev/null
+++ b/mediagoblin/tests/auth_configs/authentication_disabled_appconfig.ini
@@ -0,0 +1,25 @@
+[mediagoblin]
+direct_remote_path = /test_static/
+email_sender_address = "notice@mediagoblin.example.org"
+email_debug_mode = true
+
+# TODO: Switch to using an in-memory database
+sql_engine = "sqlite:///%(here)s/user_dev/mediagoblin.db"
+
+# Celery shouldn't be set up by the application as it's setup via
+# mediagoblin.init.celery.from_celery
+celery_setup_elsewhere = true
+
+[storage:publicstore]
+base_dir = %(here)s/user_dev/media/public
+base_url = /mgoblin_media/
+
+[storage:queuestore]
+base_dir = %(here)s/user_dev/media/queue
+
+[celery]
+CELERY_ALWAYS_EAGER = true
+CELERY_RESULT_DBURI = "sqlite:///%(here)s/user_dev/celery.db"
+BROKER_HOST = "sqlite:///%(here)s/user_dev/kombu.db"
+
+[plugins]
diff --git a/mediagoblin/tests/test_auth.py b/mediagoblin/tests/test_auth.py
index 48b148dd..f973ebd8 100644
--- a/mediagoblin/tests/test_auth.py
+++ b/mediagoblin/tests/test_auth.py
@@ -13,54 +13,16 @@
#
# 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 urlparse
import datetime
+import pkg_resources
+import pytest
from mediagoblin import mg_globals
-from mediagoblin.auth import lib as auth_lib
from mediagoblin.db.models import User
-from mediagoblin.tests.tools import fixture_add_user
+from mediagoblin.tests.tools import get_app, fixture_add_user
from mediagoblin.tools import template, mail
-
-
-########################
-# Test bcrypt auth funcs
-########################
-
-def test_bcrypt_check_password():
- # Check known 'lollerskates' password against check function
- assert auth_lib.bcrypt_check_password(
- 'lollerskates',
- '$2a$12$PXU03zfrVCujBhVeICTwtOaHTUs5FFwsscvSSTJkqx/2RQ0Lhy/nO')
-
- assert not auth_lib.bcrypt_check_password(
- 'notthepassword',
- '$2a$12$PXU03zfrVCujBhVeICTwtOaHTUs5FFwsscvSSTJkqx/2RQ0Lhy/nO')
-
- # Same thing, but with extra fake salt.
- assert not auth_lib.bcrypt_check_password(
- 'notthepassword',
- '$2a$12$ELVlnw3z1FMu6CEGs/L8XO8vl0BuWSlUHgh0rUrry9DUXGMUNWwl6',
- '3><7R45417')
-
-
-def test_bcrypt_gen_password_hash():
- pw = 'youwillneverguessthis'
-
- # Normal password hash generation, and check on that hash
- hashed_pw = auth_lib.bcrypt_gen_password_hash(pw)
- assert auth_lib.bcrypt_check_password(
- pw, hashed_pw)
- assert not auth_lib.bcrypt_check_password(
- 'notthepassword', hashed_pw)
-
- # Same thing, extra salt.
- hashed_pw = auth_lib.bcrypt_gen_password_hash(pw, '3><7R45417')
- assert auth_lib.bcrypt_check_password(
- pw, hashed_pw, '3><7R45417')
- assert not auth_lib.bcrypt_check_password(
- 'notthepassword', hashed_pw, '3><7R45417')
+from mediagoblin.auth import tools as auth_tools
def test_register_views(test_app):
@@ -286,7 +248,6 @@ def test_authentication_views(test_app):
context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html']
form = context['login_form']
assert form.username.errors == [u'This field is required.']
- assert form.password.errors == [u'This field is required.']
# Failed login - blank user
# -------------------------
@@ -304,9 +265,7 @@ def test_authentication_views(test_app):
response = test_app.post(
'/auth/login/', {
'username': u'chris'})
- context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html']
- form = context['login_form']
- assert form.password.errors == [u'This field is required.']
+ assert 'mediagoblin/auth/login.html' in template.TEMPLATE_TEST_CONTEXT
# Failed login - bad user
# -----------------------
@@ -370,3 +329,47 @@ def test_authentication_views(test_app):
'password': 'toast',
'next' : '/u/chris/'})
assert urlparse.urlsplit(response.location)[2] == '/u/chris/'
+
+
+@pytest.fixture()
+def authentication_disabled_app(request):
+ return get_app(
+ request,
+ mgoblin_config=pkg_resources.resource_filename(
+ 'mediagoblin.tests.auth_configs',
+ 'authentication_disabled_appconfig.ini'))
+
+
+def test_authentication_disabled_app(authentication_disabled_app):
+ # app.auth should = false
+ assert mg_globals.app.auth is False
+
+ # Try to visit register page
+ template.clear_test_template_context()
+ response = authentication_disabled_app.get('/auth/register/')
+ response.follow()
+
+ # Correct redirect?
+ assert urlparse.urlsplit(response.location)[2] == '/'
+ assert 'mediagoblin/root.html' in template.TEMPLATE_TEST_CONTEXT
+
+ # Try to vist login page
+ template.clear_test_template_context()
+ response = authentication_disabled_app.get('/auth/login/')
+ response.follow()
+
+ # Correct redirect?
+ assert urlparse.urlsplit(response.location)[2] == '/'
+ assert 'mediagoblin/root.html' in template.TEMPLATE_TEST_CONTEXT
+
+ ## Test check_login_simple should return None
+ assert auth_tools.check_login_simple('test', 'simple') is None
+
+ # Try to visit the forgot password page
+ template.clear_test_template_context()
+ response = authentication_disabled_app.get('/auth/register/')
+ response.follow()
+
+ # Correct redirect?
+ assert urlparse.urlsplit(response.location)[2] == '/'
+ assert 'mediagoblin/root.html' in template.TEMPLATE_TEST_CONTEXT
diff --git a/mediagoblin/tests/test_basic_auth.py b/mediagoblin/tests/test_basic_auth.py
new file mode 100644
index 00000000..cdd80fca
--- /dev/null
+++ b/mediagoblin/tests/test_basic_auth.py
@@ -0,0 +1,59 @@
+# 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/>.
+from mediagoblin.plugins.basic_auth import tools as auth_tools
+from mediagoblin.tools.testing import _activate_testing
+
+_activate_testing()
+
+
+########################
+# Test bcrypt auth funcs
+########################
+
+
+def test_bcrypt_check_password():
+ # Check known 'lollerskates' password against check function
+ assert auth_tools.bcrypt_check_password(
+ 'lollerskates',
+ '$2a$12$PXU03zfrVCujBhVeICTwtOaHTUs5FFwsscvSSTJkqx/2RQ0Lhy/nO')
+
+ assert not auth_tools.bcrypt_check_password(
+ 'notthepassword',
+ '$2a$12$PXU03zfrVCujBhVeICTwtOaHTUs5FFwsscvSSTJkqx/2RQ0Lhy/nO')
+
+ # Same thing, but with extra fake salt.
+ assert not auth_tools.bcrypt_check_password(
+ 'notthepassword',
+ '$2a$12$ELVlnw3z1FMu6CEGs/L8XO8vl0BuWSlUHgh0rUrry9DUXGMUNWwl6',
+ '3><7R45417')
+
+
+def test_bcrypt_gen_password_hash():
+ pw = 'youwillneverguessthis'
+
+ # Normal password hash generation, and check on that hash
+ hashed_pw = auth_tools.bcrypt_gen_password_hash(pw)
+ assert auth_tools.bcrypt_check_password(
+ pw, hashed_pw)
+ assert not auth_tools.bcrypt_check_password(
+ 'notthepassword', hashed_pw)
+
+ # Same thing, extra salt.
+ hashed_pw = auth_tools.bcrypt_gen_password_hash(pw, '3><7R45417')
+ assert auth_tools.bcrypt_check_password(
+ pw, hashed_pw, '3><7R45417')
+ assert not auth_tools.bcrypt_check_password(
+ 'notthepassword', hashed_pw, '3><7R45417')
diff --git a/mediagoblin/tests/test_edit.py b/mediagoblin/tests/test_edit.py
index 2afc519a..acc638d9 100644
--- a/mediagoblin/tests/test_edit.py
+++ b/mediagoblin/tests/test_edit.py
@@ -19,8 +19,8 @@ import urlparse
from mediagoblin import mg_globals
from mediagoblin.db.models import User
from mediagoblin.tests.tools import fixture_add_user
+from mediagoblin import auth
from mediagoblin.tools import template, mail
-from mediagoblin.auth.lib import bcrypt_check_password
class TestUserEdit(object):
@@ -74,7 +74,7 @@ class TestUserEdit(object):
# test_user has to be fetched again in order to have the current values
test_user = User.query.filter_by(username=u'chris').first()
- assert bcrypt_check_password('123456', test_user.pw_hash)
+ assert auth.check_password('123456', test_user.pw_hash)
# Update current user passwd
self.user_password = '123456'
@@ -88,7 +88,7 @@ class TestUserEdit(object):
})
test_user = User.query.filter_by(username=u'chris').first()
- assert not bcrypt_check_password('098765', test_user.pw_hash)
+ assert not auth.check_password('098765', test_user.pw_hash)
def test_change_bio_url(self, test_app):
diff --git a/mediagoblin/tests/test_mgoblin_app.ini b/mediagoblin/tests/test_mgoblin_app.ini
index 0466b53b..5b060d36 100644
--- a/mediagoblin/tests/test_mgoblin_app.ini
+++ b/mediagoblin/tests/test_mgoblin_app.ini
@@ -31,3 +31,4 @@ BROKER_HOST = "sqlite:///%(here)s/user_dev/kombu.db"
[[mediagoblin.plugins.oauth]]
[[mediagoblin.plugins.httpapiauth]]
[[mediagoblin.plugins.piwigo]]
+[[mediagoblin.plugins.basic_auth]]
diff --git a/mediagoblin/tests/tools.py b/mediagoblin/tests/tools.py
index 222649f1..2584c62f 100644
--- a/mediagoblin/tests/tools.py
+++ b/mediagoblin/tests/tools.py
@@ -30,7 +30,7 @@ from mediagoblin.tools import testing
from mediagoblin.init.config import read_mediagoblin_config
from mediagoblin.db.base import Session
from mediagoblin.meddleware import BaseMeddleware
-from mediagoblin.auth.lib import bcrypt_gen_password_hash
+from mediagoblin.auth import gen_password_hash
from mediagoblin.gmg_commands.dbupdate import run_dbupdate
@@ -178,7 +178,7 @@ def fixture_add_user(username=u'chris', password=u'toast',
test_user.username = username
test_user.email = username + u'@example.com'
if password is not None:
- test_user.pw_hash = bcrypt_gen_password_hash(password)
+ test_user.pw_hash = gen_password_hash(password)
if active_user:
test_user.email_verified = True
test_user.status = u'active'
diff --git a/mediagoblin/tools/template.py b/mediagoblin/tools/template.py
index 3d651a6e..615ce129 100644
--- a/mediagoblin/tools/template.py
+++ b/mediagoblin/tools/template.py
@@ -71,6 +71,7 @@ def get_jinja_env(template_loader, locale):
template_env.globals['app_config'] = mg_globals.app_config
template_env.globals['global_config'] = mg_globals.global_config
template_env.globals['version'] = _version.__version__
+ template_env.globals['auth'] = mg_globals.app.auth
template_env.filters['urlencode'] = url_quote_plus