diff options
author | Vishakha Agarwal <agarwalvishakha18@gmail.com> | 2018-08-02 16:31:54 +0530 |
---|---|---|
committer | Vishakha Agarwal <agarwalvishakha18@gmail.com> | 2020-03-19 20:14:41 +0530 |
commit | dda426b61a18590a81c5b3af281eb0c410756692 (patch) | |
tree | 80c4df4d98ff8be30b7124b72799f26fb424ed64 /keystone/federation | |
parent | 326b014434cc760ba08763e1870ac057f7917e98 (diff) | |
download | keystone-dda426b61a18590a81c5b3af281eb0c410756692.tar.gz |
Add openstack_groups to assertion
Currently, a keystone IdP does not provide the
groups to which user belong when generating SAML
assertions.This patch adds an additional attribute
called "openstack_groups" in the assertion.
Change-Id: I205e8bbf9a4579b16177f57e29e363f4205a2b48
Closes-Bug: #1641625
Diffstat (limited to 'keystone/federation')
-rw-r--r-- | keystone/federation/idp.py | 26 | ||||
-rw-r--r-- | keystone/federation/utils.py | 62 |
2 files changed, 67 insertions, 21 deletions
diff --git a/keystone/federation/idp.py b/keystone/federation/idp.py index e0c983e7b..fd464f5c2 100644 --- a/keystone/federation/idp.py +++ b/keystone/federation/idp.py @@ -48,7 +48,8 @@ class SAMLGenerator(object): self.assertion_id = uuid.uuid4().hex def samlize_token(self, issuer, recipient, user, user_domain_name, roles, - project, project_domain_name, expires_in=None): + project, project_domain_name, groups, + expires_in=None): """Convert Keystone attributes to a SAML assertion. :param issuer: URL of the issuing party @@ -65,6 +66,9 @@ class SAMLGenerator(object): :type project: string :param project_domain_name: Project Domain name :type project_domain_name: string + :param groups: List of strings of user groups and domain name, where + strings are serialized dictionaries. + :type groups: list :param expires_in: Sets how long the assertion is valid for, in seconds :type expires_in: int @@ -76,7 +80,8 @@ class SAMLGenerator(object): saml_issuer = self._create_issuer(issuer) subject = self._create_subject(user, expiration_time, recipient) attribute_statement = self._create_attribute_statement( - user, user_domain_name, roles, project, project_domain_name) + user, user_domain_name, roles, project, project_domain_name, + groups) authn_statement = self._create_authn_statement(issuer, expiration_time) signature = self._create_signature() @@ -162,7 +167,8 @@ class SAMLGenerator(object): return subject def _create_attribute_statement(self, user, user_domain_name, roles, - project, project_domain_name): + project, project_domain_name, + groups): """Create an object that represents a SAML AttributeStatement. <ns0:AttributeStatement> @@ -188,6 +194,15 @@ class SAMLGenerator(object): <ns0:AttributeValue xsi:type="xs:string">Default</ns0:AttributeValue> </ns0:Attribute> + <ns0:Attribute Name="openstack_groups"> + <ns0:AttributeValue + xsi:type="xs:string">JSON:{"name":"group1","domain":{"name":"Default"}} + </ns0:AttributeValue> + <ns0:AttributeValue + xsi:type="xs:string">JSON:{"name":"group2","domain":{"name":"Default"}} + </ns0:AttributeValue> + </ns0:Attribute> + </ns0:AttributeStatement> :returns: XML <AttributeStatement> object @@ -218,6 +233,11 @@ class SAMLGenerator(object): attribute_statement.attribute.append(project_attribute) attribute_statement.attribute.append(project_domain_attribute) attribute_statement.attribute.append(user_domain_attribute) + + if groups: + groups_attribute = _build_attribute( + 'openstack_groups', groups) + attribute_statement.attribute.append(groups_attribute) return attribute_statement def _create_authn_statement(self, issuer, expiration_time): diff --git a/keystone/federation/utils.py b/keystone/federation/utils.py index 7dea74f08..aab9b3524 100644 --- a/keystone/federation/utils.py +++ b/keystone/federation/utils.py @@ -19,6 +19,7 @@ import flask import jsonschema from oslo_config import cfg from oslo_log import log +from oslo_serialization import jsonutils from oslo_utils import timeutils import six @@ -555,6 +556,48 @@ class RuleProcessor(object): LOG.debug('mapped_properties: %s', mapped_properties) return mapped_properties + def _normalize_groups(self, identity_value): + # In this case, identity_value['groups'] is a string + # representation of a list, and we want a real list. This is + # due to the way we do direct mapping substitutions today (see + # function _update_local_mapping() ) + if 'name' in identity_value['groups']: + try: + group_names_list = ast.literal_eval( + identity_value['groups']) + except (ValueError, SyntaxError): + group_names_list = [identity_value['groups']] + + def convert_json(group): + if group.startswith('JSON:'): + return jsonutils.loads(group.lstrip('JSON:')) + return group + + group_dicts = [convert_json(g) for g in group_names_list] + for g in group_dicts: + if 'domain' not in g: + msg = _("Invalid rule: %(identity_value)s. Both " + "'groups' and 'domain' keywords must be " + "specified.") + msg = msg % {'identity_value': identity_value} + raise exception.ValidationError(msg) + else: + if 'domain' not in identity_value: + msg = _("Invalid rule: %(identity_value)s. Both " + "'groups' and 'domain' keywords must be " + "specified.") + msg = msg % {'identity_value': identity_value} + raise exception.ValidationError(msg) + try: + group_names_list = ast.literal_eval( + identity_value['groups']) + except (ValueError, SyntaxError): + group_names_list = [identity_value['groups']] + domain = identity_value['domain'] + group_dicts = [{'name': name, 'domain': domain} for name in + group_names_list] + return group_dicts + def _transform(self, identity_values): """Transform local mappings, to an easier to understand format. @@ -642,24 +685,7 @@ class RuleProcessor(object): groups_by_domain.setdefault(domain, list()).append(group) group_names.extend(extract_groups(groups_by_domain)) if 'groups' in identity_value: - if 'domain' not in identity_value: - msg = _("Invalid rule: %(identity_value)s. Both 'groups' " - "and 'domain' keywords must be specified.") - msg = msg % {'identity_value': identity_value} - raise exception.ValidationError(msg) - # In this case, identity_value['groups'] is a string - # representation of a list, and we want a real list. This is - # due to the way we do direct mapping substitutions today (see - # function _update_local_mapping() ) - try: - group_names_list = ast.literal_eval( - identity_value['groups']) - except (ValueError, SyntaxError): - group_names_list = [identity_value['groups']] - domain = identity_value['domain'] - group_dicts = [{'name': name, 'domain': domain} for name in - group_names_list] - + group_dicts = self._normalize_groups(identity_value) group_names.extend(group_dicts) if 'group_ids' in identity_value: # If identity_values['group_ids'] is a string representation |