예제 #1
0
    def create_role_without_change_related_project_permission(self):
        # create role 'can not change related project' which can read and change projects and contacts but can not
        # change the related project of the contact object
        role = Role.objects.create(name='CannotChangeRelatedProject')

        permissions = []

        # get view_project permission
        view_project_permission = Permission.objects.filter(
            codename='view_project',
            content_type=Project.get_content_type()).first()
        permissions.append(view_project_permission)

        # get change_project permission
        edit_project_permission = Permission.objects.filter(
            codename='change_project',
            content_type=Project.get_content_type()).first()
        permissions.append(edit_project_permission)

        # get view_contact permission
        view_contact_permission = Permission.objects.filter(
            codename='view_contact',
            content_type=Contact.get_content_type()).first()
        permissions.append(view_contact_permission)

        # get change_contact permission
        edit_contact_permission = Permission.objects.filter(
            codename='change_contact',
            content_type=Contact.get_content_type()).first()
        permissions.append(edit_contact_permission)

        for p in permissions:
            # and assign it to this role
            RolePermissionAssignment.objects.create(role=role, permission=p)
        return role
예제 #2
0
    def test_favourites_can_not_be_created_if_not_viewable_by_user(self):
        """
        Tries to create favourites for a object that is not viewable by the current user (which should fail)
        :return:
        """

        # verify that user2 has no access to project2
        response = self.rest_get_project(self.token2, self.project2.pk,
                                         HTTP_USER_AGENT, REMOTE_ADDR)
        self.assertEquals(response.status_code, status.HTTP_404_NOT_FOUND)

        # verify that user2 can not favourite project2 (should not have access to project2 -> 403)
        response = self.rest_create_favourite(self.token2, self.project2.pk,
                                              Project.get_content_type().pk)
        self.assertEquals(response.status_code, status.HTTP_403_FORBIDDEN)

        # verify that user1 has access to project2
        response = self.rest_get_project(self.token1, self.project2.pk,
                                         HTTP_USER_AGENT, REMOTE_ADDR)
        self.assertEquals(response.status_code, status.HTTP_200_OK)

        # verify that user1 can favourite project2 (should have access to project2 -> 201)
        response = self.rest_create_project_favourite(self.token1,
                                                      self.project2.pk)
        self.assertEquals(response.status_code, status.HTTP_201_CREATED)

        # finally, there should have been one favourite created
        self.assertEquals(Favourite.objects.all().count(),
                          1,
                          msg="There should be one favourite")
예제 #3
0
def create_notification_based_on_delete_user_from_project(
        sender, instance, *args, **kwargs):
    """Notifies the attended user that he was removed from the project"""
    if get_current_user().is_anonymous:
        return

    assigned_user = instance.user

    # check that the role of this instance has the view_project role
    if not instance.role.permissions.filter(
            codename='view_project',
            content_type=Project.get_content_type()).exists():
        # this role does not have view_project, so we are not sending a notification
        return

    if assigned_user != get_current_user():
        project = instance.project

        # render html message
        context = {'user': assigned_user, 'instance': project}
        html_message = render_to_string(
            'notification/project_remove_user.html', context)

        Notification.objects.create(
            user=assigned_user,
            title=_("You have been removed from project {project}").format(
                project=project.name),
            message=html_message,
            content_type=project.get_content_type(),
            object_id=project.pk,
            notification_type=NotificationConfiguration.
            NOTIFICATION_CONF_PROJECT_USER_CHANGED)
예제 #4
0
    def test_delete_favourites(self):
        """ tries to delete a favourite """
        # verify that user1 has access to project1
        response = self.rest_get_project(self.token1, self.project1.pk,
                                         HTTP_USER_AGENT, REMOTE_ADDR)
        self.assertEquals(response.status_code, status.HTTP_200_OK)

        # mark project1 as favourite of user1
        favourite_response = self.rest_create_project_favourite(
            self.token1, self.project1.pk)

        # try to get the new favourite with student1
        response = self.rest_get_favourite(self.token1,
                                           favourite_response.data['pk'])
        self.assertEquals(response.status_code, status.HTTP_200_OK)

        # delete favourite with student 1 (allowed)
        response = self.rest_delete_favourite(self.token1, self.project1.pk,
                                              Project.get_content_type())
        self.assertEquals(response.status_code, status.HTTP_204_NO_CONTENT)

        # try to get the just deleted favourite with student1 (does not work obviously)
        response = self.rest_get_favourite(self.token1,
                                           favourite_response.data['pk'])
        self.assertEquals(response.status_code, status.HTTP_404_NOT_FOUND)

        # mark project1 as favourite for user1
        favourite_response = self.rest_create_project_favourite(
            self.token1, self.project1.pk)

        # try to get favourite with student 2 (doesn't work)
        response = self.rest_get_favourite(self.token2,
                                           favourite_response.data['pk'])
        self.assertEquals(response.status_code, status.HTTP_404_NOT_FOUND)

        # delete favourite with student 2 (not allowed)
        response = self.rest_delete_favourite(self.token2, self.project1.pk,
                                              Project.get_content_type())
        self.assertEquals(response.status_code, status.HTTP_404_NOT_FOUND)

        # verify the favourite still exists
        response = self.rest_get_favourite(self.token1,
                                           favourite_response.data['pk'])
        self.assertEquals(response.status_code, status.HTTP_200_OK)
