diff --git a/doc/source/topics/settings.rst b/doc/source/topics/settings.rst index e53042a5f..8f7dba527 100644 --- a/doc/source/topics/settings.rst +++ b/doc/source/topics/settings.rst @@ -745,6 +745,19 @@ are using HTTPS, running your Keystone server on a nonstandard port, or using a nonstandard URL scheme you shouldn't need to touch this setting. +``OPENSTACK_KEYSTONE_FEDERATION_MANAGEMENT`` +-------------------------------------------- + +.. versionadded:: 9.0.0(Mitaka) + +Default: ``False`` + +Set this to True to enable panels that provide the ability for users to manage +Identity Providers (IdPs) and establish a set of rules to map federation protocol +attributes to Identity API attributes. This extension requires v3.0+ of the +Identity API. + + ``WEBSSO_ENABLED`` ------------------ diff --git a/openstack_dashboard/api/keystone.py b/openstack_dashboard/api/keystone.py index bbc41c4c3..0ccfde57c 100644 --- a/openstack_dashboard/api/keystone.py +++ b/openstack_dashboard/api/keystone.py @@ -752,3 +752,46 @@ def keystone_backend_name(): def get_version(): return VERSIONS.active + + +def is_federation_management_enabled(): + return getattr(settings, 'OPENSTACK_KEYSTONE_FEDERATION_MANAGEMENT', False) + + +def identity_provider_create(request, idp_id, description=None, + enabled=False, remote_ids=None): + manager = keystoneclient(request, admin=True).federation.identity_providers + try: + return manager.create(id=idp_id, + description=description, + enabled=enabled, + remote_ids=remote_ids) + except keystone_exceptions.Conflict: + raise exceptions.Conflict() + + +def identity_provider_get(request, idp_id): + manager = keystoneclient(request, admin=True).federation.identity_providers + return manager.get(idp_id) + + +def identity_provider_update(request, idp_id, description=None, + enabled=False, remote_ids=None): + manager = keystoneclient(request, admin=True).federation.identity_providers + try: + return manager.update(idp_id, + description=description, + enabled=enabled, + remote_ids=remote_ids) + except keystone_exceptions.Conflict: + raise exceptions.Conflict() + + +def identity_provider_delete(request, idp_id): + manager = keystoneclient(request, admin=True).federation.identity_providers + return manager.delete(idp_id) + + +def identity_provider_list(request): + manager = keystoneclient(request, admin=True).federation.identity_providers + return manager.list() diff --git a/openstack_dashboard/dashboards/identity/identity_providers/__init__.py b/openstack_dashboard/dashboards/identity/identity_providers/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/openstack_dashboard/dashboards/identity/identity_providers/forms.py b/openstack_dashboard/dashboards/identity/identity_providers/forms.py new file mode 100644 index 000000000..cb2ef9475 --- /dev/null +++ b/openstack_dashboard/dashboards/identity/identity_providers/forms.py @@ -0,0 +1,114 @@ +# 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 exceptions +from horizon import forms +from horizon import messages + +from openstack_dashboard import api + + +class RegisterIdPForm(forms.SelfHandlingForm): + id = forms.CharField( + label=_("Identity Provider ID"), + max_length=64, + help_text=_("User-defined unique id to identify the identity " + "provider.")) + remote_ids = forms.CharField( + label=_("Remote IDs"), + required=False, + help_text=_("Comma-delimited list of valid remote IDs from the " + "identity provider.")) + description = forms.CharField( + label=_("Description"), + widget=forms.widgets.Textarea(attrs={'rows': 4}), + required=False) + enabled = forms.BooleanField( + label=_("Enabled"), + required=False, + help_text=_("Indicates whether this identity provider should accept " + "federated authentication requests."), + initial=True) + + def handle(self, request, data): + try: + remote_ids = data["remote_ids"] or [] + if remote_ids: + remote_ids = [rid.strip() for rid in remote_ids.split(',')] + new_idp = api.keystone.identity_provider_create( + request, + data["id"], + description=data["description"], + enabled=data["enabled"], + remote_ids=remote_ids) + messages.success(request, + _("Identity provider registered successfully.")) + return new_idp + except exceptions.Conflict: + msg = _("Unable to register identity provider. Please check that " + "the Identity Provider ID and Remote IDs provided are " + "not already in use.") + messages.error(request, msg) + except Exception: + exceptions.handle(request, + _("Unable to register identity provider.")) + return False + + +class UpdateIdPForm(forms.SelfHandlingForm): + id = forms.CharField( + label=_("Identity Provider ID"), + widget=forms.TextInput(attrs={'readonly': 'readonly'}), + help_text=_("User-defined unique id to identify the identity " + "provider.")) + remote_ids = forms.CharField( + label=_("Remote IDs"), + required=False, + help_text=_("Comma-delimited list of valid remote IDs from the " + "identity provider.")) + description = forms.CharField( + label=_("Description"), + widget=forms.widgets.Textarea(attrs={'rows': 4}), + required=False) + enabled = forms.BooleanField( + label=_("Enabled"), + required=False, + help_text=_("Indicates whether this identity provider should accept " + "federated authentication requests."), + initial=True) + + def handle(self, request, data): + try: + remote_ids = data["remote_ids"] or [] + if remote_ids: + remote_ids = [rid.strip() for rid in remote_ids.split(',')] + api.keystone.identity_provider_update( + request, + data['id'], + description=data["description"], + enabled=data["enabled"], + remote_ids=remote_ids) + messages.success(request, + _("Identity provider updated successfully.")) + return True + except exceptions.Conflict: + msg = _("Unable to update identity provider. Please check that " + "the Remote IDs provided are not already in use.") + messages.error(request, msg) + except Exception: + exceptions.handle(request, + _('Unable to update identity provider.')) + return False diff --git a/openstack_dashboard/dashboards/identity/identity_providers/panel.py b/openstack_dashboard/dashboards/identity/identity_providers/panel.py new file mode 100644 index 000000000..30a7825ee --- /dev/null +++ b/openstack_dashboard/dashboards/identity/identity_providers/panel.py @@ -0,0 +1,30 @@ +# 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 _ + +import horizon + +from openstack_dashboard.api import keystone + + +class IdentityProviders(horizon.Panel): + name = _("Identity Providers") + slug = 'identity_providers' + policy_rules = (("identity", "identity:list_identity_providers"),) + + @staticmethod + def can_register(): + return (keystone.VERSIONS.active >= 3 and + keystone.is_federation_management_enabled()) diff --git a/openstack_dashboard/dashboards/identity/identity_providers/tables.py b/openstack_dashboard/dashboards/identity/identity_providers/tables.py new file mode 100644 index 000000000..745af1227 --- /dev/null +++ b/openstack_dashboard/dashboards/identity/identity_providers/tables.py @@ -0,0 +1,88 @@ +# 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.template import defaultfilters as filters +from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import ungettext_lazy + +from horizon import tables + +from openstack_dashboard import api + + +class RegisterIdPLink(tables.LinkAction): + name = "register" + verbose_name = _("Register Identity Provider") + url = "horizon:identity:identity_providers:register" + classes = ("ajax-modal",) + icon = "plus" + policy_rules = (("identity", "identity:create_identity_provider"),) + + +class EditIdPLink(tables.LinkAction): + name = "edit" + verbose_name = _("Edit") + url = "horizon:identity:identity_providers:update" + classes = ("ajax-modal",) + icon = "pencil" + policy_rules = (("identity", "identity:update_identity_provider"),) + + +class DeleteIdPsAction(tables.DeleteAction): + @staticmethod + def action_present(count): + return ungettext_lazy( + u"Unregister Identity Provider", + u"Unregister Identity Providers", + count + ) + + @staticmethod + def action_past(count): + return ungettext_lazy( + u"Unregistered Identity Provider", + u"Unregistered Identity Providers", + count + ) + policy_rules = (("identity", "identity:delete_identity_provider"),) + + def delete(self, request, obj_id): + api.keystone.identity_provider_delete(request, obj_id) + + +class IdPFilterAction(tables.FilterAction): + def filter(self, table, idps, filter_string): + """Naive case-insensitive search.""" + q = filter_string.lower() + return [idp for idp in idps + if q in idp.ud.lower()] + + +class IdentityProvidersTable(tables.DataTable): + id = tables.Column('id', verbose_name=_('Identity Provider ID')) + description = tables.Column(lambda obj: getattr(obj, 'description', None), + verbose_name=_('Description')) + enabled = tables.Column('enabled', verbose_name=_('Enabled'), status=True, + filters=(filters.yesno, filters.capfirst)) + remote_ids = tables.Column( + lambda obj: getattr(obj, 'remote_ids', []), + verbose_name=_("Remote IDs"), + wrap_list=True, + filters=(filters.unordered_list,)) + + class Meta(object): + name = "identity_providers" + verbose_name = _("Identity Providers") + row_actions = (EditIdPLink, DeleteIdPsAction) + table_actions = (IdPFilterAction, RegisterIdPLink, DeleteIdPsAction) diff --git a/openstack_dashboard/dashboards/identity/identity_providers/templates/identity_providers/_register.html b/openstack_dashboard/dashboards/identity/identity_providers/templates/identity_providers/_register.html new file mode 100644 index 000000000..9eb46a3cc --- /dev/null +++ b/openstack_dashboard/dashboards/identity/identity_providers/templates/identity_providers/_register.html @@ -0,0 +1,9 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block modal-body-right %} +

