aboutsummaryrefslogtreecommitdiffstats
path: root/mediagoblin/auth
diff options
context:
space:
mode:
Diffstat (limited to 'mediagoblin/auth')
-rw-r--r--mediagoblin/auth/__init__.py47
-rw-r--r--mediagoblin/auth/forms.py26
-rw-r--r--mediagoblin/auth/lib.py120
-rw-r--r--mediagoblin/auth/tools.py81
-rw-r--r--mediagoblin/auth/views.py49
5 files changed, 153 insertions, 170 deletions
diff --git a/mediagoblin/auth/__init__.py b/mediagoblin/auth/__init__.py
index 621845ba..53182eaa 100644
--- a/mediagoblin/auth/__init__.py
+++ b/mediagoblin/auth/__init__.py
@@ -13,3 +13,50 @@
#
# 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 check_login(user, password):
+ result = hook_handle("auth_check_login", user, password)
+ if result:
+ return result
+ return False
+
+
+def get_user(form):
+ return hook_handle("auth_get_user", form)
+
+
+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 get_login_form(request):
+ return hook_handle("auth_get_login_form", request)
+
+
+def get_registration_form(request):
+ return hook_handle("auth_get_registration_form", request)
+
+
+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)
+
+
+def fake_login_attempt():
+ return hook_handle("auth_fake_login_attempt")
diff --git a/mediagoblin/auth/forms.py b/mediagoblin/auth/forms.py
index 0a391d67..7a67285b 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'),
diff --git a/mediagoblin/auth/lib.py b/mediagoblin/auth/lib.py
deleted file mode 100644
index bfc36b28..00000000
--- a/mediagoblin/auth/lib.py
+++ /dev/null
@@ -1,120 +0,0 @@
-# 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 random
-
-import bcrypt
-
-from mediagoblin.tools.mail import send_email
-from mediagoblin.tools.template import render_template
-from mediagoblin import mg_globals
-
-
-def bcrypt_check_password(raw_pass, stored_hash, extra_salt=None):
- """
- Check to see if this password matches.
-
- Args:
- - raw_pass: user submitted password to check for authenticity.
- - stored_hash: The hash of the raw password (and possibly extra
- salt) to check against
- - extra_salt: (optional) If this password is with stored with a
- non-database extra salt (probably in the config file) for extra
- security, factor this into the check.
-
- Returns:
- True or False depending on success.
- """
- if extra_salt:
- raw_pass = u"%s:%s" % (extra_salt, raw_pass)
-
- hashed_pass = bcrypt.hashpw(raw_pass.encode('utf-8'), stored_hash)
-
- # Reduce risk of timing attacks by hashing again with a random
- # number (thx to zooko on this advice, which I hopefully
- # incorporated right.)
- #
- # See also:
- rand_salt = bcrypt.gensalt(5)
- randplus_stored_hash = bcrypt.hashpw(stored_hash, rand_salt)
- randplus_hashed_pass = bcrypt.hashpw(hashed_pass, rand_salt)
-
- return randplus_stored_hash == randplus_hashed_pass
-
-
-def bcrypt_gen_password_hash(raw_pass, extra_salt=None):
- """
- Generate a salt for this new password.
-
- Args:
- - raw_pass: user submitted password
- - extra_salt: (optional) If this password is with stored with a
- non-database extra salt
- """
- if extra_salt:
- raw_pass = u"%s:%s" % (extra_salt, raw_pass)
-
- return unicode(
- bcrypt.hashpw(raw_pass.encode('utf-8'), bcrypt.gensalt()))
-
-
-def fake_login_attempt():
- """
- Pretend we're trying to login.
-
- Nothing actually happens here, we're just trying to take up some
- time, approximately the same amount of time as
- bcrypt_check_password, so as to avoid figuring out what users are
- on the system by intentionally faking logins a bunch of times.
- """
- rand_salt = bcrypt.gensalt(5)
-
- hashed_pass = bcrypt.hashpw(str(random.random()), rand_salt)
-
- randplus_stored_hash = bcrypt.hashpw(str(random.random()), rand_salt)
- randplus_hashed_pass = bcrypt.hashpw(hashed_pass, rand_salt)
-
- randplus_stored_hash == randplus_hashed_pass
-
-
-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)
diff --git a/mediagoblin/auth/tools.py b/mediagoblin/auth/tools.py
index db6b6e37..f38a292a 100644
--- a/mediagoblin/auth/tools.py
+++ b/mediagoblin/auth/tools.py
@@ -14,7 +14,6 @@
# 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
@@ -25,8 +24,12 @@ from mediagoblin.auth import lib as auth_lib
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.template import render_template
+from mediagoblin.tools.pluginapi import hook_handle
+from mediagoblin import auth
+
+_log = logging.getLogger(__name__)
_log = logging.getLogger(__name__)
@@ -61,6 +64,35 @@ def normalize_user_or_email_field(allow_email=True, allow_user=True):
return _normalize_field
+class AuthError(Exception):
+ def __init__(self):
+ self.value = 'No Authentication Plugin is enabled and no_auth = false'\
+ ' in config!'
+
+ def __str__(self):
+ return repr(self.value)
+
+
+def check_auth_enabled():
+ no_auth = mg_globals.app_config['no_auth']
+ auth_plugin = hook_handle('authentication')
+
+ if no_auth == 'false' and not auth_plugin:
+ raise AuthError
+
+ if no_auth == 'true' and not auth_plugin:
+ _log.warning('No authentication is enabled')
+ return False
+ else:
+ return True
+
+
+def no_auth_logout(request):
+ """Log out the user if in no_auth mode"""
+ if not mg_globals.app.auth:
+ request.session.delete()
+
+
EMAIL_VERIFICATION_TEMPLATE = (
u"http://{host}{uri}?"
u"userid={userid}&token={verification_key}")
@@ -98,9 +130,9 @@ def send_verification_email(user, request):
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
@@ -118,17 +150,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)
@@ -137,12 +163,41 @@ def register_user(request, register_form):
# send verification email
email_debug_message(request)
send_verification_email(user, request)
-
return user
return None
+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 check_login_simple(username, password, username_might_be_email=False):
search = (User.username == username)
if username_might_be_email and ('@' in username):
diff --git a/mediagoblin/auth/views.py b/mediagoblin/auth/views.py
index bb7bda77..109763ce 100644
--- a/mediagoblin/auth/views.py
+++ b/mediagoblin/auth/views.py
@@ -24,9 +24,11 @@ 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.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,
+from mediagoblin.auth.tools import (send_verification_email,
+ register_user,
+ send_fp_verification_email,
check_login_simple)
+from mediagoblin import auth
def register(request):
@@ -35,15 +37,20 @@ 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:
+ if 'openid' in request.template_env.globals:
+ return redirect(request, 'mediagoblin.plugins.openid.register')
+
+ register_form = auth.get_registration_form(request)
if request.method == 'POST' and register_form.validate():
# TODO: Make sure the user doesn't exist already
@@ -59,7 +66,9 @@ def register(request):
return render_to_response(
request,
'mediagoblin/auth/register.html',
- {'register_form': register_form})
+ {'register_form': register_form,
+ 'focus': 'username',
+ 'post_url': request.urlgen('mediagoblin.auth.register')})
def login(request):
@@ -68,13 +77,24 @@ 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:
+ if 'openid' in request.template_env.globals:
+ return redirect(request, 'mediagoblin.plugins.openid.login')
+
+ login_form = 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)
@@ -97,6 +117,8 @@ def login(request):
{'login_form': login_form,
'next': request.GET.get('next') or request.form.get('next'),
'login_failed': login_failed,
+ 'focus': 'username',
+ 'post_url': request.urlgen('mediagoblin.auth.login'),
'allow_registration': mg_globals.app_config["allow_registration"]})
@@ -188,13 +210,17 @@ 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,
+ 'focus': 'username'})
# 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
@@ -290,7 +316,8 @@ def verify_forgot_password(request):
return render_to_response(
request,
'mediagoblin/auth/change_fp.html',
- {'cp_form': cp_form})
+ {'cp_form': cp_form,
+ 'focus': 'password'})
# in case there is a valid id but no user with that id in the db
# or the token expired