Add RBAC policies feature to horizon dashboard

Add RBAC Policies panel to support Role-Based Access Control
functionality.

Implements: blueprint rbac-policies
Change-Id: I883ad629d735dadf49e8bf9c50475050fdfcf797
This commit is contained in:
shutingm 2018-11-21 14:44:30 +08:00
parent c4515d1bf3
commit d65ebe2054
20 changed files with 830 additions and 0 deletions

View File

@ -1645,6 +1645,7 @@ Default:
'enable_ha_router': False, 'enable_ha_router': False,
'enable_ipv6': True, 'enable_ipv6': True,
'enable_quotas': False, 'enable_quotas': False,
'enable_rbac_policy': True,
'enable_router': True, 'enable_router': True,
'extra_provider_types': {}, 'extra_provider_types': {},
'physical_networks': [], 'physical_networks': [],
@ -1749,6 +1750,16 @@ Enable support for Neutron quotas feature. To make this feature work
appropriately, you need to use Neutron plugins with quotas extension support appropriately, you need to use Neutron plugins with quotas extension support
and quota_driver should be DbQuotaDriver (default config). and quota_driver should be DbQuotaDriver (default config).
enable_rbac_policy
##################
.. versionadded:: 15.0.0(Stein)
Default: ``True``
Set this to True to enable RBAC Policies panel that provide the ability for
users to use RBAC function. This option only affects when Neutron is enabled.
enable_router enable_router
############# #############

View File

@ -1988,3 +1988,59 @@ def list_availability_zones(request, resource=None, state=None):
az_list = [az for az in az_list if az['state'] == state] az_list = [az for az in az_list if az['state'] == state]
return sorted(az_list, key=lambda zone: zone['name']) return sorted(az_list, key=lambda zone: zone['name'])
class RBACPolicy(NeutronAPIDictWrapper):
"""Wrapper for neutron RBAC Policy."""
def rbac_policy_create(request, **kwargs):
"""Create a RBAC Policy.
:param request: request context
:param target_tenant: target tenant of the policy
:param tenant_id: owner tenant of the policy(Not recommended)
:param object_type: network or qos_policy
:param object_id: object id of policy
:param action: access_as_shared or access_as_external
:return: RBACPolicy object
"""
body = {'rbac_policy': kwargs}
rbac_policy = neutronclient(request).create_rbac_policy(
body=body).get('rbac_policy')
return RBACPolicy(rbac_policy)
def rbac_policy_list(request, **kwargs):
"""List of RBAC Policies."""
policies = neutronclient(request).list_rbac_policies(
**kwargs).get('rbac_policies')
return [RBACPolicy(p) for p in policies]
def rbac_policy_update(request, policy_id, **kwargs):
"""Update a RBAC Policy.
:param request: request context
:param policy_id: target policy id
:param target_tenant: target tenant of the policy
:return: RBACPolicy object
"""
body = {'rbac_policy': kwargs}
rbac_policy = neutronclient(request).update_rbac_policy(
policy_id, body=body).get('rbac_policy')
return RBACPolicy(rbac_policy)
@profiler.trace
def rbac_policy_get(request, policy_id, **kwargs):
"""Get RBAC policy for a given policy id."""
policy = neutronclient(request).show_rbac_policy(
policy_id, **kwargs).get('rbac_policy')
return RBACPolicy(policy)
@profiler.trace
def rbac_policy_delete(request, policy_id):
"""Delete RBAC policy for a given policy id."""
neutronclient(request).delete_rbac_policy(policy_id)

View File

