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
|
# Rename the user
|
||||||
user.username = new_username
|
user.username = new_username
|
||||||
user.save()
|
user.save()
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
def change_invoice_email(user, invoice_email):
|
def change_invoice_email(user, invoice_email):
|
||||||
|
@ -2623,6 +2624,10 @@ def delete_matching_notifications(target, kind_name, **kwargs):
|
||||||
notification.delete_instance()
|
notification.delete_instance()
|
||||||
|
|
||||||
|
|
||||||
|
def get_organizations():
|
||||||
|
return User.select().where(User.organization == True, User.robot == False)
|
||||||
|
|
||||||
|
|
||||||
def get_active_users():
|
def get_active_users():
|
||||||
return User.select().where(User.organization == False, User.robot == False)
|
return User.select().where(User.organization == False, User.robot == False)
|
||||||
|
|
||||||
|
|
|
@ -103,6 +103,12 @@ class SuperUserLogs(ApiResource):
|
||||||
abort(403)
|
abort(403)
|
||||||
|
|
||||||
|
|
||||||
|
def org_view(org):
|
||||||
|
return {
|
||||||
|
'name': org.username,
|
||||||
|
'avatar': avatar.get_data_for_org(org),
|
||||||
|
}
|
||||||
|
|
||||||
def user_view(user):
|
def user_view(user):
|
||||||
return {
|
return {
|
||||||
'username': user.username,
|
'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/')
|
@resource('/v1/superuser/users/')
|
||||||
@internal_only
|
@internal_only
|
||||||
@show_if(features.SUPER_USERS)
|
@show_if(features.SUPER_USERS)
|
||||||
|
@ -248,6 +273,10 @@ class SuperUserManagement(ApiResource):
|
||||||
'email': {
|
'email': {
|
||||||
'type': 'string',
|
'type': 'string',
|
||||||
'description': 'The new e-mail address for the user',
|
'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)
|
return user_view(user)
|
||||||
|
|
||||||
abort(403)
|
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.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() {
|
$scope.loadUsers = function() {
|
||||||
if ($scope.users) {
|
if ($scope.users) {
|
||||||
return;
|
return;
|
||||||
|
@ -183,6 +198,40 @@
|
||||||
}, ApiService.errorDisplay('Could not change user'));
|
}, 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) {
|
$scope.changeUserPassword = function(user) {
|
||||||
$('#changePasswordModal').modal('hide');
|
$('#changePasswordModal').modal('hide');
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,10 @@
|
||||||
tab-target="#users" tab-init="loadUsers()">
|
tab-target="#users" tab-init="loadUsers()">
|
||||||
<i class="fa fa-group"></i>
|
<i class="fa fa-group"></i>
|
||||||
</span>
|
</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"
|
<span class="cor-tab" tab-title="Dashboard" tab-target="#dashboard"
|
||||||
tab-shown="setDashboardActive(true)" tab-hidden="setDashboardActive(false)">
|
tab-shown="setDashboardActive(true)" tab-hidden="setDashboardActive(false)">
|
||||||
<i class="fa fa-tachometer"></i>
|
<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>.
|
For more information: <a href="https://coreos.com/products/enterprise-registry/plans/">See Here</a>.
|
||||||
</div> <!-- /usage-counter tab-->
|
</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 -->
|
<!-- Users tab -->
|
||||||
<div id="users" class="tab-pane active">
|
<div id="users" class="tab-pane active">
|
||||||
<div class="cor-loader" ng-show="!users"></div>
|
<div class="cor-loader" ng-show="!users"></div>
|
||||||
|
@ -168,6 +211,7 @@
|
||||||
</table>
|
</table>
|
||||||
</div> <!-- /show if users -->
|
</div> <!-- /show if users -->
|
||||||
</div> <!-- users-tab -->
|
</div> <!-- users-tab -->
|
||||||
|
|
||||||
</div> <!-- /cor-tab-content -->
|
</div> <!-- /cor-tab-content -->
|
||||||
</div> <!-- /cor-tab-panel -->
|
</div> <!-- /cor-tab-panel -->
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,8 @@ from endpoints.api.permission import (RepositoryUserPermission, RepositoryTeamPe
|
||||||
RepositoryTeamPermissionList, RepositoryUserPermissionList)
|
RepositoryTeamPermissionList, RepositoryUserPermissionList)
|
||||||
|
|
||||||
from endpoints.api.superuser import (SuperUserLogs, SuperUserList, SuperUserManagement,
|
from endpoints.api.superuser import (SuperUserLogs, SuperUserList, SuperUserManagement,
|
||||||
SuperUserSendRecoveryEmail, UsageInformation)
|
SuperUserSendRecoveryEmail, UsageInformation,
|
||||||
|
SuperUserOrganizationManagement, SuperUserOrganizationList)
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -3927,6 +3928,56 @@ class TestUsageInformation(ApiTestCase):
|
||||||
self._run_test('GET', 200, 'devtable', None)
|
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):
|
class TestSuperUserList(ApiTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
ApiTestCase.setUp(self)
|
ApiTestCase.setUp(self)
|
||||||
|
|
Reference in a new issue