src/cm/security.py
changeset 0 40c8f766c9b8
child 16 036705244cab
equal deleted inserted replaced
-1:000000000000 0:40c8f766c9b8
       
     1 from django.conf import settings
       
     2 from django.contrib.auth import REDIRECT_FIELD_NAME
       
     3 from django.contrib.auth.models import Permission
       
     4 from django.contrib.contenttypes.models import ContentType
       
     5 from django.shortcuts import get_object_or_404
       
     6 from django.core.urlresolvers import reverse
       
     7 from django.http import HttpResponseRedirect
       
     8 from django.utils.http import urlquote
       
     9 from django.db.models import Q
       
    10 
       
    11 import logging
       
    12 
       
    13 from cm.models import *
       
    14 from cm import cm_settings
       
    15 from cm.exception import UnauthorizedException
       
    16 
       
    17 def get_request_user(request):
       
    18     if request and request.user and not request.user.is_anonymous():
       
    19         user = request.user
       
    20     else:
       
    21         user = None
       
    22     return user
       
    23     
       
    24 ## Permission functions
       
    25 class FakeRequest(object):
       
    26     def __init__(self, user):
       
    27         self.user = user
       
    28 
       
    29 def user_has_perm(user, perm_name, text=None):
       
    30     return has_perm(FakeRequest(user),perm_name, text)
       
    31     
       
    32 def has_perm(request, perm_name, text=None):
       
    33     # bypass sec if NO_SECURITY
       
    34     if cm_settings.NO_SECURITY:
       
    35         return True
       
    36     
       
    37     # make sure perm exist
       
    38     assert Permission.objects.get(codename=perm_name)
       
    39     
       
    40     user = get_request_user(request)
       
    41 
       
    42     if user and user.is_staff:
       
    43         return True
       
    44     
       
    45     if not text:
       
    46         return UserRole.objects.filter(user=user, text=None).filter(Q(role__permissions__codename__exact=perm_name)).count() != 0
       
    47     else:
       
    48         # local role only ADDS permissions:
       
    49         # either a global or a local role with appropriate permissions
       
    50         #return UserRole.objects.filter(user=user).filter(Q(text=text) | Q(text=None)).filter(Q(role__permissions__codename__exact=perm_name)).count() != 0
       
    51 
       
    52         # local role OVERRIDES global role:
       
    53         if UserRole.objects.filter(Q(user=user),Q(text=text),~Q(role=None)): # if non void local role
       
    54             return UserRole.objects.filter(user=user).filter(text=text).filter(Q(role__permissions__codename__exact=perm_name)).count() != 0
       
    55         else:
       
    56             return UserRole.objects.filter(user=user).filter(text=None).filter(Q(role__permissions__codename__exact=perm_name)).count() != 0            
       
    57         
       
    58 def has_own_perm(request, perm_name, text, comment):
       
    59     
       
    60     user = get_request_user(request)
       
    61     
       
    62     if not user:
       
    63         return False
       
    64     
       
    65     # bypass sec if NO_SECURITY
       
    66     if cm_settings.NO_SECURITY:
       
    67         return True
       
    68     
       
    69     # make sure perm exist
       
    70     assert Permission.objects.get(codename=perm_name)
       
    71     
       
    72     # 2 special cases for comment own edition:
       
    73     
       
    74     # 1
       
    75     # if perm = can_edit_own_comment and 
       
    76     # text is a priori moderated and
       
    77     # comment is approved and
       
    78     # don't have moderation rights and
       
    79     if comment and comment.state == 'approved' and \
       
    80        perm_name == 'can_edit_comment_own' and \
       
    81        text.last_text_version.mod_posteriori == False and \
       
    82        not has_perm(request, 'can_manage_text', text=text):
       
    83         return False
       
    84     
       
    85     # 2       
       
    86     # if perm = can_edit_own_comment and and 
       
    87     # text is a posteriori moderated and
       
    88     # there is a reply
       
    89     # don't have moderation rights and
       
    90     if comment and comment.state == 'approved' and \
       
    91        perm_name == 'can_edit_comment_own' and \
       
    92        text.last_text_version.mod_posteriori == True and \
       
    93        comment.comment_set.count() != 0 and \
       
    94        not has_perm(request, 'can_manage_text', text=text):
       
    95         return False
       
    96     
       
    97     return (comment.user == request.user and has_perm(request, perm_name, text=text)) 
       
    98         
       
    99 def is_authenticated(request):
       
   100     # We customize this to be able to monkey patch it if needed
       
   101     return request.user.is_authenticated()
       
   102     
       
   103 
       
   104 ## Content access functions
       
   105 
       
   106 def get_texts_with_perm(request, perm_name):
       
   107     assert Permission.objects.get(codename=perm_name)
       
   108 
       
   109     user = get_request_user(request)
       
   110     
       
   111     if user and user.is_staff:
       
   112         return Text.objects.all()
       
   113     
       
   114     # local role only ADDS permissions:
       
   115     ## global perm
       
   116     # if UserRole.objects.filter(text=None).filter(role__permissions__codename__exact=perm_name).filter(Q(user=user) | Q(user=None) ).count() != 0:
       
   117     #    return Text.objects.all().distinct()
       
   118     ## only texts with role with perm
       
   119     #else:
       
   120     #    return Text.objects.filter(Q(userrole__role__permissions__codename__exact=perm_name), Q(userrole__user=user) | Q(userrole__user=None)).distinct()
       
   121 
       
   122     # local role OVERRIDES global role:
       
   123     texts_with_local_role = Text.objects.filter(userrole__in=UserRole.objects.filter(user=user).filter(~Q(role=None)))
       
   124     #Text.objects.filter(Q(userrole__user=user) & ~Q(userrole__role=None))
       
   125     texts_without_local_role = Text.objects.exclude(id__in=texts_with_local_role)
       
   126 
       
   127     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()
       
   128     
       
   129     # global perm?
       
   130     if UserRole.objects.filter(text=None).filter(role__permissions__codename__exact=perm_name).filter(Q(user=user) | Q(user=None) ).count() != 0:    
       
   131         texts_without_local_role_with_perm = Text.objects.filter(id__in=texts_without_local_role)
       
   132     else:
       
   133         texts_without_local_role_with_perm = []
       
   134     
       
   135     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]))
       
   136     return Text.objects.filter(id__in=ids)
       
   137     
       
   138 def get_viewable_comments(request, comments, text, order_by=('created',)):
       
   139     """
       
   140     Get comments visibles by user
       
   141     comments: queryset
       
   142     """
       
   143     user = get_request_user(request)
       
   144         
       
   145     if user and has_perm(request, 'can_view_unapproved_comment', text=text):
       
   146         return list(comments.order_by(*order_by))
       
   147     else:
       
   148         if has_perm(request, 'can_view_approved_comment', text=text):
       
   149             visible_comments = comments.filter(state = 'approved').order_by(*order_by)
       
   150             # filter comments with a non visible (i.e. moderated) comment in the above thread 
       
   151             comments_thread_viewable = [c for c in visible_comments if c.is_thread_full_visible()]
       
   152             return comments_thread_viewable 
       
   153         elif user and has_perm(request, 'can_view_comment_own', text=text):
       
   154             visible_comments = comments.filter(user=user).order_by(*order_by)
       
   155             # filter comments with a non visible (i.e. moderated) comment in the above thread 
       
   156             comments_thread_viewable = [c for c in visible_comments if c.is_thread_full_visible()]
       
   157             return comments_thread_viewable                
       
   158         else:
       
   159             return []
       
   160     
       
   161 def get_viewable_activities(request=None, act_types={}, text=None):
       
   162     """
       
   163     Get activities user in request is allowed to see
       
   164     """
       
   165     from cm.security import has_perm, get_texts_with_perm, get_viewable_comments
       
   166     
       
   167     selected_activities = reduce(list.__add__,[Activity.VIEWABLE_ACTIVITIES[k] for k in act_types.keys() if act_types[k]], [])
       
   168     
       
   169     activities = Activity.objects.filter(type__in=selected_activities)
       
   170     if text:
       
   171         activities = activities.filter(text=text)
       
   172         
       
   173     if not has_perm(request, 'can_manage_workspace'):
       
   174         texts = get_texts_with_perm(request, 'can_view_text')
       
   175         activities = activities.filter(Q(text__in=texts))
       
   176         
       
   177         comments = [] 
       
   178         [comments.extend(get_viewable_comments(request, t.last_text_version.comment_set.all(), t)) for t in texts]
       
   179 
       
   180         activities = activities.filter(Q(comment__in=comments) | Q(comment=None))
       
   181     return activities.order_by('-created')
       
   182 
       
   183 
       
   184 # won't need to be overridden, should it be moved to another file ? 
       
   185 def list_viewable_comments(request, comments_list, text):
       
   186     ret = []
       
   187     for comment in comments_list :
       
   188         ret += [comment] + list_viewable_comments(request, get_viewable_comments(request, comment.comment_set, text), text)
       
   189     return ret
       
   190 
       
   191 
       
   192 # decorators (simple wrappers around above functions)
       
   193 def has_global_perm(perm_name, must_be_logged_in=False, redirect_field_name=REDIRECT_FIELD_NAME):
       
   194     def _dec(view_func):
       
   195         def _check_global_perm(request, *args, **kwargs):
       
   196             if must_be_logged_in and not is_authenticated(request):
       
   197                 login_url = reverse('login')
       
   198                 return HttpResponseRedirect('%s?%s=%s' % (login_url, redirect_field_name, urlquote(request.get_full_path())))
       
   199             
       
   200             if has_perm(request, perm_name, text=None): 
       
   201                 return view_func(request, *args, **kwargs)
       
   202             
       
   203             raise UnauthorizedException('No global perm %s' % perm_name)
       
   204         _check_global_perm.__doc__ = view_func.__doc__
       
   205         _check_global_perm.__dict__ = view_func.__dict__
       
   206 
       
   207         return _check_global_perm
       
   208     return _dec    
       
   209     
       
   210 def has_perm_on_text(perm_name, must_be_logged_in=False, redirect_field_name=REDIRECT_FIELD_NAME):    
       
   211     """
       
   212     decorator protection checking for perm for logged in user
       
   213     force logged in (i.e. redirect to connection screen if not if must_be_logged_in 
       
   214     """    
       
   215     def _dec(view_func):
       
   216         def _check_local_perm(request, *args, **kwargs):
       
   217             if cm_settings.NO_SECURITY:
       
   218                 return view_func(request, *args, **kwargs)
       
   219 
       
   220             if must_be_logged_in and not is_authenticated(request):
       
   221                 login_url = reverse('login')
       
   222                 return HttpResponseRedirect('%s?%s=%s' % (login_url, redirect_field_name, urlquote(request.get_full_path())))
       
   223             
       
   224             if 'key' in kwargs: 
       
   225                 text = get_object_or_404(Text, key=kwargs['key'])                
       
   226             else:
       
   227                 raise Exception('no security check possible')
       
   228                                     
       
   229             if has_perm(request, perm_name, text=text): 
       
   230                 return view_func(request, *args, **kwargs)
       
   231             #else:
       
   232                 # TODO: (? useful ?) if some user have the perm and not logged-in : redirect to login
       
   233                 #if not request.user.is_authenticated() and number_has_perm_on_text(permission, text_id) > 0:
       
   234                 #    return HttpResponseRedirect('%s?%s=%s' % (login_url, redirect_field_name, urlquote(request.get_full_path())))                    
       
   235             # else : unauthorized
       
   236             
       
   237             raise UnauthorizedException('No perm %s' % perm_name)
       
   238         _check_local_perm.__doc__ = view_func.__doc__
       
   239         _check_local_perm.__dict__ = view_func.__dict__
       
   240 
       
   241         return _check_local_perm
       
   242     return _dec
       
   243         
       
   244 def has_perm_on_comment(perm_name):    
       
   245     """
       
   246     decorator protection checking for perm for logged in user on to comment
       
   247     perm_name: 'virtual' permission name 
       
   248     """    
       
   249     def _dec(view_func):
       
   250         def _check_local_perm(request, *args, **kwargs):
       
   251             if cm_settings.NO_SECURITY:
       
   252                 return view_func(request, *args, **kwargs)
       
   253             
       
   254             if 'key' in kwargs: 
       
   255                 text = get_object_or_404(Text, key=kwargs['key'])
       
   256                 # first try permission on text                
       
   257                 if has_perm(request, perm_name, text=text) :
       
   258                     return view_func(request, *args, **kwargs)
       
   259                 if 'comment_key' in kwargs:
       
   260                     comment = get_object_or_404(Comment, key=kwargs['comment_key'])
       
   261                     if has_own_perm(request, perm_name + "_own", text, comment) :
       
   262                         return view_func(request, *args, **kwargs)
       
   263                 else:
       
   264                     raise Exception('no security check possible: no comment key')
       
   265             else:
       
   266                 raise Exception('no security check possible: no text key')
       
   267 
       
   268             raise UnauthorizedException('No perm %s on comment' % perm_name)
       
   269         _check_local_perm.__doc__ = view_func.__doc__
       
   270         _check_local_perm.__dict__ = view_func.__dict__
       
   271 
       
   272         return _check_local_perm
       
   273     return _dec        
       
   274     
       
   275 
       
   276