@ -0,0 +1,170 @@
# Copyright 2019 vmware, 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.
import logging
from django.urls import reverse
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import forms
from horizon import messages
from openstack_dashboard import api
LOG = logging.getLogger(__name__)
# Predefined provider types.
ACTIONS = [
{
'name': 'access_as_shared',
'value': _('Access as Shared')
},
{
'name': 'access_as_external',
'value': _('Access as External')
}
]
# Predefined provider object types.
OBJECT_TYPES = [
{
'name': 'network',
'value': _('Network')
}
]
QOS_POLICY_TYPE = {
'name': 'qos_policy',
'value': _('QoS Policy')
}
class CreatePolicyForm(forms.SelfHandlingForm):
target_tenant = forms.ThemableChoiceField(label=_("Target Project"))
object_type = forms.ThemableChoiceField(
label=_("Object Type"),
widget=forms.ThemableSelectWidget(
attrs={
'class': 'switchable',
'data-slug': 'object_type'
}))
network_id = forms.ThemableChoiceField(
label=_("Network"),
widget=forms.ThemableSelectWidget(attrs={
'class': 'switched',
'data-switch-on': 'object_type',
}),
required=False)
qos_policy_id = forms.ThemableChoiceField(
label=_("QoS Policy"),
widget=forms.ThemableSelectWidget(attrs={
'class': 'switched',
'data-switch-on': 'object_type',
}),
required=False)
action = forms.ThemableChoiceField(label=_("Action"))
def __init__(self, request, *args, **kwargs):
super(CreatePolicyForm, self).__init__(request, *args, **kwargs)
tenant_choices = [('', _("Select a project"))]
tenants, has_more = api.keystone.tenant_list(request)
tenant_choices.append(("*", "*"))
for tenant in tenants:
tenant_choices.append((tenant.id, tenant.name))
self.fields['target_tenant'].choices = tenant_choices
action_choices = [('', _("Select an action"))]
for action in ACTIONS:
action_choices.append((action['name'],
action['value']))
self.fields['action'].choices = action_choices
network_choices = []
networks = api.neutron.network_list(request)
for network in networks:
network_choices.append((network.id, network.name))
self.fields['network_id'].choices = network_choices
# If enable QoS Policy
if api.neutron.is_extension_supported(request, extension_alias='qos'):
qos_policies = api.neutron.policy_list(request)
qos_choices = [(qos_policy['id'], qos_policy['name'])
for qos_policy in qos_policies]
self.fields['qos_policy_id'].choices = qos_choices
if QOS_POLICY_TYPE not in OBJECT_TYPES:
OBJECT_TYPES.append(QOS_POLICY_TYPE)
object_type_choices = [('', _("Select an object type"))]
for object_type in OBJECT_TYPES:
object_type_choices.append((object_type['name'],
object_type['value']))
self.fields['object_type'].choices = object_type_choices
# Register object types which required
self.fields['network_id'].widget.attrs.update(
{'data-object_type-network': _('Network')})
self.fields['qos_policy_id'].widget.attrs.update(
{'data-object_type-qos_policy': _('QoS Policy')})
def handle(self, request, data):
try:
params = {
'target_tenant': data['target_tenant'],
'action': data['action'],
'object_type': data['object_type'],
}
if data['object_type'] == 'network':
params['object_id'] = data['network_id']
elif data['object_type'] == 'qos_policy':
params['object_id'] = data['qos_policy_id']
rbac_policy = api.neutron.rbac_policy_create(request, **params)
msg = _('RBAC Policy was successfully created.')
messages.success(request, msg)
return rbac_policy
except Exception:
redirect = reverse('horizon:admin:rbac_policies:index')
msg = _('Failed to create a rbac policy.')
exceptions.handle(request, msg, redirect=redirect)
return False
class UpdatePolicyForm(forms.SelfHandlingForm):
target_tenant = forms.ThemableChoiceField(label=_("Target Project"))
failure_url = 'horizon:admin:rbac_policies:index'
def __init__(self, request, *args, **kwargs):
super(UpdatePolicyForm, self).__init__(request, *args, **kwargs)
tenant_choices = [('', _("Select a project"))]
tenants, has_more = api.keystone.tenant_list(request)
for tenant in tenants:
tenant_choices.append((tenant.id, tenant.name))
self.fields['target_tenant'].choices = tenant_choices
def handle(self, request, data):
try:
params = {'target_tenant': data['target_tenant']}
rbac_policy = api.neutron.rbac_policy_update(
request, self.initial['rbac_policy_id'], **params)
msg = _('RBAC Policy %s was successfully updated.') \
% self.initial['rbac_policy_id']
messages.success(request, msg)
return rbac_policy
except Exception as e:
LOG.info('Failed to update rbac policy %(id)s: %(exc)s',
{'id': self.initial['rbac_policy_id'], 'exc': e})
msg = _('Failed to update rbac policy %s') \
% self.initial['rbac_policy_id']
redirect = reverse(self.failure_url)
exceptions.handle(request, msg, redirect=redirect)

