Skip to content

Commit 0e2ee59

Browse files
committed
Adapt PR. Toolkit will only support 1 AttributeConsumingService. Adding AttributeConsumingService with String style
1 parent b31d574 commit 0e2ee59

8 files changed

Lines changed: 158 additions & 215 deletions

File tree

README.md

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -227,17 +227,17 @@ This is the settings.json file:
227227
// attributeConsumingService. nameFormat, attributeValue and
228228
// friendlyName can be ommited
229229
"attributeConsumingService": {
230-
"ServiceName": "SP test",
231-
"serviceDescription": "Test Service",
232-
"requestedAttributes": [
233-
{
234-
"name": "",
235-
"isRequired": false,
236-
"nameFormat": "",
237-
"friendlyName": "",
238-
"attributeValue": ""
239-
}
240-
]
230+
"ServiceName": "SP test",
231+
"serviceDescription": "Test Service",
232+
"requestedAttributes": [
233+
{
234+
"name": "",
235+
"isRequired": false,
236+
"nameFormat": "",
237+
"friendlyName": "",
238+
"attributeValue": ""
239+
}
240+
]
241241
},
242242
// Specifies info about where and how the <Logout Response> message MUST be
243243
// returned to the requester, in this case our SP.

src/onelogin/saml2/authn_request.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,6 @@ def __init__(self, settings, force_authn=False, is_passive=False):
8888

8989
attr_consuming_service_str = ''
9090
if 'attributeConsumingService' in sp_data and sp_data['attributeConsumingService']:
91-
# TODO: Do we have to account for the case when we have multiple attributeconsumers?
92-
# like will the index be > 1?
9391
attr_consuming_service_str = 'AttributeConsumingServiceIndex="1"'
9492

