From be12bd7d997923e2c224dc9f8dcc0e58c34dd580 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Thu, 24 Aug 2017 11:35:09 +0200 Subject: [PATCH] Orgs: allow users to leave an organization --- pillar/api/organizations/patch.py | 12 +- src/templates/organizations/view_embed.jade | 118 +++++++++++--------- tests/test_api/test_organizations.py | 26 +++++ 3 files changed, 96 insertions(+), 60 deletions(-) diff --git a/pillar/api/organizations/patch.py b/pillar/api/organizations/patch.py index 3cb9fedb..2e69fe6e 100644 --- a/pillar/api/organizations/patch.py +++ b/pillar/api/organizations/patch.py @@ -104,16 +104,18 @@ class OrganizationPatchHandler(patch_handler.AbstractPatchHandler): The calling user must be admin of the organization. """ - self._assert_is_admin(org_id) - # Do some basic validation. email = patch.get('email') or None user_id = patch.get('user_id') - user_oid = str2id(user_id) if user_id else None - log.info('User %s uses PATCH to remove user from organization %s', - current_user().user_id, org_id) + # Users require admin rights on the org, except when removing themselves. + current_user_id = current_user().user_id + if user_oid is None or user_oid != current_user_id: + self._assert_is_admin(org_id) + + log.info('User %s uses PATCH to remove user %s from organization %s', + current_user_id, user_oid, org_id) org_doc = current_app.org_manager.remove_user(org_id, user_id=user_oid, email=email) return jsonify(org_doc) diff --git a/src/templates/organizations/view_embed.jade b/src/templates/organizations/view_embed.jade index 628fcd6b..4812c52b 100644 --- a/src/templates/organizations/view_embed.jade +++ b/src/templates/organizations/view_embed.jade @@ -115,7 +115,7 @@ | {% endif %} span.sharing-users-extra {{ member['username'] }} .sharing-users-action - | {% if can_edit %} + | {% if can_edit or current_user.objectid == member['_id'] %} | {% if current_user.objectid == member['_id'] %} button.user-remove(title="Leave as member of this organization") i.pi-trash @@ -180,28 +180,6 @@ | {% if can_edit %} script. - - function patchOrganization(patch) { - if (typeof patch == 'undefined') { - throw 'patchOrganization(undefined) called'; - } - - if (console) console.log('patchOrganization', patch); - - var promise = $.ajax({ - url: '/api/organizations/{{ organization._id }}', - method: 'PATCH', - contentType: 'application/json', - data: JSON.stringify(patch), - }) - .fail(function(err) { - if (console) console.log('Error patching: ', err); - }) - ; - - return promise; - } - $(document).ready(function() { $('#user-select').userSearch( '{{config.ALGOLIA_USER}}', @@ -232,13 +210,6 @@ script. setAdmin(hit.objectID, hit.full_name); } ); - - $('body').off('click', '.user-remove'); // remove previous handlers. - $('body').on('click', '.user-remove', function(e) { - var user_id = $(this).closest('*[data-user-id]').data('user-id'); - var user_email = $(this).closest('*[data-user-email]').data('user-email'); - removeUser(user_id, user_email); - }); }); function addUser(userId) { @@ -264,31 +235,6 @@ script. }); }; - function removeUser(user_id, email) { - if (typeof user_id == 'undefined' && typeof email == 'undefined') { - throw "removeUser(undefined, undefined) called"; - } - var organization_id = '{{ organization._id }}'; - var patch = {op: 'remove-user'}; - - if (typeof user_id !== 'undefined') { - patch.user_id = user_id; - } - if (typeof email !== 'undefined') { - patch.email = String(email); - } - - patchOrganization(patch) - .done(function() { - $("ul.sharing-users-list").find("[data-user-id='" + user_id + "']").remove(); - item_open('{{ organization._id }}', false); - toastr.success('User removed from this organization'); - }).fail(function (data) { - var msg = xhrErrorResponseMessage(data); - toastr.error('Error removing user: ' + msg); - }); - } - function setAdmin(user_id, full_name) { if (!user_id || user_id.length == 0) { toastr.error('Please select a user from the list'); @@ -382,4 +328,66 @@ script. } | {% endif %} +script. + $(document).ready(function() { + $('body').off('click', '.user-remove'); // remove previous handlers. + $('body').on('click', '.user-remove', function(e) { + var user_id = $(this).closest('*[data-user-id]').data('user-id'); + var user_email = $(this).closest('*[data-user-email]').data('user-email'); + removeUser(user_id, user_email); + }); + }); + + function patchOrganization(patch) { + if (typeof patch == 'undefined') { + throw 'patchOrganization(undefined) called'; + } + + if (console) console.log('patchOrganization', patch); + + var promise = $.ajax({ + url: '/api/organizations/{{ organization._id }}', + method: 'PATCH', + contentType: 'application/json', + data: JSON.stringify(patch), + }) + .fail(function(err) { + if (console) console.log('Error patching: ', err); + }) + ; + + return promise; + } + + function removeUser(user_id, email) { + if (typeof user_id == 'undefined' && typeof email == 'undefined') { + throw "removeUser(undefined, undefined) called"; + } + var organization_id = '{{ organization._id }}'; + var patch = {op: 'remove-user'}; + + if (typeof user_id !== 'undefined') { + patch.user_id = user_id; + } + if (typeof email !== 'undefined') { + patch.email = String(email); + } + + patchOrganization(patch) + .done(function() { + $("ul.sharing-users-list").find("[data-user-id='" + user_id + "']").remove(); + if ('{{ current_user.user_id }}' == user_id) { + // User removed self, so we cannot open this organization again. + $('#organization-{{ organization._id}}').remove(); + toastr.success('You left the organization.') + } else { + item_open('{{ organization._id }}', false); + toastr.success('User removed from this organization'); + } + }).fail(function (data) { + var msg = xhrErrorResponseMessage(data); + toastr.error('Error removing user: ' + msg); + }); + } + | {% endblock %} diff --git a/tests/test_api/test_organizations.py b/tests/test_api/test_organizations.py index 748099ae..dfc0718a 100644 --- a/tests/test_api/test_organizations.py +++ b/tests/test_api/test_organizations.py @@ -331,6 +331,32 @@ class OrganizationPatchTest(AbstractPillarTest): self.assertEqual([], db_org['members']) self.assertEqual(['member2@example.com'], db_org['unknown_members']) + def test_remove_self(self): + self.enter_app_context() + om = self.app.org_manager + + admin_uid = self.create_user(24 * 'a', token='admin-token') + member_uid = self.create_user(24 * 'b', email='member1@example.com', token='member-token') + org_doc = om.create_new_org('Хакеры', admin_uid, 25) + org_id = org_doc['_id'] + + om.assign_users(org_id, ['member1@example.com']) + + # Try the PATCH to remove self. + resp = self.patch(f'/api/organizations/{org_id}', + json={ + 'op': 'remove-user', + 'user_id': str(member_uid), + }, + auth_token='member-token') + new_org_doc = resp.get_json() + + db = self.app.db('organizations') + db_org = db.find_one(org_id) + + self.assertEqual([], db_org['members']) + self.assertEqual([], new_org_doc['members']) + def test_edit_from_web(self): self.enter_app_context() om = self.app.org_manager