summaryrefslogtreecommitdiff
path: root/keystone/federation
diff options
context:
space:
mode:
authorZuul <zuul@review.opendev.org>2020-03-23 19:24:38 +0000
committerGerrit Code Review <review@openstack.org>2020-03-23 19:24:38 +0000
commit9f9040257f876ff32d71483ccf4a47e135f0bb4d (patch)
tree9df32ab3d1727ec5aa02b54bc5d096585adb6225 /keystone/federation
parentd9af77e1d854b7a49c749a2f86002fbe0336ae39 (diff)
parentdda426b61a18590a81c5b3af281eb0c410756692 (diff)
downloadkeystone-9f9040257f876ff32d71483ccf4a47e135f0bb4d.tar.gz
Merge "Add openstack_groups to assertion"
Diffstat (limited to 'keystone/federation')
-rw-r--r--keystone/federation/idp.py26
-rw-r--r--keystone/federation/utils.py62
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 9bfaea8c0..95a8e8305 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
from keystone.common import provider_api
@@ -554,6 +555,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.
@@ -641,24 +684,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