src/cm/security.py
changeset 0 40c8f766c9b8
child 16 036705244cab
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/cm/security.py	Mon Nov 23 15:14:29 2009 +0100
@@ -0,0 +1,276 @@
+from django.conf import settings
+from django.contrib.auth import REDIRECT_FIELD_NAME
+from django.contrib.auth.models import Permission
+from django.contrib.contenttypes.models import ContentType
+from django.shortcuts import get_object_or_404
+from django.core.urlresolvers import reverse
+from django.http import HttpResponseRedirect
+from django.utils.http import urlquote
+from django.db.models import Q
+
+import logging
+
+from cm.models import *
+from cm import cm_settings
+from cm.exception import UnauthorizedException
+
+def get_request_user(request):
+    if request and request.user and not request.user.is_anonymous():
+        user = request.user
+    else:
+        user = None
+    return user
+    
+## Permission functions
+class FakeRequest(object):
+    def __init__(self, user):
+        self.user = user
+
+def user_has_perm(user, perm_name, text=None):
+    return has_perm(FakeRequest(user),perm_name, text)
+    
+def has_perm(request, perm_name, text=None):
+    # bypass sec if NO_SECURITY
+    if cm_settings.NO_SECURITY:
+        return True
+    
+    # make sure perm exist
+    assert Permission.objects.get(codename=perm_name)
+    
+    user = get_request_user(request)
+
+    if user and user.is_staff:
+        return True
+    
+    if not text:
+        return UserRole.objects.filter(user=user, text=None).filter(Q(role__permissions__codename__exact=perm_name)).count() != 0
+    else:
+        # local role only ADDS permissions:
+        # either a global or a local role with appropriate permissions
+        #return UserRole.objects.filter(user=user).filter(Q(text=text) | Q(text=None)).filter(Q(role__permissions__codename__exact=perm_name)).count() != 0
+
+        # local role OVERRIDES global role:
+        if UserRole.objects.filter(Q(user=user),Q(text=text),~Q(role=None)): # if non void local role
+            return UserRole.objects.filter(user=user).filter(text=text).filter(Q(role__permissions__codename__exact=perm_name)).count() != 0
+        else:
+            return UserRole.objects.filter(user=user).filter(text=None).filter(Q(role__permissions__codename__exact=perm_name)).count() != 0            
+        
+def has_own_perm(request, perm_name, text, comment):
+    
+    user = get_request_user(request)
+    
+    if not user:
+        return False
+    
+    # bypass sec if NO_SECURITY
+    if cm_settings.NO_SECURITY:
+        return True
+    
+    # make sure perm exist
+    assert Permission.objects.get(codename=perm_name)
+    
+    # 2 special cases for comment own edition:
+    
+    # 1
+    # if perm = can_edit_own_comment and 
+    # text is a priori moderated and
+    # comment is approved and
+    # don't have moderation rights and
+    if comment and comment.state == 'approved' and \
+       perm_name == 'can_edit_comment_own' and \
+       text.last_text_version.mod_posteriori == False and \
+       not has_perm(request, 'can_manage_text', text=text):
+        return False
+    
+    # 2       
+    # if perm = can_edit_own_comment and and 
+    # text is a posteriori moderated and
+    # there is a reply
+    # don't have moderation rights and
+    if comment and comment.state == 'approved' and \
+       perm_name == 'can_edit_comment_own' and \
+       text.last_text_version.mod_posteriori == True and \
+       comment.comment_set.count() != 0 and \
+       not has_perm(request, 'can_manage_text', text=text):
+        return False
+    
+    return (comment.user == request.user and has_perm(request, perm_name, text=text)) 
+        
+def is_authenticated(request):
+    # We customize this to be able to monkey patch it if needed
+    return request.user.is_authenticated()
+    
+
+## Content access functions
+
+def get_texts_with_perm(request, perm_name):
+    assert Permission.objects.get(codename=perm_name)
+
+    user = get_request_user(request)
+    
+    if user and user.is_staff:
+        return Text.objects.all()
+    
+    # local role only ADDS permissions:
+    ## global perm
+    # if UserRole.objects.filter(text=None).filter(role__permissions__codename__exact=perm_name).filter(Q(user=user) | Q(user=None) ).count() != 0:
+    #    return Text.objects.all().distinct()
+    ## only texts with role with perm
+    #else:
+    #    return Text.objects.filter(Q(userrole__role__permissions__codename__exact=perm_name), Q(userrole__user=user) | Q(userrole__user=None)).distinct()
+
+    # local role OVERRIDES global role:
+    texts_with_local_role = Text.objects.filter(userrole__in=UserRole.objects.filter(user=user).filter(~Q(role=None)))
+    #Text.objects.filter(Q(userrole__user=user) & ~Q(userrole__role=None))
+    texts_without_local_role = Text.objects.exclude(id__in=texts_with_local_role)
+
+    texts_with_local_role_with_perm = Text.objects.filter(id__in=texts_with_local_role).filter(Q(userrole__role__permissions__codename__exact=perm_name), Q(userrole__user=user) | Q(userrole__user=None)).distinct()
+    
+    # global perm?
+    if UserRole.objects.filter(text=None).filter(role__permissions__codename__exact=perm_name).filter(Q(user=user) | Q(user=None) ).count() != 0:    
+        texts_without_local_role_with_perm = Text.objects.filter(id__in=texts_without_local_role)
+    else:
+        texts_without_local_role_with_perm = []
+    
+    ids = set([t.id for t in texts_with_local_role_with_perm]).union(set([t.id for t in texts_without_local_role_with_perm]))
+    return Text.objects.filter(id__in=ids)
+    
+def get_viewable_comments(request, comments, text, order_by=('created',)):
+    """
+    Get comments visibles by user
+    comments: queryset
+    """
+    user = get_request_user(request)
+        
+    if user and has_perm(request, 'can_view_unapproved_comment', text=text):
+        return list(comments.order_by(*order_by))
+    else:
+        if has_perm(request, 'can_view_approved_comment', text=text):
+            visible_comments = comments.filter(state = 'approved').order_by(*order_by)
+            # filter comments with a non visible (i.e. moderated) comment in the above thread 
+            comments_thread_viewable = [c for c in visible_comments if c.is_thread_full_visible()]
+            return comments_thread_viewable 
+        elif user and has_perm(request, 'can_view_comment_own', text=text):
+            visible_comments = comments.filter(user=user).order_by(*order_by)
+            # filter comments with a non visible (i.e. moderated) comment in the above thread 
+            comments_thread_viewable = [c for c in visible_comments if c.is_thread_full_visible()]
+            return comments_thread_viewable                
+        else:
+            return []
+    
+def get_viewable_activities(request=None, act_types={}, text=None):
+    """
+    Get activities user in request is allowed to see
+    """
+    from cm.security import has_perm, get_texts_with_perm, get_viewable_comments
+    
+    selected_activities = reduce(list.__add__,[Activity.VIEWABLE_ACTIVITIES[k] for k in act_types.keys() if act_types[k]], [])
+    
+    activities = Activity.objects.filter(type__in=selected_activities)
+    if text:
+        activities = activities.filter(text=text)
+        
+    if not has_perm(request, 'can_manage_workspace'):
+        texts = get_texts_with_perm(request, 'can_view_text')
+        activities = activities.filter(Q(text__in=texts))
+        
+        comments = [] 
+        [comments.extend(get_viewable_comments(request, t.last_text_version.comment_set.all(), t)) for t in texts]
+
+        activities = activities.filter(Q(comment__in=comments) | Q(comment=None))
+    return activities.order_by('-created')
+
+
+# won't need to be overridden, should it be moved to another file ? 
+def list_viewable_comments(request, comments_list, text):
+    ret = []
+    for comment in comments_list :
+        ret += [comment] + list_viewable_comments(request, get_viewable_comments(request, comment.comment_set, text), text)
+    return ret
+
+
+# decorators (simple wrappers around above functions)
+def has_global_perm(perm_name, must_be_logged_in=False, redirect_field_name=REDIRECT_FIELD_NAME):
+    def _dec(view_func):
+        def _check_global_perm(request, *args, **kwargs):
+            if must_be_logged_in and not is_authenticated(request):
+                login_url = reverse('login')
+                return HttpResponseRedirect('%s?%s=%s' % (login_url, redirect_field_name, urlquote(request.get_full_path())))
+            
+            if has_perm(request, perm_name, text=None): 
+                return view_func(request, *args, **kwargs)
+            
+            raise UnauthorizedException('No global perm %s' % perm_name)
+        _check_global_perm.__doc__ = view_func.__doc__
+        _check_global_perm.__dict__ = view_func.__dict__
+
+        return _check_global_perm
+    return _dec    
+    
+def has_perm_on_text(perm_name, must_be_logged_in=False, redirect_field_name=REDIRECT_FIELD_NAME):    
+    """
+    decorator protection checking for perm for logged in user
+    force logged in (i.e. redirect to connection screen if not if must_be_logged_in 
+    """    
+    def _dec(view_func):
+        def _check_local_perm(request, *args, **kwargs):
+            if cm_settings.NO_SECURITY:
+                return view_func(request, *args, **kwargs)
+
+            if must_be_logged_in and not is_authenticated(request):
+                login_url = reverse('login')
+                return HttpResponseRedirect('%s?%s=%s' % (login_url, redirect_field_name, urlquote(request.get_full_path())))
+            
+            if 'key' in kwargs: 
+                text = get_object_or_404(Text, key=kwargs['key'])                
+            else:
+                raise Exception('no security check possible')
+                                    
+            if has_perm(request, perm_name, text=text): 
+                return view_func(request, *args, **kwargs)
+            #else:
+                # TODO: (? useful ?) if some user have the perm and not logged-in : redirect to login
+                #if not request.user.is_authenticated() and number_has_perm_on_text(permission, text_id) > 0:
+                #    return HttpResponseRedirect('%s?%s=%s' % (login_url, redirect_field_name, urlquote(request.get_full_path())))                    
+            # else : unauthorized
+            
+            raise UnauthorizedException('No perm %s' % perm_name)
+        _check_local_perm.__doc__ = view_func.__doc__
+        _check_local_perm.__dict__ = view_func.__dict__
+
+        return _check_local_perm
+    return _dec
+        
+def has_perm_on_comment(perm_name):    
+    """
+    decorator protection checking for perm for logged in user on to comment
+    perm_name: 'virtual' permission name 
+    """    
+    def _dec(view_func):
+        def _check_local_perm(request, *args, **kwargs):
+            if cm_settings.NO_SECURITY:
+                return view_func(request, *args, **kwargs)
+            
+            if 'key' in kwargs: 
+                text = get_object_or_404(Text, key=kwargs['key'])
+                # first try permission on text                
+                if has_perm(request, perm_name, text=text) :
+                    return view_func(request, *args, **kwargs)
+                if 'comment_key' in kwargs:
+                    comment = get_object_or_404(Comment, key=kwargs['comment_key'])
+                    if has_own_perm(request, perm_name + "_own", text, comment) :
+                        return view_func(request, *args, **kwargs)
+                else:
+                    raise Exception('no security check possible: no comment key')
+            else:
+                raise Exception('no security check possible: no text key')
+
+            raise UnauthorizedException('No perm %s on comment' % perm_name)
+        _check_local_perm.__doc__ = view_func.__doc__
+        _check_local_perm.__dict__ = view_func.__dict__
+
+        return _check_local_perm
+    return _dec        
+    
+
+