Ejemplo n.º 1
0
 def test_unit__anonymize_user__ok__explicit_name(self, session,
                                                  app_config) -> None:
     api = UserApi(current_user=None, session=session, config=app_config)
     u = api.create_minimal_user("bob@bob")
     assert u.display_name == "bob"
     assert u.email == "bob@bob"
     cleanup_lib = CleanupLib(app_config=app_config, session=session)
     cleanup_lib.anonymize_user(u, anonymized_user_display_name="anonymous")
     assert u.display_name == "anonymous"
     assert u.email.endswith("@anonymous.local")
Ejemplo n.º 2
0
 def test_safe_update__ok__nominal_case(self, session, app_config,
                                        admin_user) -> None:
     assert session.query(Workspace).all() == []
     cleanup_lib = CleanupLib(app_config=app_config,
                              session=session,
                              dry_run_mode=False)
     test_workspace = Workspace()
     test_workspace.owner_id = admin_user.user_id
     cleanup_lib.safe_update(test_workspace)
     transaction.commit()
     assert session.query(Workspace).one() == test_workspace
Ejemplo n.º 3
0
 def test_safe_update__ok__dry_run(self, session, app_config,
                                   admin_user) -> None:
     assert session.query(Workspace).all() == []
     cleanup_lib = CleanupLib(app_config=app_config,
                              session=session,
                              dry_run_mode=True)
     test_workspace = Workspace()
     test_workspace.owner_id = admin_user.user_id
     cleanup_lib.safe_update(test_workspace)
     transaction.commit()
     with pytest.raises(NoResultFound):
         assert session.query(Workspace).one() == test_workspace
Ejemplo n.º 4
0
 def test_unit__anonymize_user__ok__nominal_case(self, session,
                                                 app_config) -> None:
     api = UserApi(current_user=None, session=session, config=app_config)
     u = api.create_minimal_user(email="bob@bob", username="******")
     assert u.display_name == "bob"
     assert u.username == "bob"
     assert u.email == "bob@bob"
     cleanup_lib = CleanupLib(app_config=app_config, session=session)
     cleanup_lib.anonymize_user(u)
     assert u.display_name == "Deleted user"
     assert u.email.endswith("@anonymous.local")
     assert u.username is None
Ejemplo n.º 5
0
 def test_safe_delete__ok__dry_run(self, session, app_config,
                                   admin_user) -> None:
     assert session.query(Workspace).all() == []
     cleanup_lib = CleanupLib(app_config=app_config,
                              session=session,
                              dry_run_mode=True)
     test_workspace = Workspace()
     test_workspace.owner_id = admin_user.user_id
     session.add(test_workspace)
     transaction.commit()
     cleanup_lib.safe_delete(test_workspace)
     transaction.commit()
     assert session.query(Workspace).one() == test_workspace
Ejemplo n.º 6
0
 def test_safe_delete_dir__ok__dry_run(self, session, app_config,
                                       admin_user) -> None:
     assert session.query(Workspace).all() == []
     cleanup_lib = CleanupLib(app_config=app_config,
                              session=session,
                              dry_run_mode=True)
     dir_path = tempfile.mkdtemp()
     file_path = "{}/my_file.txt".format(dir_path)
     Path(file_path).touch()
     assert Path(dir_path).is_dir()
     assert Path(file_path).is_file()
     cleanup_lib.safe_delete_dir(dir_path)
     assert Path(dir_path).is_dir()
     assert Path(file_path).is_file()
Ejemplo n.º 7
0
    def take_app_action(self, parsed_args: argparse.Namespace,
                        app_context: AppEnvironment) -> None:
        self._session = app_context["request"].dbsession
        self._app_config = app_context["registry"].settings["CFG"]

        if parsed_args.dry_run_mode:
            print("(!) Running in dry-run mode, not change will be applied.")
            app_context["request"].tm.doom()

        with unprotected_content_revision(self._session) as session:
            uapi = UserApi(
                config=self._app_config,
                session=session,
                current_user=None,
                show_deleted=True,
                show_deactivated=True,
            )
            user_list = []  # type: typing.List[User]
            for login in parsed_args.logins:
                try:
                    user = uapi.get_one_by_login(login)
                    user_list.append(user)
                except UserDoesNotExist as exc:
                    print('ERROR: user with email "{}" does not exist'.format(
                        login))
                    raise exc
            for user in user_list:
                print("~~~~~~~~~~")
                cleanup_lib = CleanupLib(session,
                                         self._app_config,
                                         dry_run_mode=parsed_args.dry_run_mode)
                print("anonymize user {}.".format(user.user_id))
                cleanup_lib.anonymize_user(
                    user,
                    anonymized_user_display_name=parsed_args.anonymize_name)
                self._session.flush()
                print('user {} anonymized to "{} <{}/{}>".'.format(
                    user.user_id, user.display_name, user.email,
                    user.username))
                print("~~~~~~~~~~")
