# -*- coding: utf-8 -*-
# Copyright (c) 2010-2018 OneLogin, Inc.
# MIT License
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()