View File

@ -0,0 +1,44 @@
# 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.
import logging
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
import horizon
from openstack_dashboard.api import neutron
LOG = logging.getLogger(__name__)
class RBACPolicies(horizon.Panel):
name = _("RBAC Policies")
slug = "rbac_policies"
permissions = ('openstack.services.network',)
policy_rules = (("network", "context_is_admin"),)
def allowed(self, context):
request = context['request']
network_config = getattr(settings, 'OPENSTACK_NEUTRON_NETWORK', {})
try:
return (
network_config.get('enable_rbac_policy', True) and
neutron.is_extension_supported(request,
extension_alias='rbac-policies')
)
except Exception:
LOG.error("Call to list enabled services failed. This is likely "
"due to a problem communicating with the Neutron "
"endpoint. RBAC Policies panel will not be displayed.")
return False

View File

@ -0,0 +1,82 @@
# Copyright 2019 vmware, 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_lazy as _
from django.utils.translation import ungettext_lazy
from horizon import tables
from openstack_dashboard import api
from openstack_dashboard import policy
class CreateRBACPolicy(policy.PolicyTargetMixin, tables.LinkAction):
name = "create"
verbose_name = _("Create RBAC Policy")
url = "horizon:admin:rbac_policies:create"
classes = ("ajax-modal",)
icon = "plus"
policy_rules = (("network", "create_rbac_policy"),)
class DeleteRBACPolicy(policy.PolicyTargetMixin, tables.DeleteAction):
help_text = _("Deleted RBAC policy is not recoverable.")
@staticmethod
def action_present(count):
return ungettext_lazy(
u"Delete RBAC Policy",
u"Delete RBAC Policies",
count
)
@staticmethod
def action_past(count):
return ungettext_lazy(
u"Deleted RBAC Policy",
u"Deleted RBAC Policies",
count
)
policy_rules = (("network", "delete_rbac_policy"),)
def delete(self, request, obj_id):
api.neutron.rbac_policy_delete(request, obj_id)
class UpdateRBACPolicy(policy.PolicyTargetMixin, tables.LinkAction):
name = "update"
verbose_name = _("Edit Policy")
url = "horizon:admin:rbac_policies:update"
classes = ("ajax-modal",)
icon = "pencil"
policy_rules = (("network", "update_rbac_policy"),)
class RBACPoliciesTable(tables.DataTable):
tenant = tables.Column("tenant_name", verbose_name=_("Project"))
id = tables.WrappingColumn('id',
verbose_name=_('ID'),
link="horizon:admin:rbac_policies:detail")
object_type = tables.WrappingColumn('object_type',
verbose_name=_('Object Type'))
object_name = tables.Column("object_name", verbose_name=_("Object"))
target_tenant = tables.Column("target_tenant_name",
verbose_name=_("Target Project"))
class Meta(object):
name = "rbac policies"
verbose_name = _("RBAC Policies")
table_actions = (CreateRBACPolicy, DeleteRBACPolicy,)
row_actions = (UpdateRBACPolicy, DeleteRBACPolicy)

View File

@ -0,0 +1,57 @@
# Copyright 2019 vmware, Inc.
# All Rights Reserved.
#
# 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_lazy as _
from horizon import exceptions
from horizon import tabs
from horizon.utils import memoized
from openstack_dashboard import api
class OverviewTab(tabs.Tab):
name = _("Overview")
slug = "overview"
template_name = "admin/rbac_policies/_detail_overview.html"
preload = False
@memoized.memoized_method
def _get_data(self):
rbac_policy = {}
rbac_policy_id = None
try:
rbac_policy_id = self.tab_group.kwargs['rbac_policy_id']
rbac_policy = api.neutron.rbac_policy_get(self.request,
rbac_policy_id)
except Exception:
msg = _('Unable to retrieve details for rbac_policy "%s".') \
% (rbac_policy_id)
exceptions.handle(self.request, msg)
return rbac_policy
def get_context_data(self, request, **kwargs):
context = super(OverviewTab, self).get_context_data(request, **kwargs)
rbac_policy = self._get_data()
context["rbac_policy"] = rbac_policy
return context
class RBACDetailsTabs(tabs.DetailTabsGroup):
slug = "rbac_policy_tabs"
tabs = (OverviewTab, )
sticky = True

