Default role checker should be case-insensitive.

Keystone role names are case-insensistive and
Horizon should handle role names in a case-insensitive manner.
For example, when keystone bootstraps default roles,
it creates “admin”, “member”, and “reader”.
If another role, “Member” (note the upper case ‘M’) is created,
keystone will return a 409 Conflict since it considers the name “Member” equivalent to “member”.
Note that case is preserved in this event.
https://docs.openstack.org/keystone/latest/admin/case-insensitive.html#roles
Also whatever is written in defaults can be overridden in settings by the operator -
especially these days when actually the default should be 'member'
(one of the default roles created by Keystone during the bootstrap),
not _member_ which is there for legacy reasons I presume.

Change-Id: Ibfb80a47a8aaed8f33e4e1dcfb428e70c829f0dd
This commit is contained in:
Mitya_Eremeev 2021-05-13 21:29:27 +03:00
parent d862623c27
commit 3aaeadf895
2 changed files with 24 additions and 2 deletions

View File

@ -781,7 +781,7 @@ def get_default_role(request):
to request. Supports lookup by name or id.
"""
global DEFAULT_ROLE
default = settings.OPENSTACK_KEYSTONE_DEFAULT_ROLE
default = settings.OPENSTACK_KEYSTONE_DEFAULT_ROLE.lower()
if default and DEFAULT_ROLE is None:
try:
roles = keystoneclient(request, admin=True).roles.list()
@ -789,7 +789,7 @@ def get_default_role(request):
roles = []
exceptions.handle(request)
for role in roles:
if default in (role.id, role.name):
if default in (role.id.lower(), role.name.lower()):
DEFAULT_ROLE = role
break
return DEFAULT_ROLE

View File

@ -15,6 +15,7 @@
# 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 random import choice
from unittest import mock
@ -68,6 +69,27 @@ class RoleAPITests(test.APIMockTestCase):
role = api.keystone.get_default_role(self.request)
keystoneclient.roles.list.assert_called_once_with()
@mock.patch.object(api.keystone, 'keystoneclient')
@mock.patch.object(api.keystone, 'DEFAULT_ROLE', new=None)
def test_get_case_insensitive_default_role(self, mock_keystoneclient):
def convert_to_random_case(role_):
new_name = list()
for char in list(role_.name):
new_name.append(getattr(char, choice(["lower", "upper"]))())
role_.name = role_._info["name"] = "".join(new_name)
return role_
keystoneclient = mock_keystoneclient.return_value
keystoneclient.roles.list.return_value = list()
for role in self.roles:
role = convert_to_random_case(role)
keystoneclient.roles.list.return_value.append(role)
role = api.keystone.get_default_role(self.request)
self.assertEqual(self.role.name.lower(), role.name.lower())
# Verify that a second call doesn't hit the API again,
# so we use assert_called_once_with() here.
api.keystone.get_default_role(self.request)
keystoneclient.roles.list.assert_called_once_with()
class ServiceAPITests(test.APIMockTestCase):
@override_settings(OPENSTACK_ENDPOINT_TYPE='internalURL')