예제 #5
0
def send_new_comment_notification_to_project_members(sender, instance, created,
                                                     **kwargs):
    """
    When a new comment is created, send a notification to all other project members
    """

    # check if the left content type is a comment and the right content type is a project
    if instance.left_content_type != Comment.get_content_type() or \
       instance.right_content_type != Project.get_content_type():
        return

    # ignore raw inserts (e.g. from fixtures) and updates (not created)
    if kwargs.get('raw') or not created:
        return

    comment = instance.left_content_object
    project = instance.right_content_object
    project_assignments = ProjectRoleUserAssignment.objects.filter(
        project__pk=project.pk)

    # send notifications for all assignments
    for assignment in project_assignments:
        # If the user of the assignment is the comment creator then skip this iteration
        if assignment.user.pk == comment.created_by.pk:
            continue

        message = render_to_string('notification/project_comment.html', {
            'comment': comment,
            'project': project
        })

        Notification.objects.create(
            user=assignment.user,
            title=_(f"A new comment has been posted for {project.name}"),
            message=message,
            content_type=Project.get_content_type(),
            object_id=project.pk,
            notification_type=NotificationConfiguration.
            NOTIFICATION_CONF_PROJECT_COMMENT,
        )
예제 #6
0
    def duplicate(self, request, format=None, *args, **kwargs):
        """
        Duplicates the project with all its sub-projects. The duplicated instance will not have a parent project.
        """

        project_object = Project.objects.viewable().get(pk=kwargs['pk'])
        original_project_pk = project_object.pk
        duplicate_metadata = request.data.get('duplicate_metadata', False)

        # duplicates the project
        # change name to "Copy of" + project name
        # the duplicated project can not be a sub project so set parent_project_id to NONE if it was set
        duplicated_project = project_object.duplicate(
            name=_("Copy of") + f" {project_object.name}",
            parent_project_id=None,
            metadata=project_object.metadata.all()
            if duplicate_metadata else None,
        )

        dict_duplicated_project_pk = dict()
        dict_duplicated_project_pk[original_project_pk] = duplicated_project.pk

        # duplicate all tasks assigned to the project
        from eric.shared_elements.models import Task
        tasks = Task.objects.viewable().filter(
            projects__in=[original_project_pk])
        if tasks:
            for task in tasks:
                duplicated_task = task.duplicate(projects=[duplicated_project])
                if duplicate_metadata:
                    duplicated_task.metadata.set(task.metadata.all())

        # duplicate sub projects
        Project.duplicate_sub_projects(dict_duplicated_project_pk,
                                       duplicate_metadata)

        serializer = self.get_serializer(duplicated_project)

        return Response(serializer.data)
예제 #7
0
    def test_create_favourite_and_verify_response(self):
        """
        Tries to create a project favourite and verifies the response-details
        """
        self.client.credentials(HTTP_AUTHORIZATION='Token ' + self.token1)

        # store the favourite length
        initial_favourite_length = Favourite.objects.all().count()
        content_type = Project.get_content_type()

        # create favourite via REST API
        response = self.rest_create_project_favourite(self.token1,
                                                      self.project1.pk)

        # favourite should have been created, HTTP response code should be 201
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)

        # decode response and load it into json
        content = response.content.decode()
        decoded_response = json.loads(content)

        self.assertTrue('"pk":' in content, msg="primary key (pk) in response")
        self.assertTrue(f'"content_type":{content_type.pk}' in content,
                        msg="correct content_type in response")
        self.assertTrue(f'"object_id":"{self.project1.pk}"' in content,
                        msg="correct object_id in response")

        # see what the actual Favourite element from database looks like
        pk = decoded_response['pk']
        # get the favourite object from database
        favourite = Favourite.objects.get(pk=pk)

        # check if the favourite object was created
        self.assertEqual(Favourite.objects.all().count(),
                         initial_favourite_length + 1,
                         msg="check if the favourite was created")

        # verify several favourite attributes with api response
        self.assertEqual(str(favourite.pk), decoded_response['pk'])
        self.assertEqual(str(favourite.object_id),
                         decoded_response['object_id'])

        # check if the correct content_type, object_id were saved
        self.assertEqual(favourite.content_type,
                         content_type,
                         msg="check if correct content_type was saved")
        self.assertEqual(favourite.object_id,
                         self.project1.pk,
                         msg="check if correct object_id was saved")

        return response
