Add ability for super users to rename and delete organizations

This commit is contained in:
Joseph Schorr 2015-05-11 18:03:25 -04:00
parent 27d8ea3d5c
commit 3e1abba284
5 changed files with 230 additions and 1 deletions

View file

@ -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)

View file

@ -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)

View file

@ -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');

View file

@ -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 -->

View file

@ -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)