From f1edb901d16735d402660f48e233a819263fa328 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Wed, 23 Aug 2017 17:13:39 +0200 Subject: [PATCH] Orgs: allow setting org admin via web interface / PATCH request --- pillar/api/organizations/__init__.py | 16 +++++++ pillar/api/organizations/patch.py | 20 +++++++++ pillar/web/organizations/routes.py | 6 ++- src/templates/organizations/view_embed.jade | 34 +++++++++++++++ tests/test_api/test_organizations.py | 48 +++++++++++++++++++++ 5 files changed, 122 insertions(+), 2 deletions(-) diff --git a/pillar/api/organizations/__init__.py b/pillar/api/organizations/__init__.py index 14ca5afe..2b6ffaf7 100644 --- a/pillar/api/organizations/__init__.py +++ b/pillar/api/organizations/__init__.py @@ -144,6 +144,22 @@ class OrgManager: return org_doc + def assign_admin(self, org_id: bson.ObjectId, *, user_id: bson.ObjectId): + """Assigns a user as admin user for this organization.""" + + assert isinstance(org_id, bson.ObjectId) + assert isinstance(user_id, bson.ObjectId) + + org_coll = current_app.db('organizations') + users_coll = current_app.db('users') + + if users_coll.count({'_id': user_id}) == 0: + raise ValueError('User not found') + + self._log.info('Updating organization %s, setting admin user to %s', org_id, user_id) + org_coll.update_one({'_id': org_id}, + {'$set': {'admin_uid': user_id}}) + def remove_user(self, org_id: bson.ObjectId, *, diff --git a/pillar/api/organizations/patch.py b/pillar/api/organizations/patch.py index 16c5c1fd..27fd790e 100644 --- a/pillar/api/organizations/patch.py +++ b/pillar/api/organizations/patch.py @@ -64,6 +64,26 @@ class OrganizationPatchHandler(patch_handler.AbstractPatchHandler): org_doc = current_app.org_manager.assign_single_user(org_id, user_id=user_oid) return jsonify(org_doc) + @authorization.require_login() + def patch_assign_admin(self, org_id: bson.ObjectId, patch: dict): + """Assigns a single user by User ID as admin of the organization. + + The calling user must be admin of the organization. + """ + + self._assert_is_admin(org_id) + + # Do some basic validation. + try: + user_id = patch['user_id'] + except KeyError: + raise wz_exceptions.BadRequest('No key "user_id" in patch.') + + user_oid = str2id(user_id) + log.info('User %s uses PATCH to set user %s as admin for organization %s', + current_user().user_id, user_oid, org_id) + current_app.org_manager.assign_admin(org_id, user_id=user_oid) + @authorization.require_login() def patch_remove_user(self, org_id: bson.ObjectId, patch: dict): """Removes a user from an organization. diff --git a/pillar/web/organizations/routes.py b/pillar/web/organizations/routes.py index 40a36d25..012e0797 100644 --- a/pillar/web/organizations/routes.py +++ b/pillar/web/organizations/routes.py @@ -9,8 +9,7 @@ from pillar import current_app from pillar.api.utils import authorization, str2id, gravatar from pillar.web.system_util import pillar_api - -from pillarsdk import Organization +from pillarsdk import Organization, User log = logging.getLogger(__name__) blueprint = Blueprint('pillar.web.organizations', __name__, url_prefix='/organizations') @@ -51,6 +50,8 @@ def view_embed(organization_id: str): member['avatar'] = gravatar(member.get('email')) member['_id'] = str(member['_id']) + admin_user = User.find(organization.admin_uid, api=api) + # Make sure it's never None organization.unknown_members = organization.unknown_members or [] @@ -61,6 +62,7 @@ def view_embed(organization_id: str): return render_template('organizations/view_embed.html', organization=organization, + admin_user=admin_user, members=members, can_edit=can_edit, can_super_edit=can_super_edit, diff --git a/src/templates/organizations/view_embed.jade b/src/templates/organizations/view_embed.jade index ebfe615b..23c7b79d 100644 --- a/src/templates/organizations/view_embed.jade +++ b/src/templates/organizations/view_embed.jade @@ -39,6 +39,12 @@ placeholder="Organization roles", value="{{ organization.org_roles | hide_none | sort | join(' ') }}") | {% endif %} + .input-group + input#admin-select.form-control( + name='admin_user', + type='text', + placeholder='Administrator', + value='{{ admin_user.full_name }}') .input-group button#item-save.btn.btn-default.btn-block(type='submit') i.pi-check @@ -49,6 +55,7 @@ p.item-description {{ organization.description | hide_none }} p.item-website {{ organization.website | hide_none }} p.item-location {{ organization.location | hide_none }} + p.item-admin-user {{ admin_user.full_name }} | {% endif %} | {% endif %} @@ -201,6 +208,14 @@ script. } } ); + $('#admin-select').userSearch( + '{{config.ALGOLIA_USER}}', + '{{config.ALGOLIA_PUBLIC_KEY}}', + '{{config.ALGOLIA_INDEX_USERS}}', + function (event, hit, dataset) { + setAdmin(hit.objectID); + } + ); function addUser(userId) { if (!userId || userId.length == 0) { @@ -225,6 +240,25 @@ script. }); }; + function setAdmin(userId) { + if (!userId || userId.length == 0) { + toastr.error('Please select a user from the list'); + return; + } + patchOrganization({ + op: 'assign-admin', + user_id: userId + }) + .done(function (data) { + window.location.reload(); + toastr.info('Updated admin user'); + }) + .fail(function (err) { + var msg = xhrErrorResponseMessage(err); + toastr.error('Could not add member: ' + msg); + }); + }; + }); | {% endif %} diff --git a/tests/test_api/test_organizations.py b/tests/test_api/test_organizations.py index 9268879a..178ee830 100644 --- a/tests/test_api/test_organizations.py +++ b/tests/test_api/test_organizations.py @@ -357,6 +357,54 @@ class OrganizationPatchTest(AbstractPillarTest): self.assertEqual('Open Source animation studio', db_org['description']) self.assertEqual('https://blender.institute/', db_org['website']) + def test_assign_admin(self): + self.enter_app_context() + om = self.app.org_manager + + admin_uid = self.create_user(24 * 'a', token='admin-token') + new_admin_uid = self.create_user(24 * 'b', token='other-admin-token') + + org_doc = om.create_new_org('Хакеры', admin_uid, 25) + org_id = org_doc['_id'] + + # Try the PATCH to assign the other user as admin + self.patch(f'/api/organizations/{org_id}', + json={ + 'op': 'assign-admin', + 'user_id': str(new_admin_uid), + }, + auth_token='admin-token', + expected_status=204) + + db = self.app.db('organizations') + db_org = db.find_one(org_id) + + self.assertEqual(new_admin_uid, db_org['admin_uid']) + + def test_assign_admin_as_nonadmin(self): + self.enter_app_context() + om = self.app.org_manager + + admin_uid = self.create_user(24 * 'a', token='admin-token') + new_admin_uid = self.create_user(24 * 'b', token='other-admin-token') + + org_doc = om.create_new_org('Хакеры', admin_uid, 25) + org_id = org_doc['_id'] + + # Try the PATCH to assign the other user as admin + self.patch(f'/api/organizations/{org_id}', + json={ + 'op': 'assign-admin', + 'user_id': str(new_admin_uid), + }, + auth_token='other-admin-token', + expected_status=403) + + db = self.app.db('organizations') + db_org = db.find_one(org_id) + + self.assertEqual(admin_uid, db_org['admin_uid']) + class OrganizationResourceEveTest(AbstractPillarTest): """Test GET/POST/PUT access to Organization resource"""