Ejemplo n.º 8
0
    def should_anonymize(self, user: User,
                         owned_workspaces_will_be_deleted: bool,
                         cleanup_lib: CleanupLib) -> UserNeedAnonymization:
        # INFO - G.M - 2019-12-20 - check user revisions that need to be deleted for consistent database
        # if we do not want to anonymize user but delete him
        should_anonymize = cleanup_lib.should_anonymize(
            user,
            owned_workspace_will_be_deleted=owned_workspaces_will_be_deleted)

        if should_anonymize.blocking_revisions:
            print(
                '{} revision of user "{}" in sharespaces found, deleting them, can cause inconsistent'
                " database.".format(len(should_anonymize.blocking_revisions),
                                    user.user_id))

        if should_anonymize.blocking_workspaces:
            print(
                '{} workspace(s) of user "{}" found, cannot delete user without deleting/changing ownership'
                .format(len(should_anonymize.blocking_workspaces),
                        user.user_id))
        return should_anonymize
Ejemplo n.º 9
0
    def test_unit__delete_content__ok__nominal_case(
        self,
        session,
        app_config,
        content_type_list,
        content_api_factory,
        workspace_api_factory,
        share_lib_factory,
    ) -> None:

        content_api = content_api_factory.get(show_deleted=True,
                                              show_active=True,
                                              show_archived=True)
        workspace_api = workspace_api_factory.get()
        test_workspace = workspace_api.create_workspace("test_workspace")
        folder = content_api.create(
            label="test-folder",
            content_type_slug=content_type_list.Folder.slug,
            workspace=test_workspace,
            do_save=True,
            do_notify=False,
        )
        folder_id = folder.content_id
        file_ = content_api.create(
            content_type_slug=content_type_list.File.slug,
            workspace=test_workspace,
            parent=folder,
            label="Test file",
            do_save=True,
            do_notify=False,
        )
        file_id = file_.content_id
        comment = content_api.create_comment(workspace=test_workspace,
                                             parent=file_,
                                             content="Toto",
                                             do_save=True,
                                             do_notify=False)
        comment_id = comment.content_id
        share_api = share_lib_factory.get()
        shares = share_api.share_content(file_, emails=["*****@*****.**"])
        share_id = shares[0].share_id
        session.flush()
        transaction.commit()
        assert content_api.get_one(folder_id,
                                   content_type=content_type_list.Any_SLUG)
        assert content_api.get_one(file_id,
                                   content_type=content_type_list.Any_SLUG)
        assert content_api.get_one(comment_id,
                                   content_type=content_type_list.Any_SLUG)
        assert session.query(ContentShare).filter(
            ContentShare.share_id == share_id).one()

        with unprotected_content_revision(session) as unprotected_session:
            cleanup_lib = CleanupLib(app_config=app_config,
                                     session=unprotected_session)
            cleanup_lib.delete_content(folder)
            session.flush()
        transaction.commit()
        with pytest.raises(ContentNotFound):
            content_api.get_one(folder_id,
                                content_type=content_type_list.Any_SLUG)
        with pytest.raises(ContentNotFound):
            content_api.get_one(file_id,
                                content_type=content_type_list.Any_SLUG)
        with pytest.raises(ContentNotFound):
            content_api.get_one(comment_id,
                                content_type=content_type_list.Any_SLUG)
        with pytest.raises(NoResultFound):
            session.query(ContentShare).filter(
                ContentShare.share_id == share_id).one()
