From 2ce5de16ee12731582fa28e1d4f914f6418fe97d Mon Sep 17 00:00:00 2001 From: lin-hua-cheng Date: Mon, 23 Nov 2015 10:22:15 -0800 Subject: [PATCH] Add support for identity provider protocol CRUD Add the protocol tab under the Identity Provider detail panel, this allows the user to manage the protocol on the context of the Identity Provider, Change-Id: I0e232b174382b1bc325e04cc343ae4d50e0cfed1 Implements: blueprint keystone-federation-protocol-mapping --- openstack_dashboard/api/keystone.py | 28 +++++++ .../identity_providers/protocols/__init__.py | 0 .../identity_providers/protocols/forms.py | 74 +++++++++++++++++ .../identity_providers/protocols/tables.py | 79 +++++++++++++++++++ .../identity_providers/protocols/tests.py | 79 +++++++++++++++++++ .../identity_providers/protocols/urls.py | 27 +++++++ .../identity_providers/protocols/views.py | 48 +++++++++++ .../identity/identity_providers/tables.py | 14 +++- .../identity/identity_providers/tabs.py | 48 +++++++++++ .../identity_providers/_detail_overview.html | 14 ++++ .../identity_providers/protocols/_create.html | 8 ++ .../identity_providers/protocols/create.html | 7 ++ .../identity/identity_providers/tests.py | 40 ++++++++++ .../identity/identity_providers/urls.py | 15 +++- .../identity/identity_providers/views.py | 48 +++++++++++ .../test/test_data/keystone_data.py | 9 +++ 16 files changed, 535 insertions(+), 3 deletions(-) create mode 100644 openstack_dashboard/dashboards/identity/identity_providers/protocols/__init__.py create mode 100644 openstack_dashboard/dashboards/identity/identity_providers/protocols/forms.py create mode 100644 openstack_dashboard/dashboards/identity/identity_providers/protocols/tables.py create mode 100644 openstack_dashboard/dashboards/identity/identity_providers/protocols/tests.py create mode 100644 openstack_dashboard/dashboards/identity/identity_providers/protocols/urls.py create mode 100644 openstack_dashboard/dashboards/identity/identity_providers/protocols/views.py create mode 100644 openstack_dashboard/dashboards/identity/identity_providers/tabs.py create mode 100644 openstack_dashboard/dashboards/identity/identity_providers/templates/identity_providers/_detail_overview.html create mode 100644 openstack_dashboard/dashboards/identity/identity_providers/templates/identity_providers/protocols/_create.html create mode 100644 openstack_dashboard/dashboards/identity/identity_providers/templates/identity_providers/protocols/create.html diff --git a/openstack_dashboard/api/keystone.py b/openstack_dashboard/api/keystone.py index 3c90970198..5a3e61a753 100644 --- a/openstack_dashboard/api/keystone.py +++ b/openstack_dashboard/api/keystone.py @@ -824,3 +824,31 @@ def mapping_delete(request, mapping_id): def mapping_list(request): manager = keystoneclient(request, admin=True).federation.mappings return manager.list() + + +def protocol_create(request, protocol_id, identity_provider, mapping): + manager = keystoneclient(request).federation.protocols + try: + return manager.create(protocol_id, identity_provider, mapping) + except keystone_exceptions.Conflict: + raise exceptions.Conflict() + + +def protocol_get(request, identity_provider, protocol): + manager = keystoneclient(request).federation.protocols + return manager.get(identity_provider, protocol) + + +def protocol_update(request, identity_provider, protocol, mapping): + manager = keystoneclient(request).federation.protocols + return manager.update(identity_provider, protocol, mapping) + + +def protocol_delete(request, identity_provider, protocol): + manager = keystoneclient(request).federation.protocols + return manager.delete(identity_provider, protocol) + + +def protocol_list(request, identity_provider): + manager = keystoneclient(request).federation.protocols + return manager.list(identity_provider) diff --git a/openstack_dashboard/dashboards/identity/identity_providers/protocols/__init__.py b/openstack_dashboard/dashboards/identity/identity_providers/protocols/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openstack_dashboard/dashboards/identity/identity_providers/protocols/forms.py b/openstack_dashboard/dashboards/identity/identity_providers/protocols/forms.py new file mode 100644 index 0000000000..757c930d36 --- /dev/null +++ b/openstack_dashboard/dashboards/identity/identity_providers/protocols/forms.py @@ -0,0 +1,74 @@ +# Copyright (C) 2015 Yahoo! 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. + +import logging + +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__) + + +class AddProtocolForm(forms.SelfHandlingForm): + idp_id = forms.CharField(label=_("Identity Provider ID"), + widget=forms.TextInput( + attrs={'readonly': 'readonly'})) + id = forms.CharField(label=_("Protocol ID")) + mapping_id = forms.ChoiceField(label=_("Mapping ID")) + + def __init__(self, request, *args, **kwargs): + super(AddProtocolForm, self).__init__(request, *args, **kwargs) + self.populate_mapping_id_choices(request) + + def populate_mapping_id_choices(self, request): + try: + mappings = api.keystone.mapping_list(request) + except Exception as e: + msg = _('Failed to get mapping list %s') % e + LOG.info(msg) + messages.error(request, msg) + + choices = [(m.id, m.id) for m in mappings] + choices.sort() + + if choices: + choices.insert(0, ("", _("Select Mapping"))) + else: + choices.insert(0, ("", _("No mappings available"))) + + self.fields['mapping_id'].choices = choices + + def handle(self, request, data): + try: + new_mapping = api.keystone.protocol_create( + request, + data["id"], + data["idp_id"], + data["mapping_id"]) + messages.success( + request, + _("Identity provider protocol created successfully.")) + return new_mapping + except exceptions.Conflict: + msg = _('Protocol ID "%s" is already used.') % data["id"] + messages.error(request, msg) + except Exception: + exceptions.handle( + request, + _("Unable to create identity provider protocol.")) diff --git a/openstack_dashboard/dashboards/identity/identity_providers/protocols/tables.py b/openstack_dashboard/dashboards/identity/identity_providers/protocols/tables.py new file mode 100644 index 0000000000..5f8943161f --- /dev/null +++ b/openstack_dashboard/dashboards/identity/identity_providers/protocols/tables.py @@ -0,0 +1,79 @@ +# Copyright (C) 2015 Yahoo! 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. + +import logging + +from django.core.urlresolvers import reverse +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 + +LOG = logging.getLogger(__name__) + + +class AddProtocol(policy.PolicyTargetMixin, tables.LinkAction): + name = "create" + verbose_name = _("Add Protocol") + url = "horizon:identity:identity_providers:protocols:create" + classes = ("ajax-modal",) + icon = "plus" + policy_rules = (("identity", "identity:create_protocol"),) + + def get_link_url(self, datum=None): + idp_id = self.table.kwargs['identity_provider_id'] + return reverse(self.url, args=(idp_id,)) + + +class RemoveProtocol(policy.PolicyTargetMixin, tables.DeleteAction): + @staticmethod + def action_present(count): + return ungettext_lazy( + u"Delete Protocol", + u"Delete Protocols", + count + ) + + @staticmethod + def action_past(count): + return ungettext_lazy( + u"Deleted Protocol", + u"Deleted Protocols", + count + ) + + policy_rules = (("identity", "identity:delete_protocol"),) + + def delete(self, request, obj_id): + identity_provider = self.table.kwargs['identity_provider_id'] + protocol = obj_id + api.keystone.protocol_delete(request, identity_provider, protocol) + + +class ProtocolsTable(tables.DataTable): + protocol = tables.Column("id", + verbose_name=_("Protocol ID")) + mapping = tables.Column("mapping_id", + verbose_name=_("Mapping ID")) + + def get_object_display(self, datum): + return datum.id + + class Meta(object): + name = "idp_protocols" + verbose_name = _("Protocols") + table_actions = (AddProtocol, RemoveProtocol) + row_actions = (RemoveProtocol, ) diff --git a/openstack_dashboard/dashboards/identity/identity_providers/protocols/tests.py b/openstack_dashboard/dashboards/identity/identity_providers/protocols/tests.py new file mode 100644 index 0000000000..3073ed8424 --- /dev/null +++ b/openstack_dashboard/dashboards/identity/identity_providers/protocols/tests.py @@ -0,0 +1,79 @@ +# Copyright (C) 2015 Yahoo! 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.core.urlresolvers import reverse +from django import http + +from mox3.mox import IgnoreArg # noqa +from mox3.mox import IsA # noqa + +from openstack_dashboard import api +from openstack_dashboard.test import helpers as test + + +IDPS_DETAIL_URL = reverse('horizon:identity:identity_providers:detail', + args=['idp_1']) +PROTOCOLS_CREATE_URL = reverse( + 'horizon:identity:identity_providers:protocols:create', + args=['idp_1']) + + +class ProtocolsViewTests(test.BaseAdminViewTests): + + @test.create_stubs({api.keystone: ('mapping_list', + 'protocol_create', )}) + def test_create(self): + idp = self.identity_providers.first() + protocol = self.idp_protocols.first() + + api.keystone.mapping_list(IgnoreArg()). \ + AndReturn(self.idp_mappings.list()) + api.keystone.protocol_create(IgnoreArg(), + protocol.id, + idp.id, + protocol.mapping_id). \ + AndReturn(protocol) + + self.mox.ReplayAll() + + formData = {'method': 'AddProtocolForm', + 'id': protocol.id, + 'idp_id': idp.id, + 'mapping_id': protocol.mapping_id} + res = self.client.post(PROTOCOLS_CREATE_URL, formData) + + self.assertNoFormErrors(res) + self.assertMessageCount(success=1) + + @test.create_stubs({api.keystone: ('identity_provider_get', + 'protocol_list', + 'protocol_delete')}) + def test_delete(self): + idp = self.identity_providers.first() + protocol = self.idp_protocols.first() + + api.keystone.identity_provider_get(IsA(http.HttpRequest), idp.id). \ + AndReturn(idp) + api.keystone.protocol_list(IsA(http.HttpRequest), idp.id). \ + AndReturn(self.idp_protocols.list()) + api.keystone.protocol_delete(IsA(http.HttpRequest), + idp.id, + protocol.id).AndReturn(None) + + self.mox.ReplayAll() + + formData = {'action': 'idp_protocols__delete__%s' % protocol.id} + res = self.client.post(IDPS_DETAIL_URL, formData) + + self.assertNoFormErrors(res) diff --git a/openstack_dashboard/dashboards/identity/identity_providers/protocols/urls.py b/openstack_dashboard/dashboards/identity/identity_providers/protocols/urls.py new file mode 100644 index 0000000000..db5d4c2dc0 --- /dev/null +++ b/openstack_dashboard/dashboards/identity/identity_providers/protocols/urls.py @@ -0,0 +1,27 @@ +# Copyright (C) 2015 Yahoo! 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.conf.urls import patterns +from django.conf.urls import url + +from openstack_dashboard.dashboards.identity.identity_providers.protocols \ + import views + +PORTS = r'^(?P[^/]+)/%s$' + +urlpatterns = patterns( + 'horizon.dashboards.identity.identity_providers.protocols.views', + url(r'^create/$', views.AddProtocolView.as_view(), name='create'), +) diff --git a/openstack_dashboard/dashboards/identity/identity_providers/protocols/views.py b/openstack_dashboard/dashboards/identity/identity_providers/protocols/views.py new file mode 100644 index 0000000000..3fe60d4d56 --- /dev/null +++ b/openstack_dashboard/dashboards/identity/identity_providers/protocols/views.py @@ -0,0 +1,48 @@ +# Copyright (C) 2015 Yahoo! 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.core.urlresolvers import reverse +from django.utils.translation import ugettext_lazy as _ + +from horizon import forms + +from openstack_dashboard.dashboards.identity.identity_providers.protocols \ + import forms as protocol_forms + + +class AddProtocolView(forms.ModalFormView): + template_name = 'identity/identity_providers/protocols/create.html' + modal_header = _("Create Protocol") + form_id = "create_protocol_form" + form_class = protocol_forms.AddProtocolForm + submit_label = _("Create Protocol") + success_url = "horizon:identity:identity_providers:protocols_tab" + page_title = _("Create Protocol") + + def __init__(self): + super(AddProtocolView, self).__init__() + + def get_success_url(self): + return reverse(self.success_url, + args=(self.kwargs['identity_provider_id'],)) + + def get_context_data(self, **kwargs): + context = super(AddProtocolView, self).get_context_data(**kwargs) + context["submit_url"] = reverse( + "horizon:identity:identity_providers:protocols:create", + args=(self.kwargs['identity_provider_id'],)) + return context + + def get_initial(self): + return {"idp_id": self.kwargs['identity_provider_id']} diff --git a/openstack_dashboard/dashboards/identity/identity_providers/tables.py b/openstack_dashboard/dashboards/identity/identity_providers/tables.py index a2476a8301..b25ba8b3c6 100644 --- a/openstack_dashboard/dashboards/identity/identity_providers/tables.py +++ b/openstack_dashboard/dashboards/identity/identity_providers/tables.py @@ -39,6 +39,14 @@ class EditIdPLink(tables.LinkAction): policy_rules = (("identity", "identity:update_identity_provider"),) +class ManageProtocolsLink(tables.LinkAction): + name = "manage_protocols" + verbose_name = _("Manage Protocols") + url = "horizon:identity:identity_providers:protocols_tab" + icon = "pencil" + policy_rules = (("identity", "identity:list_protocols"),) + + class DeleteIdPsAction(tables.DeleteAction): @staticmethod def action_present(count): @@ -70,7 +78,9 @@ class IdPFilterAction(tables.FilterAction): class IdentityProvidersTable(tables.DataTable): - id = tables.Column('id', verbose_name=_('Identity Provider ID')) + id = tables.Column('id', + verbose_name=_('Identity Provider ID'), + link="horizon:identity:identity_providers:detail") description = tables.Column(lambda obj: getattr(obj, 'description', None), verbose_name=_('Description')) enabled = tables.Column('enabled', verbose_name=_('Enabled'), status=True, @@ -87,5 +97,5 @@ class IdentityProvidersTable(tables.DataTable): class Meta(object): name = "identity_providers" verbose_name = _("Identity Providers") - row_actions = (EditIdPLink, DeleteIdPsAction) + row_actions = (ManageProtocolsLink, EditIdPLink, DeleteIdPsAction) table_actions = (IdPFilterAction, RegisterIdPLink, DeleteIdPsAction) diff --git a/openstack_dashboard/dashboards/identity/identity_providers/tabs.py b/openstack_dashboard/dashboards/identity/identity_providers/tabs.py new file mode 100644 index 0000000000..bd59733aea --- /dev/null +++ b/openstack_dashboard/dashboards/identity/identity_providers/tabs.py @@ -0,0 +1,48 @@ +# Copyright (C) 2015 Yahoo! 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 tabs + + +from openstack_dashboard.dashboards.identity.identity_providers.protocols \ + import tables as ptbl + + +class OverviewTab(tabs.Tab): + name = _("Overview") + slug = "overview" + template_name = "identity/identity_providers/_detail_overview.html" + + def get_context_data(self, request): + return { + "identity_provider": self.tab_group.kwargs['identity_provider'] + } + + +class ProtocolsTab(tabs.TableTab): + table_classes = (ptbl.ProtocolsTable,) + name = _("Protocols") + slug = "protocols" + template_name = "horizon/common/_detail_table.html" + + def get_idp_protocols_data(self): + return self.tab_group.kwargs['protocols'] + + +class IdPDetailTabs(tabs.TabGroup): + slug = "idp_details" + tabs = (OverviewTab, ProtocolsTab) + sticky = True diff --git a/openstack_dashboard/dashboards/identity/identity_providers/templates/identity_providers/_detail_overview.html b/openstack_dashboard/dashboards/identity/identity_providers/templates/identity_providers/_detail_overview.html new file mode 100644 index 0000000000..4e1a1f195a --- /dev/null +++ b/openstack_dashboard/dashboards/identity/identity_providers/templates/identity_providers/_detail_overview.html @@ -0,0 +1,14 @@ +{% load i18n sizeformat parse_date %} + +
+
+
{% trans "Identity Provider ID" %}
+
{{ identity_provider.id }}
+
{% trans "Remote IDs" %}
+
{{ identity_provider.remote_ids|join:", "|default:_("None") }}
+
{% trans "Description" %}
+
{{ identity_provider.description|default:_("None") }}
+
{% trans "Enabled" %}
+
{{ identity_provider.enabled|yesno|capfirst }}
+
+
diff --git a/openstack_dashboard/dashboards/identity/identity_providers/templates/identity_providers/protocols/_create.html b/openstack_dashboard/dashboards/identity/identity_providers/templates/identity_providers/protocols/_create.html new file mode 100644 index 0000000000..89e3eacfd0 --- /dev/null +++ b/openstack_dashboard/dashboards/identity/identity_providers/templates/identity_providers/protocols/_create.html @@ -0,0 +1,8 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block modal-body-right %} +

