def check_update_project_parent_project(instance, *args, **kwargs): """ Checks whether the current user is allowed to change the parent project of the given project instance :param instance: :param args: :param kwargs: :return: """ # do not check for raw inserts or ChangeSet or ChangeRecord insert - those are always allowed if kwargs.get('raw') or isinstance(instance, ChangeSet) or isinstance( instance, ChangeRecord): return if permission_checks_disabled(instance): return mng = instance.__class__.objects # check if instance exists if mng.filter(pk=instance.pk).count() == 0: # new instance --> ignore (check_create_roles will handle that case) return # check whether the current user is allowed to do stuff with the current project # therefore we need to remove the original sender kwargs['original_sender'] = kwargs.pop('sender') check_create_roles_for_parent_project(get_current_user(), Project, instance, *args, **kwargs)
def check_delete_roles(sender, instance, *args, **kwargs): """ On pre_delete, check delete permission for every database object where the queryset implements "deletable" raises a PermissionDenied exception on error """ if isinstance(instance, Metadata): # deleting meta data is fine return if permission_checks_disabled(instance): return # ensure that the object has been soft deleted already, else raise an exception raise_error_if_object_not_soft_deleted(sender, instance, *args, **kwargs) user = get_current_user() # check if user has global delete permission for this class if user.has_perm(get_permission_name(instance.__class__, 'delete')): return mng = instance.__class__.objects # check if deletable exists and the instance.pk is in the deletable queryset if hasattr(mng, "deletable") and callable( mng.deletable) and mng.deletable().filter( pk=instance.pk).count() == 0: raise PermissionDenied
def prevent_delete_of_project_manager(sender, instance, *args, **kwargs): """ For each project there must be at least one project manager This pre_delete handler prevents deletion of the last project manager for a given project """ if permission_checks_disabled(instance): return if hasattr(instance, 'is_deleteable') and not instance.is_deleteable(): raise ValidationError({ 'non_field_errors': ValidationError(_( "The last project manager of this project can not be removed"), params={'assignment': instance}, code='invalid') })
def check_model_privileges(instance, *args, **kwargs): """ Checks whether editing ModelPrivileges is allowed or not; A ModelPrivilege entry is always related to an object (such as Task, Note, ...). - Allowed: A ModelPrivilege entry is created when a new entity is created (with the current user) - Allowed: Users are allowed to edit ModelPrivileges only if they are the owner of the related object :param instance: :param args: :param kwargs: :return: """ # skip raw inserts if kwargs.get('raw'): return # verify whether permission checks are enabled if permission_checks_disabled(instance): return # verify whether there are already model privileges for the current content_object if not ModelPrivilege.objects.filter( content_type=instance.content_type, object_id=instance.object_id ).exists(): # no model privilege exists for the given content_type and object_id return user = get_current_user() if user.is_superuser: return if user.is_anonymous: raise PermissionDenied # verify whether the current user is an owner of the given content_type and object_id if not ModelPrivilege.objects.filter( content_type=instance.content_type, object_id=instance.object_id, user=user, full_access_privilege=ModelPrivilege.ALLOW, ).exists(): raise PermissionDenied
def prevent_update_of_last_project_manager(sender, instance, *args, **kwargs): """ For each project there must be at least one project manager This pre_save handler prevents that the last project manager is being demoted """ if permission_checks_disabled(instance): return if instance.pk: obj = ProjectRoleUserAssignment.objects.filter(pk=instance.pk).first() if obj and not obj.is_deleteable(): raise ValidationError({ 'non_field_errors': ValidationError(_( "The last project manager of this project can not be removed" ), params={'assignment': instance}, code='invalid') })
def make_sure_workbench_entities_are_not_deleted_by_normal_users( instance, *args, **kwargs): """ Only superusers may delete workbench entities. Normal users may trash only. """ # not a protected model -> let the request through if not isinstance(instance, models_only_superuser_can_delete): return # nothing to do if permission checks are disabled if permission_checks_disabled(instance): return # check if user is superuser user = get_current_user() if not user.is_superuser: model_name = str(type(instance)) raise PermissionDenied( _("Only admins can delete {model} objects").format( model=model_name))
def on_save_relation(sender, instance, *args, **kwargs): """ On save of a relation, verify that the current user actually has access to the left and right content object :param sender: :param instance: :param args: :param kwargs: :return: """ if permission_checks_disabled(instance): return # ignore raw if kwargs.get('raw'): return # verify that the current user has access to left and right content object left = instance.left_content_object right = instance.right_content_object if not left._meta.model.objects.viewable().filter(pk=left.pk).exists(): raise ValidationError({ 'left_content_object': ValidationError( _('You do not have permission to relate to this object'), params={'relation': instance}, code='invalid') }) if not right._meta.model.objects.viewable().filter(pk=right.pk).exists(): raise ValidationError({ 'right_content_object': ValidationError( _('You do not have permission to relate to this object'), params={'relation': instance}, code='invalid') })
def check_update_roles(sender, instance, *args, **kwargs): """ Check update permission for each database object where the queryset implements "changeable" raises a PermissionDenied exception on error """ # do not check for raw inserts or ChangeSet or ChangeRecord insert - those are always allowed if kwargs.get('raw') or isinstance(instance, ChangeSet) or isinstance(instance, ChangeRecord) \ or isinstance(instance, Metadata): return if permission_checks_disabled(instance): return mng = instance.__class__.objects # get the existing instance (if it exists) old_instance = mng.filter(pk=instance.pk).first() # check if instance exists if not old_instance: return check_create_roles(sender, instance, *args, **kwargs) if hasattr(instance, 'deleted'): # we need to do some checks for soft deleted objects # check if this instance is being soft deleted or restored if old_instance.deleted != instance.deleted: # delegate permission check to another function return check_soft_delete_and_restore_roles(mng, instance, old_instance) # prevent updates of soft-deleted (trashed) objects if instance.deleted: raise ValidationError({ 'non_field_errors': ValidationError( _("You are not allowed to edit an already trashed object"), params={'instance': instance}, code='invalid') }) # prevent updates of locked elements if ElementLock.objects.for_model( instance.__class__, instance.pk ).filter( Q(webdav_lock=False, locked_at__gte=timezone.now() - timezone.timedelta( minutes=site_preferences.element_lock_time_in_minutes)) | Q(webdav_lock=True, locked_at__gte=timezone.now() - timezone.timedelta(minutes=site_preferences. element_lock_webdav_time_in_minutes)) ).exclude( # ignore if the element is locked by the current user locked_by=get_current_user()).exists(): # element is locked by another user raise ValidationError({ 'non_field_errors': ValidationError( _("This object is currently locked by another user"), params={'instance': instance}, code='invalid') }) # !!! from now on we know for sure that this is an update !!! # check if the user has global change roles user = get_current_user() if user.has_perm(get_permission_name(instance.__class__, 'change')): return # allow the user to edit if he was attending, so he can remove himself if hasattr(instance, 'attending_users') and user in instance.attending_users.all(): logger.debug( "In check_update_roles: User is attending so user is allowed to edit" ) else: # check if this instance is editable if hasattr(instance, 'is_editable') and not instance.is_editable(): logger.debug( "In check_update_roles: Checking editable() viewset - " "could not find object -> PermissionDenied") raise PermissionDenied
def check_workbench_element_relation_with_projects(sender, instance, action, model, *args, **kwargs): """ Each workbench element can be related to many projects (hence m2m_changed). Relations with projects are important, as users have roles in projects, and those roles provide permissions on the elements within a project. Therefore, everytime this relation changes (action = add or remove), we need to verify that the current user is actually allowed to change it :param sender: :param instance: BaseModel :param action: :param args: :param kwargs: :return: """ # do not handle raw inserts, or ChangeSet or ChangeRecord insert - those are always allowed if kwargs.get('raw') or isinstance(instance, ChangeSet) or isinstance( instance, ChangeRecord): return # check if disablePermissionChecks is currently set for this class if permission_checks_disabled(instance): return # check if the instance is actually related to projects if not hasattr(instance, 'projects'): # not related, ignore return # only handle pre_add and pre_remove actions if action != 'pre_add' and action != 'pre_remove': return # this handler only checks for projects if model != Project: return # We temporarily must deactivate all permission checks for files because of SITUMEWB-819 if instance.__class__ == File: return user = get_current_user() # first of all, check if the object is editable by the current user if hasattr(instance, 'is_editable') and not instance.is_editable(): raise PermissionDenied # on both actions (pre_add and pre_remove), we can get the set of primary keys that is affected from kwargs project_pk_set = kwargs.get('pk_set') # get all viewable projects of this primary key set, a newly generated cache_id is added to the request in order # to fetch a fresh result from the DB, instead of a cached result projects = Project.objects.viewable(cache_id=uuid.uuid4()).filter( pk__in=project_pk_set) # TODO: We must change the following permission checks as we run into several logic errors all the time. # We don't need to check if the user has specific permissions on all linked projects. It's satisfying enough to have # specific permissions on only one linked project as it overrules all the other ones. # # The problem of running into permission errors occurs if e.g. a user uploads a file to a storage which inherits # permissions from multiple projects. Although permission checks are disabled they can still throw PermissionDenied # errors if you link more than one project or use a parent/child project. Use this setup to reproduce this behavior: # # Project A by User A -> set User B as Observer # Project B by User A (Project A is the parent project for Project B) -> set User B as Project Member # Storage A by User A with linked projects Project A and Project B -> User B can't upload although he should be able # to because he is Project Member in Project B but not in Project A. if action == 'pre_add': # pre add: verify that the user has the permission to create a new instance of within the projects if user.has_perm(get_permission_name(instance.__class__, 'add')): return # for each project, verify that we have add roles for project in projects: if not get_permission_name( instance.__class__, 'add') in project.current_users_project_permissions_list: raise PermissionDenied elif action == 'pre_remove': # pre remove: verify that the user is allowed to remove an instance within the projects if user.has_perm(get_permission_name(instance.__class__, 'delete')): return # for each project, verify that we have remove roles for project in projects: if not get_permission_name( instance.__class__, 'delete' ) in project.current_users_project_permissions_list: raise PermissionDenied