summaryrefslogtreecommitdiff
path: root/keystone/federation
diff options
context:
space:
mode:
authorVishakha Agarwal <agarwalvishakha18@gmail.com>2018-08-02 16:31:54 +0530
committerVishakha Agarwal <agarwalvishakha18@gmail.com>2020-03-19 20:14:41 +0530
commitdda426b61a18590a81c5b3af281eb0c410756692 (patch)
tree80c4df4d98ff8be30b7124b72799f26fb424ed64 /keystone/federation
parent326b014434cc760ba08763e1870ac057f7917e98 (diff)
downloadkeystone-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.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 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