Ejemplo n.º 10
0
    def test_unit__delete_revision__ok__delete_older_revision(
        self,
        admin_user,
        session,
        app_config,
        content_type_list,
        content_api_factory,
        workspace_api_factory,
        share_lib_factory,
        upload_permission_lib_factory,
    ) -> None:

        content_api = content_api_factory.get(show_deleted=True,
                                              show_active=True,
                                              show_archived=True)
        workspace_api = workspace_api_factory.get()
        test_workspace = workspace_api.create_workspace("test_workspace")
        session.add(test_workspace)
        folder = content_api.create(
            label="test-folder",
            content_type_slug=content_type_list.Folder.slug,
            workspace=test_workspace,
            do_save=True,
            do_notify=False,
        )
        file_ = content_api.create(
            content_type_slug=content_type_list.File.slug,
            workspace=test_workspace,
            parent=folder,
            label="Test file",
            do_save=True,
            do_notify=False,
        )
        with new_revision(session=session,
                          tm=transaction.manager,
                          content=file_):
            content_api.update_file_data(file_,
                                         "Test_file.txt",
                                         new_mimetype="plain/text",
                                         new_content=b"Test file")
        content_api.mark_read(file_)
        file_id = file_.content_id
        revisions = file_.revisions
        session.flush()
        transaction.commit()
        assert len(revisions) == 2
        first_revision_id = revisions[0].revision_id
        second_revision_id = revisions[1].revision_id
        content = content_api.get_one(file_id,
                                      content_type=content_type_list.Any_SLUG)
        assert content
        assert content.revision.revision_id == second_revision_id
        assert (session.query(ContentRevisionRO).filter(
            ContentRevisionRO.revision_id == first_revision_id).one())
        assert (session.query(ContentRevisionRO).filter(
            ContentRevisionRO.revision_id == second_revision_id).one())
        assert (session.query(RevisionReadStatus).filter(
            RevisionReadStatus.revision_id == first_revision_id).one())
        assert (session.query(RevisionReadStatus).filter(
            RevisionReadStatus.revision_id == second_revision_id).one())

        with unprotected_content_revision(session) as unprotected_session:
            cleanup_lib = CleanupLib(app_config=app_config,
                                     session=unprotected_session)
            cleanup_lib.delete_revision(revision=revisions[0])
            session.flush()
        transaction.commit()
        assert content
        assert content.revision.revision_id == second_revision_id
        assert (session.query(ContentRevisionRO).filter(
            ContentRevisionRO.revision_id == second_revision_id).one())
        with pytest.raises(NoResultFound):
            assert (session.query(ContentRevisionRO).filter(
                ContentRevisionRO.revision_id == first_revision_id).one())
        assert (session.query(RevisionReadStatus).filter(
            RevisionReadStatus.revision_id == second_revision_id).one())
        with pytest.raises(NoResultFound):
            assert (session.query(RevisionReadStatus).filter(
                RevisionReadStatus.revision_id == first_revision_id).one())
Ejemplo n.º 11
0
    def test_unit__delete_user_associated_data__ok__nominal_case(
        self,
        admin_user,
        session,
        app_config,
        content_type_list,
        content_api_factory,
        workspace_api_factory,
        share_lib_factory,
        upload_permission_lib_factory,
    ) -> None:

        content_api = content_api_factory.get(show_deleted=True,
                                              show_active=True,
                                              show_archived=True)
        workspace_api = workspace_api_factory.get()
        test_workspace = workspace_api.create_workspace("test_workspace")
        session.add(test_workspace)
        session.flush()
        workspace_id = test_workspace.workspace_id
        folder = content_api.create(
            label="test-folder",
            content_type_slug=content_type_list.Folder.slug,
            workspace=test_workspace,
            do_save=True,
            do_notify=False,
        )
        folder_id = folder.content_id
        folder2 = content_api.create(
            label="test-folder2",
            content_type_slug=content_type_list.Folder.slug,
            workspace=test_workspace,
            do_save=True,
            do_notify=False,
        )
        folder2_id = folder2.content_id
        file_ = content_api.create(
            content_type_slug=content_type_list.File.slug,
            workspace=test_workspace,
            parent=folder,
            label="Test file",
            do_save=True,
            do_notify=False,
        )
        file_id = file_.content_id
        comment = content_api.create_comment(workspace=test_workspace,
                                             parent=file_,
                                             content="Toto",
                                             do_save=True,
                                             do_notify=False)
        comment_id = comment.content_id
        share_api = share_lib_factory.get()
        shares = share_api.share_content(file_, emails=["*****@*****.**"])
        share_id = shares[0].share_id
        upload_permission_lib = upload_permission_lib_factory.get()
        upload_permissions = upload_permission_lib.add_permission_to_workspace(
            workspace=test_workspace, emails=["*****@*****.**"])
        upload_permission_id = upload_permissions[0].upload_permission_id
        session.flush()
        transaction.commit()
        assert content_api.get_one(folder_id,
                                   content_type=content_type_list.Any_SLUG)
        assert content_api.get_one(file_id,
                                   content_type=content_type_list.Any_SLUG)
        assert content_api.get_one(comment_id,
                                   content_type=content_type_list.Any_SLUG)
        assert session.query(ContentShare).filter(
            ContentShare.share_id == share_id).one()
        assert (session.query(UploadPermission).filter(
            UploadPermission.upload_permission_id ==
            upload_permission_id).one())
        session.query(Workspace).filter(
            Workspace.workspace_id == workspace_id).one()

        with unprotected_content_revision(session) as unprotected_session:
            cleanup_lib = CleanupLib(app_config=app_config,
                                     session=unprotected_session)
            cleanup_lib.delete_user_associated_data(admin_user)
            session.flush()
        transaction.commit()
        # INFO - G.M - 2019-12-20 - workspace is not deleted by this method
        session.query(Workspace).filter(
            Workspace.workspace_id == workspace_id).one()
        with pytest.raises(NoResultFound):
            session.query(UserRoleInWorkspace).filter(
                UserRoleInWorkspace.workspace_id == workspace_id).one()
        with pytest.raises(NoResultFound):
            session.query(UploadPermission).filter(
                UploadPermission.workspace_id == workspace_id).one()
        with pytest.raises(ContentNotFound):
            content_api.get_one(folder2_id,
                                content_type=content_type_list.Any_SLUG)
        with pytest.raises(ContentNotFound):
            content_api.get_one(folder_id,
                                content_type=content_type_list.Any_SLUG)
        with pytest.raises(ContentNotFound):
            content_api.get_one(file_id,
                                content_type=content_type_list.Any_SLUG)
        with pytest.raises(ContentNotFound):
            content_api.get_one(comment_id,
                                content_type=content_type_list.Any_SLUG)
        with pytest.raises(NoResultFound):
            session.query(ContentShare).filter(
                ContentShare.share_id == share_id).one()
        with pytest.raises(NoResultFound):
            session.query(UploadPermission).filter(
                UploadPermission.upload_permission_id ==
                upload_permission_id).one()
