Source

django-extauth / extauth / auth / roles.py

Full commit
'''
Copyright 2009 H. Lee Moffitt Cancer Center and Research Institute, Inc. 
All rights reserved.

@author: Michael Hall <mhall119@gmail.com>
'''

from extauth import utils, session
from extauth.auth.models import Role
from extauth.fieldperms import permissions

from django.contrib.auth.models import User, Group
from django.contrib.contenttypes.models import ContentType
from django.utils.functional import curry
import copy

registered_roles = {}
def register(model_class, role_class=None):
    if registered_roles.has_key(model_class):
        raise RoleException("Model %s has already been registered with a RoleManager" % model_class)
    else:
        registered_roles[model_class] = role_class or EmptyRoles
        
def get_rolemanager(model_class):
    if not registered_roles.has_key(model_class):
        return EmptyRoles
        #raise RoleException(None, model_class, "Model %s has not been registered with a RoleManager" % model_class)
    else:
        return registered_roles[model_class]

def get_roles(model_inst, user=None):
    if user is None:
        user = session.get_current_user()
    role_class = get_rolemanager(model_inst.__class__)
    role_inst = role_class(model_inst, user)
    return role_inst

class RoleException(Exception):
    def __init__(self, user, model, msg):
        super(RoleException, self).__init__(msg)
        self.user = user
        self.model = model
        
class RoleManagerBase(type):
    """
    Metaclass for all Role Managers.
    """
    def __new__(cls, name, bases, attrs):
        super_new = super(RoleManagerBase, cls).__new__
        parents = [b for b in bases if isinstance(b, RoleManagerBase)]
        if not parents:
            # If this isn't a subclass of RoleManager, don't do anything special.
            return super_new(cls, name, bases, attrs)

        # Create the class.
        module = attrs.pop('__module__')
        new_class = super_new(cls, name, bases, {'__module__': module})
        
        roles = dict()
        for aName, a in attrs.items():
            if isinstance(a, RoleBase):
                roles[aName] = a
            else:
                setattr(new_class, aName, a)
        setattr(new_class, '_roles', roles)
        return new_class
    
class RoleManager(object):
    'Role Managers define the available roles for a given model'
    
    __metaclass__ = RoleManagerBase
    
    def __init__(self, model, user=None):
        self.model = model
        if user is None:
            self.user = session.get_current_user()
        else:
            self.user = user
        
        roles = {}
        _roles = self._roles
        for (rName, r) in self._roles.items():
            role = copy.deepcopy(r)
            role.user = self.user
            role.model = self.model
            roles[rName] = role
        setattr(self, 'roles', roles)
            
    def __getattr__(self, aName):
        if aName in self.roles:
            return self.roles[aName].hasRole()
        raise AttributeError
    
    def getUserRoles(self):
        uRoles = []
        for rName, role in self.roles.items():
            if role.hasRole():
                uRoles.append(rName)
        return uRoles
    
    def has_perm(self, pName):
        model_type = ContentType.objects.get_for_model(self.model.__class__)
        (app, sep, perm) = pName.partition('.')
        if not perm or perm == '':
            perm = app
            app = self.model._meta.app_label
            
        perms = Role.objects.filter(model=model_type, 
                                        role__in=self.getUserRoles(),
                                        permissions__content_type__app_label=app,
                                        permissions__codename=perm)
        if perms.count() > 0:
            return True
        else:
            return permissions.has_perm(pName, self.user)
                
    def has_any_perm(self, *perms):
        for p in perms:
            if self.has_perm(p):
                return True
        return False
    
    def has_all_perms(self, *perms):
        for p in perms:
            if not self.has_perm(p):
                return False
        return True

    def require_perm(self, perm):
        if self.has_perm(perm):
            return True
        else:
            raise PermissionDenied("User does not have required permissions: %s" % perm, params={'user': self.user, 'perm': perm})
    
class RoleBase(object):
    'Base class for all Roles'
    
    def __init__(self):
        self._user = None
        self._model = None
        pass
    
    def setUser(self, user):
        if isinstance(user, User):
            self._user = user
        else:
            raise TypeError("Argument must be django.contrib.auth.models.User, not %s" % type(user))
        
    def getUser(self):
        return self._user
    user = property(getUser, setUser)
    
    def setModel(self, model):
        self._model = model
        
    def getModel(self):
        return self._model
    model = property(getModel, setModel)
    
    def hasRole(self):
        return False
    
    def __call__(self):
        return self.hasRole()    
    
class UserIsAuthenticated(RoleBase):
    'Any Authenticated user will have this role'
    def __init__(self):
        super(UserIsAuthenticated, self).__init__()
    
    def hasRole(self):
        if self.user is None:
            return False
        
        return self.user.is_authenticated()
    
