add initial auth framework

This commit is contained in:
Lance Edgar 2012-04-16 20:04:48 -05:00
parent 4808857f40
commit df9524a6ac
12 changed files with 280 additions and 202 deletions

View file

@ -74,7 +74,7 @@ def init(config):
from edbob.db import enum
# from edbob.db.model import get_metadata
# from edbob.db.mappers import make_mappers
# from edbob.db.extensions import extend_framework
from edbob.db.extensions import extend_framework
# global inited, engines, engine, metadata
global inited, engines, engine
@ -103,7 +103,7 @@ def init(config):
# metadata = get_metadata(bind=engine)
# make_mappers(metadata)
# extend_framework()
extend_framework()
edbob.graft(edbob, edbob.db)
# edbob.graft(edbob, classes)

View file

@ -32,7 +32,6 @@ from sqlalchemy.orm import object_session
import edbob
from edbob.db import needs_session
from edbob.db.classes import Permission, Role, User
class BcryptAuthenticator(edbob.Object):
@ -55,13 +54,13 @@ def authenticate_user(session, username, password):
returns the :class:`edbob.User` instance; otherwise returns ``None``.
"""
user = session.query(User).filter_by(username=username).first()
if not user:
return None
auth = BcryptAuthenticator()
if not auth.authenticate_user(user, password):
return None
return user
q = session.query(edbob.User)
q = q.filter(edbob.User.username == username)
user = q.first()
if user:
auth = BcryptAuthenticator()
if auth.authenticate_user(user, password):
return user
def administrator_role(session):
@ -103,3 +102,26 @@ def has_permission(obj, perm):
if permission == perm:
return True
return False
def init_database(engine, session):
"""
Initialize the auth system within an ``edbob`` database.
"""
# Create 'admin' user with full rights.
admin = edbob.User()
admin.username = 'admin'
set_user_password(admin, 'admin')
# admin.roles.append(administrator_role(session))
session.add(admin)
session.flush()
def set_user_password(user, password):
"""
Sets the password for the given :class:`edbob.User` instance.
"""
auth = BcryptAuthenticator()
auth.populate_user(user, password)

View file

@ -93,36 +93,43 @@ class Extension(edbob.Object):
# """
# pass
def add_class(self, cls):
# def add_class(self, cls):
# """
# Convenience method for use in :meth:`extend_classes()`.
# """
# from edbob.db import classes
# name = cls.__name__
# edbob.graft(classes, {name:cls}, name)
# def extend_classes(self):
# """
# Any extra classes provided by the extension should be added to the ORM
# whenever this method is called.
# Note that the :meth:`add_class()` convenience method is designed to be
# used when adding classes.
# """
# pass
def extend_framework(self):
"""
Convenience method for use in :meth:`extend_classes()`.
Extends the framework...
"""
from edbob.db import classes
edbob.graft(edbob, self.get_model_module())
name = cls.__name__
edbob.graft(classes, {name:cls}, name)
# def extend_mappers(self, metadata):
# """
# All SQLAlchemy mapping to be done by the extension should be done
# within this method.
def extend_classes(self):
"""
Any extra classes provided by the extension should be added to the ORM
whenever this method is called.
Note that the :meth:`add_class()` convenience method is designed to be
used when adding classes.
"""
pass
def extend_mappers(self, metadata):
"""
All SQLAlchemy mapping to be done by the extension should be done
within this method.
Any extra classes the extension provides will typically be mapped here.
Any manipulation the extension needs to perform on the ``edbob`` core
ORM should be done here as well.
"""
pass
# Any extra classes the extension provides will typically be mapped here.
# Any manipulation the extension needs to perform on the ``edbob`` core
# ORM should be done here as well.
# """
# pass
def get_metadata(self, recurse=False):
"""
@ -173,25 +180,25 @@ class Extension(edbob.Object):
if isinstance(obj, type) and issubclass(obj, model.Base):
obj.__table__.tometadata(metadata)
def remove_class(self, name):
"""
Convenience method for use in :meth:`restore_classes()`.
"""
# def remove_class(self, name):
# """
# Convenience method for use in :meth:`restore_classes()`.
# """
from edbob.db import classes
# from edbob.db import classes
if name in classes.__all__:
classes.__all__.remove(name)
if hasattr(classes, name):
del classes.__dict__[name]
# if name in classes.__all__:
# classes.__all__.remove(name)
# if hasattr(classes, name):
# del classes.__dict__[name]
def restore_classes(self):
"""
This method should remove any extra classes which were added within
:meth:`extend_classes()`. Note that there is a :meth:`remove_class()`
method for convenience in doing so.
"""
pass
# def restore_classes(self):
# """
# This method should remove any extra classes which were added within
# :meth:`extend_classes()`. Note that there is a :meth:`remove_class()`
# method for convenience in doing so.
# """
# pass
def activate_extension(extension, engine=None):
@ -233,6 +240,7 @@ def activate_extension(extension, engine=None):
# merge_extension_metadata(extension)
# extension.extend_classes()
# extension.extend_mappers(Base.metadata)
extension.extend_framework()
# Add extension to in-memory active extensions tracker.
active_extensions(engine).append(extension.name)
@ -318,23 +326,24 @@ def extend_framework():
session = Session()
try:
active_extensions = session.query(ActiveExtension).all()
active = session.query(ActiveExtension).all()
except sqlalchemy.exc.ProgrammingError:
session.close()
return
extensions = {}
for ext in active_extensions:
for ext in active:
extensions[ext.name] = get_extension(ext.name)
session.close()
for name in sorted(extensions, extension_sorter(extensions)):
ext = extensions[name]
log.info("Applying active extension: %s" % name)
merge_extension_metadata(ext)
ext.extend_classes()
ext.extend_mappers(rattail.metadata)
active_extensions[name] = ext
ext = extensions[name]
# merge_extension_metadata(ext)
# ext.extend_classes()
# ext.extend_mappers(rattail.metadata)
ext.extend_framework()
_active_extensions[name] = ext
def extension_active(extension, engine=None):