Ejemplo n.º 12
0
    def _delete_user_database_info(
        self,
        user: User,
        cleanup_lib: CleanupLib,
        delete_owned_workspaces: bool = False,
        force_delete_all_user_revisions: bool = False,
        anonymize_if_required: bool = False,
        anonymized_user_display_name: typing.Optional[str] = None,
    ):
        print('trying to delete user {}: "{}"\n'.format(
            user.user_id, user.login))

        deleted_workspace_ids = []
        deleted_user_id = user.user_id
        should_anonymize = self.should_anonymize(
            user,
            owned_workspaces_will_be_deleted=delete_owned_workspaces,
            cleanup_lib=cleanup_lib)
        force_delete_all_associated_data = (force_delete_all_user_revisions
                                            and delete_owned_workspaces)

        revision_conflict_for_deleting_user = (
            should_anonymize.blocking_revisions
            and not force_delete_all_associated_data)
        workspace_conflict_for_deleting_user = (
            should_anonymize.blocking_workspaces
            and not delete_owned_workspaces)
        if (revision_conflict_for_deleting_user
                or workspace_conflict_for_deleting_user
            ) and not anonymize_if_required:
            raise UserCannotBeDeleted(
                'user "{}" has revisions or workspaces left, cannot delete it'.
                format(user.user_id))

        if delete_owned_workspaces:
            deleted_workspace_ids = cleanup_lib.delete_user_owned_workspace(
                user)
            print('owned workspace for user "{}" deleted'.format(user.user_id))

        if force_delete_all_user_revisions:
            cleanup_lib.delete_user_revisions(user)
            print('all user "{}" revisions deleted'.format(user.user_id))

        if should_anonymize.need_anonymization and not force_delete_all_associated_data:
            # NOTE S.G. 2020-10-14 - Need to load the user config now as loading it after
            # delete_user_associated_data() in dry-run mode doesn't work
            user_config = user.config
            cleanup_lib.delete_user_associated_data(user)
            cleanup_lib.safe_delete(user_config)
            cleanup_lib.anonymize_user(
                user,
                anonymized_user_display_name=anonymized_user_display_name)
            print('user {} anonymized to "{} <{}/{}>".'.format(
                user.user_id, user.display_name, user.email, user.username))
        else:
            print('delete user "{}"'.format(user.user_id))
            # NOTE S.G. 2020-10-14 - Need to load the user config now as loading it after
            # delete_user_associated_data() in dry-run mode doesn't work
            user_config = user.config
            cleanup_lib.delete_user_associated_data(user)
            cleanup_lib.safe_delete(user_config)
            cleanup_lib.safe_delete(user)
            print('user "{}" deleted'.format(user.user_id))

        self._session.flush()
        return DeleteResultIds(deleted_user_id, deleted_workspace_ids)