View File

@ -0,0 +1,7 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% block modal-body-right %}
<h3>{% trans "Description:" %}</h3>
<p>{% trans "From here you can create a rbac policy." %}</p>
{% endblock %}

View File

@ -0,0 +1,18 @@
{% load i18n sizeformat %}
<div class="info detail">
<dl class="dl-horizontal">
<dt title="{% trans 'ID' %}">{% trans "ID" %}</dt>
<dd>{{ rbac_policy.id|default:_("None") }}</dd>
<dt title="{% trans 'Project ID' %}">{% trans "Project ID" %}</dt>
<dd>{{ rbac_policy.project_id|default:_("-") }}</dd>
<dt title="{% trans 'Object Type' %}">{% trans "Object Type" %}</dt>
<dd>{{ rbac_policy.object_type|default:_("Unknown") }}</dd>
<dt title="{% trans 'Object ID' %}">{% trans "Object ID" %}</dt>
<dd>{{ rbac_policy.object_id|default:_("Unknown") }}</dd>
<dt title="{% trans 'Action' %}">{% trans "Action" %}</dt>
<dd>{{ rbac_policy.action|default:_("Unknown") }}</dd>
<dt title="{% trans 'Target Tenant' %}">{% trans "Target Tenant" %}</dt>
<dd>{{ rbac_policy.target_tenant|default:_("None") }}</dd>
</dl>
</div>

View File

@ -0,0 +1,7 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% block modal-body-right %}
<h3>{% trans "Description:" %}</h3>
<p>{% trans "You may update the editable properties of the RBAC policy here." %}</p>
{% endblock %}

View File

@ -0,0 +1,7 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Create a RBAC Policy" %}{% endblock %}
{% block main %}
{% include 'admin/rbac_policies/_create.html' %}
{% endblock %}

View File

@ -0,0 +1,20 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "RBAC Policy Details" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_detail_header.html" %}
{% endblock %}
{% block main %}
<div class="row">
<div class="col-sm-12">
<hr>
<div class="row">
<div class="col-sm-12">
{{ tab_group.render }}
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,7 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Update RBAC Policy" %}{% endblock %}
{% block main %}
{% include 'project/rbac_policies/_update.html' %}
{% endblock %}

View File

