From fbf10e9dad357b43ab68a6b3bd5b9ef47c837d4d Mon Sep 17 00:00:00 2001 From: lin-hua-cheng Date: Sun, 22 Nov 2015 23:22:53 -0800 Subject: [PATCH] Add basic CRUD for federation mapping Change-Id: Ie3991efda6d2437821f67e3c87e111886578e830 Partially-Implements: blueprint keystone-federation-mapping --- openstack_dashboard/api/keystone.py | 28 +++++ .../dashboards/identity/mappings/__init__.py | 0 .../dashboards/identity/mappings/forms.py | 86 ++++++++++++++ .../dashboards/identity/mappings/panel.py | 30 +++++ .../dashboards/identity/mappings/tables.py | 91 +++++++++++++++ .../mappings/templates/mappings/_create.html | 9 ++ .../mappings/templates/mappings/_update.html | 8 ++ .../mappings/templates/mappings/create.html | 7 ++ .../mappings/templates/mappings/index.html | 7 ++ .../mappings/templates/mappings/update.html | 7 ++ .../dashboards/identity/mappings/tests.py | 107 ++++++++++++++++++ .../dashboards/identity/mappings/urls.py | 25 ++++ .../dashboards/identity/mappings/views.py | 101 +++++++++++++++++ .../enabled/_3080_identity_mappings_panel.py | 10 ++ .../test/test_data/keystone_data.py | 38 +++++++ ...stone-federation-idp-d4456dd3b3081a53.yaml | 4 +- 16 files changed, 557 insertions(+), 1 deletion(-) create mode 100644 openstack_dashboard/dashboards/identity/mappings/__init__.py create mode 100644 openstack_dashboard/dashboards/identity/mappings/forms.py create mode 100644 openstack_dashboard/dashboards/identity/mappings/panel.py create mode 100644 openstack_dashboard/dashboards/identity/mappings/tables.py create mode 100644 openstack_dashboard/dashboards/identity/mappings/templates/mappings/_create.html create mode 100644 openstack_dashboard/dashboards/identity/mappings/templates/mappings/_update.html create mode 100644 openstack_dashboard/dashboards/identity/mappings/templates/mappings/create.html create mode 100644 openstack_dashboard/dashboards/identity/mappings/templates/mappings/index.html create mode 100644 openstack_dashboard/dashboards/identity/mappings/templates/mappings/update.html create mode 100644 openstack_dashboard/dashboards/identity/mappings/tests.py create mode 100644 openstack_dashboard/dashboards/identity/mappings/urls.py create mode 100644 openstack_dashboard/dashboards/identity/mappings/views.py create mode 100644 openstack_dashboard/enabled/_3080_identity_mappings_panel.py diff --git a/openstack_dashboard/api/keystone.py b/openstack_dashboard/api/keystone.py index 0ccfde57c0..00f93c2df9 100644 --- a/openstack_dashboard/api/keystone.py +++ b/openstack_dashboard/api/keystone.py @@ -795,3 +795,31 @@ def identity_provider_delete(request, idp_id): def identity_provider_list(request): manager = keystoneclient(request, admin=True).federation.identity_providers return manager.list() + + +def mapping_create(request, mapping_id, rules): + manager = keystoneclient(request, admin=True).federation.mappings + try: + return manager.create(mapping_id=mapping_id, rules=rules) + except keystone_exceptions.Conflict: + raise exceptions.Conflict() + + +def mapping_get(request, mapping_id): + manager = keystoneclient(request, admin=True).federation.mappings + return manager.get(mapping_id) + + +def mapping_update(request, mapping_id, rules): + manager = keystoneclient(request, admin=True).federation.mappings + return manager.update(mapping_id, rules=rules) + + +def mapping_delete(request, mapping_id): + manager = keystoneclient(request, admin=True).federation.mappings + return manager.delete(mapping_id) + + +def mapping_list(request): + manager = keystoneclient(request, admin=True).federation.mappings + return manager.list() diff --git a/openstack_dashboard/dashboards/identity/mappings/__init__.py b/openstack_dashboard/dashboards/identity/mappings/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openstack_dashboard/dashboards/identity/mappings/forms.py b/openstack_dashboard/dashboards/identity/mappings/forms.py new file mode 100644 index 0000000000..69d23ce4ae --- /dev/null +++ b/openstack_dashboard/dashboards/identity/mappings/forms.py @@ -0,0 +1,86 @@ +# 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 json + +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 CreateMappingForm(forms.SelfHandlingForm): + id = forms.CharField(label=_("Mapping ID"), + max_length=64, + help_text=_("User-defined unique id to identify " + "the mapping.")) + rules = forms.CharField(label=_("Rules"), + widget=forms.widgets.Textarea(attrs={'rows': 4}), + help_text=_("Set of rules to map federation " + "protocol attributes to Identity " + "API objects.")) + + def handle(self, request, data): + try: + rules = json.loads(data["rules"]) + new_mapping = api.keystone.mapping_create( + request, + data["id"], + rules=rules) + messages.success(request, + _("Mapping created successfully.")) + return new_mapping + except exceptions.Conflict: + msg = _('Mapping ID "%s" is already used.') % data["id"] + messages.error(request, msg) + except (TypeError, ValueError): + msg = _("Unable to create mapping. Rules has malformed JSON data.") + messages.error(request, msg) + except Exception: + exceptions.handle(request, + _("Unable to create mapping.")) + return False + + +class UpdateMappingForm(forms.SelfHandlingForm): + id = forms.CharField(label=_("Mapping ID"), + widget=forms.TextInput( + attrs={'readonly': 'readonly'}), + help_text=_("User-defined unique id to " + "identify the mapping.")) + rules = forms.CharField(label=_("Rules"), + widget=forms.widgets.Textarea(attrs={'rows': 4}), + help_text=_("Set of rules to map federation " + "protocol attributes to Identity " + "API objects.")) + + def handle(self, request, data): + try: + rules = json.loads(data["rules"]) + api.keystone.mapping_update( + request, + data['id'], + rules=rules) + messages.success(request, + _("Mapping updated successfully.")) + return True + except (TypeError, ValueError): + msg = _("Unable to update mapping. Rules has malformed JSON data.") + messages.error(request, msg) + except Exception: + exceptions.handle(request, + _('Unable to update mapping.')) diff --git a/openstack_dashboard/dashboards/identity/mappings/panel.py b/openstack_dashboard/dashboards/identity/mappings/panel.py new file mode 100644 index 0000000000..16875d7c25 --- /dev/null +++ b/openstack_dashboard/dashboards/identity/mappings/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 Mappings(horizon.Panel): + name = _("Mappings") + slug = 'mappings' + policy_rules = (("identity", "identity:list_mappings"),) + + @staticmethod + def can_register(): + return (keystone.VERSIONS.active >= 3 and + keystone.is_federation_management_enabled()) diff --git a/openstack_dashboard/dashboards/identity/mappings/tables.py b/openstack_dashboard/dashboards/identity/mappings/tables.py new file mode 100644 index 0000000000..df6e8f3072 --- /dev/null +++ b/openstack_dashboard/dashboards/identity/mappings/tables.py @@ -0,0 +1,91 @@ +# 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 json + +from django.utils import safestring +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 CreateMappingLink(tables.LinkAction): + name = "create" + verbose_name = _("Create Mapping") + url = "horizon:identity:mappings:create" + classes = ("ajax-modal",) + icon = "plus" + policy_rules = (("identity", "identity:create_mapping"),) + + +class EditMappingLink(tables.LinkAction): + name = "edit" + verbose_name = _("Edit") + url = "horizon:identity:mappings:update" + classes = ("ajax-modal",) + icon = "pencil" + policy_rules = (("identity", "identity:update_mapping"),) + + +class DeleteMappingsAction(tables.DeleteAction): + @staticmethod + def action_present(count): + return ungettext_lazy( + u"Delete Mapping", + u"Delete Mappings", + count + ) + + @staticmethod + def action_past(count): + return ungettext_lazy( + u"Deleted Mapping", + u"Deleted Mappings", + count + ) + policy_rules = (("identity", "identity:delete_mapping"),) + + def delete(self, request, obj_id): + api.keystone.mapping_delete(request, obj_id) + + +class MappingFilterAction(tables.FilterAction): + def filter(self, table, mappings, filter_string): + """Naive case-insensitive search.""" + q = filter_string.lower() + return [mapping for mapping in mappings + if q in mapping.ud.lower()] + + +def get_rules_as_json(mapping): + rules = getattr(mapping, 'rules', None) + if rules: + rules = json.dumps(rules, indent=4) + return safestring.mark_safe(rules) + + +class MappingsTable(tables.DataTable): + id = tables.Column('id', verbose_name=_('Mapping ID')) + description = tables.Column(get_rules_as_json, + verbose_name=_('Rules')) + + class Meta(object): + name = "idp_mappings" + verbose_name = _("Attribute Mappings") + row_actions = (EditMappingLink, DeleteMappingsAction) + table_actions = (MappingFilterAction, CreateMappingLink, + DeleteMappingsAction) diff --git a/openstack_dashboard/dashboards/identity/mappings/templates/mappings/_create.html b/openstack_dashboard/dashboards/identity/mappings/templates/mappings/_create.html new file mode 100644 index 0000000000..e076dd02ff --- /dev/null +++ b/openstack_dashboard/dashboards/identity/mappings/templates/mappings/_create.html @@ -0,0 +1,9 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block modal-body-right %} +