9593
request = """<samlp:AuthnRequest

src/onelogin/saml2/metadata.py

Lines changed: 55 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
from time import gmtime, strftime
1313
from datetime import datetime
1414
from defusedxml.minidom import parseString
15-
from lxml import etree
1615

1716
from onelogin.saml2.constants import OneLogin_Saml2_Constants
1817
from onelogin.saml2.utils import OneLogin_Saml2_Utils
@@ -28,63 +27,6 @@ class OneLogin_Saml2_Metadata(object):
2827
TIME_VALID = 172800 # 2 days
2928
TIME_CACHED = 604800 # 1 week
3029

31-
@staticmethod
32-
def add_attribute_consuming_service(root, attr_consuming_service):
33-
"""Helper function to add the AttributeConsumingService nodes"""
34-
attrib_index = 1
35-
spso_node = root.find('{%s}SPSSODescriptor' % OneLogin_Saml2_Constants.NS_MD)
36-
37-
# iterate through all the consuming services listed
38-
for acs in attr_consuming_service:
39-
acs_node = etree.SubElement(spso_node, "{%s}AttributeConsumingService" % OneLogin_Saml2_Constants.NS_MD)
40-
acs_node.set('index', str(attrib_index))
41-
try:
42-
acs_node.set('isDefault', str(acs['isDefault']).lower())
43-
except KeyError:
44-
pass
45-
46-
svc_name = etree.SubElement(acs_node, "{%s}ServiceName" % OneLogin_Saml2_Constants.NS_MD)
47-
svc_name.set('{%s}lang' % OneLogin_Saml2_Constants.XML, 'en')
48-
svc_name.text = acs['serviceName']
49-
try:
50-
svc_description = etree.SubElement(acs_node, "{%s}ServiceDescription" % OneLogin_Saml2_Constants.NS_MD)
51-
svc_description.set('{%s}lang' % OneLogin_Saml2_Constants.XML, 'en')
52-
svc_description.text = acs['serviceDescription']
53-
except KeyError:
54-
# serviceDescription is optional
55-
pass
56-
57-
# iterate through all the requested attributes of each service
58-
for req_attribs in acs['requestedAttributes']:
59-
60-
requested_attribute = etree.SubElement(acs_node, "{%s}RequestedAttribute" % OneLogin_Saml2_Constants.NS_MD)
61-
# construct the permissible attrib values, if present
62-
try:
63-
for attrib_val in req_attribs['attributeValue']:
64-
val = etree.SubElement(requested_attribute, "{%s}AttributeValue" % OneLogin_Saml2_Constants.NS_SAML)
65-
val.text = attrib_val
66-
except KeyError:
67-
# it's fine, attributeValue is an optional element
68-
pass
69-
70-
requested_attribute.set('Name', req_attribs['name'])
71-
try:
72-
requested_attribute.set('NameFormat', req_attribs['nameFormat'])
73-
except KeyError:
74-
pass
75-
76-
try:
77-
requested_attribute.set('FriendlyName', req_attribs['friendlyName'])
78-
except KeyError:
79-
pass
80-
81-
try:
82-
requested_attribute.set('isRequired', str(req_attribs['isRequired']).lower())
83-
except KeyError:
84-
pass
85-
86-
attrib_index += 1
87-
8830
@staticmethod
8931
def builder(sp, authnsign=False, wsign=False, valid_until=None, cache_duration=None, contacts=None, organization=None):
9032
"""
@@ -134,10 +76,54 @@ def builder(sp, authnsign=False, wsign=False, valid_until=None, cache_duration=N
13476
if organization is None:
13577
organization = {}
13678

137-
try:
138-
attr_consuming_service = sp['attributeConsumingService']
139-
except KeyError:
140-
attr_consuming_service = []
79+
str_attribute_consuming_service = ''
80+
81+
if 'attributeConsumingService' in sp and len(sp['attributeConsumingService']):
82+
attr_cs_desc_str = ''
83+
if "serviceDescription" in sp['attributeConsumingService']:
84+
attr_cs_desc_str = """ <md:ServiceDescription xml:lang="en">%s</md:ServiceDescription>
85+
""" % sp['attributeConsumingService']['serviceDescription']
86+
87+
requested_attribute_data = []
88+
for req_attribs in sp['attributeConsumingService']['requestedAttributes']:
89+
req_attr_nameformat_str = req_attr_friendlyname_str = req_attr_isrequired_str = ''
90+
req_attr_aux_str = ' \>'
91+
92+
if 'nameFormat' in req_attribs.keys() and req_attribs['nameFormat']:
93+
req_attr_nameformat_str = " NameFormat=\"%s\"" % req_attribs['nameFormat']
94+
if 'friendlyName' in req_attribs.keys() and req_attribs['friendlyName']:
95+
req_attr_nameformat_str = " FriendlyName=\"%s\"" % req_attribs['friendlyName']
96+
if 'isRequired' in req_attribs.keys() and req_attribs['isRequired']:
97+
req_attr_isrequired_str = " isRequired=\"%s\"" % req_attribs['isRequired']
98+
if 'attributeValue' in req_attribs.keys() and req_attribs['attributeValue']:
99+
req_attr_aux_str = """ >
100+
<saml:AttributeValue xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion>%(attributeValue)</saml:AttributeValue>
101+
</md:RequestedAttribute>""" % \
102+
{
103+
'attributeValue': req_attribs['attributeValue']
104+
}
105+
106+
requested_attribute = """ <md:RequestedAttribute Name="%(req_attr_name)s"%(req_attr_nameformat_str)s%(req_attr_isrequired_str)s%(req_attr_aux_str)s""" % \
107+
{
108+
'req_attr_name': req_attribs['name'],
109+
'req_attr_nameformat_str': req_attr_nameformat_str,
110+
'req_attr_friendlyname_str': req_attr_friendlyname_str,
111+
'req_attr_isrequired_str': req_attr_isrequired_str,
112+
'req_attr_aux_str': req_attr_aux_str
113+
}
114+
115+
requested_attribute_data.append(requested_attribute)
116+
117+
str_attribute_consuming_service = """ <md:AttributeConsumingService index="1">
118+
<md:ServiceName xml:lang="en">%(service_name)s</md:ServiceName>
119+
%(attr_cs_desc)s%(requested_attribute_str)s
120+
</md:AttributeConsumingService>
121+
""" % \
122+
{
123+
'service_name': sp['attributeConsumingService']['serviceName'],
124+
'attr_cs_desc': attr_cs_desc_str,
125+
'requested_attribute_str': '\n'.join(requested_attribute_data)
126+
}
141127

142128
sls = ''
143129
if 'singleLogoutService' in sp and 'url' in sp['singleLogoutService']:
@@ -163,7 +149,7 @@ def builder(sp, authnsign=False, wsign=False, valid_until=None, cache_duration=N
163149
org_data = '\n'.join(organization_names) + '\n' + '\n'.join(organization_displaynames) + '\n' + '\n'.join(organization_urls)
164150
str_organization = """ <md:Organization>
165151
%(org)s
166-
</md:Organization>""" % {'org': org_data}
152+
</md:Organization>\n""" % {'org': org_data}
167153

168154
str_contacts = ''
169155
if len(contacts) > 0:
@@ -179,10 +165,10 @@ def builder(sp, authnsign=False, wsign=False, valid_until=None, cache_duration=N
179165
'email': info['emailAddress'],
180166
}
181167
contacts_info.append(contact)
182-
str_contacts = '\n'.join(contacts_info)
168+
str_contacts = '\n'.join(contacts_info) + '\n'
183169

184170
metadata = """<?xml version="1.0"?>
185-
<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" %(saml_namespace)s
171+
<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"
186172
%(valid)s
187173
%(cache)s
188174
entityID="%(entity_id)s">
@@ -191,10 +177,8 @@ def builder(sp, authnsign=False, wsign=False, valid_until=None, cache_duration=N
191177
<md:AssertionConsumerService Binding="%(binding)s"
192178
Location="%(location)s"
193179
index="1" />
194-
</md:SPSSODescriptor>
195-
%(organization)s
196-
%(contacts)s
197-
</md:EntityDescriptor>""" % \
180+
%(attribute_consuming_service)s </md:SPSSODescriptor>
181+
%(organization)s%(contacts)s</md:EntityDescriptor>""" % \
198182
{
199183
'valid': ('validUntil="%s"' % valid_until_str) if valid_until_str else '',
200184
'cache': ('cacheDuration="%s"' % cache_duration_str) if cache_duration_str else '',
@@ -207,16 +191,9 @@ def builder(sp, authnsign=False, wsign=False, valid_until=None, cache_duration=N
207191
'sls': sls,
208192
'organization': str_organization,
209193
'contacts': str_contacts,
210-
'saml_namespace': 'xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"' if attr_consuming_service else ''
194+
'attribute_consuming_service': str_attribute_consuming_service
211195
}
212-
213-
# i'm not sure why the above xml was build by hand. Building via lxml is way easier,
214-
# especially for conditional attributes etc..
215-
# So as a work around, i'm creating a xml dom, insert the attibute_consumer_service
216-
# nodes into it and then return the serialized xml
217-
root = etree.fromstring(metadata)
218-
OneLogin_Saml2_Metadata.add_attribute_consuming_service(root, attr_consuming_service)
219-
return etree.tostring(root, pretty_print=True)
196+
return metadata
220197

221198
@staticmethod
222199
def sign_metadata(metadata, key, cert, sign_algorithm=OneLogin_Saml2_Constants.RSA_SHA1):

src/onelogin/saml2/settings.py

Lines changed: 25 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -257,9 +257,8 @@ def __add_default_values(self):
257257
if 'binding' not in self.__sp['assertionConsumerService'].keys():
258258
self.__sp['assertionConsumerService']['binding'] = OneLogin_Saml2_Constants.BINDING_HTTP_POST
259259

260-
# attributeConsumingService is optional
261-
if 'attributeConsumingService' not in self.__sp:
262-
self.__sp['attributeConsumingService'] = []
260+
if 'attributeConsumingService' not in self.__sp.keys():
261+
self.__sp['attributeConsumingService'] = {}
263262

264263
if 'singleLogoutService' not in self.__sp.keys():
265264
self.__sp['singleLogoutService'] = {}
@@ -441,48 +440,29 @@ def check_sp_settings(self, settings):
441440
errors.append('sp_acs_url_invalid')
442441

443442
if 'attributeConsumingService' in sp and len(sp['attributeConsumingService']):
444-
# so we have a attributeConsumingService element...
445-
446-
# serviceName and requestedAttrib are required
447-
attribute_consuming_service = sp['attributeConsumingService']
448-
for attrib in attribute_consuming_service:
449-
if 'serviceName' not in attrib:
450-
errors.append('sp_attributeConsumingService_serviceName_not_found')
451-
if 'requestedAttributes' not in attrib:
452-
errors.append('sp_attributeConsumingService_requestedAttributes_not_found')
453-
454-
# verify that tags are of the correct types
455-
try:
456-
if type(attrib['isDefault']) != bool:
457-
errors.append('sp_attributeConsumingService_isDefault_type_invalid')
458-
except KeyError:
459-
# isDefault attribute is optional
460-
pass
461-
462-
if 'serviceName' in attrib and not isinstance(attrib['serviceName'], basestring):
463-
errors.append('sp_attributeConsumingService_serviceName_type_invalid')
464-
465-
try:
466-
if not isinstance(attrib['serviceDescription'], basestring):
467-
errors.append('sp_attributeConsumingService_serviceDescription_type_invalid')
468-
except KeyError:
469-
# serviceDescription attribute is optional
470-
pass
471-
472-
if 'requestedAttributes' in attrib:
473-
if type(attrib['requestedAttributes']) != list:
474-
errors.append('sp_attributeConsumingService_requestedAttributes_type_invalid')
475-
476-
for req_attrib in attrib['requestedAttributes']:
477-
if 'name' not in req_attrib:
478-
errors.append('sp_attributeConsumingService_requestedAttributes_name_not_found')
479-
if 'name' in req_attrib and not req_attrib['name'].strip():
480-
# name cannot be empty
481-
errors.append('sp_attributeConsumingService_requestedAttributes_name_invalid')
482-
if 'attributeValue' in req_attrib and type(req_attrib['attributeValue']) != list:
483-
errors.append('sp_attributeConsumingService_requestedAttributes_attributeValue_type_invalid')
484-
if 'isRequired' in req_attrib and type(req_attrib['isRequired']) != bool:
485-
errors.append('sp_attributeConsumingService_requestedAttributes_isRequired_type_invalid')
443+
attributeConsumingService = sp['attributeConsumingService']
444+
if 'serviceName' not in attributeConsumingService:
445+
errors.append('sp_attributeConsumingService_serviceName_not_found')
446+
elif not isinstance(attributeConsumingService['serviceName'], basestring):
447+
errors.append('sp_attributeConsumingService_serviceName_type_invalid')
448+
449+
if 'requestedAttributes' not in attributeConsumingService:
450+
errors.append('sp_attributeConsumingService_requestedAttributes_not_found')
451+
elif not isinstance(attributeConsumingService['requestedAttributes'], list):
452+
errors.append('sp_attributeConsumingService_serviceName_type_invalid')
453+
else:
454+
for req_attrib in attributeConsumingService['requestedAttributes']:
455+
if 'name' not in req_attrib:
456+
errors.append('sp_attributeConsumingService_requestedAttributes_name_not_found')
457+
if 'name' in req_attrib and not req_attrib['name'].strip():
458+
errors.append('sp_attributeConsumingService_requestedAttributes_name_invalid')
459+
if 'attributeValue' in req_attrib and type(req_attrib['attributeValue']) != list:
460+
errors.append('sp_attributeConsumingService_requestedAttributes_attributeValue_type_invalid')
461+
if 'isRequired' in req_attrib and type(req_attrib['isRequired']) != bool:
462+
errors.append('sp_attributeConsumingService_requestedAttributes_isRequired_type_invalid')
463+
464+
if "serviceDescription" in attributeConsumingService and not isinstance(attributeConsumingService['serviceDescription'], basestring):
465+
errors.append('sp_attributeConsumingService_serviceDescription_type_invalid')
486466

487467
if 'singleLogoutService' in sp and \
488468
'url' in sp['singleLogoutService'] and \

0 commit comments

Comments
 (0)