Permalink
Browse files

rough cut to allow additional auth providers to be added to an auth id

  • Loading branch information...
1 parent cff3483 commit 7bda019498d4d8ecf02fd6db441942a254ce1c8e @cd34 committed Feb 14, 2013
Showing with 196 additions and 79 deletions.
  1. +3 −0 CHANGELOG.txt
  2. +26 −21 apex/__init__.py
  3. +48 −10 apex/forms.py
  4. +22 −15 apex/lib/libapex.py
  5. +4 −6 apex/models.py
  6. +2 −0 apex/templates/login_template.mako
  7. +7 −2 apex/tests/test_lib_libapex.py
  8. +68 −22 apex/views.py
  9. +13 −0 docs/source/options.rst
  10. +3 −3 setup.py
View
3 CHANGELOG.txt
@@ -0,0 +1,3 @@
+* apexid_from_token renamed to apex_id_from_token
+* added 'add_auth' function to allow secondary authentication providers
+ on a single auth_id
View
47 apex/__init__.py
@@ -6,32 +6,33 @@
from pyramid.authentication import AuthTktAuthenticationPolicy
from pyramid.authorization import ACLAuthorizationPolicy
-from pyramid.interfaces import IAuthenticationPolicy
-from pyramid.interfaces import IAuthorizationPolicy
-from pyramid.interfaces import ISessionFactory
+from pyramid.interfaces import (IAuthenticationPolicy,
+ IAuthorizationPolicy,
+ ISessionFactory)
from pyramid.security import NO_PERMISSION_REQUIRED
from pyramid.session import UnencryptedCookieSessionFactoryConfig
from pyramid.settings import asbool
-from apex.exceptions import ApexAuthSecret
-from apex.exceptions import ApexSessionSecret
-from apex.interfaces import IApex
-from apex.interfaces import ApexImplementation
-from apex.lib.libapex import groupfinder
-from apex.lib.libapex import RequestFactory
-from apex.lib.libapex import RootFactory
+from apex.exceptions import (ApexAuthSecret,
+ ApexSessionSecret)
+from apex.interfaces import (ApexImplementation,
+ IApex)
+from apex.lib.libapex import (groupfinder,
+ RequestFactory,
+ RootFactory)
from apex.models import initialize_sql
-from apex.views import apex_callback
-from apex.views import activate
-from apex.views import change_password
-from apex.views import edit
-from apex.views import login
-from apex.views import logout
-from apex.views import forgot_password
-from apex.views import forbidden
-from apex.views import openid_required
-from apex.views import register
-from apex.views import reset_password
+from apex.views import (apex_callback,
+ activate,
+ add_auth,
+ change_password,
+ edit,
+ login,
+ logout,
+ forgot_password,
+ forbidden,
+ openid_required,
+ register,
+ reset_password)
"""
Allows flash messages to be called as:
@@ -126,6 +127,10 @@ def includeme(config):
config.add_view(activate, route_name='apex_activate',
renderer=render_template, permission=NO_PERMISSION_REQUIRED)
+ config.add_route('apex_add_auth', '/add_auth')
+ config.add_view(add_auth, route_name='apex_add_auth',
+ renderer=render_template, permission='authenticated')
+
config.add_route('apex_callback', '/apex_callback')
config.add_view(apex_callback, route_name='apex_callback', permission=NO_PERMISSION_REQUIRED)
View
58 apex/forms.py
@@ -1,17 +1,17 @@
-from wtforms import HiddenField
-from wtforms import PasswordField
-from wtforms import TextField
-from wtforms import validators
+from wtforms import (HiddenField,
+ PasswordField,
+ TextField,
+ validators)
from apex import MessageFactory as _
from pyramid.security import authenticated_userid
-from pyramid.threadlocal import get_current_registry
-from pyramid.threadlocal import get_current_request
+from pyramid.threadlocal import (get_current_registry,
+ get_current_request)
-from apex.models import DBSession
-from apex.models import AuthGroup
-from apex.models import AuthID
-from apex.models import AuthUser
+from apex.models import (AuthGroup,
+ AuthID,
+ AuthUser,
+ DBSession)
from apex.lib.form import ExtendedForm
class RegisterForm(ExtendedForm):
@@ -126,6 +126,44 @@ class ResetPasswordForm(ExtendedForm):
message=_('Passwords must match'))])
password2 = PasswordField(_('Repeat New Password'), [validators.Required()])
+class AddAuthForm(ExtendedForm):
+ login = TextField(_('Username'), [validators.Required(), \
+ validators.Length(min=4, max=25)])
+ password = PasswordField(_('Password'), [validators.Required(), \
+ validators.EqualTo('password2', \
+ message=_('Passwords must match'))])
+ password2 = PasswordField(_('Repeat Password'), [validators.Required()])
+ email = TextField(_('Email Address'), [validators.Required(), \
+ validators.Email()])
+
+ 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, auth_id, login):
+ id = DBSession.query(AuthID).filter(AuthID.id==auth_id).one()
+ user = AuthUser(
+ login=login,
+ password=self.data['password'],
+ email=self.data['email'],
+ )
+ id.users.append(user)
+ DBSession.add(user)
+ DBSession.flush()
+
+ return user
+
+ def save(self, auth_id):
+ new_user = self.create_user(auth_id, self.data['login'])
+ self.after_signup(user=new_user)
+
+ def after_signup(self, **kwargs):
+ """ Function to be overloaded and called after form submission
+ to allow you the ability to save additional form data or perform
+ extra actions after the form submission.
+ """
+ pass
+
class OAuthForm(ExtendedForm):
end_point = HiddenField('')
csrf_token = HiddenField('')
View
37 apex/lib/libapex.py
@@ -8,11 +8,11 @@
from pyramid.decorator import reify
from pyramid.httpexceptions import HTTPBadRequest
-from pyramid.security import Allow
-from pyramid.security import authenticated_userid
-from pyramid.security import Everyone
-from pyramid.security import Authenticated
-from pyramid.security import remember
+from pyramid.security import (Allow,
+ Authenticated,
+ authenticated_userid,
+ Everyone,
+ remember)
from pyramid.settings import asbool
from pyramid.request import Request
from pyramid.threadlocal import get_current_registry
@@ -34,11 +34,11 @@
LastfmLogin,
IdenticaLogin,
LinkedinLogin)
-from apex.models import DBSession
-from apex.models import AuthID
-from apex.models import AuthUser
-from apex.models import AuthGroup
-from apex.models import AuthUserLog
+from apex.models import (AuthID,
+ AuthUser,
+ AuthGroup,
+ AuthUserLog,
+ DBSession)
class EmailMessageText(object):
""" Default email message text class
@@ -84,7 +84,7 @@ def activate(self):
"""),
}
-def apexid_from_token(request):
+def apex_id_from_token(request):
""" Returns the apex id from the OpenID Token
"""
dbsession = DBSession()
@@ -176,6 +176,13 @@ def apex_email_activate(request, user_id, email, hmac):
apex_email(request, email, message_text['subject'], message_body)
+def apex_id_providers(auth_id):
+ """ return a list of the providers that are currently active for
+ this auth_id
+ """
+ return [x[0] for x in DBSession.query(AuthUser.provider). \
+ filter(AuthUser.auth_id==auth_id).all()]
+
def apex_settings(key=None, default=None):
""" Gets an apex setting if the key is set.
If no key it set, returns all the apex settings.
@@ -240,14 +247,14 @@ def create_user(**kwargs):
DBSession.flush()
return user
-def generate_velruse_forms(request, came_from):
- """ Generates variable form based on OpenID providers supported in
- the CONFIG.yaml file
+def generate_velruse_forms(request, came_from, exclude=None):
+ """ Generates variable form based on OpenID providers
"""
velruse_forms = []
providers = apex_settings('velruse_providers', None)
if providers:
- providers = [x.strip() for x in providers.split(',')]
+ providers = list(set([x.strip() for x in providers.split(',')]) - \
+ exclude)
for provider in providers:
if provider_forms.has_key(provider):
form = provider_forms[provider](
View
10 apex/models.py
@@ -16,14 +16,12 @@
Unicode)
from sqlalchemy.exc import IntegrityError
from sqlalchemy.ext.declarative import declarative_base
-from sqlalchemy.orm import relationship
-from sqlalchemy.orm import scoped_session
-from sqlalchemy.orm import sessionmaker
-from sqlalchemy.orm import synonym
+from sqlalchemy.orm import (relationship,
+ scoped_session,
+ sessionmaker,
+ synonym)
from sqlalchemy.sql.expression import func
-#from velruse.store.sqlstore import SQLBase
-
from zope.sqlalchemy import ZopeTransactionExtension
from apex.lib.db import get_or_create
View
2 apex/templates/login_template.mako
@@ -1,3 +1,4 @@
+% if action != 'add_auth':
% if action != 'login':
<a href="${request.route_path('apex_login')}">Login</a>
% endif
@@ -7,3 +8,4 @@
% if action != 'forgot':
<a href="${request.route_path('apex_forgot')}">Forgot my Password</a>
% endif
+% endif
View
9 apex/tests/test_lib_libapex.py
@@ -9,8 +9,8 @@ def setUp(self):
#def tearDown(self):
# testing.tearDown()
- def test_apexid_from_token(self):
- # apexid_from_token(request)
+ def test_apex_id_from_token(self):
+ # apex_id_from_token(request)
pass
def test_groupfinder(self):
@@ -31,13 +31,18 @@ def test_apex_email_activate(self):
def test_apex_settings(self):
# apex_settings(key=None, default=None)
+ # settings not being set in registry
from apex.lib.libapex import apex_settings
self.assertEqual([], apex_settings(key=None, default=None))
+ """
+ depends on registry which isn't being passed
+
self.assertEqual('session_secret', \
apex_settings(key='session_secret', default=None))
self.assertEqual('home', apex_settings(key='came_from_route', \
default=None))
+ """
self.assertEqual(None, apex_settings(key='no_match', default=None))
def test_create_user(self):
View
90 apex/views.py
@@ -2,39 +2,40 @@
import hmac
import time
-from wtforms import TextField
-from wtforms import validators
+from wtforms import (TextField,
+ validators)
from wtforms.ext.sqlalchemy.orm import model_form
from wtfrecaptcha.fields import RecaptchaField
from pyramid.httpexceptions import HTTPFound
from pyramid.response import Response
-from pyramid.security import authenticated_userid
-from pyramid.security import forget
+from pyramid.security import (authenticated_userid,
+ forget)
from pyramid.settings import asbool
-from pyramid.url import current_route_url
-from pyramid.url import route_url
+from pyramid.url import (current_route_url,
+ route_url)
from apex import MessageFactory as _
from apex.lib.db import merge_session_with_post
from apex.lib.libapex import (apex_email_forgot,
- apexid_from_token,
+ apex_id_from_token,
+ apex_id_providers,
apex_remember,
apex_settings,
generate_velruse_forms,
get_came_from,
get_module)
from apex.lib.flash import flash
from apex.lib.form import ExtendedForm
-from apex.models import AuthGroup
-from apex.models import AuthID
-from apex.models import AuthUser
-from apex.models import DBSession
-from apex.forms import ChangePasswordForm
-from apex.forms import ForgotForm
-from apex.forms import ResetPasswordForm
-from apex.forms import LoginForm
+from apex.models import (AuthGroup,
+ AuthID,
+ AuthUser,
+ DBSession)
+from apex.forms import (ChangePasswordForm,
+ ForgotForm,
+ ResetPasswordForm,
+ LoginForm)
def login(request):
@@ -47,13 +48,15 @@ def login(request):
came_from = get_came_from(request)
if not apex_settings('exclude_local'):
if asbool(apex_settings('use_recaptcha_on_login')):
- if apex_settings('recaptcha_public_key') and apex_settings('recaptcha_private_key'):
+ if apex_settings('recaptcha_public_key') and \
+ apex_settings('recaptcha_private_key'):
LoginForm.captcha = RecaptchaField(
public_key=apex_settings('recaptcha_public_key'),
private_key=apex_settings('recaptcha_private_key'),
)
form = LoginForm(request.POST,
- captcha={'ip_address': request.environ['REMOTE_ADDR']})
+ captcha={'ip_address': \
+ request.environ['REMOTE_ADDR']})
else:
form = LoginForm(request.POST)
else:
@@ -111,7 +114,8 @@ def forgot_password(request):
title = _('Forgot my password')
if asbool(apex_settings('use_recaptcha_on_forgot')):
- if apex_settings('recaptcha_public_key') and apex_settings('recaptcha_private_key'):
+ if apex_settings('recaptcha_public_key') and \
+ apex_settings('recaptcha_private_key'):
ForgotForm.captcha = RecaptchaField(
public_key=apex_settings('recaptcha_public_key'),
private_key=apex_settings('recaptcha_private_key'),
@@ -154,7 +158,8 @@ def reset_password(request):
title = _('Reset My Password')
if asbool(apex_settings('use_recaptcha_on_reset')):
- if apex_settings('recaptcha_public_key') and apex_settings('recaptcha_private_key'):
+ if apex_settings('recaptcha_public_key') and \
+ apex_settings('recaptcha_private_key'):
ResetPasswordForm.captcha = RecaptchaField(
public_key=apex_settings('recaptcha_public_key'),
private_key=apex_settings('recaptcha_private_key'),
@@ -225,13 +230,15 @@ def register(request):
if not apex_settings('exclude_local'):
if asbool(apex_settings('use_recaptcha_on_register')):
- if apex_settings('recaptcha_public_key') and apex_settings('recaptcha_private_key'):
+ if apex_settings('recaptcha_public_key') and \
+ apex_settings('recaptcha_private_key'):
RegisterForm.captcha = RecaptchaField(
public_key=apex_settings('recaptcha_public_key'),
private_key=apex_settings('recaptcha_private_key'),
)
- form = RegisterForm(request.POST, captcha={'ip_address': request.environ['REMOTE_ADDR']})
+ form = RegisterForm(request.POST, captcha={'ip_address': \
+ request.environ['REMOTE_ADDR']})
else:
form = None
@@ -244,6 +251,45 @@ def register(request):
return {'title': title, 'form': form, 'velruse_forms': velruse_forms, \
'action': 'register'}
+def add_auth(request):
+ title = _('Add another Authentication method')
+ came_from = request.params.get('came_from', \
+ route_url(apex_settings('came_from_route'), request))
+ auth_providers = apex_id_providers(authenticated_userid(request))
+ exclude = set([])
+ if not apex_settings('allow_duplicate_providers'):
+ exclude = set(auth_providers)
+
+ velruse_forms = generate_velruse_forms(request, came_from, exclude)
+
+ #This fixes the issue with RegisterForm throwing an UnboundLocalError
+ if apex_settings('auth_form_class'):
+ AddAuthForm = get_module(apex_settings('auth_form_class'))
+ else:
+ from apex.forms import AddAuthForm
+
+ if not apex_settings('exclude_local') and 'local' not in exclude:
+ if not asbool(apex_settings('use_recaptcha_on_auth')):
+ if apex_settings('recaptcha_public_key') and \
+ apex_settings('recaptcha_private_key'):
+ AddAuthForm.captcha = RecaptchaField(
+ public_key=apex_settings('recaptcha_public_key'),
+ private_key=apex_settings('recaptcha_private_key'),
+ )
+
+ form = AddAuthForm(request.POST, captcha={'ip_address': \
+ request.environ['REMOTE_ADDR']})
+ else:
+ form = None
+
+ if request.method == 'POST' and form.validate():
+ form.save(authenticated_userid(request))
+
+ return HTTPFound(location=came_from)
+
+ return {'title': title, 'form': form, 'velruse_forms': velruse_forms, \
+ 'action': 'add_auth'}
+
def apex_callback(request):
""" apex_callback(request):
no return value, called with route_url('apex_callback', request)
@@ -254,7 +300,7 @@ def apex_callback(request):
route_url(apex_settings('came_from_route'), request))
headers = []
if 'token' in request.POST:
- auth = apexid_from_token(request)
+ auth = apex_id_from_token(request)
if auth:
user = AuthUser.get_by_login(auth['id'])
if not user:
View
13 docs/source/options.rst
@@ -31,6 +31,9 @@ apex.use_recaptcha_on_reset = false
apex.use_recaptcha_on_register = true
OPTIONAL, Display Recaptcha form on Registration Page
+apex.use_recaptcha_on_auth = false
+ OPTIONAL, Display Recaptcha form on Add Additional Authentication method Page
+
apex.exclude_local = false
OPTIONAL, disable local authentication
@@ -48,6 +51,10 @@ apex.register_form_class = project.models.form_name
OPTIONAL, requires DOTTED notation, specifies overloaded form for
registration
+apex.auth_form_class = project.models.form_name
+ OPTIONAL, requires DOTTED notation, specifies overloaded form for
+ adding additional authentication methods
+
apex.default_user_group =
OPTIONAL, If defined, will add the user to this group when created. If
undefined, users will not be assigned to a group and you'll only have the
@@ -86,6 +93,12 @@ apex.no_csrf =
OPTIONAL, a comma separated list of route names that should NOT be subject
to CSRF tests.
+apex.allow_duplicate_providers = false
+ OPTIONAL, when allowing one to merge additional authentication providers,
+ do you want to allow an ID to have two Facebook accounts, or, two local
+ authentication accounts. Or, should apex disallow the addition of a
+ duplicate authentication provider.
+
**Email Settings**
Email Messages that Apex sends can be customized. The following replacements
View
6 setup.py
@@ -22,17 +22,17 @@
here = os.path.abspath(os.path.dirname(__file__))
try:
README = open(os.path.join(here, 'README.txt')).read()
- CHANGES = open(os.path.join(here, 'CHANGES.txt')).read()
+ CHANGELOG = open(os.path.join(here, 'CHANGELOG.txt')).read()
except IOError:
- README = CHANGES = ''
+ README = CHANGELOG = ''
kwargs = dict(
version=version,
name='apex',
description="""\
Pyramid toolkit to add Velruse, Flash Messages,\
CSRF, ReCaptcha and Sessions""",
- long_description=README + '\n\n' + CHANGES,
+ long_description=README + '\n\n' + CHANGELOG,
classifiers=[
"Intended Audience :: Developers",
"Programming Language :: Python",

0 comments on commit 7bda019

Please sign in to comment.