Added very basic permission system. #71
[zzz-spline-users.git] / splinext / users / model / __init__.py
index 2fd444b..4ad49aa 100644 (file)
@@ -3,12 +3,30 @@ 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
 
+class AnonymousUser(object):
+    """Fake user object, for when the user isn't actually logged in.
+
+    Tests as false and tries to respond to method calls the expected way.
+    """
+
+    def __nonzero__(self):
+        return False
+    def __bool__(self):
+        return False
+
+    def can(self, action):
+        """Anonymous users aren't ever allowed to do anything."""
+        return False
+
+
 class User(TableBase):
     __tablename__ = 'users'
     id = Column(Integer, primary_key=True)
@@ -24,6 +42,32 @@ class User(TableBase):
 
         super(User, self).__init__(*args, **kwargs)
 
+    _root_user_id = None
+    _default_permissions = None
+    def can(self, action):
+        """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):
         """Returns a list of (width, '#rrggbb') tuples that semi-uniquely
@@ -88,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)