@ -0,0 +1,122 @@
# Copyright 2019 vmware, 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.urls import reverse
from openstack_dashboard import api
from openstack_dashboard.test import helpers as test
INDEX_TEMPLATE = 'horizon/common/_data_table_view.html'
INDEX_URL = reverse('horizon:admin:rbac_policies:index')
class RBACPolicyTests(test.BaseAdminViewTests):
@test.create_mocks({api.neutron: ('rbac_policy_list',
'network_list',
'policy_list',
'is_extension_supported',),
api.keystone: ('tenant_list',)})
def test_index(self):
tenants = self.tenants.list()
self.mock_tenant_list.return_value = [tenants, False]
self.mock_network_list.return_value = self.networks.list()
self.mock_policy_list.return_value = self.qos_policies.list()
self.mock_rbac_policy_list.return_value = self.rbac_policies.list()
self.mock_is_extension_supported.return_value = True
res = self.client.get(INDEX_URL)
self.assertTemplateUsed(res, INDEX_TEMPLATE)
rbac_policies = res.context['table'].data
self.assertItemsEqual(rbac_policies, self.rbac_policies.list())
self.mock_network_list.assert_called_once_with(test.IsHttpRequest())
self.mock_policy_list.assert_called_once_with(test.IsHttpRequest())
self.mock_tenant_list.assert_called_once_with(test.IsHttpRequest())
self.mock_is_extension_supported.assert_called_once_with(
test.IsHttpRequest(), extension_alias='qos')
self.mock_rbac_policy_list.assert_called_once_with(
test.IsHttpRequest())
@test.create_mocks({api.neutron: ('network_list',
'rbac_policy_create',
'is_extension_supported',),
api.keystone: ('tenant_list',)})
def test_rbac_create_post_with_network_type(self):
network = self.networks.first()
tenants = self.tenants.list()
rbac_policy = self.rbac_policies.first()
self.mock_tenant_list.return_value = [tenants, False]
self.mock_network_list.return_value = self.networks.list()
self.mock_is_extension_supported.return_value = False
self.mock_rbac_policy_create.return_value = rbac_policy
form_data = {'target_tenant': rbac_policy.target_tenant,
'action': 'access_as_external',
'object_type': 'network',
'network_id': network.id}
url = reverse('horizon:admin:rbac_policies:create')
res = self.client.post(url, form_data)
self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res, INDEX_URL)
self.mock_tenant_list.assert_called_once_with(test.IsHttpRequest())
self.mock_network_list.assert_called_once_with(test.IsHttpRequest())
self.mock_is_extension_supported.assert_called_once_with(
test.IsHttpRequest(), extension_alias='qos')
params = {'target_tenant': rbac_policy.target_tenant,
'action': 'access_as_external',
'object_type': 'network',
'object_id': network.id}
self.mock_rbac_policy_create.assert_called_once_with(
test.IsHttpRequest(), **params)
@test.create_mocks({api.neutron: ('network_list',
'policy_list',
'rbac_policy_create',
'is_extension_supported',),
api.keystone: ('tenant_list',)})
def test_rbac_create_post_with_qos_policy_type(self):
qos_policy = self.qos_policies.first()
tenants = self.tenants.list()
rbac_policy = self.rbac_policies.filter(object_type="qos_policy")[0]
self.mock_tenant_list.return_value = [tenants, False]
self.mock_network_list.return_value = self.networks.list()
self.mock_policy_list.return_value = self.qos_policies.list()
self.mock_is_extension_supported.return_value = True
self.mock_rbac_policy_create.return_value = rbac_policy
form_data = {'target_tenant': rbac_policy.target_tenant,
'action': 'access_as_shared',
'object_type': 'qos_policy',
'qos_policy_id': qos_policy.id}
url = reverse('horizon:admin:rbac_policies:create')
res = self.client.post(url, form_data)
self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res, INDEX_URL)
self.mock_tenant_list.assert_called_once_with(test.IsHttpRequest())
self.mock_network_list.assert_called_once_with(test.IsHttpRequest())
self.mock_policy_list.assert_called_once_with(test.IsHttpRequest())
self.mock_is_extension_supported.assert_called_once_with(
test.IsHttpRequest(), extension_alias='qos')
params = {'target_tenant': rbac_policy.target_tenant,
'action': 'access_as_shared',
'object_type': 'qos_policy',
'object_id': qos_policy.id}
self.mock_rbac_policy_create.assert_called_once_with(
test.IsHttpRequest(), **params)

View File

@ -0,0 +1,30 @@
# 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.conf.urls import url
from openstack_dashboard.dashboards.admin.rbac_policies import views
RBAC_POLICY_URL = r'^(?P<rbac_policy_id>[^/]+)/%s$'
urlpatterns = [
url(r'^$', views.IndexView.as_view(), name='index'),
url(r'^create/$', views.CreateView.as_view(), name='create'),
url(RBAC_POLICY_URL % '$',
views.DetailView.as_view(),
name='detail'),
url(RBAC_POLICY_URL % 'update',
views.UpdateView.as_view(),
name='update'),
]

View File

