From f98b2a09ca7645443ef1703705a36c617044961c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Tue, 24 May 2016 11:18:56 +0200 Subject: [PATCH] Allow a user to remove themselves from any project they're in. --- pillar/application/modules/projects.py | 42 +++++++++++++++----------- tests/test_project_management.py | 38 +++++++++++++++++++++++ 2 files changed, 62 insertions(+), 18 deletions(-) diff --git a/pillar/application/modules/projects.py b/pillar/application/modules/projects.py index feee3ff5..717bc7d9 100644 --- a/pillar/application/modules/projects.py +++ b/pillar/application/modules/projects.py @@ -6,6 +6,7 @@ from bson import ObjectId from eve.methods.post import post_internal from eve.methods.patch import patch_internal from flask import g, Blueprint, request, abort, current_app +from werkzeug import exceptions as wz_exceptions from application.utils import remove_private_keys, authorization, jsonify, mongo from application.utils.gcs import GoogleCloudStorageBucket @@ -252,32 +253,32 @@ def project_manage_users(): No changes are done on the project itself. """ + projects_collection = current_app.data.driver.db['projects'] + users_collection = current_app.data.driver.db['users'] + # TODO: check if user is admin of the project before anything if request.method == 'GET': project_id = request.args['project_id'] - projects_collection = current_app.data.driver.db['projects'] project = projects_collection.find_one({'_id': ObjectId(project_id)}) admin_group_id = project['permissions']['groups'][0]['group'] - users_collection = current_app.data.driver.db['users'] + users = users_collection.find( {'groups': {'$in': [admin_group_id]}}, {'username': 1, 'email': 1, 'full_name': 1}) - users_list = [user for user in users] - return jsonify({'_status': 'OK', '_items': users_list}) + return jsonify({'_status': 'OK', '_items': list(users)}) # The request is not a form, since it comes from the API sdk data = json.loads(request.data) - project_id = data['project_id'] - target_user_id = data['user_id'] + project_id = ObjectId(data['project_id']) + target_user_id = ObjectId(data['user_id']) action = data['action'] - user_id = g.current_user['user_id'] + current_user_id = g.current_user['user_id'] - projects_collection = current_app.data.driver.db['projects'] - project = projects_collection.find_one({'_id': ObjectId(project_id)}) + project = projects_collection.find_one({'_id': project_id}) - # Check if the current_user is owner of the project - # TODO: check based on permissions - if project['user'] != user_id: + # Check if the current_user is owner of the project, or removing themselves. + remove_self = target_user_id == current_user_id and action == 'remove' + if project['user'] != current_user_id and not remove_self: return abort_with_error(403) admin_group = get_admin_group(project) @@ -285,19 +286,24 @@ def project_manage_users(): # Get the user and add the admin group to it if action == 'add': operation = '$addToSet' + log.info('project_manage_users: Adding user %s to admin group of project %s', + target_user_id, project_id) elif action == 'remove': + log.info('project_manage_users: Removing user %s from admin group of project %s', + target_user_id, project_id) operation = '$pull' else: - return abort_with_error(403) + log.warning('project_manage_users: Unsupported action %r called by user %s', + action, current_user_id) + raise wz_exceptions.UnprocessableEntity() - users_collection = current_app.data.driver.db['users'] - users_collection.update({'_id': ObjectId(target_user_id)}, + users_collection.update({'_id': target_user_id}, {operation: {'groups': admin_group['_id']}}) - user = users_collection.find_one({'_id': ObjectId(target_user_id)}, + + user = users_collection.find_one({'_id': target_user_id}, {'username': 1, 'email': 1, 'full_name': 1}) - user.update({'_status': 'OK'}) - # Return the user in the response. + user['_status'] = 'OK' return jsonify(user) diff --git a/tests/test_project_management.py b/tests/test_project_management.py index 8f516273..fd6e9ac6 100644 --- a/tests/test_project_management.py +++ b/tests/test_project_management.py @@ -533,4 +533,42 @@ class ProjectNodeAccess(AbstractProjectTest): db_user = users.find_one(self.other_user_id) self.assertNotIn(admin_group['_id'], db_user['groups']) + + def test_remove_self(self): + """Every user should be able to remove themselves from a project, + regardless of permissions. + """ + + from application.modules import projects + from application.utils import dumps + + project_mng_user_url = '/p/users' + + # Use our API to add user to group + payload = { + 'project_id': self.project_id, + 'user_id': self.other_user_id, + 'action': 'add'} + + resp = self.client.post(project_mng_user_url, + data=dumps(payload), + content_type='application/json', + headers={'Authorization': self.make_header('token')}) + self.assertEqual(200, resp.status_code, resp.data) + + # Update payload to remove the user we just added, and call it as that user. + payload['action'] = 'remove' + + resp = self.client.post(project_mng_user_url, + data=dumps(payload), + content_type='application/json', + headers={'Authorization': self.make_header('other-token')}) + self.assertEqual(200, resp.status_code, resp.data) + + # Check if the user is now actually removed from the group. + with self.app.test_request_context(): + users = self.app.data.driver.db['users'] + + db_user = users.find_one(self.other_user_id) + admin_group = projects.get_admin_group(self.project) self.assertNotIn(admin_group['_id'], db_user['groups'])