models.py 9.36 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
# Copyright 2014 Budapest University of Technology and Economics (BME IK)
#
# This file is part of CIRCLE Cloud.
#
# CIRCLE is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# CIRCLE 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 General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along
# with CIRCLE.  If not, see <http://www.gnu.org/licenses/>.

Őry Máté committed
18 19
import logging

Őry Máté committed
20 21 22 23 24 25
from django.contrib.auth.models import User, Group
from django.contrib.contenttypes.generic import (
    GenericForeignKey, GenericRelation
)
from django.contrib.contenttypes.models import ContentType
from django.db.models import (
26
    ManyToManyField, ForeignKey, CharField, Model, IntegerField, Q
Őry Máté committed
27
)
Őry Máté committed
28

Őry Máté committed
29 30
logger = logging.getLogger(__name__)

Őry Máté committed
31 32 33 34 35

class Level(Model):

    """Definition of a permission level.

36
    Instances are automatically populated based on AclBase."""
Őry Máté committed
37 38 39 40 41
    name = CharField('name', max_length=50)
    content_type = ForeignKey(ContentType)
    codename = CharField('codename', max_length=100)
    weight = IntegerField('weight', null=True)

Őry Máté committed
42 43 44
    def __unicode__(self):
        return "<%s/%s>" % (unicode(self.content_type), self.name)

Őry Máté committed
45
    class Meta:
Bach Dániel committed
46
        app_label = 'acl'
Őry Máté committed
47 48 49 50 51 52 53 54 55 56 57
        unique_together = (('content_type', 'codename'),
                           # ('content_type', 'weight'),
                           # TODO find a way of temp. disabling this constr.
                           )


class ObjectLevel(Model):

    """Permission level for a specific object."""
    level = ForeignKey(Level)
    content_type = ForeignKey(ContentType)
58
    object_id = IntegerField()
Őry Máté committed
59 60 61 62
    content_object = GenericForeignKey()
    users = ManyToManyField(User)
    groups = ManyToManyField(Group)

Őry Máté committed
63 64 65
    def __unicode__(self):
        return "<%s: %s>" % (unicode(self.content_object), unicode(self.level))

Őry Máté committed
66
    class Meta:
Bach Dániel committed
67
        app_label = 'acl'
Őry Máté committed
68 69 70 71 72 73 74 75
        unique_together = (('content_type', 'object_id', 'level'),)


class AclBase(Model):

    """Define permission levels for Users/Groups per object."""
    object_level_set = GenericRelation(ObjectLevel)

Őry Máté committed
76 77 78 79 80 81 82 83 84 85 86
    def clone_acl(self, other):
        """Clone full ACL from other object."""
        assert self.id != other.id or type(self) != type(other)
        self.object_level_set.clear()
        for i in other.object_level_set.all():
            ol = self.object_level_set.create(level=i.level)
            for j in i.users.all():
                ol.users.add(j)
            for j in i.groups.all():
                ol.groups.add(j)

87 88
    @classmethod
    def get_level_object(cls, level):
89 90

        """Get Level object for this model by codename."""
91
        ct = ContentType.objects.get_for_model(cls)
Őry Máté committed
92 93 94
        return Level.objects.get(codename=level, content_type=ct)

    def set_level(self, whom, level):
95 96 97 98 99

        """Set level of object for a user or group.

        :param whom: user or group the level is set for
        :type whom: User or Group
100 101
        :param level: codename of level to set, or None
        :type level: Level or str or unicode or NoneType
102
        """
Őry Máté committed
103 104 105 106 107
        if isinstance(whom, User):
            self.set_user_level(whom, level)
        elif isinstance(whom, Group):
            self.set_group_level(whom, level)
        else:
108
            raise AttributeError('"whom" must be a User or Group object.')
Őry Máté committed
109 110

    def set_user_level(self, user, level):
111 112 113 114 115

        """Set level of object for a user.

        :param whom: user the level is set for
        :type whom: User
116 117
        :param level: codename of level to set, or None
        :type level: Level or str or unicode or NoneType
118
        """
Őry Máté committed
119 120
        logger.info('%s.set_user_level(%s, %s) called',
                    *[unicode(p) for p in [self, user, level]])
121 122 123 124 125 126 127 128
        if level is None:
            pk = None
        else:
            if isinstance(level, basestring):
                level = self.get_level_object(level)
            if not self.object_level_set.filter(level_id=level.pk).exists():
                self.object_level_set.create(level=level)
            pk = level.pk
Őry Máté committed
129
        for i in self.object_level_set.all():
130
            if i.level_id != pk:
Őry Máté committed
131 132 133 134 135 136
                i.users.remove(user)
            else:
                i.users.add(user)
            i.save()

    def set_group_level(self, group, level):
137 138 139 140 141 142 143 144

        """Set level of object for a user.

        :param whom: user the level is set for
        :type whom: User or unicode or str
        :param level: codename of level to set
        :type level: str or unicode
        """