{% trans "Description:" %}

+

{% trans "Create a identity provider protocol." %}

+

{% blocktrans %} A protocol entry contains information that dictates which mapping rules to use for a given incoming request. An Identity Provider may have multiple supported protocols.{% endblocktrans %}

+{% endblock %} \ No newline at end of file diff --git a/openstack_dashboard/dashboards/identity/identity_providers/templates/identity_providers/protocols/create.html b/openstack_dashboard/dashboards/identity/identity_providers/templates/identity_providers/protocols/create.html new file mode 100644 index 0000000000..e12a92965b --- /dev/null +++ b/openstack_dashboard/dashboards/identity/identity_providers/templates/identity_providers/protocols/create.html @@ -0,0 +1,7 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Create Protocol" %}{% endblock %} + +{% block main %} + {% include 'identity/identity_providers/protocols/_create.html' %} +{% endblock %} diff --git a/openstack_dashboard/dashboards/identity/identity_providers/tests.py b/openstack_dashboard/dashboards/identity/identity_providers/tests.py index 6c2f7010a7..49532bf36a 100644 --- a/openstack_dashboard/dashboards/identity/identity_providers/tests.py +++ b/openstack_dashboard/dashboards/identity/identity_providers/tests.py @@ -26,6 +26,8 @@ IDPS_INDEX_URL = reverse('horizon:identity:identity_providers:index') IDPS_REGISTER_URL = reverse('horizon:identity:identity_providers:register') IDPS_UPDATE_URL = reverse('horizon:identity:identity_providers:update', args=['idp_1']) +IDPS_DETAIL_URL = reverse('horizon:identity:identity_providers:detail', + args=['idp_1']) class IdPsViewTests(test.BaseAdminViewTests): @@ -109,3 +111,41 @@ class IdPsViewTests(test.BaseAdminViewTests): res = self.client.post(IDPS_INDEX_URL, formData) self.assertNoFormErrors(res) + + @test.create_stubs({api.keystone: ('identity_provider_get', + 'protocol_list')}) + def test_detail(self): + idp = self.identity_providers.first() + + api.keystone.identity_provider_get(IsA(http.HttpRequest), idp.id). \ + AndReturn(idp) + api.keystone.protocol_list(IsA(http.HttpRequest), idp.id). \ + AndReturn(self.idp_protocols.list()) + + self.mox.ReplayAll() + + res = self.client.get(IDPS_DETAIL_URL) + + self.assertTemplateUsed( + res, 'identity/identity_providers/_detail_overview.html') + self.assertTemplateUsed(res, 'horizon/common/_detail_table.html') + + @test.create_stubs({api.keystone: ('identity_provider_get', + 'protocol_list')}) + def test_detail_protocols(self): + idp = self.identity_providers.first() + + api.keystone.identity_provider_get(IsA(http.HttpRequest), idp.id). \ + AndReturn(idp) + api.keystone.protocol_list(IsA(http.HttpRequest), idp.id). \ + AndReturn(self.idp_protocols.list()) + + self.mox.ReplayAll() + + res = self.client.get(IDPS_DETAIL_URL + '?tab=idp_details__protocols') + + self.assertTemplateUsed( + res, 'identity/identity_providers/_detail_overview.html') + self.assertTemplateUsed(res, 'horizon/common/_detail_table.html') + self.assertItemsEqual(res.context['idp_protocols_table'].data, + self.idp_protocols.list()) diff --git a/openstack_dashboard/dashboards/identity/identity_providers/urls.py b/openstack_dashboard/dashboards/identity/identity_providers/urls.py index bec5fbb8d4..9170b687d7 100644 --- a/openstack_dashboard/dashboards/identity/identity_providers/urls.py +++ b/openstack_dashboard/dashboards/identity/identity_providers/urls.py @@ -12,15 +12,28 @@ # License for the specific language governing permissions and limitations # under the License. +from django.conf.urls import include from django.conf.urls import patterns from django.conf.urls import url +from openstack_dashboard.dashboards.identity.identity_providers.protocols \ + import urls as protocol_urls from openstack_dashboard.dashboards.identity.identity_providers \ import views + urlpatterns = patterns( 'openstack_dashboard.dashboards.identity.identity_providers.views', url(r'^$', views.IndexView.as_view(), name='index'), + url(r'^(?P[^/]+)/detail/$', + views.DetailView.as_view(), name='detail'), + url(r'^(?P[^/]+)/detail/' + '\?tab=idp_details__protocols$', + views.DetailView.as_view(), + name='protocols_tab'), url(r'^(?P[^/]+)/update/$', views.UpdateView.as_view(), name='update'), - url(r'^register/$', views.RegisterView.as_view(), name='register')) + url(r'^register/$', views.RegisterView.as_view(), name='register'), + url(r'(?P[^/]+)/protocols/', + include(protocol_urls, namespace='protocols')), +) diff --git a/openstack_dashboard/dashboards/identity/identity_providers/views.py b/openstack_dashboard/dashboards/identity/identity_providers/views.py index 5a577cc65f..2a64c52cc6 100644 --- a/openstack_dashboard/dashboards/identity/identity_providers/views.py +++ b/openstack_dashboard/dashboards/identity/identity_providers/views.py @@ -20,6 +20,7 @@ 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 @@ -29,6 +30,8 @@ from openstack_dashboard.dashboards.identity.identity_providers \ import forms as idp_forms from openstack_dashboard.dashboards.identity.identity_providers \ import tables as idp_tables +from openstack_dashboard.dashboards.identity.identity_providers \ + import tabs as idp_tabs class IndexView(tables.DataTableView): @@ -53,6 +56,51 @@ class IndexView(tables.DataTableView): return idps +class DetailView(tabs.TabbedTableView): + tab_group_class = idp_tabs.IdPDetailTabs + template_name = 'horizon/common/_detail.html' + failure_url = reverse_lazy('horizon:identity:identity_providers:index') + page_title = "{{ identity_provider.id }}" + + @memoized.memoized_method + def _get_data(self): + try: + return api.keystone.identity_provider_get( + self.request, + self.kwargs['identity_provider_id']) + except Exception: + redirect = reverse("horizon:identity:identity_providers:index") + exceptions.handle(self.request, + _('Unable to retrieve identity provider' + ' information.'), + redirect=redirect) + + @memoized.memoized_method + def _get_protocols_data(self): + try: + return api.keystone.protocol_list( + self.request, + self.kwargs['identity_provider_id']) + except Exception: + redirect = reverse("horizon:identity:identity_providers:index") + exceptions.handle(self.request, + _('Unable to retrieve protocol list.'), + redirect=redirect) + + def get_context_data(self, **kwargs): + context = super(DetailView, self).get_context_data(**kwargs) + idp = self._get_data() + context["identity_provider"] = idp + return context + + def get_tabs(self, request, *args, **kwargs): + identity_provider = self._get_data() + protocols = self._get_protocols_data() + return self.tab_group_class(request, + identity_provider=identity_provider, + protocols=protocols, **kwargs) + + class UpdateView(forms.ModalFormView): template_name = 'identity/identity_providers/update.html' modal_header = _("Update Identity Provider") diff --git a/openstack_dashboard/test/test_data/keystone_data.py b/openstack_dashboard/test/test_data/keystone_data.py index c4a831ace1..1b700e582c 100644 --- a/openstack_dashboard/test/test_data/keystone_data.py +++ b/openstack_dashboard/test/test_data/keystone_data.py @@ -25,6 +25,7 @@ from keystoneclient.v2_0 import tenants from keystoneclient.v2_0 import users from keystoneclient.v3.contrib.federation import identity_providers from keystoneclient.v3.contrib.federation import mappings +from keystoneclient.v3.contrib.federation import protocols from keystoneclient.v3 import domains from keystoneclient.v3 import groups from keystoneclient.v3 import role_assignments @@ -148,6 +149,7 @@ def data(TEST): TEST.identity_providers = utils.TestDataContainer() TEST.idp_mappings = utils.TestDataContainer() + TEST.idp_protocols = utils.TestDataContainer() admin_role_dict = {'id': '1', 'name': 'admin'} @@ -425,3 +427,10 @@ def data(TEST): mappings.MappingManager(None), idp_mapping_dict) TEST.idp_mappings.add(idp_mapping) + + idp_protocol_dict_1 = {'id': 'protocol_1', + 'mapping_id': 'mapping_1'} + idp_protocol = protocols.Protocol( + protocols.ProtocolManager, + idp_protocol_dict_1) + TEST.idp_protocols.add(idp_protocol)