
Added a check if the user is trying to delete their own 'admin' role on the current project. This is added to prevent the users from locking their own access to Dashboard. Also, if the 'admin' role on the current project is removed, Dashboard automatically switches to another project for which the user has 'admin' role, but the previous project still is displayed as current. Fixes bug 1046538 Also improved the unit test scenarios a bit. Change-Id: I1e60a11d628d6490ad24a8149b43ac307afb4780
381 lines
15 KiB
Python
381 lines
15 KiB
Python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
|
|
# Copyright 2012 United States Government as represented by the
|
|
# Administrator of the National Aeronautics and Space Administration.
|
|
# All Rights Reserved.
|
|
#
|
|
# Copyright 2012 Nebula, Inc.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
|
|
from django.utils.translation import ugettext as _
|
|
from django.core.urlresolvers import reverse
|
|
|
|
from horizon import exceptions
|
|
from horizon import workflows
|
|
from horizon import forms
|
|
from horizon import messages
|
|
|
|
from openstack_dashboard import api
|
|
|
|
|
|
INDEX_URL = "horizon:admin:projects:index"
|
|
ADD_USER_URL = "horizon:admin:projects:create_user"
|
|
|
|
|
|
class UpdateProjectQuotaAction(workflows.Action):
|
|
ifcb_label = _("Injected File Content Bytes")
|
|
metadata_items = forms.IntegerField(min_value=0, label=_("Metadata Items"))
|
|
cores = forms.IntegerField(min_value=0, label=_("VCPUs"))
|
|
instances = forms.IntegerField(min_value=0, label=_("Instances"))
|
|
injected_files = forms.IntegerField(min_value=0, label=_("Injected Files"))
|
|
injected_file_content_bytes = forms.IntegerField(min_value=0,
|
|
label=ifcb_label)
|
|
volumes = forms.IntegerField(min_value=0, label=_("Volumes"))
|
|
gigabytes = forms.IntegerField(min_value=0, label=_("Gigabytes"))
|
|
ram = forms.IntegerField(min_value=0, label=_("RAM (MB)"))
|
|
floating_ips = forms.IntegerField(min_value=0, label=_("Floating IPs"))
|
|
|
|
class Meta:
|
|
name = _("Quota")
|
|
slug = 'update_quotas'
|
|
help_text = _("From here you can set quotas "
|
|
"(max limits) for the project.")
|
|
|
|
|
|
class UpdateProjectQuota(workflows.Step):
|
|
action_class = UpdateProjectQuotaAction
|
|
depends_on = ("project_id",)
|
|
contributes = ("metadata_items",
|
|
"cores",
|
|
"instances",
|
|
"injected_files",
|
|
"injected_file_content_bytes",
|
|
"volumes",
|
|
"gigabytes",
|
|
"ram",
|
|
"floating_ips")
|
|
|
|
|
|
class CreateProjectInfoAction(workflows.Action):
|
|
name = forms.CharField(label=_("Name"))
|
|
description = forms.CharField(
|
|
widget=forms.widgets.Textarea(),
|
|
label=_("Description"),
|
|
required=False)
|
|
enabled = forms.BooleanField(label=_("Enabled"),
|
|
required=False,
|
|
initial=True)
|
|
|
|
class Meta:
|
|
name = _("Project Info")
|
|
help_text = _("From here you can create a new "
|
|
"project to organize users.")
|
|
|
|
|
|
class CreateProjectInfo(workflows.Step):
|
|
action_class = CreateProjectInfoAction
|
|
contributes = ("project_id",
|
|
"name",
|
|
"description",
|
|
"enabled")
|
|
|
|
|
|
class UpdateProjectMembersAction(workflows.Action):
|
|
default_role = forms.CharField(required=False)
|
|
|
|
def __init__(self, request, *args, **kwargs):
|
|
super(UpdateProjectMembersAction, self).__init__(request,
|
|
*args,
|
|
**kwargs)
|
|
err_msg = _('Unable to retrieve user list. Please try again later.')
|
|
project_id = ''
|
|
if 'project_id' in args[0]:
|
|
project_id = args[0]['project_id']
|
|
|
|
# Get the default role
|
|
try:
|
|
default_role = api.get_default_role(self.request).id
|
|
except:
|
|
exceptions.handle(self.request,
|
|
err_msg,
|
|
redirect=reverse(INDEX_URL))
|
|
self.fields['default_role'].initial = default_role
|
|
|
|
# Get list of available users
|
|
all_users = []
|
|
try:
|
|
all_users = api.keystone.user_list(request)
|
|
except:
|
|
exceptions.handle(request, err_msg)
|
|
users_list = [(user.id, user.name) for user in all_users]
|
|
|
|
# Get list of roles
|
|
role_list = []
|
|
try:
|
|
role_list = api.keystone.role_list(request)
|
|
except:
|
|
exceptions.handle(request,
|
|
err_msg,
|
|
redirect=reverse(INDEX_URL))
|
|
for role in role_list:
|
|
field_name = "role_" + role.id
|
|
label = _(role.name)
|
|
self.fields[field_name] = forms.MultipleChoiceField(required=False,
|
|
label=label)
|
|
self.fields[field_name].choices = users_list
|
|
self.fields[field_name].initial = []
|
|
|
|
# Figure out users & roles
|
|
if project_id:
|
|
for user in all_users:
|
|
try:
|
|
roles = api.roles_for_user(self.request,
|
|
user.id,
|
|
project_id)
|
|
except:
|
|
exceptions.handle(request,
|
|
err_msg,
|
|
redirect=reverse(INDEX_URL))
|
|
if roles:
|
|
primary_role = roles[0].id
|
|
self.fields["role_" + primary_role].initial.append(user.id)
|
|
|
|
class Meta:
|
|
name = _("Project Members")
|
|
slug = "update_members"
|
|
|
|
|
|
class UpdateProjectMembers(workflows.Step):
|
|
action_class = UpdateProjectMembersAction
|
|
template_name = "admin/projects/_update_members.html"
|
|
|
|
def contribute(self, data, context):
|
|
if data:
|
|
try:
|
|
roles = api.keystone.role_list(self.workflow.request)
|
|
except:
|
|
exceptions.handle(self.workflow.request,
|
|
_('Unable to retrieve user list.'))
|
|
|
|
post = self.workflow.request.POST
|
|
for role in roles:
|
|
field = "role_" + role.id
|
|
context[field] = post.getlist(field)
|
|
return context
|
|
|
|
|
|
class CreateProject(workflows.Workflow):
|
|
slug = "add_project"
|
|
name = _("Add Project")
|
|
finalize_button_name = _("Finish")
|
|
success_message = _('Created new project "%s".')
|
|
failure_message = _('Unable to create project "%s".')
|
|
success_url = "horizon:admin:projects:index"
|
|
default_steps = (CreateProjectInfo,
|
|
UpdateProjectMembers,
|
|
UpdateProjectQuota)
|
|
|
|
def format_status_message(self, message):
|
|
return message % self.context.get('name', 'unknown project')
|
|
|
|
def handle(self, request, data):
|
|
# create the project
|
|
try:
|
|
desc = data['description']
|
|
self.object = api.keystone.tenant_create(request,
|
|
tenant_name=data['name'],
|
|
description=desc,
|
|
enabled=data['enabled'])
|
|
except:
|
|
exceptions.handle(request, ignore=True)
|
|
return False
|
|
|
|
project_id = self.object.id
|
|
|
|
# update project members
|
|
users_to_add = 0
|
|
try:
|
|
available_roles = api.keystone.role_list(request)
|
|
|
|
# count how many users are to be added
|
|
for role in available_roles:
|
|
role_list = data["role_" + role.id]
|
|
users_to_add += len(role_list)
|
|
# add new users to project
|
|
for role in available_roles:
|
|
role_list = data["role_" + role.id]
|
|
users_added = 0
|
|
for user in role_list:
|
|
api.add_tenant_user_role(request,
|
|
tenant_id=project_id,
|
|
user_id=user,
|
|
role_id=role.id)
|
|
users_added += 1
|
|
users_to_add -= users_added
|
|
except:
|
|
exceptions.handle(request, _('Failed to add %s project members '
|
|
'and set project quotas.'
|
|
% users_to_add))
|
|
|
|
# update the project quota
|
|
ifcb = data['injected_file_content_bytes']
|
|
try:
|
|
api.nova.tenant_quota_update(request,
|
|
project_id,
|
|
metadata_items=data['metadata_items'],
|
|
injected_file_content_bytes=ifcb,
|
|
volumes=data['volumes'],
|
|
gigabytes=data['gigabytes'],
|
|
ram=data['ram'],
|
|
floating_ips=data['floating_ips'],
|
|
instances=data['instances'],
|
|
injected_files=data['injected_files'],
|
|
cores=data['cores'])
|
|
except:
|
|
exceptions.handle(request, _('Unable to set project quotas.'))
|
|
return True
|
|
|
|
|
|
class UpdateProjectInfoAction(CreateProjectInfoAction):
|
|
enabled = forms.BooleanField(required=False, label=_("Enabled"))
|
|
|
|
class Meta:
|
|
name = _("Project Info")
|
|
slug = 'update_info'
|
|
help_text = _("From here you can edit the project details.")
|
|
|
|
|
|
class UpdateProjectInfo(workflows.Step):
|
|
action_class = UpdateProjectInfoAction
|
|
depends_on = ("project_id",)
|
|
contributes = ("name",
|
|
"description",
|
|
"enabled")
|
|
|
|
|
|
class UpdateProject(workflows.Workflow):
|
|
slug = "update_project"
|
|
name = _("Edit Project")
|
|
finalize_button_name = _("Save")
|
|
success_message = _('Modified project "%s".')
|
|
failure_message = _('Unable to modify project "%s".')
|
|
success_url = "horizon:admin:projects:index"
|
|
default_steps = (UpdateProjectInfo,
|
|
UpdateProjectMembers,
|
|
UpdateProjectQuota)
|
|
|
|
def format_status_message(self, message):
|
|
return message % self.context.get('name', 'unknown project')
|
|
|
|
def handle(self, request, data):
|
|
project_id = data['project_id']
|
|
# update project info
|
|
try:
|
|
api.tenant_update(request,
|
|
tenant_id=project_id,
|
|
tenant_name=data['name'],
|
|
description=data['description'],
|
|
enabled=data['enabled'])
|
|
except:
|
|
exceptions.handle(request, ignore=True)
|
|
return False
|
|
|
|
# update project members
|
|
users_to_modify = 0
|
|
try:
|
|
available_roles = api.keystone.role_list(request)
|
|
project_members = api.keystone.user_list(request,
|
|
tenant_id=project_id)
|
|
users_to_modify = len(project_members)
|
|
for user in project_members:
|
|
current_roles = [role for role in
|
|
api.roles_for_user(self.request,
|
|
user.id,
|
|
project_id)]
|
|
effective_roles = []
|
|
for role in available_roles:
|
|
role_list = data["role_" + role.id]
|
|
if user.id in role_list:
|
|
effective_roles.append(role)
|
|
if role not in current_roles:
|
|
# user role has changed
|
|
api.add_tenant_user_role(request,
|
|
tenant_id=project_id,
|
|
user_id=user.id,
|
|
role_id=role.id)
|
|
else:
|
|
# user role is unchanged
|
|
current_roles.pop(current_roles.index(role))
|
|
if user.id == request.user.id and \
|
|
project_id == request.user.tenant_id and \
|
|
any(x.name == 'admin' for x in current_roles):
|
|
# Cannot remove "admin" role on current(admin) project
|
|
msg = _('You cannot remove the "admin" role from the '
|
|
'project you are currently logged into. Please '
|
|
'switch to another project with admin permissions '
|
|
'or remove the role manually via the CLI')
|
|
messages.warning(request, msg)
|
|
else:
|
|
# delete user's removed roles
|
|
for to_delete in current_roles:
|
|
api.remove_tenant_user_role(request,
|
|
tenant_id=project_id,
|
|
user_id=user.id,
|
|
role_id=to_delete.id)
|
|
users_to_modify -= 1
|
|
|
|
# add new roles to project
|
|
for role in available_roles:
|
|
# count how many users may be added for exception handling
|
|
role_list = data["role_" + role.id]
|
|
users_to_modify += len(role_list)
|
|
for role in available_roles:
|
|
role_list = data["role_" + role.id]
|
|
users_added = 0
|
|
for user_id in role_list:
|
|
if not filter(lambda x: user_id == x.id, project_members):
|
|
api.add_tenant_user_role(request,
|
|
tenant_id=project_id,
|
|
user_id=user_id,
|
|
role_id=role.id)
|
|
users_added += 1
|
|
users_to_modify -= users_added
|
|
except:
|
|
exceptions.handle(request, _('Failed to modify %s project members '
|
|
'and update project quotas.'
|
|
% users_to_modify))
|
|
return True
|
|
|
|
# update the project quota
|
|
ifcb = data['injected_file_content_bytes']
|
|
try:
|
|
api.tenant_quota_update(request,
|
|
project_id,
|
|
metadata_items=data['metadata_items'],
|
|
injected_file_content_bytes=ifcb,
|
|
volumes=data['volumes'],
|
|
gigabytes=data['gigabytes'],
|
|
ram=data['ram'],
|
|
floating_ips=data['floating_ips'],
|
|
instances=data['instances'],
|
|
injected_files=data['injected_files'],
|
|
cores=data['cores'])
|
|
return True
|
|
except:
|
|
exceptions.handle(request, _('Modified project information and '
|
|
'members, but unable to modify '
|
|
'project quotas.'))
|
|
return True
|