Permalink
Browse files

merge branch because git didn't like long branch name

  • Loading branch information...
1 parent d22e4c4 commit a2006c73a8f176dd405b7eebf504ceb388ded7ac @cd34 committed Mar 13, 2012
View
@@ -1,2 +1,3 @@
graft apex/static
graft apex/templates
+graft apex/locale
View
20 README
@@ -1,11 +1,24 @@
+THIS BREAKS ALL BACKWARDS COMPATIBILITY.
+
+This means that existing auth systems need to be modified - check the install
+instructions as you'll need to slightly modify your SQL, do some imports,
+and make sure you're running Velruse trunk.
+
Authentication, Form Library, I18N/L10N, Flash Message Template
(not associated with Pyramid, a Pylons project)
Uses pyramid_routesalchemy
* Authentication
-Local authentication uses BCrypt
+Authentication has a single authentication id which can have multiple
+associated credentials. A user can create a username and associate their
+Facebook and Google login records with their current record and log in
+with any of them. It is planned that Apex will act as an endpoint for
+multi-domain multi-site installations - allowing one to associate a login
+account from one domain to another.
+
+Local authentication uses salt + BCrypt
http://codahale.com/how-to-safely-store-a-password/
Velruse is used for OpenID/OpenAuth providers and supports
@@ -14,6 +27,11 @@ Velruse is used for OpenID/OpenAuth providers and supports
* Twitter
* Yahoo
* Microsoft Live
+ * Bitbucket
+ * Github
+ * Identi.ca
+ * Last.fm
+ * LinkedIn
* Any OpenID provider
Ability to overload the login form, extend the AuthUser class through
View
@@ -1,5 +1,16 @@
-Registration Hooks
-* before/after methods that pass user object for local interaction
+Username/Displayname
+* display_name added to Auth_ID
+* With multiple accounts referencing single ID, how do we know what
+ the display name should be?
+
+Multiple Accounts
+* Associate account mechanism
+** Add another association
+** Takeover/merge existing association (which auth_id wins?)
+** allow duplicate login types? i.e. assign two twitter accounts, two
+ local login accounts?
+** change password - we need to know whether we have any local auth
+ accounts. Which one gets changed? Selector box to choose?
Email Issues
* Activation Email
@@ -12,3 +23,5 @@ Email Issues
UX Issues
* add more detail to exceptions
+SQL Issues
+* unique index on auth_user_group on user_id,group_id
View
@@ -7,7 +7,6 @@
from pyramid.interfaces import IAuthenticationPolicy
from pyramid.interfaces import IAuthorizationPolicy
from pyramid.interfaces import ISessionFactory
-from pyramid.security import NO_PERMISSION_REQUIRED
from pyramid.session import UnencryptedCookieSessionFactoryConfig
from pyramid.settings import asbool
from pyramid.authentication import AuthTktAuthenticationPolicy
@@ -93,47 +92,47 @@ def includeme(config):
config.add_static_view('apex/static', 'apex:static')
- config.add_view(forbidden, context=Forbidden, permission=NO_PERMISSION_REQUIRED)
+ config.add_view(forbidden, context=Forbidden)
render_template = settings['apex.apex_render_template'
] = settings.get('apex.apex_template',
'apex:templates/apex_template.mako')
config.add_route('apex_login', '/login')
config.add_view(login, route_name='apex_login',
- renderer=render_template, permission=NO_PERMISSION_REQUIRED)
+ renderer=render_template)
config.add_route('apex_logout', '/logout')
config.add_view(logout, route_name='apex_logout',
- renderer=render_template, permission=NO_PERMISSION_REQUIRED)
+ renderer=render_template)
config.add_route('apex_register', '/register')
config.add_view(register, route_name='apex_register',
- renderer=render_template, permission=NO_PERMISSION_REQUIRED)
+ renderer=render_template)
config.add_route('apex_password', '/password')
config.add_view(change_password, route_name='apex_password',
renderer=render_template, permission='authenticated')
config.add_route('apex_forgot', '/forgot')
config.add_view(forgot_password, route_name='apex_forgot',
- renderer=render_template, permission=NO_PERMISSION_REQUIRED)
+ renderer=render_template)
config.add_route('apex_reset', '/reset/:user_id/:hmac')
config.add_view(reset_password, route_name='apex_reset',
- renderer=render_template, permission=NO_PERMISSION_REQUIRED)
+ renderer=render_template)
config.add_route('apex_activate', '/activate/:user_id/:hmac')
config.add_view(activate, route_name='apex_activate',
- renderer=render_template, permission=NO_PERMISSION_REQUIRED)
+ renderer=render_template)
config.add_route('apex_callback', '/apex_callback')
- config.add_view(apex_callback, route_name='apex_callback', permission=NO_PERMISSION_REQUIRED)
+ config.add_view(apex_callback, route_name='apex_callback')
config.add_route('apex_openid_required', '/openid_required')
config.add_view(openid_required, route_name= \
'apex_openid_required', \
- renderer=render_template, permission=NO_PERMISSION_REQUIRED)
+ renderer=render_template)
if settings.has_key('apex.auth_profile'):
use_edit = asbool(settings.get('apex.use_apex_edit', False))
View
@@ -10,13 +10,14 @@
from apex.models import DBSession
from apex.models import AuthGroup
+from apex.models import AuthID
from apex.models import AuthUser
from apex.lib.form import ExtendedForm
class RegisterForm(ExtendedForm):
""" Registration Form
"""
- username = TextField(_('Username'), [validators.Required(), \
+ login = TextField(_('Username'), [validators.Required(), \
validators.Length(min=4, max=25)])
password = PasswordField(_('Password'), [validators.Required(), \
validators.EqualTo('password2', \
@@ -25,28 +26,31 @@ class RegisterForm(ExtendedForm):
email = TextField(_('Email Address'), [validators.Required(), \
validators.Email()])
- def validate_username(form, field):
- if AuthUser.get_by_username(field.data) is not None:
+ def validate_login(form, field):
+ if AuthUser.get_by_login(field.data) is not None:
raise validators.ValidationError(_('Sorry that username already exists.'))
- def create_user(self, username):
+ def create_user(self, login):
+ id = AuthID()
+ DBSession.add(id)
user = AuthUser(
- username=username,
+ login=login,
password=self.data['password'],
email=self.data['email'],
)
+ id.users.append(user)
DBSession.add(user)
settings = get_current_registry().settings
if settings.has_key('apex.default_user_group'):
group = DBSession.query(AuthGroup). \
filter(AuthGroup.name==settings['apex.default_user_group']).one()
- user.groups.append(group)
+ id.groups.append(group)
DBSession.flush()
return user
def save(self):
- new_user = self.create_user(self.data['username'])
+ new_user = self.create_user(self.data['login'])
self.after_signup(new_user)
return new_user
@@ -61,6 +65,7 @@ def after_signup(self, user, **kwargs):
class ChangePasswordForm(ExtendedForm):
""" Change Password Form
"""
+ user_id = HiddenField('')
old_password = PasswordField(_('Old Password'), [validators.Required()])
password = PasswordField(_('New Password'), [validators.Required(), \
validators.EqualTo('password2', \
@@ -74,18 +79,18 @@ def validate_old_password(form, field):
raise validators.ValidationError(_('Your old password doesn\'t match'))
class LoginForm(ExtendedForm):
- username = TextField(_('Username'), validators=[validators.Required()])
+ login = TextField(_('Username'), validators=[validators.Required()])
password = PasswordField(_('Password'), validators=[validators.Required()])
def clean(self):
errors = []
- if not AuthUser.check_password(username=self.data.get('username'), \
+ if not AuthUser.check_password(login=self.data.get('login'), \
password=self.data.get('password')):
errors.append(_('Login Error -- please try again'))
return errors
class ForgotForm(ExtendedForm):
- username = TextField(_('Username'))
+ login = TextField(_('Username'), [validators.Optional()])
label = HiddenField(label='Or')
email = TextField(_('Email Address'), [validators.Optional(), \
validators.Email()])
@@ -100,8 +105,8 @@ class ForgotForm(ExtendedForm):
information, however, that is an enhancement that will be added
at a later point.
"""
- def validate_username(form, field):
- if AuthUser.get_by_username(field.data) is None:
+ def validate_login(form, field):
+ if AuthUser.get_by_login(field.data) is None:
raise validators.ValidationError(_('Sorry that username doesn\'t exist.'))
def validate_email(form, field):
@@ -110,7 +115,7 @@ def validate_email(form, field):
def clean(self):
errors = []
- if not self.data.get('username') and not self.data.get('email'):
+ if not self.data.get('login') and not self.data.get('email'):
errors.append(_('You need to specify either a Username or ' \
'Email address'))
return errors
@@ -153,5 +158,25 @@ class WindowsLiveLogin(OAuthForm):
provider_name = 'live'
provider_proper_name = 'Microsoft Live'
+class BitbucketLogin(OAuthForm):
+ provider_name = 'bitbucket'
+ provider_proper_name = 'Bitbucket'
+
+class GithubLogin(OAuthForm):
+ provider_name = 'github'
+ provider_proper_name = 'Github'
+
+class IdenticaLogin(OAuthForm):
+ provider_name = 'identica'
+ provider_proper_name = 'Identi.ca'
+
+class LastfmLogin(OAuthForm):
+ provider_name = 'lastfm'
+ provider_proper_name = 'Last.fm'
+
+class LinkedinLogin(OAuthForm):
+ provider_name = 'linkedin'
+ provider_proper_name = 'LinkedIn'
+
class OpenIDRequiredForm(ExtendedForm):
pass
View
@@ -0,0 +1,60 @@
+import hashlib
+
+from apex.lib.libapex import apex_settings
+
+"""
+This fallback routine attempts to check existing hashes that have failed
+the bcrypt check against md5, hardcoded salt+md5, fieldbased salt+md5,
+sha1, hardcoded salt+sha1, fieldbased salt+md5, and plaintext.
+
+If any of the hash methods match, the user record is updated with the new
+password. You can also write your own GenericFallback class to handle
+any other authentication scheme.
+
+Options set in (development|production).ini:
+
+apex.fallback_prefix_salt = salt to be prepended to password string
+apex.fallback_salt_field = field in user table containing salt
+
+"""
+
+class GenericFallback(object):
+ def check(self, DBSession, request, user, password):
+ salted_passwd = user.password
+ prefix_salt = apex_settings('fallback_prefix_salt', None)
+ if prefix_salt:
+ salted_passwd = '%s%s' % (prefix_salt, salted_passwd)
+ salt_field = apex_settings('fallback_salt_field', None)
+ if salt_field:
+ prefix_salt = getattr(user, salt_field)
+ salted_passwd = '%s%s' % (prefix_salt, salted_passwd)
+
+ if salted_passwd is not None:
+ if len(salted_passwd) == 32:
+ # md5
+ m = hashlib.md5()
+ m.update(password)
+ if m.hexdigest() == salted_passwd:
+ user.password = password
+ DBSession.merge(user)
+ DBSession.flush()
+ return True
+
+ if len(salted_passwd) == 40:
+ # sha1
+ m = hashlib.sha1()
+ m.update(password)
+ if m.hexdigest() == salted_passwd:
+ user.password = password
+ DBSession.merge(user)
+ DBSession.flush()
+ return True
+
+ if salted_passwd == password:
+ # plaintext
+ user.password = password
+ DBSession.merge(user)
+ DBSession.flush()
+ return True
+
+ return False
View
@@ -40,16 +40,6 @@ def __init__(self, formdata=None, obj=None, prefix='', **kwargs):
self.process(formdata, obj, **kwargs)
- def hidden_fields(self):
- """ Returns all the hidden fields.
- """
- return [self._fields[name] for name, field in self._unbound_fields if self._fields[name].type == 'HiddenField']
-
- def visible_fields(self):
- """ Returns all the visible fields.
- """
- return [self._fields[name] for name, field in self._unbound_fields if not self._fields[name].type == 'HiddenField']
-
def _get_translations(self):
if self.request:
localizer = get_localizer(self.request)
Oops, something went wrong.

0 comments on commit a2006c7

Please sign in to comment.