class IsSelf(RoleBase):
    """
    Users will have this role on their own django.contrib.auth.models.User 
    object (or any child-class of it)
    """
    def __init__(self):
        super(IsSelf, self).__init__()
    
    def hasRole(self):
        if isinstance(self.model, type(self.user)) and self.user.id == self.model.id:
            return True
        return False
    
class UserIs(RoleBase):
    """
    Users will have this role of the given model field is either a ForeignKey
    to their django.contrib.auth.models.User object (or any child-class of it)
    or a field who's string value matches their username
    """    
    def __init__(self, model_field):
        super(UserIs, self).__init__()
        self._model_field = model_field
        
    def setModelField(self, model_field):
        self._model_field = model_field
    
    def getModelField(self):
        return self._model_field
    model_field = property(getModelField, setModelField)
    
    def hasRole(self):
        if self.model is None or self.user is None:
            return False
        
        v = utils.get_model_value(self.model, self.model_field)
        if v is None:
            return False
        
        if isinstance(v, User):
            return v.username == self.user.username
        else:
            return v == self.user.username
    
class UserIn(RoleBase):
    """
    Users will have this role of the given model field is either a ForeignKey
    to a django.contrib.auth.models.Group object (or any child-class of it)
    of which they are a member, or the field's string value is that of a group
    of which they are a member.
    """    
    
    def __init__(self, model_field):
        super(UserIn, self).__init__()
        self._model_field = model_field        
        
    def setModelField(self, model_field):
        self._model_field = model_field
    
    def getModelField(self):
        return self._model_field
    model_field = property(getModelField, setModelField)
    
    def hasRole(self):
        if self.model is None or self.user is None:
            return False
        
        v = utils.get_model_value(self.model, self.model_field)
        if v is None:
            return False
        
        if isinstance(v, Group):
            return v in self.user.groups.all()
        else:
            return self.user.groups.filter(name=v).count() > 0
    
class Not(RoleBase):
    """
    Wraps another Role instance, and returns the inverse.  If the user has the
    given role, this will say that they don't.  If the user doesn't have the
    role, this will say that they do
    """
    def __init__(self, role):
        self.role = role
        
    def setUser(self, user):
        super(Not, self).setUser(user)
        self.role.setUser(user)
        
    def setModel(self, model):
        super(Not, self).setModel(model)
        self.role.setModel(model)
    
    def hasRole(self):
        return not self.role.hasRole()
        
class Any(RoleBase):
    """
    Wraps multiple Role instances.  A user will have this role if they have any
    one of the contained roles.
    """
    def __init__(self, *roles):
        self.roles = []
        for r in roles:
            if isinstance(r, RoleBase):
                self.roles.append(r)
                
    def setUser(self, user):
        super(Not, self).setUser(user)
        for r in self.roles:
            r.setUser(user)
        
    def setModel(self, model):
        super(Not, self).setModel(model)
        for r in self.roles:
            r.setModel(model)
    
    def hasRole(self):
        for r in self.roles:
            if r.hasRole():
                return True
        return False
        
class All(Any):
    """
    Wraps multiple Role instances.  A user will have this role if they have all
    of the contained roles.
    """

    def hasRole(self):
        for r in self.roles:
            if not r.hasRole():
                return False
        return True
        
class EmptyRoles(RoleManager):
    """
    Provides no Roles, this is used when requesting Roles from an object type
    that does not have a RoleManager defined
    """
    pass
    
def autodiscover():
    """
    Auto-discover INSTALLED_APPS admin.py modules and fail silently when 
    not present. This forces an import on them to register any role bits they
    may want.
    """
    import imp
    from django.conf import settings

    for app in settings.INSTALLED_APPS:
        # For each app, we need to look for an admin.py inside that app's
        # package. We can't use os.path here -- recall that modules may be
        # imported different ways (think zip files) -- so we need to get
        # the app's __path__ and look for admin.py on that path.

        # Step 1: find out the app's __path__ Import errors here will (and
        # should) bubble up, but a missing __path__ (which is legal, but weird)
        # fails silently -- apps that do weird things with __path__ might
        # need to roll their own admin registration.
        try:
            app_path = __import__(app, {}, {}, [app.split('.')[-1]]).__path__
        except AttributeError:
            continue

        # Step 2: use imp.find_module to find the app's admin.py. For some
        # reason imp.find_module raises ImportError if the app can't be found
        # but doesn't actually try to import the module. So skip this app if
        # its admin.py doesn't exist
        try:
            imp.find_module('roles', app_path)
        except ImportError:
            continue

        # Step 3: import the app's admin file. If this has errors we want them
        # to bubble up.
        __import__("%s.roles" % app)