Orgs: allow setting org admin via web interface / PATCH request
This commit is contained in:
@@ -144,6 +144,22 @@ class OrgManager:
|
|||||||
|
|
||||||
return org_doc
|
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,
|
def remove_user(self,
|
||||||
org_id: bson.ObjectId,
|
org_id: bson.ObjectId,
|
||||||
*,
|
*,
|
||||||
|
@@ -64,6 +64,26 @@ class OrganizationPatchHandler(patch_handler.AbstractPatchHandler):
|
|||||||
org_doc = current_app.org_manager.assign_single_user(org_id, user_id=user_oid)
|
org_doc = current_app.org_manager.assign_single_user(org_id, user_id=user_oid)
|
||||||
return jsonify(org_doc)
|
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()
|
@authorization.require_login()
|
||||||
def patch_remove_user(self, org_id: bson.ObjectId, patch: dict):
|
def patch_remove_user(self, org_id: bson.ObjectId, patch: dict):
|
||||||
"""Removes a user from an organization.
|
"""Removes a user from an organization.
|
||||||
|
@@ -9,8 +9,7 @@ from pillar import current_app
|
|||||||
from pillar.api.utils import authorization, str2id, gravatar
|
from pillar.api.utils import authorization, str2id, gravatar
|
||||||
from pillar.web.system_util import pillar_api
|
from pillar.web.system_util import pillar_api
|
||||||
|
|
||||||
|
from pillarsdk import Organization, User
|
||||||
from pillarsdk import Organization
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
blueprint = Blueprint('pillar.web.organizations', __name__, url_prefix='/organizations')
|
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['avatar'] = gravatar(member.get('email'))
|
||||||
member['_id'] = str(member['_id'])
|
member['_id'] = str(member['_id'])
|
||||||
|
|
||||||
|
admin_user = User.find(organization.admin_uid, api=api)
|
||||||
|
|
||||||
# Make sure it's never None
|
# Make sure it's never None
|
||||||
organization.unknown_members = organization.unknown_members or []
|
organization.unknown_members = organization.unknown_members or []
|
||||||
|
|
||||||
@@ -61,6 +62,7 @@ def view_embed(organization_id: str):
|
|||||||
|
|
||||||
return render_template('organizations/view_embed.html',
|
return render_template('organizations/view_embed.html',
|
||||||
organization=organization,
|
organization=organization,
|
||||||
|
admin_user=admin_user,
|
||||||
members=members,
|
members=members,
|
||||||
can_edit=can_edit,
|
can_edit=can_edit,
|
||||||
can_super_edit=can_super_edit,
|
can_super_edit=can_super_edit,
|
||||||
|
@@ -39,6 +39,12 @@
|
|||||||
placeholder="Organization roles",
|
placeholder="Organization roles",
|
||||||
value="{{ organization.org_roles | hide_none | sort | join(' ') }}")
|
value="{{ organization.org_roles | hide_none | sort | join(' ') }}")
|
||||||
| {% endif %}
|
| {% endif %}
|
||||||
|
.input-group
|
||||||
|
input#admin-select.form-control(
|
||||||
|
name='admin_user',
|
||||||
|
type='text',
|
||||||
|
placeholder='Administrator',
|
||||||
|
value='{{ admin_user.full_name }}')
|
||||||
.input-group
|
.input-group
|
||||||
button#item-save.btn.btn-default.btn-block(type='submit')
|
button#item-save.btn.btn-default.btn-block(type='submit')
|
||||||
i.pi-check
|
i.pi-check
|
||||||
@@ -49,6 +55,7 @@
|
|||||||
p.item-description {{ organization.description | hide_none }}
|
p.item-description {{ organization.description | hide_none }}
|
||||||
p.item-website {{ organization.website | hide_none }}
|
p.item-website {{ organization.website | hide_none }}
|
||||||
p.item-location {{ organization.location | hide_none }}
|
p.item-location {{ organization.location | hide_none }}
|
||||||
|
p.item-admin-user {{ admin_user.full_name }}
|
||||||
| {% endif %}
|
| {% endif %}
|
||||||
| {% 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) {
|
function addUser(userId) {
|
||||||
if (!userId || userId.length == 0) {
|
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 %}
|
| {% endif %}
|
||||||
|
@@ -357,6 +357,54 @@ class OrganizationPatchTest(AbstractPillarTest):
|
|||||||
self.assertEqual('Open Source animation studio', db_org['description'])
|
self.assertEqual('Open Source animation studio', db_org['description'])
|
||||||
self.assertEqual('https://blender.institute/', db_org['website'])
|
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):
|
class OrganizationResourceEveTest(AbstractPillarTest):
|
||||||
"""Test GET/POST/PUT access to Organization resource"""
|
"""Test GET/POST/PUT access to Organization resource"""
|
||||||
|
Reference in New Issue
Block a user