{% trans "Description:" %}

+

{% trans "Register a identity provider that is trusted by the Identity API to authenticate identities." %}

+

{% blocktrans %}Remote IDs are associated with the identity provider and are globally unique. This indicates the header attributes to use for mapping federation protocol attributes to Identity API objects. If no value is provided, the list will be set to empty.{% endblocktrans %}

+

{% blocktrans %}Example: For mod_shib this would be Shib-Identity-Provider, for mod_auth_openidc, this could be HTTP_OIDC_ISS.{% endblocktrans %}

+{% endblock %} \ No newline at end of file diff --git a/openstack_dashboard/dashboards/identity/identity_providers/templates/identity_providers/_update.html b/openstack_dashboard/dashboards/identity/identity_providers/templates/identity_providers/_update.html new file mode 100644 index 000000000..b7682214b --- /dev/null +++ b/openstack_dashboard/dashboards/identity/identity_providers/templates/identity_providers/_update.html @@ -0,0 +1,8 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block modal-body-right %} +

{% trans "Description:" %}

+

{% trans "Edit the identity provider's details." %}

+

{% blocktrans %}Remote IDs are associated with the identity provider and are globally unique. This indicates the header attributes to use for mapping federation protocol attributes to Identity API objects. If no value is provided, the list will be set to empty.{% endblocktrans %}