예제 #8
0
def create_notification_based_on_add_user_to_project(sender, instance, created,
                                                     *args, **kwargs):
    """Notifies the assigned user that he was added to the project or the role changed"""
    if get_current_user().is_anonymous:
        return

    assigned_user = instance.user

    if assigned_user != get_current_user():
        project = instance.project

        # define attributes for the html message
        context = {
            'user': assigned_user,
            'instance': project,
            'new_role': instance.role.name
        }

        if created:  # user was added to the project
            # verify that the user role has access to the project
            if not instance.role.permissions.filter(
                    codename='view_project',
                    content_type=Project.get_content_type()).exists():
                # this role does not have view_project, so we are not sending a notification
                return

            html_message = render_to_string(
                'notification/project_add_user.html', context)
            title = _("You have been added to project {project}").format(
                project=project.name)

        else:  # user role has changed
            html_message = render_to_string(
                'notification/project_change_user_role.html', context)
            title = _(
                "Your role in project {project} has been changed to {role}".
                format(role=instance.role.name, project=project.name))

        Notification.objects.create(
            user=assigned_user,
            title=title,
            message=html_message,
            content_type=project.get_content_type(),
            object_id=project.pk,
            notification_type=NotificationConfiguration.
            NOTIFICATION_CONF_PROJECT_USER_CHANGED)
예제 #9
0
    def create_student_role(self):
        # create role 'Student' which has read access to projects
        role = Role.objects.create(name='Student')

        # get view_project permission
        view_project_permission = Permission.objects.filter(
            codename='view_project',
            content_type=Project.get_content_type()).first()
        # and assign it to this role
        RolePermissionAssignment.objects.create(
            role=role, permission=view_project_permission)
        # get view_resource permission
        view_resource_permission = Permission.objects.filter(
            codename='view_resource',
            content_type=Resource.get_content_type()).first()
        # and assign it to this role
        RolePermissionAssignment.objects.create(
            role=role, permission=view_resource_permission)
        return role
예제 #10
0
    def get_all_project_ids_with_permission(entity, permission, cache_id=None):
        """
        Generic method that gets all projects (and respective sub projects) where the current user has a role
        with permission 'permission'
        :param entity: the entity to use for the lookup
        :type entity: eric.core.models.base.BaseModel
        :param permission: the permission to look up (e.g., view_project)
        :type permission: basestring
        :param cache_id: a unique id to make sure the request is not cached by @cache_for_request, not used in the
                        function per se
        :type cache_id: basestring
        :return: a list of project primary keys
        :rtype: list
        """
        from eric.projects.models import Project, ProjectRoleUserAssignment

        user = get_current_user()

        if not user.pk:  # handle anonymous user
            return list()

        # if the user has the global permission "view_project", they can view all projects anyway
        if user.has_perm('projects.{}'.format(permission)):
            # return codeword "all"
            return list(["all"], )
        else:
            # access ProjectRoleUserAssignment and get all projects where user has the permission
            projects_ids = ProjectRoleUserAssignment.objects.filter(
                user=user,
                role__permissions__content_type=entity.get_content_type(),
                role__permissions__codename=permission).distinct().values_list(
                    'project__id', flat=True)

            pk_list_with_subprojects = Project.get_all_projects_with_descendants(
                projects_ids)

            return pk_list_with_subprojects
예제 #11
0
def create_notification_based_on_project_changes(sender, instance, *args,
                                                 **kwargs):
    """Notifies the user that something has changed in the project"""
    if get_current_user().is_anonymous:
        return

    assigned_users = instance.assigned_users_roles.filter(
        role__permissions__codename='view_project',
        role__permissions__content_type=Project.get_content_type()).exclude(
            user=get_current_user())

    for assigned_user in assigned_users:
        context = {'user': assigned_user.user, 'instance': instance}
        html_message = render_to_string('notification/project_changed.html',
                                        context)

        Notification.objects.create(
            user=assigned_user.user,
            title=_("Project {name} has changed".format(name=instance.name)),
            message=html_message,
            content_type=instance.get_content_type(),
            object_id=instance.pk,
            notification_type=NotificationConfiguration.
            NOTIFICATION_CONF_PROJECT_CHANGED)