View file

@ -26,10 +26,11 @@
``edbob.db.extensions.auth`` -- 'auth' Extension
"""
from sqlalchemy import MetaData
from edbob.db.extensions import Extension
from edbob.db.extensions.auth.model import *
from edbob.db.extensions.auth.model import __all__
class AuthExtension(Extension):

View file

@ -28,9 +28,10 @@
from sqlalchemy import *
from sqlalchemy.orm import relationship
from sqlalchemy.ext.associationproxy import association_proxy
import edbob
from edbob.db.model import Base
from edbob.db.model import Base, uuid_column
__all__ = ['Person', 'User']
@ -52,7 +53,7 @@ class Person(Base):
__tablename__ = 'people'
uuid = Column(String(32), primary_key=True, default=edbob.get_uuid)
uuid = uuid_column()
first_name = Column(String(50))
last_name = Column(String(50))
display_name = Column(String(100), default=get_person_display_name)
@ -72,11 +73,14 @@ class User(Base):
__tablename__ = 'users'
uuid = Column(String(32), primary_key=True, default=edbob.get_uuid)
uuid = uuid_column()
username = Column(String(25), nullable=False, unique=True)
password = Column(String(60))
salt = Column(String(29))
person_uuid = Column(String(32), ForeignKey('people.uuid'))
person = relationship(Person, backref='user')
# display_name = association_proxy('person', 'display_name')
# roles = association_proxy('_roles', 'role',
# creator=lambda x: UserRole(role=x),
@ -87,3 +91,13 @@ class User(Base):
def __str__(self):
return str(self.username or '')
@property
def display_name(self):
"""
Returns the user's ``person.display_name``, if present, otherwise the
``username``.
"""
if self.person and self.person.display_name:
return self.person.display_name
return self.username

View file

@ -77,7 +77,7 @@ table.wrapper td.right {
}
#login {
margin: 8px auto auto 20px;
margin: 8px 20px auto auto;
}
#user-menu {

View file

@ -32,6 +32,7 @@ from pyramid.security import authenticated_userid
import edbob
from edbob.db.auth import has_permission
from edbob.pyramid import helpers
from edbob.pyramid import Session
def before_render(event):
@ -62,9 +63,9 @@ def context_found(event):
def has_perm_func(request):
def has_perm(perm):
if not request.current_user:
if not request.user:
return False
return has_permission(request.current_user, perm)
return has_permission(request.user, perm)
return has_perm
request = event.request
@ -73,7 +74,7 @@ def context_found(event):
uuid = authenticated_userid(request)
if uuid:
request.user = get_session().query(rattail.User).get(uuid)
request.user = Session.query(edbob.User).get(uuid)
def includeme(config):

View file

@ -30,13 +30,12 @@
<div id="header">
${self.home_link()}
<h1 class="left">${self.title()}</h1>
<div id="login" class="left">
## <% user = request.current_user %>
% if user:
logged in as <strong>${user.display_name}</strong>
<div id="login" class="right">
% if request.user:
logged in as <strong>${request.user.display_name}</strong>
(${h.link_to("logout", url('logout'))})
% else:
## ${h.link_to("login", url('login'))}
${h.link_to("login", url('login'))}
% endif
</div>
</div><!-- header -->

View file

@ -0,0 +1,64 @@
<%inherit file="/base.mako" />
<%def name="title()">Login</%def>
<%def name="head_tags()">
${h.stylesheet_link(request.static_url('edbob.pyramid:static/css/login.css'))}
</%def>
${h.image(request.static_url('edbob.pyramid:static/img/logo.jpg'), "edbob logo")}
<div class="fieldset">
${h.form('')}
## <input type="hidden" name="login" value="True" />
<input type="hidden" name="referer" value="${referer}" />
% if error:
<div class="error">${error}</div>
% endif
<div class="field-couple">
<label for="username">Username:</label>
<input type="text" name="username" id="username" value="" />
</div>
<div class="field-couple">
<label for="password">Password:</label>
<input type="password" name="password" id="password" value="" />
</div>
<div class="buttons">
${h.submit('submit', "Login")}
<input type="reset" value="Reset" />
</div>
${h.end_form()}
</div>
<script language="javascript" type="text/javascript">
$(function() {
$('form').submit(function() {
if (! $('#username').val()) {
with ($('#username').get(0)) {
select();
focus();
}
return false;
}
if (! $('#password').val()) {
with ($('#password').get(0)) {
select();
focus();
}
return false;
}
return true;
});
$('#username').focus();
});
</script>

View file

@ -1,64 +1,2 @@
<%inherit file="base.mako" />
<%def name="title()">Login</%def>
<%def name="head_tags()">
${h.stylesheet_link('edbob/css/login.css')}
</%def>
${h.image('edbob/img/logo.jpg', "edbob logo")}
<div class="fieldset">
${h.form('')}
## <input type="hidden" name="login" value="True" />
## <input type="hidden" name="referrer" value="${referrer}" />
% if error:
<div class="error">${error}</div>
% endif
<div class="field-couple">
<label for="username">Username:</label>
<input type="text" name="username" id="username" value="" />
</div>
<div class="field-couple">
<label for="password">Password:</label>
<input type="password" name="password" id="password" value="" />
</div>
<div class="buttons">
${h.submit('submit', "Login")}
<input type="reset" value="Reset" />
</div>
${h.end_form()}
</div>
<script language="javascript" type="text/javascript">
$(function() {
$('form').submit(function() {
if (! $('#username').val()) {
with ($('#username').get(0)) {
select();
focus();
}
return false;
}
if (! $('#password').val()) {
with ($('#password').get(0)) {
select();
focus();
}
return false;
}
return true;
});
$('#username').focus();
});
</script>
<%inherit file="/edbob/login.mako" />
${parent.body()}

View file

@ -26,71 +26,6 @@
``edbob.pyramid.views`` -- Views
"""
import os
import os.path
from pyramid.response import Response
from pyramid.view import view_config
from pyramid.httpexceptions import HTTPFound
from edbob.db.auth import authenticate_user
_here = os.path.join(os.path.dirname(__file__), os.pardir)
# _favicon = open(os.path.join(_here, 'static', 'favicon.ico'), 'rb').read()
# _favicon_response = Response(content_type='image/x-icon', body=_favicon)
# @view_config(route_name='favicon.ico')
# def favicon_ico(context, request):
# return _favicon_response
# @view_config(route_name='home', renderer='/home.mako')
# def home(context, request):
# return {}
@view_config(route_name='login', renderer='login.mako')
def login(context, request):
"""
The login view, responsible for displaying and handling the login form.
"""
if request.params.get('referer'):
referer = request.params['referer']
elif request.session.get('referer'):
referer = request.session.pop('referer')
else:
referer = request.referer or request.route_url('home')
# if request.current_user:
# return HTTPFound(location=referer)
# form = Form(self.request, schema=UserLogin)
# if form.validate():
# user = authenticate_user(self.Session(), form.data['username'], form.data['password'])
# if user:
# self.request.session.flash("%s logged in at %s" % (
# user.display_name,
# datetime.datetime.now().strftime("%I:%M %p")))
# headers = remember(self.request, user.uuid)
# return HTTPFound(location=referer, headers=headers)
# self.request.session.flash("Invalid username or password.")
# return {'form':FormRenderer(form), 'referer':referer}
return {}
# _robots = open(os.path.join(_here, 'static', 'robots.txt')).read()
# _robots_response = Response(content_type='text/plain', body=_robots)
# @view_config(route_name='robots.txt')
# def robots_txt(context, request):
# return _robots_response
def includeme(config):
# config.add_route('home', '/')
# config.add_route('favicon.ico', '/favicon.ico')
# config.add_route('robots.txt', '/robots.txt')
config.add_route('login', '/login')
config.scan()
config.include('edbob.pyramid.views.auth')

