1212from time import gmtime , strftime
1313from datetime import datetime
1414from defusedxml .minidom import parseString
15- from lxml import etree
1615
1716from onelogin .saml2 .constants import OneLogin_Saml2_Constants
1817from 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 ):
0 commit comments