Őry Máté committed
145 146
        logger.info('%s.set_group_level(%s, %s) called',
                    *[unicode(p) for p in [self, group, level]])
147 148 149 150 151 152 153 154
        if level is None:
            pk = None
        else:
            if isinstance(level, basestring):
                level = self.get_level_object(level)
            if not self.object_level_set.filter(level_id=level.pk).exists():
                self.object_level_set.create(level=level)
            pk = level.pk
Őry Máté committed
155
        for i in self.object_level_set.all():
156
            if i.level_id != pk:
Őry Máté committed
157 158 159 160 161 162
                i.groups.remove(group)
            else:
                i.groups.add(group)
            i.save()

    def has_level(self, user, level, group_also=True):
Őry Máté committed
163 164
        logger.debug('%s.has_level(%s, %s, %s) called',
                     *[unicode(p) for p in [self, user, level, group_also]])
165 166
        if user is None or not user.is_authenticated():
            return False
167 168 169
        if getattr(user, 'is_superuser', False):
            logger.debug('- superuser granted')
            return True
Őry Máté committed
170 171
        if isinstance(level, basestring):
            level = self.get_level_object(level)
Őry Máté committed
172
            logger.debug("- level set by str: %s", unicode(level))
Őry Máté committed
173 174 175

        object_levels = self.object_level_set.filter(
            level__weight__gte=level.weight).all()
176 177 178 179 180 181
        groups = user.groups.values_list('id', flat=True) if group_also else []
        for i in object_levels:
            if i.users.filter(pk=user.pk).exists():
                return True
            if group_also and i.groups.filter(pk__in=groups).exists():
                return True
Őry Máté committed
182 183
        return False

184
    def get_users_with_level(self, **kwargs):
Őry Máté committed
185
        logger.debug('%s.get_users_with_level() called', unicode(self))
186
        object_levels = (self.object_level_set.filter(**kwargs).select_related(
187
            'level').prefetch_related('users').all())
Őry Máté committed
188 189 190
        users = []
        for object_level in object_levels:
            name = object_level.level.codename
Őry Máté committed
191 192 193
            olusers = object_level.users.all()
            users.extend([(u, name) for u in olusers])
            logger.debug('- %s: %s' % (name, [u.username for u in olusers]))
Őry Máté committed
194 195 196
        return users

    def get_groups_with_level(self):
Őry Máté committed
197
        logger.debug('%s.get_groups_with_level() called', unicode(self))
Őry Máté committed
198
        object_levels = (self.object_level_set.select_related(
199
            'level').prefetch_related('groups').all())
Őry Máté committed
200 201 202
        groups = []
        for object_level in object_levels:
            name = object_level.level.codename
Őry Máté committed
203 204 205
            olgroups = object_level.groups.all()
            groups.extend([(g, name) for g in olgroups])
            logger.debug('- %s: %s' % (name, [g.name for g in olgroups]))
Őry Máté committed
206 207
        return groups

208
    @classmethod
209
    def get_objects_with_level(cls, level, user,
210 211
                               group_also=True, owner_also=False,
                               disregard_superuser=False):
212 213 214 215
        logger.debug('%s.get_objects_with_level(%s,%s) called',
                     unicode(cls), unicode(level), unicode(user))
        if user is None or not user.is_authenticated():
            return cls.objects.none()
216
        if getattr(user, 'is_superuser', False) and not disregard_superuser:
217
            logger.debug('- superuser granted')
218
            return cls.objects.all()
219 220 221 222 223
        if isinstance(level, basestring):
            level = cls.get_level_object(level)
            logger.debug("- level set by str: %s", unicode(level))

        ct = ContentType.objects.get_for_model(cls)
224 225 226
        levelfilter = Q(users=user)
        if group_also:
            levelfilter |= Q(groups__in=user.groups.all())
227
        ols = ObjectLevel.objects.filter(
228
            levelfilter,
229
            content_type=ct, level__weight__gte=level.weight).distinct()
230 231 232
        clsfilter = Q(object_level_set__in=ols.all())
        if owner_also:
            clsfilter |= Q(owner=user)
233
        return cls.objects.filter(clsfilter).distinct()
234

235 236 237 238 239 240 241 242 243 244
    @classmethod
    def get_objects_with_group_level(cls, level, group):
        if isinstance(level, basestring):
            level = cls.get_level_object(level)
        ct = ContentType.objects.get_for_model(cls)
        levelfilter = Q(groups=group)
        ols = ObjectLevel.objects.filter(
            levelfilter,
            content_type=ct, level__weight__gte=level.weight).distinct()
        clsfilter = Q(object_level_set__in=ols.all())
245
        return cls.objects.filter(clsfilter).distinct()
246

247 248
    def save(self, *args, **kwargs):
        super(AclBase, self).save(*args, **kwargs)
249 250
        if 'owner' in dict(self.ACL_LEVELS) and (hasattr(self, 'owner') and
                                                 self.owner):
251
            self.set_user_level(self.owner, 'owner')
252

Őry Máté committed
253 254
    class Meta:
        abstract = True