Added very basic permission system. #71 veekun-promotions/2010080801 veekun-promotions/2010080802
authorEevee <git@veekun.com>
Sun, 8 Aug 2010 17:54:58 +0000 (10:54 -0700)
committerEevee <git@veekun.com>
Sun, 8 Aug 2010 17:54:58 +0000 (10:54 -0700)
migration/versions/003_Add_permission_tables.py [new file with mode: 0644]
splinext/users/__init__.py
splinext/users/controllers/admin.py [new file with mode: 0644]
splinext/users/deployment.ini_tmpl [new file with mode: 0644]
splinext/users/model/__init__.py
splinext/users/templates/users/admin/permissions.mako [new file with mode: 0644]

diff --git a/migration/versions/003_Add_permission_tables.py b/migration/versions/003_Add_permission_tables.py
new file mode 100644 (file)
index 0000000..7245753
--- /dev/null
@@ -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()
index 0e6f44f..f01995d 100644 (file)
@@ -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 (file)
index 0000000..4497774
--- /dev/null
@@ -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 (file)
index 0000000..495e5fe
--- /dev/null
@@ -0,0 +1,4 @@
+## encoding: utf8
+#-- Users plugin
+# id of the root user, who can automatically do anything
+#spline-users.root_user_id = 0
index d6c0cb5..4ad49aa 100644 (file)
@@ -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 (file)
index 0000000..fb47661
--- /dev/null
@@ -0,0 +1,27 @@
+<%inherit file="/base.mako" />
+
+<%def name="title()">Permissions - User admin</%def>
+
+<%def name="title_in_page()">
+<ul id="breadcrumbs">
+    <li><a href="${url(controller='users', action='list')}">Users</a></li>
+    <li>Permissions admin</li>
+</ul>
+</%def>
+
+<h1>Roles</h1>
+
+<ul class="classic-list">
+    % for role in c.roles:
+    <li>
+        <img src="${h.static_uri('spline', "icons/{0}.png".format(role.icon))}" alt="">
+        ${role.name}
+
+        <ul class="classic-list">
+            % for role_permission in role.role_permissions:
+            <li>${role_permission.permission}</li>
+            % endfor
+        </ul>
+    </li>
+    % endfor
+</ul>