예제 #12
0
    def validate_create_project(self,
                                token,
                                project_name='Test Project',
                                project_description="Test Project Description",
                                HTTP_USER_AGENT="APITestClient",
                                REMOTE_ADDR="127.0.0.1"):
        """
        Tries to create a project with the provided credentials
        Checks if the project has been created
        Checks if the user gets the permission "view_project" for the new project
        """
        # store the project length
        initial_project_length = Project.objects.all().count()

        project = self.create_project(token, project_name, project_description,
                                      Project.INITIALIZED, HTTP_USER_AGENT,
                                      REMOTE_ADDR)

        self.assertEquals(project.name, project_name)
        self.assertEquals(project.description, project_description)

        self.assertEqual(
            Project.objects.all().count(),
            initial_project_length + 1,
            msg="Validate that a new project has been created in the database")

        # check that this user has the default role (e.g., project manager)
        has_default_role = ProjectRoleUserAssignment.objects.filter(
            project=project, role__default_role_on_project_create=True)

        self.assertEqual(
            has_default_role.count(),
            1,
            msg="Validate that the user has the default role on project create"
        )

        current_role = has_default_role.first().role

        # check that this user can view the project (has a role with permission 'view_project')
        role_permissions = current_role.permissions.all()

        self.assertTrue(
            role_permissions.filter(content_type=Project.get_content_type(),
                                    codename='view_project').exists(),
            msg=
            "Validate that the user has the view_project role for the newly created project"
        )

        # try to get a list of projects from the API
        response = self.client.get('/api/projects/',
                                   HTTP_USER_AGENT=HTTP_USER_AGENT,
                                   REMOTE_ADDR=REMOTE_ADDR)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        api_data = json.loads(response.content.decode())
        decoded = api_data['results']

        for item in decoded:
            self.assertTrue(
                'created_by' in item,
                msg=
                "projects endpoint returned a json object with created_by in the project"
            )
            self.assertTrue(
                'name' in item,
                msg=
                "projects endpoint returned a json object with name in the project"
            )
            self.assertTrue(
                'project_state' in item,
                msg=
                "projects endpoint returned a json object with project_state in the project"
            )
            self.assertTrue(
                'description' in item,
                msg=
                "projects endpoint returned a json object with description in the project"
            )

        return project
예제 #13
0
    def get_relations(self, filter_by_pk=None):
        # get all relations and prefetch the changesets
        relations = Relation.objects.viewable().for_model(
            self.__class__,
            self.pk).prefetch_related('created_by', 'created_by__userprofile',
                                      'last_modified_by',
                                      'last_modified_by__userprofile')

        if filter_by_pk:
            relations = relations.filter(pk__in=filter_by_pk)

        workbench_models = get_all_workbench_models_with_args(
            WorkbenchEntityMixin)

        pks_by_ct = {}
        model_by_ct = {}
        prefetched_objects_by_ct = {}

        for param, model_details in workbench_models.items():
            ct = model_details['content_type'].id
            pks_by_ct[ct] = []
            model_by_ct[ct] = model_details['entity']

        from eric.projects.models import Project
        # add projects to the list
        project_content_type_id = Project.get_content_type().id
        pks_by_ct[project_content_type_id] = []
        model_by_ct[project_content_type_id] = Project

        # collect all PKs of related objects
        for relation in relations:
            assert relation.left_content_type_id in pks_by_ct, "Left Content Type ID is not valid"
            assert relation.right_content_type_id in pks_by_ct, "Right Content Type ID is not valid"

            pks_by_ct[relation.left_content_type_id].append(
                relation.left_object_id)
            pks_by_ct[relation.right_content_type_id].append(
                relation.right_object_id)

        # prefetch all models
        for ct in pks_by_ct:
            pks = pks_by_ct[ct]
            if len(pks) > 0:
                prefetched_objects_by_ct[ct] = model_by_ct[
                    ct].objects.related_viewable().filter(
                        pk__in=pks).prefetch_common()

                # prefetch projects (for most relations)
                if ct != project_content_type_id:
                    prefetched_objects_by_ct[ct] = prefetched_objects_by_ct[
                        ct].prefetch_related('projects')

                # retrieve the prefetched objects (via in_bulk)
                prefetched_objects_by_ct[ct] = prefetched_objects_by_ct[
                    ct].in_bulk()

        # iterate over all relations and fill in the prefetched elements in left_content_object/right_content_object
        for relation in relations:
            # prefetch the left content object
            relation.left_content_object = prefetched_objects_by_ct[
                relation.left_content_type_id].get(
                    relation.left_object_id,
                    None  # get the object, or None if not available
                )
            # prefetch the right content object
            relation.right_content_object = prefetched_objects_by_ct[
                relation.right_content_type_id].get(
                    relation.right_object_id,
                    None  # get the object, or None if not available
                )

        return relations
예제 #14
0
 def rest_create_project_favourite(
         self, auth_token, project_id,
         HTTP_USER_AGENT=HTTP_USER_AGENT, REMOTE_ADDR=REMOTE_ADDR
 ):
     content_type = Project.get_content_type()
     return self.rest_create_favourite(auth_token, project_id, content_type.pk, HTTP_USER_AGENT, REMOTE_ADDR)