@ -0,0 +1,144 @@
# 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 collections import OrderedDict
from django.urls import reverse
from django.urls import reverse_lazy
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import forms
from horizon import messages
from horizon import tables
from horizon import tabs
from horizon.utils import memoized
from openstack_dashboard import api
from openstack_dashboard.dashboards.admin.rbac_policies \
import forms as rbac_policy_forms
from openstack_dashboard.dashboards.admin.rbac_policies \
import tables as rbac_policy_tables
from openstack_dashboard.dashboards.admin.rbac_policies \
import tabs as rbac_policy_tabs
class IndexView(tables.DataTableView):
table_class = rbac_policy_tables.RBACPoliciesTable
page_title = _("RBAC Policies")
@memoized.memoized_method
def _get_tenants(self):
try:
tenants, has_more = api.keystone.tenant_list(self.request)
except Exception:
tenants = []
msg = _("Unable to retrieve information about the "
"policies' projects.")
exceptions.handle(self.request, msg)
tenant_dict = OrderedDict([(t.id, t.name) for t in tenants])
return tenant_dict
def _get_networks(self):
try:
networks = api.neutron.network_list(self.request)
except Exception:
networks = []
msg = _("Unable to retrieve information about the "
"policies' networks.")
exceptions.handle(self.request, msg)
return dict([(n.id, n.name) for n in networks])
def _get_qos_policies(self):
qos_policies = []
try:
if api.neutron.is_extension_supported(self.request,
extension_alias='qos'):
qos_policies = api.neutron.policy_list(self.request)
except Exception:
msg = _("Unable to retrieve information about the "
"policies' qos policies.")
exceptions.handle(self.request, msg)
return dict([(q.id, q.name) for q in qos_policies])
def get_data(self):
try:
rbac_policies = api.neutron.rbac_policy_list(self.request)
except Exception:
rbac_policies = []
messages.error(self.request,
_("Unable to retrieve RBAC policies."))
if rbac_policies:
tenant_dict = self._get_tenants()
network_dict = self._get_networks()
qos_policy_dict = self._get_qos_policies()
for p in rbac_policies:
# Set tenant name and object name
p.tenant_name = tenant_dict.get(p.tenant_id, p.tenant_id)
p.target_tenant_name = tenant_dict.get(p.target_tenant,
p.target_tenant)
if p.object_type == "network":
p.object_name = network_dict.get(p.object_id, p.object_id)
elif p.object_type == "qos_policy":
p.object_name = qos_policy_dict.get(p.object_id,
p.object_id)
return rbac_policies
class CreateView(forms.ModalFormView):
template_name = 'admin/rbac_policies/create.html'
form_id = "create_rbac_policy_form"
form_class = rbac_policy_forms.CreatePolicyForm
submit_label = _("Create RBAC Policy")
submit_url = reverse_lazy("horizon:admin:rbac_policies:create")
success_url = reverse_lazy("horizon:admin:rbac_policies:index")
page_title = _("Create A RBAC Policy")
class UpdateView(forms.ModalFormView):
context_object_name = 'rbac_policies'
template_name = 'admin/rbac_policies/update.html'
form_class = rbac_policy_forms.UpdatePolicyForm
form_id = "update_rbac_policy_form"
submit_label = _("Save Changes")
submit_url = 'horizon:admin:rbac_policies:update'
success_url = reverse_lazy('horizon:admin:rbac_policies:index')
page_title = _("Update RBAC Policy")
def get_context_data(self, **kwargs):
context = super(UpdateView, self).get_context_data(**kwargs)
args = (self.kwargs['rbac_policy_id'],)
context["rbac_policy_id"] = self.kwargs['rbac_policy_id']
context["submit_url"] = reverse(self.submit_url, args=args)
return context
@memoized.memoized_method
def _get_object(self, *args, **kwargs):
rbac_policy_id = self.kwargs['rbac_policy_id']
try:
return api.neutron.rbac_policy_get(self.request, rbac_policy_id)
except Exception:
redirect = self.success_url
msg = _('Unable to retrieve rbac policy details.')
exceptions.handle(self.request, msg, redirect=redirect)
def get_initial(self):
rbac_policy = self._get_object()
return {'rbac_policy_id': rbac_policy['id'],
'target_tenant': rbac_policy['target_tenant']}
class DetailView(tabs.TabView):
tab_group_class = rbac_policy_tabs.RBACDetailsTabs
template_name = 'horizon/common/_detail.html'
page_title = "{{ rbac_policy.id }}"

View File

@ -0,0 +1,10 @@
# The slug of the panel to be added to HORIZON_CONFIG. Required.
PANEL = 'rbac_policies'
# The slug of the dashboard the PANEL associated with. Required.
PANEL_DASHBOARD = 'admin'
# The slug of the panel group the PANEL is associated with.
PANEL_GROUP = 'network'
# Python panel class of the PANEL to be added.
ADD_PANEL = ('openstack_dashboard.dashboards.admin.'
'rbac_policies.panel.RBACPolicies')

View File