{% trans "Description:" %}

+

{% trans "Create a mapping." %}

+

{% trans "A mapping is a set of rules to map federation protocol attributes to Identity API objects. An identity provider can have a single mapping specified per protocol. A mapping is simply a list of rules. The only Identity API objects that will support mapping are: user and group." %}

+

{% trans "A rule contains a remote attribute description and the destination local attribute." %}

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

{% trans "Description:" %}

+

{% trans "Edit the mapping's details." %}

+

{% trans "A rule contains a remote attribute description and the destination local attribute." %}

+{% endblock %} diff --git a/openstack_dashboard/dashboards/identity/mappings/templates/mappings/create.html b/openstack_dashboard/dashboards/identity/mappings/templates/mappings/create.html new file mode 100644 index 0000000000..f5908e4d62 --- /dev/null +++ b/openstack_dashboard/dashboards/identity/mappings/templates/mappings/create.html @@ -0,0 +1,7 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Create Mapping" %}{% endblock %} + +{% block main %} + {% include 'identity/mappings/_create.html' %} +{% endblock %} diff --git a/openstack_dashboard/dashboards/identity/mappings/templates/mappings/index.html b/openstack_dashboard/dashboards/identity/mappings/templates/mappings/index.html new file mode 100644 index 0000000000..a75343ca0a --- /dev/null +++ b/openstack_dashboard/dashboards/identity/mappings/templates/mappings/index.html @@ -0,0 +1,7 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Mappings" %}{% endblock %} + +{% block main %} + {{ table.render }} +{% endblock %} diff --git a/openstack_dashboard/dashboards/identity/mappings/templates/mappings/update.html b/openstack_dashboard/dashboards/identity/mappings/templates/mappings/update.html new file mode 100644 index 0000000000..ad7e91bd55 --- /dev/null +++ b/openstack_dashboard/dashboards/identity/mappings/templates/mappings/update.html @@ -0,0 +1,7 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Update Mapping" %}{% endblock %} + +{% block main %} + {% include 'identity/mappings/_update.html' %} +{% endblock %} diff --git a/openstack_dashboard/dashboards/identity/mappings/tests.py b/openstack_dashboard/dashboards/identity/mappings/tests.py new file mode 100644 index 0000000000..4d7fca6451 --- /dev/null +++ b/openstack_dashboard/dashboards/identity/mappings/tests.py @@ -0,0 +1,107 @@ +# 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 json + +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 + + +MAPPINGS_INDEX_URL = reverse('horizon:identity:mappings:index') +MAPPINGS_CREATE_URL = reverse('horizon:identity:mappings:create') +MAPPINGS_UPDATE_URL = reverse('horizon:identity:mappings:update', + args=['mapping_1']) + + +class MappingsViewTests(test.BaseAdminViewTests): + @test.create_stubs({api.keystone: ('mapping_list',)}) + def test_index(self): + api.keystone.mapping_list(IgnoreArg()). \ + AndReturn(self.idp_mappings.list()) + + self.mox.ReplayAll() + + res = self.client.get(MAPPINGS_INDEX_URL) + + self.assertTemplateUsed(res, 'identity/mappings/index.html') + self.assertItemsEqual(res.context['table'].data, + self.idp_mappings.list()) + + @test.create_stubs({api.keystone: ('mapping_create', )}) + def test_create(self): + mapping = self.idp_mappings.first() + + api.keystone.mapping_create(IgnoreArg(), + mapping.id, + rules=mapping.rules). \ + AndReturn(mapping) + + self.mox.ReplayAll() + + formData = {'method': 'CreateMappingForm', + 'id': mapping.id, + 'rules': json.dumps(mapping.rules)} + res = self.client.post(MAPPINGS_CREATE_URL, formData) + + self.assertNoFormErrors(res) + self.assertMessageCount(success=1) + + @test.create_stubs({api.keystone: ('mapping_get', + 'mapping_update')}) + def test_update(self): + mapping = self.idp_mappings.first() + new_rules = [{"local": [], "remote": []}] + + api.keystone.mapping_get(IsA(http.HttpRequest), + mapping.id). \ + AndReturn(mapping) + api.keystone.mapping_update(IsA(http.HttpRequest), + mapping.id, + rules=new_rules). \ + AndReturn(None) + + self.mox.ReplayAll() + + formData = {'method': 'UpdateMappingForm', + 'id': mapping.id, + 'rules': json.dumps(new_rules)} + + res = self.client.post(MAPPINGS_UPDATE_URL, formData) + + self.assertNoFormErrors(res) + self.assertMessageCount(success=1) + + @test.create_stubs({api.keystone: ('mapping_list', + 'mapping_delete')}) + def test_delete(self): + mapping = self.idp_mappings.first() + + api.keystone.mapping_list(IsA(http.HttpRequest)) \ + .AndReturn(self.idp_mappings.list()) + api.keystone.mapping_delete(IsA(http.HttpRequest), + mapping.id) \ + .AndReturn(None) + + self.mox.ReplayAll() + + formData = {'action': 'idp_mappings__delete__%s' % mapping.id} + res = self.client.post(MAPPINGS_INDEX_URL, formData) + + self.assertNoFormErrors(res) diff --git a/openstack_dashboard/dashboards/identity/mappings/urls.py b/openstack_dashboard/dashboards/identity/mappings/urls.py new file mode 100644 index 0000000000..1847f154f1 --- /dev/null +++ b/openstack_dashboard/dashboards/identity/mappings/urls.py @@ -0,0 +1,25 @@ +# 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.mappings import views + +urlpatterns = patterns( + 'openstack_dashboard.dashboards.identity.mappings.views', + url(r'^$', views.IndexView.as_view(), name='index'), + url(r'^(?P[^/]+)/update/$', + views.UpdateView.as_view(), name='update'), + url(r'^create/$', views.CreateView.as_view(), name='create')) diff --git a/openstack_dashboard/dashboards/identity/mappings/views.py b/openstack_dashboard/dashboards/identity/mappings/views.py new file mode 100644 index 0000000000..34b8e54fa7 --- /dev/null +++ b/openstack_dashboard/dashboards/identity/mappings/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. + +import json + +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.mappings \ + import forms as mapping_forms +from openstack_dashboard.dashboards.identity.mappings \ + import tables as mapping_tables + + +class IndexView(tables.DataTableView): + table_class = mapping_tables.MappingsTable + template_name = 'identity/mappings/index.html' + page_title = _("Mappings") + + def get_data(self): + mappings = [] + if policy.check((("identity", "identity:list_mappings"),), + self.request): + try: + mappings = api.keystone.mapping_list(self.request) + except Exception: + exceptions.handle( + self.request, + _('Unable to retrieve mapping list.')) + else: + msg = _("Insufficient privilege level to view mapping " + "information.") + messages.info(self.request, msg) + return mappings + + +class UpdateView(forms.ModalFormView): + template_name = 'identity/mappings/update.html' + modal_header = _("Update Mapping") + form_id = "update_mapping_form" + form_class = mapping_forms.UpdateMappingForm + submit_label = _("Update Mapping") + submit_url = "horizon:identity:mappings:update" + success_url = reverse_lazy('horizon:identity:mappings:index') + page_title = _("Update Mapping") + + @memoized.memoized_method + def get_object(self): + try: + return api.keystone.mapping_get( + self.request, + self.kwargs['mapping_id']) + except Exception: + redirect = reverse("horizon:identity:mappings:index") + exceptions.handle(self.request, + _('Unable to update mapping.'), + 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): + mapping = self.get_object() + rules = json.dumps(mapping.rules, indent=4) + return {'id': mapping.id, + 'rules': rules} + + +class CreateView(forms.ModalFormView): + template_name = 'identity/mappings/create.html' + modal_header = _("Create Mapping") + form_id = "create_mapping_form" + form_class = mapping_forms.CreateMappingForm + submit_label = _("Create Mapping") + submit_url = reverse_lazy("horizon:identity:mappings:create") + success_url = reverse_lazy('horizon:identity:mappings:index') + page_title = _("Create Mapping") diff --git a/openstack_dashboard/enabled/_3080_identity_mappings_panel.py b/openstack_dashboard/enabled/_3080_identity_mappings_panel.py new file mode 100644 index 0000000000..1c71962bb9 --- /dev/null +++ b/openstack_dashboard/enabled/_3080_identity_mappings_panel.py @@ -0,0 +1,10 @@ +# The slug of the panel to be added to HORIZON_CONFIG. Required. +PANEL = 'mappings' +# 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.' + 'mappings.panel.Mappings') diff --git a/openstack_dashboard/test/test_data/keystone_data.py b/openstack_dashboard/test/test_data/keystone_data.py index 7393282f24..a1e9db6770 100644 --- a/openstack_dashboard/test/test_data/keystone_data.py +++ b/openstack_dashboard/test/test_data/keystone_data.py @@ -24,6 +24,7 @@ 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.contrib.federation import mappings from keystoneclient.v3 import domains from keystoneclient.v3 import groups from keystoneclient.v3 import role_assignments @@ -146,6 +147,7 @@ def data(TEST): TEST.ec2 = utils.TestDataContainer() TEST.identity_providers = utils.TestDataContainer() + TEST.idp_mappings = utils.TestDataContainer() admin_role_dict = {'id': '1', 'name': 'admin'} @@ -387,3 +389,39 @@ def data(TEST): identity_providers.IdentityProviderManager, idp_dict_2) TEST.identity_providers.add(idp_1, idp_2) + + idp_mapping_dict = { + "id": "mapping_1", + "rules": [ + { + "local": [ + { + "user": { + "name": "{0}" + } + }, + { + "group": { + "id": "0cd5e9" + } + } + ], + "remote": [ + { + "type": "UserName" + }, + { + "type": "orgPersonType", + "not_any_of": [ + "Contractor", + "Guest" + ] + } + ] + } + ] + } + idp_mapping = mappings.Mapping( + mappings.MappingManager, + idp_mapping_dict) + TEST.idp_mappings.add(idp_mapping) diff --git a/releasenotes/notes/keystone-federation-idp-d4456dd3b3081a53.yaml b/releasenotes/notes/keystone-federation-idp-d4456dd3b3081a53.yaml index 799d0765e0..1e1462ad45 100644 --- a/releasenotes/notes/keystone-federation-idp-d4456dd3b3081a53.yaml +++ b/releasenotes/notes/keystone-federation-idp-d4456dd3b3081a53.yaml @@ -4,4 +4,6 @@ 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. - + - > + [`blueprint keystone-federation-mapping `_] + Add basic support for managing keystone federation mapping.