diff options
Diffstat (limited to 'mediagoblin/plugins/basic_auth')
-rw-r--r-- | mediagoblin/plugins/basic_auth/__init__.py | 98 | ||||
-rw-r--r-- | mediagoblin/plugins/basic_auth/forms.py | 45 | ||||
-rw-r--r-- | mediagoblin/plugins/basic_auth/tools.py | 84 |
3 files changed, 227 insertions, 0 deletions
diff --git a/mediagoblin/plugins/basic_auth/__init__.py b/mediagoblin/plugins/basic_auth/__init__.py new file mode 100644 index 00000000..71e96d73 --- /dev/null +++ b/mediagoblin/plugins/basic_auth/__init__.py @@ -0,0 +1,98 @@ +# 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 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 --git a/mediagoblin/plugins/basic_auth/forms.py b/mediagoblin/plugins/basic_auth/forms.py new file mode 100644 index 00000000..f389b21e --- /dev/null +++ b/mediagoblin/plugins/basic_auth/forms.py @@ -0,0 +1,45 @@ +# 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'), + [wtforms.validators.Required(), + wtforms.validators.Length(min=5, max=1024)]) diff --git a/mediagoblin/plugins/basic_auth/tools.py b/mediagoblin/plugins/basic_auth/tools.py new file mode 100644 index 00000000..1300bb9a --- /dev/null +++ b/mediagoblin/plugins/basic_auth/tools.py @@ -0,0 +1,84 @@ +# 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 bcrypt +import random + + +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 |