From 61467d3f78bc332dda9642d7f04a94dffadd8b3d Mon Sep 17 00:00:00 2001 From: Eevee Date: Sun, 8 Aug 2010 10:54:58 -0700 Subject: [PATCH] Added very basic permission system. #71 --- migration/versions/003_Add_permission_tables.py | 43 +++++++++++++ splinext/users/__init__.py | 18 +++++- splinext/users/controllers/admin.py | 21 +++++++ splinext/users/deployment.ini_tmpl | 4 ++ splinext/users/model/__init__.py | 73 +++++++++++++++++++--- .../users/templates/users/admin/permissions.mako | 27 ++++++++ 6 files changed, 177 insertions(+), 9 deletions(-) create mode 100644 migration/versions/003_Add_permission_tables.py create mode 100644 splinext/users/controllers/admin.py create mode 100644 splinext/users/deployment.ini_tmpl create mode 100644 splinext/users/templates/users/admin/permissions.mako diff --git a/migration/versions/003_Add_permission_tables.py b/migration/versions/003_Add_permission_tables.py new file mode 100644 index 0000000..7245753 --- /dev/null +++ b/migration/versions/003_Add_permission_tables.py @@ -0,0 +1,43 @@ +from sqlalchemy import * +from migrate import * +import migrate.changeset # monkeypatches Column + +from sqlalchemy import orm +from sqlalchemy.ext.declarative import declarative_base +TableBase = declarative_base() + + +class User(TableBase): + __tablename__ = 'users' + id = Column(Integer, primary_key=True) + + +class Role(TableBase): + __tablename__ = 'roles' + id = Column(Integer, primary_key=True, nullable=False) + name = Column(Unicode(64), nullable=False) + icon = Column(Unicode(64), nullable=False) + +class UserRole(TableBase): + __tablename__ = 'user_roles' + user_id = Column(Integer, ForeignKey('users.id'), primary_key=True, nullable=False, autoincrement=False) + role_id = Column(Integer, ForeignKey('roles.id'), primary_key=True, nullable=False, autoincrement=False) + +class RolePermission(TableBase): + __tablename__ = 'role_permissions' + id = Column(Integer, nullable=False, primary_key=True) + role_id = Column(Integer, ForeignKey('roles.id'), nullable=True) + permission = Column(Unicode(64), nullable=False) + + +def upgrade(migrate_engine): + TableBase.metadata.bind = migrate_engine + Role.__table__.create() + UserRole.__table__.create() + RolePermission.__table__.create() + +def downgrade(migrate_engine): + TableBase.metadata.bind = migrate_engine + RolePermission.__table__.drop() + UserRole.__table__.drop() + Role.__table__.drop() diff --git a/splinext/users/__init__.py b/splinext/users/__init__.py index 0e6f44f..f01995d 100644 --- a/splinext/users/__init__.py +++ b/splinext/users/__init__.py @@ -1,12 +1,12 @@ from pkg_resources import resource_filename -from pylons import c, session +from pylons import c, config, session -from spline.lib.plugin import PluginBase from spline.lib.plugin import PluginBase, PluginLink, Priority import spline.model.meta as meta import splinext.users.controllers.accounts +import splinext.users.controllers.admin import splinext.users.controllers.users from splinext.users import model as users_model @@ -36,6 +36,18 @@ def add_routes_hook(map, *args, **kwargs): map.connect('/users/{id}', controller='users', action='profile', conditions=dict(function=id_is_numeric)) + # Big-boy admin + map.connect('/admin/users/permissions', controller='admin_users', action='permissions') + +def monkeypatch_user_hook(*args, **kwargs): + """Hook to tell the `User` model who the root user is.""" + try: + users_model.User._root_user_id \ + = int(config['spline-users.root_user_id']) + except KeyError: + # No config set; oh well! + pass + def check_userid_hook(action, **params): """Hook to see if a user is logged in and, if so, stick a user object in c. @@ -61,6 +73,7 @@ class UsersPlugin(PluginBase): def controllers(self): return dict( accounts = splinext.users.controllers.accounts.AccountsController, + admin_users = splinext.users.controllers.admin.AdminController, users = splinext.users.controllers.users.UsersController, ) @@ -72,6 +85,7 @@ class UsersPlugin(PluginBase): def hooks(self): return [ ('routes_mapping', Priority.NORMAL, add_routes_hook), + ('after_setup', Priority.NORMAL, monkeypatch_user_hook), ('before_controller', Priority.VERY_FIRST, check_userid_hook), ] diff --git a/splinext/users/controllers/admin.py b/splinext/users/controllers/admin.py new file mode 100644 index 0000000..4497774 --- /dev/null +++ b/splinext/users/controllers/admin.py @@ -0,0 +1,21 @@ +import logging + +from pylons import config, request, response, session, tmpl_context as c, url +from pylons.controllers.util import abort, redirect_to + +from spline.model import meta +from spline.lib.base import BaseController, render +from splinext.users import model as users_model + +log = logging.getLogger(__name__) + + +class AdminController(BaseController): + + def permissions(self): + if not c.user.can('administrate'): + abort(403) + + c.roles = meta.Session.query(users_model.Role) \ + .order_by(users_model.Role.id.asc()).all() + return render('/users/admin/permissions.mako') diff --git a/splinext/users/deployment.ini_tmpl b/splinext/users/deployment.ini_tmpl new file mode 100644 index 0000000..495e5fe --- /dev/null +++ b/splinext/users/deployment.ini_tmpl @@ -0,0 +1,4 @@ +## encoding: utf8 +#-- Users plugin +# id of the root user, who can automatically do anything +#spline-users.root_user_id = 0 diff --git a/splinext/users/model/__init__.py b/splinext/users/model/__init__.py index d6c0cb5..4ad49aa 100644 --- a/splinext/users/model/__init__.py +++ b/splinext/users/model/__init__.py @@ -3,8 +3,10 @@ import colorsys from math import sin, pi import random -from sqlalchemy import Column, ForeignKey +from sqlalchemy import Column, ForeignKey, or_ +from sqlalchemy.ext.associationproxy import association_proxy from sqlalchemy.orm import relation +from sqlalchemy.orm.session import Session from sqlalchemy.types import Integer, Unicode from spline.model.meta import TableBase @@ -21,7 +23,7 @@ class AnonymousUser(object): return False def can(self, action): - # XXX if viewing is ever a permission, this should probably change. + """Anonymous users aren't ever allowed to do anything.""" return False @@ -40,9 +42,31 @@ class User(TableBase): super(User, self).__init__(*args, **kwargs) + _root_user_id = None + _default_permissions = None def can(self, action): - # XXX this is probably not desired. - return True + """Returns True iff this user has permission to do `action`. + + If `_root_user_id` is this user's id, all permissions are allowed. The + property is usually set by the spline-users after_setup hook. + """ + + if self.id == self._root_user_id: + return True + + if action in self.permissions: + return True + + # Permissions assigned to NULL apply to all roles + if self._default_permissions is None: + session = Session.object_session(self) + self._default_permissions = [ + row.permission + for row in session.query(RolePermission) + .filter_by(role_id=None) + ] + + return (action in self._default_permissions) @property def unique_colors(self): @@ -108,9 +132,44 @@ class User(TableBase): return ret - class OpenID(TableBase): __tablename__ = 'openid' openid = Column(Unicode(length=255), primary_key=True) - user_id = Column(Integer, ForeignKey('users.id')) - user = relation(User, lazy=False, backref='openids') + user_id = Column(Integer, ForeignKey('users.id'), nullable=False) + + +# Permissions stuff +class Role(TableBase): + __tablename__ = 'roles' + id = Column(Integer, primary_key=True, nullable=False) + name = Column(Unicode(64), nullable=False) + icon = Column(Unicode(64), nullable=False) + +class UserRole(TableBase): + __tablename__ = 'user_roles' + user_id = Column(Integer, ForeignKey('users.id'), primary_key=True, nullable=False, autoincrement=False) + role_id = Column(Integer, ForeignKey('roles.id'), primary_key=True, nullable=False, autoincrement=False) + +class RolePermission(TableBase): + __tablename__ = 'role_permissions' + id = Column(Integer, nullable=False, primary_key=True) + role_id = Column(Integer, ForeignKey('roles.id'), nullable=True) + permission = Column(Unicode(64), nullable=False) + + +### Relations +OpenID.user = relation(User, lazy=False, backref='openids') + +Role.role_permissions = relation(RolePermission, backref='role') + +User.roles = relation(Role, secondary=UserRole.__table__, backref='users') +User.role_permissions = relation(RolePermission, + primaryjoin=User.id==UserRole.user_id, + secondary=UserRole.__table__, + secondaryjoin=UserRole.role_id==RolePermission.role_id, + foreign_keys=[UserRole.user_id, RolePermission.role_id], +) +User.permissions = association_proxy('role_permissions', 'permission') + +UserRole.user = relation(User) +UserRole.role = relation(Role) diff --git a/splinext/users/templates/users/admin/permissions.mako b/splinext/users/templates/users/admin/permissions.mako new file mode 100644 index 0000000..fb47661 --- /dev/null +++ b/splinext/users/templates/users/admin/permissions.mako @@ -0,0 +1,27 @@ +<%inherit file="/base.mako" /> + +<%def name="title()">Permissions - User admin + +<%def name="title_in_page()"> + + + +

Roles

+ + -- 2.7.4