@ -300,6 +300,11 @@ TEST_GLOBAL_MOCKS_ON_PANELS = {
'.network_qos.panel.NetworkQoS.can_access'), '.network_qos.panel.NetworkQoS.can_access'),
'return_value': True, 'return_value': True,
}, },
'rbac_policies': {
'method': ('openstack_dashboard.dashboards.admin'
'.rbac_policies.panel.RBACPolicies.can_access'),
'return_value': True,
},
'server_groups': { 'server_groups': {
'method': ('openstack_dashboard.dashboards.project' 'method': ('openstack_dashboard.dashboards.project'
'.server_groups.panel.ServerGroups.can_access'), '.server_groups.panel.ServerGroups.can_access'),

View File

@ -45,6 +45,7 @@ def data(TEST):
TEST.neutron_quota_usages = utils.TestDataContainer() TEST.neutron_quota_usages = utils.TestDataContainer()
TEST.ip_availability = utils.TestDataContainer() TEST.ip_availability = utils.TestDataContainer()
TEST.qos_policies = utils.TestDataContainer() TEST.qos_policies = utils.TestDataContainer()
TEST.rbac_policies = utils.TestDataContainer()
TEST.tp_ports = utils.TestDataContainer() TEST.tp_ports = utils.TestDataContainer()
TEST.neutron_availability_zones = utils.TestDataContainer() TEST.neutron_availability_zones = utils.TestDataContainer()
@ -66,6 +67,7 @@ def data(TEST):
TEST.api_monitors = utils.TestDataContainer() TEST.api_monitors = utils.TestDataContainer()
TEST.api_extensions = utils.TestDataContainer() TEST.api_extensions = utils.TestDataContainer()
TEST.api_ip_availability = utils.TestDataContainer() TEST.api_ip_availability = utils.TestDataContainer()
TEST.api_rbac_policies = utils.TestDataContainer()
TEST.api_qos_policies = utils.TestDataContainer() TEST.api_qos_policies = utils.TestDataContainer()
TEST.api_tp_trunks = utils.TestDataContainer() TEST.api_tp_trunks = utils.TestDataContainer()
TEST.api_tp_ports = utils.TestDataContainer() TEST.api_tp_ports = utils.TestDataContainer()
@ -773,6 +775,26 @@ def data(TEST):
TEST.api_qos_policies.add(policy_dict1) TEST.api_qos_policies.add(policy_dict1)
TEST.qos_policies.add(neutron.QoSPolicy(policy_dict1)) TEST.qos_policies.add(neutron.QoSPolicy(policy_dict1))
# rbac policies
rbac_policy_dict = {"project_id": "1",
"object_type": "network",
"id": "7f27e61a-9863-448a-a769-eb922fdef3f8",
"object_id": "82288d84-e0a5-42ac-95be-e6af08727e42",
"target_tenant": "2",
"action": "access_as_external",
"tenant_id": "1"}
TEST.api_rbac_policies.add(rbac_policy_dict)
TEST.rbac_policies.add(neutron.RBACPolicy(rbac_policy_dict))
rbac_policy_dict1 = {"project_id": "1",
"object_type": "qos_policy",
"id": "7f27e61a-9863-448a-a769-eb922fdef3f8",
"object_id": "a21dcd22-7189-cccc-aa32-22adafaf16a7",
"target_tenant": "2",
"action": "access_as_shared",
"tenant_id": "1"}
TEST.api_rbac_policies.add(rbac_policy_dict1)
TEST.rbac_policies.add(neutron.RBACPolicy(rbac_policy_dict1))
# TRUNKPORT # TRUNKPORT
# #
# The test setup was created by the following command sequence: # The test setup was created by the following command sequence:

View File

@ -0,0 +1,11 @@
---
features:
- >
[`blueprint neutron-rbac-policies <https://blueprints.launchpad.net/horizon/+spec/rbac-policies>`_]
This blueprint adds RBAC policies panel to the Admin Network group.
This panel will be enabled by default when the RBAC extension is
enabled. Remove this panel by setting "'enable_rbac_policy': False"
in 'local_settings.py'. RBAC policy supports the control of two
resources: networks and qos policies, because qos policies is
an extension function of neutron, need to enable this extension
if wants to use it.