# -*- coding: utf-8 -*-
# Copyright (c) 2014, OneLogin, Inc.
# All rights reserved.
from time import gmtime, strftime
from datetime import datetime
from xml.dom.minidom import parseString
from saml2.constants import OneLogin_Saml2_Constants
from saml2.utils import OneLogin_Saml2_Utils
[docs]class OneLogin_Saml2_Metadata:
TIME_VALID = 172800 # 2 days
TIME_CACHED = 604800 # 1 week
@staticmethod
[docs] def builder(sp, authnsign=False, wsign=False, valid_until=None, cache_duration=None, contacts=None, organization=None):
"""
Build the metadata of the SP
:param sp: The SP data
:type sp: string
:param authnsign: authnRequestsSigned attribute
:type authnsign: string
:param wsign: wantAssertionsSigned attribute
:type wsign: string
:param valid_until: Metadata's valid time
:type valid_until: DateTime
:param cache_duration: Duration of the cache in seconds
:type cache_duration: Timestamp
:param contacts: Contacts info
:type contacts: dict
:param organization: Organization ingo
:type organization: dict
"""
if valid_until is None:
valid_until = int(datetime.now().strftime("%s")) + OneLogin_Saml2_Metadata.TIME_VALID
valid_until_time = gmtime(valid_until)
valid_until_time = strftime(r'%Y-%m-%dT%H:%M:%SZ', valid_until_time)
if cache_duration is None:
cache_duration = int(datetime.now().strftime("%s")) + OneLogin_Saml2_Metadata.TIME_CACHED
if contacts is None:
contacts = {}
if organization is None:
organization = {}
sls = ''
if 'singleLogoutService' in sp:
sls = """<md:SingleLogoutService Binding="%(binding)s"
Location="%(location)s" />""" % {
'binding': sp['singleLogoutService']['binding'],
'location': sp['singleLogoutService']['url'],
}
str_authnsign = 'true' if authnsign else 'false'
str_wsign = 'true' if wsign else 'false'
str_organization = ''
if len(organization) > 0:
organization_info = []
for (lang, info) in organization.items():
organization_info.append(""" <md:Organization>
<md:OrganizationName xml:lang="%(lang)s">%(name)s</md:OrganizationName>
<md:OrganizationDisplayName xml:lang="%(lang)s">%(display_name)s</md:OrganizationDisplayName>
<md:OrganizationURL xml:lang="%(lang)s">%(url)s</md:OrganizationURL>
</md:Organization>""" % {
'lang': lang,
'name': info['name'],
'display_name': info['displayname'],
'url': info['url'],
})
str_organization = '\n'.join(organization_info)
str_contacts = ''
if len(contacts) > 0:
contacts_info = []
for (ctype, info) in contacts.items():
contacts_info.append(""" <md:ContactPerson contactType="%(type)s">
<md:GivenName>%(name)s</md:GivenName>
<md:EmailAddress>%(email)s</md:EmailAddress>
</md:ContactPerson>""" % {
'type': ctype,
'name': info['givenName'],
'email': info['emailAddress'],
})
str_contacts = '\n'.join(contacts_info)
metadata = """<?xml version="1.0"?>
<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"
validUntil="%(valid)s"
cacheDuration="PT%(cache)sS"
entityID="%(entity_id)s">
<md:SPSSODescriptor AuthnRequestsSigned="%(authnsign)s" WantAssertionsSigned="%(wsign)s" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<md:NameIDFormat>%(name_id_format)s</md:NameIDFormat>
<md:AssertionConsumerService Binding="%(binding)s"
Location="%(location)s"
index="1" />
%(sls)s
</md:SPSSODescriptor>
%(organization)s
%(contacts)s
</md:EntityDescriptor>""" % {
'valid': valid_until_time,
'cache': cache_duration,
'entity_id': sp['entityId'],
'authnsign': str_authnsign,
'wsign': str_wsign,
'name_id_format': sp['NameIDFormat'],
'binding': sp['assertionConsumerService']['binding'],
'location': sp['assertionConsumerService']['url'],
'sls': sls,
'organization': str_organization,
'contacts': str_contacts,
}
return metadata
@staticmethod
[docs] def sign_metadata(metadata, key, cert):
"""
Sign the metadata with the key/cert provided
:param metadata: SAML Metadata XML
:type metadata: string
:param key: x509 key
:type key: string
:param cert: x509 cert
:type cert: string
:returns: Signed Metadata
:rtype: string
"""
return OneLogin_Saml2_Utils.add_sign(metadata, key, cert)
@staticmethod
[docs] def add_x509_key_descriptors(metadata, cert):
"""
Add the x509 descriptors (sign/encriptation to the metadata
The same cert will be used for sign/encrypt
:param metadata: SAML Metadata XML
:type metadata: string
:param cert: x509 cert
:type cert: string
:returns: Metadata with KeyDescriptors
:rtype: string
"""
try:
xml = parseString(metadata)
except Exception as e:
raise Exception('Error parsing metadata. ' + e.message)
formated_cert = OneLogin_Saml2_Utils.format_cert(cert, False)
x509_certificate = xml.createElementNS(OneLogin_Saml2_Constants.NS_DS, 'ds:X509Certificate')
content = xml.createTextNode(formated_cert)
x509_certificate.appendChild(content)
key_data = xml.createElementNS(OneLogin_Saml2_Constants.NS_DS, 'ds:X509Data')
key_data.appendChild(x509_certificate)
key_info = xml.createElementNS(OneLogin_Saml2_Constants.NS_DS, 'ds:KeyInfo')
key_info.appendChild(key_data)
key_descriptor = xml.createElementNS(OneLogin_Saml2_Constants.NS_DS, 'md:KeyDescriptor')
entity_descriptor = sp_sso_descriptor = xml.getElementsByTagName('md:EntityDescriptor')[0]
entity_descriptor.setAttribute('xmlns:ds', OneLogin_Saml2_Constants.NS_DS)
sp_sso_descriptor = xml.getElementsByTagName('md:SPSSODescriptor')[0]
sp_sso_descriptor.insertBefore(key_descriptor.cloneNode(True), sp_sso_descriptor.firstChild)
sp_sso_descriptor.insertBefore(key_descriptor.cloneNode(True), sp_sso_descriptor.firstChild)
signing = xml.getElementsByTagName('md:KeyDescriptor')[0]
signing.setAttribute('use', 'signing')
encryption = xml.getElementsByTagName('md:KeyDescriptor')[1]
encryption.setAttribute('use', 'encryption')
signing.appendChild(key_info)
encryption.appendChild(key_info.cloneNode(True))
return xml.toxml()