Add ability for super users to rename and delete organizations
This commit is contained in:
parent
27d8ea3d5c
commit
3e1abba284
5 changed files with 230 additions and 1 deletions
|
@ -1118,6 +1118,7 @@ def change_username(user_id, new_username):
|
|||
# Rename the user
|
||||
user.username = new_username
|
||||
user.save()
|
||||
return user
|
||||
|
||||
|
||||
def change_invoice_email(user, invoice_email):
|
||||
|
@ -2623,6 +2624,10 @@ def delete_matching_notifications(target, kind_name, **kwargs):
|
|||
notification.delete_instance()
|
||||
|
||||
|
||||
def get_organizations():
|
||||
return User.select().where(User.organization == True, User.robot == False)
|
||||
|
||||
|
||||
def get_active_users():
|
||||
return User.select().where(User.organization == False, User.robot == False)
|
||||
|
||||
|
|
|
@ -103,6 +103,12 @@ class SuperUserLogs(ApiResource):
|
|||
abort(403)
|
||||
|
||||
|
||||
def org_view(org):
|
||||
return {
|
||||
'name': org.username,
|
||||
'avatar': avatar.get_data_for_org(org),
|
||||
}
|
||||
|
||||
def user_view(user):
|
||||
return {
|
||||
'username': user.username,
|
||||
|
@ -132,6 +138,25 @@ class UsageInformation(ApiResource):
|
|||
|
||||
|
||||
|
||||
@resource('/v1/superuser/organizations/')
|
||||
@internal_only
|
||||
@show_if(features.SUPER_USERS)
|
||||
class SuperUserOrganizationList(ApiResource):
|
||||
""" Resource for listing organizations in the system. """
|
||||
@require_fresh_login
|
||||
@verify_not_prod
|
||||
@nickname('listAllOrganizations')
|
||||
def get(self):
|
||||
""" Returns a list of all organizations in the system. """
|
||||
if SuperUserPermission().can():
|
||||
orgs = model.get_organizations()
|
||||
return {
|
||||
'organizations': [org_view(org) for org in orgs]
|
||||
}
|
||||
|
||||
abort(403)
|
||||
|
||||
|
||||
@resource('/v1/superuser/users/')
|
||||
@internal_only
|
||||
@show_if(features.SUPER_USERS)
|
||||
|
@ -248,6 +273,10 @@ class SuperUserManagement(ApiResource):
|
|||
'email': {
|
||||
'type': 'string',
|
||||
'description': 'The new e-mail address for the user',
|
||||
},
|
||||
'enabled': {
|
||||
'type': 'boolean',
|
||||
'description': 'Whether the user is enabled'
|
||||
}
|
||||
},
|
||||
},
|
||||
|
@ -309,3 +338,54 @@ class SuperUserManagement(ApiResource):
|
|||
return user_view(user)
|
||||
|
||||
abort(403)
|
||||
|
||||
|
||||
@resource('/v1/superuser/organizations/<name>')
|
||||
@path_param('name', 'The name of the organizaton being managed')
|
||||
@internal_only
|
||||
@show_if(features.SUPER_USERS)
|
||||
class SuperUserOrganizationManagement(ApiResource):
|
||||
""" Resource for managing organizations in the system. """
|
||||
schemas = {
|
||||
'UpdateOrg': {
|
||||
'id': 'UpdateOrg',
|
||||
'type': 'object',
|
||||
'description': 'Description of updates for an organization',
|
||||
'properties': {
|
||||
'name': {
|
||||
'type': 'string',
|
||||
'description': 'The new name for the organization',
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@require_fresh_login
|
||||
@verify_not_prod
|
||||
@nickname('deleteOrganization')
|
||||
def delete(self, name):
|
||||
""" Deletes the specified organization. """
|
||||
if SuperUserPermission().can():
|
||||
org = model.get_organization(name)
|
||||
|
||||
model.delete_user(org)
|
||||
return 'Deleted', 204
|
||||
|
||||
abort(403)
|
||||
|
||||
@require_fresh_login
|
||||
@verify_not_prod
|
||||
@nickname('changeOrganization')
|
||||
@validate_json_request('UpdateOrg')
|
||||
def put(self, name):
|
||||
""" Updates information about the specified user. """
|
||||
if SuperUserPermission().can():
|
||||
org = model.get_organization(name)
|
||||
org_data = request.get_json()
|
||||
|
||||
if 'name' in org_data:
|
||||
org = model.change_username(org.id, org_data['name'])
|
||||
|
||||
return org_view(org)
|
||||
|
||||
abort(403)
|
||||
|
|
|
@ -101,6 +101,21 @@
|
|||
$scope.logsCounter++;
|
||||
};
|
||||
|
||||
$scope.loadOrganizations = function() {
|
||||
if ($scope.organizations) {
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.loadOrganizationsInternal();
|
||||
};
|
||||
|
||||
$scope.loadOrganizationsInternal = function() {
|
||||
$scope.organizationsResource = ApiService.listAllOrganizationsAsResource().get(function(resp) {
|
||||
$scope.organizations = resp['organizations'];
|
||||
return $scope.organizations;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.loadUsers = function() {
|
||||
if ($scope.users) {
|
||||
return;
|
||||
|
@ -183,6 +198,40 @@
|
|||
}, ApiService.errorDisplay('Could not change user'));
|
||||
};
|
||||
|
||||
$scope.askDeleteOrganization = function(org) {
|
||||
bootbox.confirm('Are you sure you want to delete this organization? Its data will be deleted with it.',
|
||||
function(result) {
|
||||
if (!result) { return; }
|
||||
|
||||
var params = {
|
||||
'name': org.name
|
||||
};
|
||||
|
||||
ApiService.deleteOrganization(null, params).then(function(resp) {
|
||||
$scope.loadOrganizationsInternal();
|
||||
}, ApiService.errorDisplay('Could not delete organization'));
|
||||
});
|
||||
};
|
||||
|
||||
$scope.askRenameOrganization = function(org) {
|
||||
bootbox.prompt('Enter a new name for the organization:', function(newName) {
|
||||
if (!newName) { return; }
|
||||
|
||||
var params = {
|
||||
'name': org.name
|
||||
};
|
||||
|
||||
var data = {
|
||||
'name': newName
|
||||
};
|
||||
|
||||
ApiService.changeOrganization(data, params).then(function(resp) {
|
||||
$scope.loadOrganizationsInternal();
|
||||
org.name = newName;
|
||||
}, ApiService.errorDisplay('Could not rename organization'));
|
||||
});
|
||||
};
|
||||
|
||||
$scope.changeUserPassword = function(user) {
|
||||
$('#changePasswordModal').modal('hide');
|
||||
|
||||
|
|
|
@ -20,6 +20,10 @@
|
|||
tab-target="#users" tab-init="loadUsers()">
|
||||
<i class="fa fa-group"></i>
|
||||
</span>
|
||||
<span class="cor-tab" tab-title="Manage Organizations"
|
||||
tab-target="#organizations" tab-init="loadOrganizations()">
|
||||
<i class="fa fa-sitemap"></i>
|
||||
</span>
|
||||
<span class="cor-tab" tab-title="Dashboard" tab-target="#dashboard"
|
||||
tab-shown="setDashboardActive(true)" tab-hidden="setDashboardActive(false)">
|
||||
<i class="fa fa-tachometer"></i>
|
||||
|
@ -107,6 +111,45 @@
|
|||
For more information: <a href="https://coreos.com/products/enterprise-registry/plans/">See Here</a>.
|
||||
</div> <!-- /usage-counter tab-->
|
||||
|
||||
<!-- Organizations tab -->
|
||||
<div id="organizations" class="tab-pane">
|
||||
<div class="resource-view" resource="organizationsResource"
|
||||
error-message="'Could not load organizations'">
|
||||
<div class="manager-header" header-title="Organizations">
|
||||
</div>
|
||||
|
||||
<div class="filter-box" collection="organization" filter-model="search" filter-name="Organizations"></div>
|
||||
|
||||
<table class="cor-table">
|
||||
<thead>
|
||||
<td style="width: 24px;"></td>
|
||||
<td>Name</td>
|
||||
<td style="width: 24px;"></td>
|
||||
</thead>
|
||||
|
||||
<tr ng-repeat="current_org in (organizations | filter:search | orderBy:'name')"
|
||||
class="org-row">
|
||||
<td>
|
||||
<span class="avatar" data="current_org.avatar" size="24"></span>
|
||||
</td>
|
||||
<td>
|
||||
{{ current_org.name }}
|
||||
</td>
|
||||
<td style="text-align: center;">
|
||||
<span class="cor-options-menu">
|
||||
<span class="cor-option" option-click="askRenameOrganization(current_org)">
|
||||
<i class="fa fa-arrow-right"></i> Rename Organization
|
||||
</span>
|
||||
<span class="cor-option" option-click="askDeleteOrganization(current_org)">
|
||||
<i class="fa fa-times"></i> Delete Organization
|
||||
</span>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div> <!-- /resource -->
|
||||
</div> <!-- organizations tab -->
|
||||
|
||||
<!-- Users tab -->
|
||||
<div id="users" class="tab-pane active">
|
||||
<div class="cor-loader" ng-show="!users"></div>
|
||||
|
@ -168,6 +211,7 @@
|
|||
</table>
|
||||
</div> <!-- /show if users -->
|
||||
</div> <!-- users-tab -->
|
||||
|
||||
</div> <!-- /cor-tab-content -->
|
||||
</div> <!-- /cor-tab-panel -->
|
||||
|
||||
|
|
|
@ -45,7 +45,8 @@ from endpoints.api.permission import (RepositoryUserPermission, RepositoryTeamPe
|
|||
RepositoryTeamPermissionList, RepositoryUserPermissionList)
|
||||
|
||||
from endpoints.api.superuser import (SuperUserLogs, SuperUserList, SuperUserManagement,
|
||||
SuperUserSendRecoveryEmail, UsageInformation)
|
||||
SuperUserSendRecoveryEmail, UsageInformation,
|
||||
SuperUserOrganizationManagement, SuperUserOrganizationList)
|
||||
|
||||
|
||||
try:
|
||||
|
@ -3927,6 +3928,56 @@ class TestUsageInformation(ApiTestCase):
|
|||
self._run_test('GET', 200, 'devtable', None)
|
||||
|
||||
|
||||
class TestSuperUserOrganizationList(ApiTestCase):
|
||||
def setUp(self):
|
||||
ApiTestCase.setUp(self)
|
||||
self._set_url(SuperUserOrganizationList)
|
||||
|
||||
def test_get_anonymous(self):
|
||||
self._run_test('GET', 401, None, None)
|
||||
|
||||
def test_get_freshuser(self):
|
||||
self._run_test('GET', 403, 'freshuser', None)
|
||||
|
||||
def test_get_reader(self):
|
||||
self._run_test('GET', 403, 'reader', None)
|
||||
|
||||
def test_get_devtable(self):
|
||||
self._run_test('GET', 200, 'devtable', None)
|
||||
|
||||
|
||||
class TestSuperUserOrganizationManagement(ApiTestCase):
|
||||
def setUp(self):
|
||||
ApiTestCase.setUp(self)
|
||||
self._set_url(SuperUserOrganizationManagement, name='buynlarge')
|
||||
|
||||
def test_put_anonymous(self):
|
||||
self._run_test('PUT', 401, None, {})
|
||||
|
||||
def test_put_freshuser(self):
|
||||
self._run_test('PUT', 403, 'freshuser', {})
|
||||
|
||||
def test_put_reader(self):
|
||||
self._run_test('PUT', 403, 'reader', {})
|
||||
|
||||
def test_put_devtable(self):
|
||||
self._run_test('PUT', 200, 'devtable', {})
|
||||
|
||||
|
||||
def test_delete_anonymous(self):
|
||||
self._run_test('DELETE', 401, None, None)
|
||||
|
||||
def test_delete_freshuser(self):
|
||||
self._run_test('DELETE', 403, 'freshuser', None)
|
||||
|
||||
def test_delete_reader(self):
|
||||
self._run_test('DELETE', 403, 'reader', None)
|
||||
|
||||
def test_delete_devtable(self):
|
||||
self._run_test('DELETE', 204, 'devtable', None)
|
||||
|
||||
|
||||
|
||||
class TestSuperUserList(ApiTestCase):
|
||||
def setUp(self):
|
||||
ApiTestCase.setUp(self)
|
||||
|
|
Reference in a new issue