diff -r 000000000000 -r 40c8f766c9b8 src/cm/security.py --- /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 + + +