Ejemplo n.º 13
0
    def take_app_action(self, parsed_args: argparse.Namespace,
                        app_context: AppEnvironment) -> None:
        self._session = app_context["request"].dbsession
        self._app_config = app_context["registry"].settings["CFG"]

        delete_user_revision = parsed_args.force or parsed_args.delete_revisions
        delete_owned_sharespaces = (parsed_args.force
                                    or parsed_args.best_effort
                                    or parsed_args.delete_sharespaces)
        anonymize_if_required = parsed_args.best_effort or parsed_args.anonymize_if_required

        if parsed_args.dry_run_mode:
            print("(!) Running in dry-run mode, no changes will be applied.")
            app_context["request"].tm.doom()
        if parsed_args.force:
            print("(!) Running in force mode")
        if parsed_args.best_effort:
            print("(!) Running in best-effort mode")

        if delete_user_revision:
            print(
                "/!\\ Delete all user revisions, database created may be broken /!\\."
            )
        if delete_owned_sharespaces:
            print("(!) User owned sharespaces will be deleted too.")
        if anonymize_if_required:
            print("(!) Will anonymize user if not possible to delete it")
            if parsed_args.anonymize_name:
                print('(!) Custom anonymize name choosen is: "{}"'.format(
                    parsed_args.anonymize_name))
        print("")
        deleted_user_ids = set()  # typing.Set[int]
        deleted_workspace_ids = set()  # typing.Set[int]
        with unprotected_content_revision(self._session) as session:
            uapi = UserApi(
                config=self._app_config,
                session=session,
                current_user=None,
                show_deleted=True,
                show_deactivated=True,
            )
            user_list = []  # type: typing.List[User]
            for login in parsed_args.logins:
                try:
                    user = uapi.get_one_by_login(login)
                    user_list.append(user)
                except UserDoesNotExist as exc:
                    print(
                        'ERROR: user with email/username "{}" does not exist'.
                        format(login))
                    raise exc
            print("~~~~")
            print("Deletion of user from Database")
            print("~~~~\n")
            print("~~~~")
            for user in user_list:
                cleanup_lib = CleanupLib(session,
                                         self._app_config,
                                         dry_run_mode=parsed_args.dry_run_mode)
                deleted_user_ids_result = self._delete_user_database_info(
                    user,
                    force_delete_all_user_revisions=delete_user_revision,
                    anonymize_if_required=anonymize_if_required,
                    delete_owned_workspaces=delete_owned_sharespaces,
                    anonymized_user_display_name=parsed_args.anonymize_name,
                    cleanup_lib=cleanup_lib,
                )
                deleted_user_ids.add(deleted_user_ids_result.user_id)
                deleted_workspace_ids.update(
                    deleted_user_ids_result.workspace_ids)
                print("~~~~")
            print(
                "deletion of user(s) from database process almost finished, change will be applied at end "
                "of this script.\n")
            print("~~~~")
            print("Deletion of Caldav Agenda\n")
            app_lib = ApplicationApi(app_list=app_list)
            if app_lib.exist(AGENDA__APP_SLUG):
                # INFO - G.M - 2019-12-13 - cleanup agenda at end of process
                if deleted_workspace_ids:
                    deleted_workspace_ids_str = [
                        '"{}"'.format(workspace_id)
                        for workspace_id in deleted_workspace_ids
                    ]
                    print("delete agenda of workspaces {}".format(
                        ", ".join(deleted_workspace_ids_str)))
                    for workspace_id in deleted_workspace_ids:
                        try:
                            cleanup_lib.delete_workspace_agenda(workspace_id)
                        except AgendaNotFoundError:
                            print(
                                'Warning: Cannot delete agenda for workspace "{}", agenda not found. Agenda path may be incorrect or agenda not created'
                                .format(workspace_id))
                            print(traceback.format_exc())

                if deleted_user_ids:
                    deleted_user_ids_str = [
                        '"{}"'.format(user_id) for user_id in deleted_user_ids
                    ]
                    print("delete agenda of users {}".format(
                        ", ".join(deleted_user_ids_str)))
                    for user_id in deleted_user_ids:
                        try:
                            cleanup_lib.delete_user_agenda(user_id)
                        except AgendaNotFoundError:
                            print(
                                'Warning: Cannot delete agenda for user "{}", agenda not found. Agenda path may be incorrect or agenda not created'
                                .format(user_id))
                            print(traceback.format_exc())
            else:
                print(
                    "Warning ! Agenda app not enabled, agenda will not be deleted."
                )
            print("~~~~")
            print("deletion of Agenda process finished")
            print("~~~~")
            if parsed_args.dry_run_mode:
                print("Finished (dry-run mode, no change applied)")
            else:
                print("Finished")