+{% endblock %} diff --git a/openstack_dashboard/dashboards/identity/identity_providers/templates/identity_providers/index.html b/openstack_dashboard/dashboards/identity/identity_providers/templates/identity_providers/index.html new file mode 100644 index 000000000..24609ff1f --- /dev/null +++ b/openstack_dashboard/dashboards/identity/identity_providers/templates/identity_providers/index.html @@ -0,0 +1,7 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Identity Providers" %}{% endblock %} + +{% block main %} + {{ table.render }} +{% endblock %} diff --git a/openstack_dashboard/dashboards/identity/identity_providers/templates/identity_providers/register.html b/openstack_dashboard/dashboards/identity/identity_providers/templates/identity_providers/register.html new file mode 100644 index 000000000..c2708c7a8 --- /dev/null +++ b/openstack_dashboard/dashboards/identity/identity_providers/templates/identity_providers/register.html @@ -0,0 +1,7 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Register Identity Provider" %}{% endblock %} + +{% block main %} + {% include 'identity/identity_providers/_register.html' %} +{% endblock %} diff --git a/openstack_dashboard/dashboards/identity/identity_providers/templates/identity_providers/update.html b/openstack_dashboard/dashboards/identity/identity_providers/templates/identity_providers/update.html new file mode 100644 index 000000000..b8bacff25 --- /dev/null +++ b/openstack_dashboard/dashboards/identity/identity_providers/templates/identity_providers/update.html @@ -0,0 +1,7 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Update Identity Provider" %}{% endblock %} + +{% block main %} + {% include 'identity/identity_providers/_update.html' %} +{% endblock %} diff --git a/openstack_dashboard/dashboards/identity/identity_providers/tests.py b/openstack_dashboard/dashboards/identity/identity_providers/tests.py new file mode 100644 index 000000000..6c2f7010a --- /dev/null +++ b/openstack_dashboard/dashboards/identity/identity_providers/tests.py @@ -0,0 +1,111 @@ +# 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_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']) + + +class IdPsViewTests(test.BaseAdminViewTests): + @test.create_stubs({api.keystone: ('identity_provider_list',)}) + def test_index(self): + api.keystone.identity_provider_list(IgnoreArg()). \ + AndReturn(self.identity_providers.list()) + + self.mox.ReplayAll() + + res = self.client.get(IDPS_INDEX_URL) + + self.assertTemplateUsed(res, 'identity/identity_providers/index.html') + self.assertItemsEqual(res.context['table'].data, + self.identity_providers.list()) + + @test.create_stubs({api.keystone: ('identity_provider_create', )}) + def test_create(self): + idp = self.identity_providers.first() + + api.keystone.identity_provider_create(IgnoreArg(), + idp.id, + description=idp.description, + enabled=idp.enabled, + remote_ids=idp.remote_ids). \ + AndReturn(idp) + + self.mox.ReplayAll() + + formData = {'method': 'RegisterIdPForm', + 'id': idp.id, + 'description': idp.description, + 'enabled': idp.enabled, + 'remote_ids': ', '.join(idp.remote_ids)} + res = self.client.post(IDPS_REGISTER_URL, formData) + + self.assertNoFormErrors(res) + self.assertMessageCount(success=1) + + @test.create_stubs({api.keystone: ('identity_provider_get', + 'identity_provider_update')}) + def test_update(self): + idp = self.identity_providers.first() + new_description = 'new_idp_desc' + + api.keystone.identity_provider_get(IsA(http.HttpRequest), idp.id). \ + AndReturn(idp) + api.keystone.identity_provider_update(IsA(http.HttpRequest), + idp.id, + description=new_description, + enabled=idp.enabled, + remote_ids=idp.remote_ids). \ + AndReturn(None) + + self.mox.ReplayAll() + + formData = {'method': 'UpdateIdPForm', + 'id': idp.id, + 'description': new_description, + 'enabled': idp.enabled, + 'remote_ids': ', '.join(idp.remote_ids)} + + res = self.client.post(IDPS_UPDATE_URL, formData) + + self.assertNoFormErrors(res) + self.assertMessageCount(success=1) + + @test.create_stubs({api.keystone: ('identity_provider_list', + 'identity_provider_delete')}) + def test_delete(self): + idp = self.identity_providers.first() + + api.keystone.identity_provider_list(IsA(http.HttpRequest)) \ + .AndReturn(self.identity_providers.list()) + api.keystone.identity_provider_delete(IsA(http.HttpRequest), + idp.id).AndReturn(None) + + self.mox.ReplayAll() + + formData = {'action': 'identity_providers__delete__%s' % idp.id} + res = self.client.post(IDPS_INDEX_URL, formData) + + self.assertNoFormErrors(res) diff --git a/openstack_dashboard/dashboards/identity/identity_providers/urls.py b/openstack_dashboard/dashboards/identity/identity_providers/urls.py new file mode 100644 index 000000000..bec5fbb8d --- /dev/null +++ b/openstack_dashboard/dashboards/identity/identity_providers/urls.py @@ -0,0 +1,26 @@ +# 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 \ + import views + +urlpatterns = patterns( + 'openstack_dashboard.dashboards.identity.identity_providers.views', + url(r'^$', views.IndexView.as_view(), name='index'), + url(r'^(?P[^/]+)/update/$', + views.UpdateView.as_view(), name='update'), + url(r'^register/$', views.RegisterView.as_view(), name='register')) diff --git a/openstack_dashboard/dashboards/identity/identity_providers/views.py b/openstack_dashboard/dashboards/identity/identity_providers/views.py new file mode 100644 index 000000000..5a577cc65 --- /dev/null +++ b/openstack_dashboard/dashboards/identity/identity_providers/views.py @@ -0,0 +1,101 @@ +# 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.core.urlresolvers 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.utils import memoized + +from openstack_dashboard import api +from openstack_dashboard import policy + +from openstack_dashboard.dashboards.identity.identity_providers \ + import forms as idp_forms +from openstack_dashboard.dashboards.identity.identity_providers \ + import tables as idp_tables + + +class IndexView(tables.DataTableView): + table_class = idp_tables.IdentityProvidersTable + template_name = 'identity/identity_providers/index.html' + page_title = _("Identity Providers") + + def get_data(self): + idps = [] + if policy.check((("identity", "identity:list_identity_providers"),), + self.request): + try: + idps = api.keystone.identity_provider_list(self.request) + except Exception: + exceptions.handle( + self.request, + _('Unable to retrieve identity provider list.')) + else: + msg = _("Insufficient privilege level to view identity provider " + "information.") + messages.info(self.request, msg) + return idps + + +class UpdateView(forms.ModalFormView): + template_name = 'identity/identity_providers/update.html' + modal_header = _("Update Identity Provider") + form_id = "update_identity_providers_form" + form_class = idp_forms.UpdateIdPForm + submit_label = _("Update Identity Provider") + submit_url = "horizon:identity:identity_providers:update" + success_url = reverse_lazy('horizon:identity:identity_providers:index') + page_title = _("Update Identity Provider") + + @memoized.memoized_method + def get_object(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 update identity provider.'), + redirect=redirect) + + def get_context_data(self, **kwargs): + context = super(UpdateView, self).get_context_data(**kwargs) + args = (self.get_object().id,) + context['submit_url'] = reverse(self.submit_url, args=args) + return context + + def get_initial(self): + idp = self.get_object() + remote_ids = ', '.join(idp.remote_ids) + return {'id': idp.id, + 'description': idp.description, + 'enabled': idp.enabled, + 'remote_ids': remote_ids} + + +class RegisterView(forms.ModalFormView): + template_name = 'identity/identity_providers/register.html' + modal_header = _("Register Identity Provider") + form_id = "register_identity_provider_form" + form_class = idp_forms.RegisterIdPForm + submit_label = _("Register Identity Provider") + submit_url = reverse_lazy("horizon:identity:identity_providers:register") + success_url = reverse_lazy('horizon:identity:identity_providers:index') + page_title = _("Register Identity Provider") diff --git a/openstack_dashboard/enabled/_3060_federation_panel_group.py b/openstack_dashboard/enabled/_3060_federation_panel_group.py new file mode 100644 index 000000000..b4ef8e399 --- /dev/null +++ b/openstack_dashboard/enabled/_3060_federation_panel_group.py @@ -0,0 +1,8 @@ +from django.utils.translation import ugettext_lazy as _ + +# The slug of the panel group to be added to HORIZON_CONFIG. Required. +PANEL_GROUP = 'federation' +# The display name of the PANEL_GROUP. Required. +PANEL_GROUP_NAME = _('Federation') +# The slug of the dashboard the PANEL_GROUP associated with. Required. +PANEL_GROUP_DASHBOARD = 'identity' diff --git a/openstack_dashboard/enabled/_3070_identity_identity_providers_panel.py b/openstack_dashboard/enabled/_3070_identity_identity_providers_panel.py new file mode 100644 index 000000000..0d0f814c3 --- /dev/null +++ b/openstack_dashboard/enabled/_3070_identity_identity_providers_panel.py @@ -0,0 +1,10 @@ +# The slug of the panel to be added to HORIZON_CONFIG. Required. +PANEL = 'identity_providers' +# The slug of the dashboard the PANEL associated with. Required. +PANEL_DASHBOARD = 'identity' +# The slug of the panel group the PANEL is associated with. +PANEL_GROUP = 'federation' + +# Python panel class of the PANEL to be added. +ADD_PANEL = ('openstack_dashboard.dashboards.identity.' + 'identity_providers.panel.IdentityProviders') diff --git a/openstack_dashboard/local/local_settings.py.example b/openstack_dashboard/local/local_settings.py.example index 7ce30776c..95c863577 100644 --- a/openstack_dashboard/local/local_settings.py.example +++ b/openstack_dashboard/local/local_settings.py.example @@ -61,6 +61,12 @@ WEBROOT = '/' # with Keystone V3. All entities will be created in the default domain. #OPENSTACK_KEYSTONE_DEFAULT_DOMAIN = 'Default' +# Set this to True to enable panels that provide the ability for users to +# manage Identity Providers (IdPs) and establish a set of rules to map +# federation protocol attributes to Identity API attributes. +# This extension requires v3.0+ of the Identity API. +#OPENSTACK_KEYSTONE_FEDERATION_MANAGEMENT = False + # Set Console type: # valid options are "AUTO"(default), "VNC", "SPICE", "RDP", "SERIAL" or None # Set to None explicitly if you want to deactivate the console. diff --git a/openstack_dashboard/test/settings.py b/openstack_dashboard/test/settings.py index ceee26935..20fbfa420 100644 --- a/openstack_dashboard/test/settings.py +++ b/openstack_dashboard/test/settings.py @@ -112,6 +112,7 @@ OPENSTACK_KEYSTONE_DEFAULT_ROLE = "_member_" OPENSTACK_KEYSTONE_MULTIDOMAIN_SUPPORT = True OPENSTACK_KEYSTONE_DEFAULT_DOMAIN = 'test_domain' +OPENSTACK_KEYSTONE_FEDERATION_MANAGEMENT = True OPENSTACK_KEYSTONE_BACKEND = { 'name': 'native', diff --git a/openstack_dashboard/test/test_data/keystone_data.py b/openstack_dashboard/test/test_data/keystone_data.py index eb0fb039c..7393282f2 100644 --- a/openstack_dashboard/test/test_data/keystone_data.py +++ b/openstack_dashboard/test/test_data/keystone_data.py @@ -23,6 +23,7 @@ from keystoneclient.v2_0 import ec2 from keystoneclient.v2_0 import roles from keystoneclient.v2_0 import tenants from keystoneclient.v2_0 import users +from keystoneclient.v3.contrib.federation import identity_providers from keystoneclient.v3 import domains from keystoneclient.v3 import groups from keystoneclient.v3 import role_assignments @@ -144,6 +145,8 @@ def data(TEST): TEST.roles = utils.TestDataContainer() TEST.ec2 = utils.TestDataContainer() + TEST.identity_providers = utils.TestDataContainer() + admin_role_dict = {'id': '1', 'name': 'admin'} admin_role = roles.Role(roles.RoleManager, admin_role_dict) @@ -368,3 +371,19 @@ def data(TEST): "secret": "secret", "tenant_id": tenant.id}) TEST.ec2.add(access_secret) + + idp_dict_1 = {'id': 'idp_1', + 'description': 'identiy provider 1', + 'enabled': True, + 'remote_ids': ['rid_1', 'rid_2']} + idp_1 = identity_providers.IdentityProvider( + identity_providers.IdentityProviderManager, + idp_dict_1) + idp_dict_2 = {'id': 'idp_2', + 'description': 'identiy provider 2', + 'enabled': True, + 'remote_ids': ['rid_3', 'rid_4']} + idp_2 = identity_providers.IdentityProvider( + identity_providers.IdentityProviderManager, + idp_dict_2) + TEST.identity_providers.add(idp_1, idp_2) diff --git a/releasenotes/notes/keystone-federation-idp-d4456dd3b3081a53.yaml b/releasenotes/notes/keystone-federation-idp-d4456dd3b3081a53.yaml new file mode 100644 index 000000000..799d0765e --- /dev/null +++ b/releasenotes/notes/keystone-federation-idp-d4456dd3b3081a53.yaml @@ -0,0 +1,7 @@ +--- +features: + - > + [`blueprint keystone-federation-idp `_] + Add support for managing keystone identity provider. To enable the panel, + set ``OPENSTACK_KEYSTONE_FEDERATION_MANAGEMENT`` in the local_settting.py to True. +