View file

@ -0,0 +1,95 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
################################################################################
#
# edbob -- Pythonic Software Framework
# Copyright © 2010-2012 Lance Edgar
#
# This file is part of edbob.
#
# edbob 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.
#
# edbob 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 edbob. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
``edbob.pyramid.views.auth`` -- Auth Views
"""
import formencode
from pyramid.view import view_config
from pyramid.httpexceptions import HTTPFound
from pyramid.security import remember, forget
from pyramid_simpleform import Form
from pyramid_simpleform.renderers import FormRenderer
import edbob
from edbob.db.auth import authenticate_user
from edbob.pyramid import Session
class UserLogin(formencode.Schema):
allow_extra_fields = True
filter_extra_fields = True
username = formencode.validators.NotEmpty()
password = formencode.validators.NotEmpty()
@view_config(route_name='login', renderer='/login.mako')
def login(context, request):
"""
The login view, responsible for displaying and handling the login form.
"""
if request.params.get('referer'):
referer = request.params['referer']
elif request.session.get('referer'):
referer = request.session.pop('referer')
else:
referer = request.referer or request.route_url('home')
if referer == request.route_url('login'):
referer = request.route_url('home')
# Redirect if already logged in.
if request.user:
return HTTPFound(location=referer)
form = Form(request, schema=UserLogin)
if form.validate():
user = authenticate_user(form.data['username'],
form.data['password'],
session=Session())
if user:
request.session.flash("%s logged in at %s" % (
user.display_name,
edbob.local_time().strftime('%I:%M %p')))
headers = remember(request, user.uuid)
return HTTPFound(location=referer, headers=headers)
request.session.flash("Invalid username or password")
return {'form': FormRenderer(form), 'referer': referer}
@view_config(route_name='logout')
def logout(context, request):
request.session.delete()
request.session.flash("%s logged out at %s" % (
request.user.display_name,
edbob.local_time().strftime("%I:%M %p")))
headers = forget(request)
return HTTPFound(location=request.route_url('login'), headers=headers)
def includeme(config):
config.add_route('login', '/login')
config.add_route('logout', '/logout')
config.scan()