diff --git a/docs/saml2/.buildinfo b/docs/saml2/.buildinfo deleted file mode 100644 index 5e197dbf..00000000 --- a/docs/saml2/.buildinfo +++ /dev/null @@ -1,4 +0,0 @@ -# Sphinx build info version 1 -# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. -config: e10660514f5c62e16e90878c60a15170 -tags: fbb0d17656682115ca4d033fb2f83ba1 diff --git a/docs/saml2/_modules/index.html b/docs/saml2/_modules/index.html deleted file mode 100644 index d672e465..00000000 --- a/docs/saml2/_modules/index.html +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - -
- - -
-# -*- coding: utf-8 -*-
-
-# Copyright (c) 2010-2018 OneLogin, Inc.
-# MIT License
-
-from base64 import b64encode
-from urllib import urlencode, quote
-from xml.etree.ElementTree import tostring
-
-import dm.xmlsec.binding as xmlsec
-
-from saml2.settings import OneLogin_Saml2_Settings
-from saml2.response import OneLogin_Saml2_Response
-from saml2.errors import OneLogin_Saml2_Error
-from saml2.logout_response import OneLogin_Saml2_Logout_Response
-from saml2.constants import OneLogin_Saml2_Constants
-from saml2.utils import OneLogin_Saml2_Utils
-from saml2.logout_request import OneLogin_Saml2_Logout_Request
-from saml2.authn_request import OneLogin_Saml2_Authn_Request
-
-
-[docs]class OneLogin_Saml2_Auth(object):
-
- def __init__(self, request_data, old_settings=None):
- """
- Initializes the SP SAML instance.
-
- Arguments are:
- * (dict) old_settings. Setting data
- """
- self.__request_data = request_data
- self.__settings = OneLogin_Saml2_Settings(old_settings)
- self.__attributes = []
- self.__nameid = ''
- self.__authenticated = False
- self.__errors = []
-
-[docs] def get_settings(self):
- """
- Returns the settings info
- :return: Setting info
- :rtype: OneLogin_Saml2_Setting object
- """
- return self.__settings
-
-[docs] def set_strict(self, value):
- """
- Set the strict mode active/disable
-
- :param value:
- :type value: bool
- """
- assert isinstance(value, bool)
- self.__settings.set_strict(value)
-
-[docs] def process_response(self, request_id=None):
- """
- Process the SAML Response sent by the IdP.
-
- :param request_id: Is an optional argumen. Is the ID of the AuthNRequest sent by this SP to the IdP.
- :type request_id: string
-
- :raises: OneLogin_Saml2_Error.SAML_RESPONSE_NOT_FOUND, when a POST with a SAMLResponse is not found
- """
- self.__errors = []
-
- if 'post_data' in self.__request_data and 'SAMLResponse' in self.__request_data['post_data']:
- # AuthnResponse -- HTTP_POST Binding
- response = OneLogin_Saml2_Response(self.__settings, self.__request_data['post_data']['SAMLResponse'])
-
- if response.is_valid(request_id):
- self.__attributes = response.get_attributes()
- self.__nameid = response.get_nameid()
- self.__authenticated = True
- else:
- self.__errors.append('invalid_response')
-
- else:
- self.__errors.append('invalid_binding')
- raise OneLogin_Saml2_Error(
- 'SAML Response not found, Only supported HTTP_POST Binding',
- OneLogin_Saml2_Error.SAML_RESPONSE_NOT_FOUND
- )
-
-[docs] def process_slo(self, keep_local_session=False, request_id=None, delete_session_cb=None):
- """
- Process the SAML Logout Response / Logout Request sent by the IdP.
-
- :param keep_local_session: When false will destroy the local session, otherwise will destroy it
- :type keep_local_session: bool
-
- :param request_id: The ID of the LogoutRequest sent by this SP to the IdP
- :type request_id: string
-
- :returns: Redirection url
- """
- self.__errors = []
-
- if 'get_data' in self.__request_data and 'SAMLResponse' in self.__request_data['get_data']:
- logout_response = OneLogin_Saml2_Logout_Response(self.__settings, self.__request_data['get_data']['SAMLResponse'])
- if not logout_response.is_valid(self.__request_data, request_id):
- self.__errors.append('invalid_logout_response')
- elif logout_response.get_status() != OneLogin_Saml2_Constants.STATUS_SUCCESS:
- self.__errors.append('logout_not_success')
- elif not keep_local_session:
- OneLogin_Saml2_Utils.delete_local_session(delete_session_cb)
-
- elif 'get_data' in self.__request_data and 'SAMLRequest' in self.__request_data['get_data']:
- request = OneLogin_Saml2_Utils.decode_base64_and_inflate(self.__request_data['get_data']['SAMLRequest'])
- if not OneLogin_Saml2_Logout_Request.is_valid(self.__settings, request, self.__request_data):
- self.__errors.append('invalid_logout_request')
- else:
- if not keep_local_session:
- OneLogin_Saml2_Utils.delete_local_session(delete_session_cb)
-
- in_response_to = OneLogin_Saml2_Logout_Request.get_id(request)
- response_builder = OneLogin_Saml2_Logout_Response(self.__settings)
- response_builder.build(in_response_to)
- logout_response = response_builder.get_response()
-
- parameters = {'SAMLResponse': logout_response}
- if 'RelayState' in self.__request_data['get_data']:
- parameters['RelayState'] = self.__request_data['get_data']['RelayState']
-
- security = self.__settings.get_security_data()
- if 'logoutResponseSigned' in security and security['logoutResponseSigned']:
- signature = self.build_response_signature(logout_response, parameters.get('RelayState', None))
- parameters['SigAlg'] = OneLogin_Saml2_Constants.RSA_SHA1
- parameters['Signature'] = signature
-
- return self.redirect_to(self.get_slo_url(), parameters)
-
- else:
- self.__errors.append('invalid_binding')
- raise OneLogin_Saml2_Error(
- 'SAML LogoutRequest/LogoutResponse not found. Only supported HTTP_REDIRECT Binding',
- OneLogin_Saml2_Error.SAML_LOGOUTMESSAGE_NOT_FOUND
- )
-
-[docs] def redirect_to(self, url=None, parameters={}):
- """
- Redirects the user to the url past by parameter or to the url that we defined in our SSO Request.
-
- :param url: The target URL to redirect the user
- :type url: string
- :param parameters: Extra parameters to be passed as part of the url
- :type parameters: dict
-
- :returns: Redirection url
- """
- if url is None and 'RelayState' in self.__request_data['get_data']:
- url = self.__request_data['get_data']['RelayState']
- return OneLogin_Saml2_Utils.redirect(url, parameters, request_data=self.__request_data)
-
-[docs] def is_authenticated(self):
- """
- Checks if the user is authenticated or not.
-
- :returns: True if is authenticated, False if not
- :rtype: bool
- """
- return self.__authenticated
-
-[docs] def get_attributes(self):
- """
- Returns the set of SAML attributes.
-
- :returns: SAML attributes
- :rtype: dict
- """
- return self.__attributes
-
-[docs] def get_nameid(self):
- """
- Returns the nameID.
-
- :returns: NameID
- :rtype: string
- """
- return self.__nameid
-
-[docs] def get_errors(self):
- """
- Returns a list with code errors if something went wrong
-
- :returns: List of errors
- :rtype: list
- """
- return self.__errors
-
-[docs] def get_attribute(self, name):
- """
- Returns the requested SAML attribute.
-
- :param name: Name of the attribute
- :type name: string
-
- :returns: Attribute value if exists or None
- :rtype: string
- """
- assert isinstance(name, basestring)
- value = None
- if name in self.__attributes.keys():
- value = self.__attributes[name]
- return value
-
-[docs] def login(self, return_to=None):
- """
- Initiates the SSO process.
-
- :param return_to: Optional argument. The target URL the user should be redirected to after login.
- :type return_to: string
-
- :returns: Redirection url
- """
- authn_request = OneLogin_Saml2_Authn_Request(self.__settings)
-
- saml_request = authn_request.get_request()
- parameters = {'SAMLRequest': saml_request}
-
- if return_to is not None:
- parameters['RelayState'] = return_to
- else:
- parameters['RelayState'] = OneLogin_Saml2_Utils.get_self_url_no_query(self.__request_data)
-
- security = self.__settings.get_security_data()
- if security.get('authnRequestsSigned', False):
- parameters['SigAlg'] = OneLogin_Saml2_Constants.RSA_SHA1
- parameters['Signature'] = self.build_request_signature(saml_request, parameters['RelayState'])
- return self.redirect_to(self.get_sso_url(), parameters)
-
-[docs] def logout(self, return_to=None, name_id=None, session_index=None):
- """
- Initiates the SLO process.
-
- :param return_to: Optional argument. The target URL the user should be redirected to after logout.
- :type return_to: string
- :param name_id: Optional argument. The NameID that will be set in the LogoutRequest.
- :type name_id: string
- :param session_index: Optional argument. SessionIndex that identifies the session of the user.
- :type session_index: string
- :returns: Redirection url
- """
- slo_url = self.get_slo_url()
- if slo_url is None:
- raise OneLogin_Saml2_Error(
- 'The IdP does not support Single Log Out',
- OneLogin_Saml2_Error.SAML_SINGLE_LOGOUT_NOT_SUPPORTED
- )
-
- logout_request = OneLogin_Saml2_Logout_Request(self.__settings)
-
- saml_request = logout_request.get_request()
-
- parameters = {'SAMLRequest': logout_request.get_request()}
- if return_to is not None:
- parameters['RelayState'] = return_to
- else:
- parameters['RelayState'] = OneLogin_Saml2_Utils.get_self_url_no_query(self.__request_data)
-
- security = self.__settings.get_security_data()
- if security.get('logoutRequestSigned', False):
- parameters['SigAlg'] = OneLogin_Saml2_Constants.RSA_SHA1
- parameters['Signature'] = self.build_request_signature(saml_request, parameters['RelayState'])
- return self.redirect_to(slo_url, parameters)
-
-[docs] def get_sso_url(self):
- """
- Gets the SSO url.
-
- :returns: An URL, the SSO endpoint of the IdP
- :rtype: string
- """
- idp_data = self.__settings.get_idp_data()
- return idp_data['singleSignOnService']['url']
-
-[docs] def get_slo_url(self):
- """
- Gets the SLO url.
-
- :returns: An URL, the SLO endpoint of the IdP
- :rtype: string
- """
- url = None
- idp_data = self.__settings.get_idp_data()
- if 'singleLogoutService' in idp_data.keys() and 'url' in idp_data['singleLogoutService']:
- url = idp_data['singleLogoutService']['url']
- return url
-
-[docs] def build_request_signature(self, saml_request, relay_state):
- """
- Builds the Signature of the SAML Request.
-
- :param saml_request: The SAML Request
- :type saml_request: string
-
- :param relay_state: The target URL the user should be redirected to
- :type relay_state: string
- """
- if not self.__settings.check_sp_certs():
- raise OneLogin_Saml2_Error(
- "Trying to sign the SAML Request but can't load the SP certs",
- OneLogin_Saml2_Error.SP_CERTS_NOT_FOUND
- )
-
- xmlsec.initialize()
-
- # Load the key into the xmlsec context
- key = self.__settings.get_sp_key()
- file_key = OneLogin_Saml2_Utils.write_temp_file(key) # FIXME avoid writing a file
-
- dsig_ctx = xmlsec.DSigCtx()
- dsig_ctx.signKey = xmlsec.Key.load(file_key.name, xmlsec.KeyDataFormatPem, None)
- file_key.close()
-
- data = {
- 'SAMLRequest': quote(saml_request),
- 'RelayState': quote(relay_state),
- 'SignAlg': quote(OneLogin_Saml2_Constants.RSA_SHA1),
- }
- msg = urlencode(data)
- signature = dsig_ctx.signBinary(msg, xmlsec.TransformRsaSha1)
- return b64encode(signature)
-
-[docs] def build_response_signature(self, saml_response, relay_state):
- """
- Builds the Signature of the SAML Response.
- :param saml_request: The SAML Response
- :type saml_request: string
-
- :param relay_state: The target URL the user should be redirected to
- :type relay_state: string
- """
- if not self.__settings.check_sp_certs():
- raise OneLogin_Saml2_Error(
- "Trying to sign the SAML Response but can't load the SP certs",
- OneLogin_Saml2_Error.SP_CERTS_NOT_FOUND
- )
-
- xmlsec.initialize()
-
- # Load the key into the xmlsec context
- key = self.__settings.get_sp_key()
- file_key = OneLogin_Saml2_Utils.write_temp_file(key) # FIXME avoid writing a file
-
- dsig_ctx = xmlsec.DSigCtx()
- dsig_ctx.signKey = xmlsec.Key.load(file_key.name, xmlsec.KeyDataFormatPem, None)
- file_key.close()
-
- data = {
- 'SAMLResponse': quote(saml_response),
- 'RelayState': quote(relay_state),
- 'SignAlg': quote(OneLogin_Saml2_Constants.RSA_SHA1),
- }
- msg = urlencode(data)
- import pdb; dbp.set_trace()
- print msg
- data2 = {
- 'SAMLResponse': saml_response,
- 'RelayState': relay_state,
- 'SignAlg': OneLogin_Saml2_Constants.RSA_SHA1,
- }
- msg2 = urlencode(data2)
- print msg2
- signature = dsig_ctx.signBinary(msg, xmlsec.TransformRsaSha1)
- return b64encode(signature)
-
-# -*- coding: utf-8 -*-
-
-# Copyright (c) 2010-2018 OneLogin, Inc.
-# MIT License
-
-from base64 import b64encode
-from datetime import datetime
-from zlib import compress
-
-from saml2.utils import OneLogin_Saml2_Utils
-from saml2.constants import OneLogin_Saml2_Constants
-
-
-[docs]class OneLogin_Saml2_Authn_Request:
-
- def __init__(self, settings):
- """
- Constructs the AuthnRequest object.
-
- Arguments are:
- * (OneLogin_Saml2_Settings) settings. Setting data
- """
- self.__settings = settings
-
- sp_data = self.__settings.get_sp_data()
- security = self.__settings.get_security_data()
-
- uid = OneLogin_Saml2_Utils.generate_unique_id()
- issue_instant = OneLogin_Saml2_Utils.parse_time_to_SAML(
- int(datetime.now().strftime("%s"))
- )
-
- name_id_policy_format = sp_data['NameIDFormat']
- if 'wantNameIdEncrypted' in security and security['wantNameIdEncrypted']:
- name_id_policy_format = OneLogin_Saml2_Constants.NAMEID_ENCRYPTED
-
- provider_name_str = ''
- organization_data = settings.get_organization()
- if isinstance(organization_data, dict):
- langs = organization_data.keys()
- if 'en-US' in langs:
- lang = 'en-US'
- else:
- lang = langs[0]
- if 'displayname' in organization_data[lang] and organization_data[lang]['displayname'] is not None:
- provider_name_str = 'ProviderName="%s"' % organization_data[lang]['displayname']
-
- request = """<samlp:AuthnRequest
- xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
- xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
- ID="%(id)s"
- Version="2.0"
- %(provider_name)s
- IssueInstant="%(issue_instant)s"
- ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
- AssertionConsumerServiceURL="%(assertion_url)s">
- <saml:Issuer>%(entity_id)s</saml:Issuer>
- <samlp:NameIDPolicy
- Format="%(name_id_policy)s"
- AllowCreate="true" />
- <samlp:RequestedAuthnContext Comparison="exact">
- <saml:AuthnContextMethodRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextMethodRef>
- </samlp:RequestedAuthnContext>
-</samlp:AuthnRequest>""" % {
- 'id': uid,
- 'provider_name': provider_name_str,
- 'issue_instant': issue_instant,
- 'assertion_url': sp_data['assertionConsumerService']['url'],
- 'entity_id': sp_data['entityId'],
- 'name_id_policy': name_id_policy_format,
- }
-
- self.__authn_request = request
-
-[docs] def get_request(self):
- """
- Returns unsigned AuthnRequest.
- :return: Unsigned AuthnRequest
- :rtype: str object
- """
- deflated_request = compress(self.__authn_request)[2:-4]
- return b64encode(deflated_request)
-
-# -*- coding: utf-8 -*-
-
-# Copyright (c) 2010-2018 OneLogin, Inc.
-# MIT License
-
-
-[docs]class OneLogin_Saml2_Constants:
- # Value added to the current time in time condition validations
- ALOWED_CLOCK_DRIFT = 180
-
- # NameID Formats
- NAMEID_EMAIL_ADDRESS = 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress'
- NAMEID_X509_SUBJECT_NAME = 'urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName'
- NAMEID_WINDOWS_DOMAIN_QUALIFIED_NAME = 'urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName'
- NAMEID_KERBEROS = 'urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos'
- NAMEID_ENTITY = 'urn:oasis:names:tc:SAML:2.0:nameid-format:entity'
- NAMEID_TRANSIENT = 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
- NAMEID_PERSISTENT = 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent'
- NAMEID_ENCRYPTED = 'urn:oasis:names:tc:SAML:2.0:nameid-format:encrypted'
-
- # Attribute Name Formats
- ATTRNAME_FORMAT_UNSPECIFIED = 'urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified'
- ATTRNAME_FORMAT_URI = 'urn:oasis:names:tc:SAML:2.0:attrname-format:uri'
- ATTRNAME_FORMAT_BASIC = 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic'
-
- # Namespaces
- NS_SAML = 'urn:oasis:names:tc:SAML:2.0:assertion'
- NS_SAMLP = 'urn:oasis:names:tc:SAML:2.0:protocol'
- NS_SOAP = 'http://schemas.xmlsoap.org/soap/envelope/'
- NS_MD = 'urn:oasis:names:tc:SAML:2.0:metadata'
- NS_XS = 'http://www.w3.org/2001/XMLSchema'
- NS_XSI = 'http://www.w3.org/2001/XMLSchema-instance'
- NS_XENC = 'http://www.w3.org/2001/04/xmlenc#'
- NS_DS = 'http://www.w3.org/2000/09/xmldsig#'
-
- # Bindings
- BINDING_HTTP_POST = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'
- BINDING_HTTP_REDIRECT = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
- BINDING_HTTP_ARTIFACT = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact'
- BINDING_SOAP = 'urn:oasis:names:tc:SAML:2.0:bindings:SOAP'
- BINDING_DEFLATE = 'urn:oasis:names:tc:SAML:2.0:bindings:URL-Encoding:DEFLATE'
-
- # Auth Context Method
- AC_UNSPECIFIED = 'urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified'
- AC_PASSWORD = 'urn:oasis:names:tc:SAML:2.0:ac:classes:Password'
- AC_X509 = 'urn:oasis:names:tc:SAML:2.0:ac:classes:X509'
- AC_SMARTCARD = 'urn:oasis:names:tc:SAML:2.0:ac:classes:Smartcard'
- AC_KERBEROS = 'urn:oasis:names:tc:SAML:2.0:ac:classes:Kerberos'
-
- # Subject Confirmation
- CM_BEARER = 'urn:oasis:names:tc:SAML:2.0:cm:bearer'
- CM_HOLDER_KEY = 'urn:oasis:names:tc:SAML:2.0:cm:holder-of-key'
- CM_SENDER_VOUCHES = 'urn:oasis:names:tc:SAML:2.0:cm:sender-vouches'
-
- # Status Codes
- STATUS_SUCCESS = 'urn:oasis:names:tc:SAML:2.0:status:Success'
- STATUS_REQUESTER = 'urn:oasis:names:tc:SAML:2.0:status:Requester'
- STATUS_RESPONDER = 'urn:oasis:names:tc:SAML:2.0:status:Responder'
- STATUS_VERSION_MISMATCH = 'urn:oasis:names:tc:SAML:2.0:status:VersionMismatch'
- STATUS_NO_PASSIVE = 'urn:oasis:names:tc:SAML:2.0:status:NoPassive'
- STATUS_PARTIAL_LOGOUT = 'urn:oasis:names:tc:SAML:2.0:status:PartialLogout'
- STATUS_PROXY_COUNT_EXCEEDED = 'urn:oasis:names:tc:SAML:2.0:status:ProxyCountExceeded'
-
- # Crypto
- RSA_SHA1 = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1'
-
- NSMAP = {
- 'samlp': NS_SAMLP,
- 'saml': NS_SAML,
- 'ds': NS_DS,
- 'xenc': NS_XENC
- }
-
-# -*- coding: utf-8 -*-
-
-# Copyright (c) 2010-2018 OneLogin, Inc.
-# MIT License
-
-
-[docs]class OneLogin_Saml2_Error(Exception):
-
- # Errors
- SETTINGS_FILE_NOT_FOUND = 0
- SETTINGS_INVALID_SYNTAX = 1
- SETTINGS_INVALID = 2
- METADATA_SP_INVALID = 3
- SP_CERTS_NOT_FOUND = 4
- REDIRECT_INVALID_URL = 5
- PUBLIC_CERT_FILE_NOT_FOUND = 6
- PRIVATE_KEY_FILE_NOT_FOUND = 7
- SAML_RESPONSE_NOT_FOUND = 8
- SAML_LOGOUTMESSAGE_NOT_FOUND = 9
- SAML_LOGOUTREQUEST_INVALID = 10
- SAML_LOGOUTRESPONSE_INVALID = 11
- SAML_SINGLE_LOGOUT_NOT_SUPPORTED = 12
-
- def __init__(self, message, code=0, errors=None):
- """
- Initializes the Exception instance.
-
- Arguments are:
- * (str) message. Describes the error.
- * (int) code. The code error (defined in the error class).
- """
- from saml2.utils import _
-
- assert isinstance(message, basestring)
- assert isinstance(code, int)
-
- if errors is not None:
- message = message % errors
-
- Exception.__init__(self, _(message))
- self.code = code
-
-# -*- coding: utf-8 -*-
-
-# Copyright (c) 2010-2018 OneLogin, Inc.
-# MIT License
-
-from base64 import b64decode
-from datetime import datetime
-from lxml import etree
-from os.path import basename
-from urllib import urlencode
-from urlparse import parse_qs
-from xml.dom.minidom import Document, parseString
-
-import dm.xmlsec.binding as xmlsec
-
-from saml2.constants import OneLogin_Saml2_Constants
-from saml2.utils import OneLogin_Saml2_Utils
-
-
-[docs]class OneLogin_Saml2_Logout_Request:
-
- def __init__(self, settings,request=None,name_id=None, session_index=None):
- """
- Constructs the Logout Request object.
-
- Arguments are:
- * (OneLogin_Saml2_Settings) settings. Setting data
- """
- self.__settings = settings
-
- sp_data = self.__settings.get_sp_data()
- idp_data = self.__settings.get_idp_data()
- security = self.__settings.get_security_data()
-
- uid = OneLogin_Saml2_Utils.generate_unique_id()
- name_id_value = OneLogin_Saml2_Utils.generate_unique_id()
- issue_instant = OneLogin_Saml2_Utils.parse_time_to_SAML(int(datetime.now().strftime("%s")))
-
- key = None
- if 'nameIdEncrypted' in security and security['nameIdEncrypted']:
- key = idp_data['x509cert']
-
- name_id = OneLogin_Saml2_Utils.generate_name_id(
- name_id_value,
- sp_data['entityId'],
- sp_data['NameIDFormat'],
- key
- )
-
- logout_request = """<samlp:LogoutRequest
- xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
- xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
- ID="%(id)s"
- Version="2.0"
- IssueInstant="%(issue_instant)s"
- Destination="%(single_logout_url)s">
- <saml:Issuer>%(entity_id)s</saml:Issuer>
- %(name_id)s
-</samlp:LogoutRequest>""" % {
- 'id': uid,
- 'issue_instant': issue_instant,
- 'single_logout_url': idp_data['singleLogoutService']['url'],
- 'entity_id': sp_data['entityId'],
- 'name_id': name_id,
- }
-
- self.__logout_request = logout_request
-
-[docs] def get_request(self):
- """
- Returns the Logout Request defated, base64encoded
- :return: Deflated base64 encoded Logout Request
- :rtype: str object
- """
- return OneLogin_Saml2_Utils.deflate_and_base64_encode(self.__logout_request)
-
- @staticmethod
-[docs] def get_id(request):
- """
- Returns the ID of the Logout Request
- :param request: Logout Request Message
- :type request: string|DOMDocument
- :return: string ID
- :rtype: str object
- """
- if isinstance(request, Document):
- dom = request
- else:
- dom = parseString(request)
- return dom.documentElement.getAttribute('ID')
-
- @staticmethod
-[docs] def get_name_id_data(request, key=None):
- """
- Gets the NameID Data of the the Logout Request
- :param request: Logout Request Message
- :type request: string|DOMDocument
- :param key: The SP key
- :type key: string
- :return: Name ID Data (Value, Format, NameQualifier, SPNameQualifier)
- :rtype: dict
- """
- if isinstance(request, Document):
- request = request.toxml()
- dom = etree.fromstring(request)
- name_id = None
-
- encrypted_entries = OneLogin_Saml2_Utils.query(dom, '/samlp:LogoutRequest/saml:EncryptedID')
-
- if len(encrypted_entries) == 1:
- if key is None:
- raise Exception('Key is required in order to decrypt the NameID')
-
- elem = parseString(etree.tostring(encrypted_entries[0]))
- encrypted_data_nodes = elem.documentElement.getElementsByTagName('xenc:EncryptedData')
- encrypted_data = encrypted_data_nodes[0]
-
- xmlsec.initialize()
-
- # Load the key into the xmlsec context
- file_key = OneLogin_Saml2_Utils.write_temp_file(key) # FIXME avoid writing a file
- enc_key = xmlsec.Key.load(file_key.name, xmlsec.KeyDataFormatPem, None)
- enc_key.name = basename(file_key.name)
- file_key.close()
- enc_ctx = xmlsec.EncCtx()
- enc_ctx.encKey = enc_key
-
- name_id = OneLogin_Saml2_Utils.decrypt_element(encrypted_data, enc_ctx)
- else:
- entries = OneLogin_Saml2_Utils.query(dom, '/samlp:LogoutRequest/saml:NameID')
- if len(entries) == 1:
- name_id = entries[0]
-
- if name_id is None:
- raise Exception('Not NameID found in the Logout Request')
-
- name_id_data = {
- 'Value': name_id.text
- }
- for attr in ['Format', 'SPNameQualifier', 'NameQualifier']:
- if attr in name_id.attrib.keys():
- name_id_data[attr] = name_id.attrib[attr]
-
- return name_id_data
-
- @staticmethod
-[docs] def get_name_id(request, key=None):
- """
- Gets the NameID of the Logout Request Message
- :param request: Logout Request Message
- :type request: string|DOMDocument
- :param key: The SP key
- :type key: string
- :return: Name ID Value
- :rtype: string
- """
- name_id = OneLogin_Saml2_Logout_Request.get_name_id_data(request, key)
- return name_id['Value']
-
- @staticmethod
-[docs] def get_issuer(request):
- """
- Gets the Issuer of the Logout Request Message
- :param request: Logout Request Message
- :type request: string|DOMDocument
- :return: The Issuer
- :rtype: string
- """
- if isinstance(request, Document):
- request = request.toxml()
- dom = etree.fromstring(request)
-
- issuer = None
- issuer_nodes = OneLogin_Saml2_Utils.query(dom, '/samlp:LogoutRequest/saml:Issuer')
- if len(issuer_nodes) == 1:
- issuer = issuer_nodes[0].text
- return issuer
-
- @staticmethod
-[docs] def get_session_indexes(request):
- """
- Gets the SessionIndexes from the Logout Request
- :param request: Logout Request Message
- :type request: string|DOMDocument
- :return: The SessionIndex value
- :rtype: list
- """
- if isinstance(request, Document):
- request = request.toxml()
- dom = etree.fromstring(request)
-
- session_indexes = []
- session_index_nodes = OneLogin_Saml2_Utils.query(dom, '/samlp:LogoutRequest/samlp:SessionIndex')
- for session_index_node in session_index_nodes:
- session_indexes.append(session_index_node.text)
- return session_indexes
-
- @staticmethod
-[docs] def is_valid(settings, request, get_data, debug=False):
- """
- Checks if the Logout Request recieved is valid
- :param settings: Settings
- :type settings: OneLogin_Saml2_Settings
- :param request: Logout Request Message
- :type request: string|DOMDocument
- :return: If the Logout Request is or not valid
- :rtype: boolean
- """
- try:
- if isinstance(request, Document):
- dom = request
- else:
- dom = parseString(request)
-
- idp_data = settings.get_idp_data()
- idp_entity_id = idp_data['entityId']
-
- if settings.is_strict():
- res = OneLogin_Saml2_Utils.validate_xml(dom, 'saml-schema-protocol-2.0.xsd', debug)
- if not isinstance(res, Document):
- raise Exception('Invalid SAML Logout Request. Not match the saml-schema-protocol-2.0.xsd')
-
- security = settings.get_security_data()
-
- current_url = OneLogin_Saml2_Utils.get_self_url_no_query(get_data)
-
- # Check NotOnOrAfter
- if dom.documentElement.hasAttribute('NotOnOrAfter'):
- na = OneLogin_Saml2_Utils.parse_SAML_to_time(dom.documentElement.getAttribute('NotOnOrAfter'))
- if na <= datetime.now():
- raise Exception('Timing issues (please check your clock settings)')
-
- # Check destination
- if dom.documentElement.hasAttribute('Destination'):
- destination = dom.documentElement.getAttribute('Destination')
- if destination is not None:
- if current_url not in destination:
- raise Exception('The LogoutRequest was received at $currentURL instead of $destination')
-
- # Check issuer
- issuer = OneLogin_Saml2_Logout_Request.get_issuer(dom)
- if issuer is None or issuer != idp_entity_id:
- raise Exception('Invalid issuer in the Logout Request')
-
- if security['wantMessagesSigned']:
- if 'Signature' not in get_data:
- raise Exception('The Message of the Logout Request is not signed and the SP require it')
-
- if 'Signature' in get_data:
- if 'SigAlg' not in get_data:
- sign_alg = OneLogin_Saml2_Constants.RSA_SHA1
- else:
- sign_alg = get_data['SigAlg']
-
- if sign_alg != OneLogin_Saml2_Constants.RSA_SHA1:
- raise Exception('Invalid signAlg in the recieved Logout Request')
-
- signed_query = 'SAMLRequest=%s' % urlencode(get_data['SAMLRequest'])
- if 'RelayState' in get_data:
- signed_query = '%s&RelayState=%s' % (signed_query, urlencode(get_data['RelayState']))
- signed_query = '%s&SigAlg=%s' % (signed_query, urlencode(sign_alg))
-
- if 'x509cert' not in idp_data or idp_data['x509cert'] is None:
- raise Exception('In order to validate the sign on the Logout Request, the x509cert of the IdP is required')
- cert = idp_data['x509cert']
-
- xmlsec.initialize()
- objkey = xmlsec.Key.load(cert, xmlsec.KeyDataFormatPem, None) # FIXME is this right?
-
- if not objkey.verifySignature(signed_query, b64decode(get_data['Signature'])):
- raise Exception('Signature validation failed. Logout Request rejected')
-
- return True
- except Exception as e:
- debug = settings.is_debug_active()
- if debug:
- print(e.strerror)
- return False
-
-# -*- coding: utf-8 -*-
-
-# Copyright (c) 2010-2018 OneLogin, Inc.
-# MIT License
-
-from base64 import b64decode
-from datetime import datetime
-from lxml import etree
-from urllib import quote_plus
-from xml.dom.minidom import Document, parseString
-
-import dm.xmlsec.binding as xmlsec
-
-from saml2.constants import OneLogin_Saml2_Constants
-from saml2.utils import OneLogin_Saml2_Utils
-
-
-[docs]class OneLogin_Saml2_Logout_Response():
-
- def __init__(self, settings, response=None):
- """
- Constructs a Logout Response object (Initialize params from settings
- and if provided load the Logout Response.
-
- Arguments are:
- * (OneLogin_Saml2_Settings) settings. Setting data
- * (string) response. An UUEncoded SAML Logout
- response from the IdP.
- """
- self.__settings = settings
- if response is not None:
- self.__logout_response = OneLogin_Saml2_Utils.decode_base64_and_inflate(response)
- self.document = parseString(self.__logout_response)
-
-[docs] def get_issuer(self):
- """
- Gets the Issuer of the Logout Response Message
- :return: The Issuer
- :rtype: string
- """
- issuer = None
- issuer_nodes = self.__query('/samlp:LogoutResponse/saml:Issuer')
- if len(issuer_nodes) == 1:
- issuer = issuer_nodes[0].text
- return issuer
-
-[docs] def get_status(self):
- """
- Gets the Status
- :return: The Status
- :rtype: string
- """
- entries = self.__query('/samlp:LogoutResponse/samlp:Status/samlp:StatusCode')
- if len(entries) == 0:
- return None
- status = entries[0].attrib['Value']
- return status
-
-[docs] def is_valid(self, request_data, request_id=None):
- """
- Determines if the SAML LogoutResponse is valid
- :param request_id: The ID of the LogoutRequest sent by this SP to the IdP
- :type request_id: string
- :return: Returns if the SAML LogoutResponse is or not valid
- :rtype: boolean
- """
- try:
- idp_data = self.__settings.get_idp_data()
- idp_entity_id = idp_data['entityId']
- get_data = request_data['get_data']
-
- if self.__settings.is_strict():
- res = OneLogin_Saml2_Utils.validate_xml(self.document, 'saml-schema-protocol-2.0.xsd', self.__settings.is_debug_active())
- if not isinstance(res, Document):
- raise Exception('Invalid SAML Logout Request. Not match the saml-schema-protocol-2.0.xsd')
-
- security = self.__settings.get_security_data()
-
- # Check if the InResponseTo of the Logout Response matchs the ID of the Logout Request (requestId) if provided
- if request_id is not None and self.document.documentElement.hasAttribute('InResponseTo'):
- in_response_to = self.document.documentElement.getAttribute('InResponseTo')
- if request_id != in_response_to:
- raise Exception('The InResponseTo of the Logout Response: %s, does not match the ID of the Logout request sent by the SP: %s' % (in_response_to, request_id))
-
- # Check issuer
- issuer = self.get_issuer()
- if issuer is None or issuer != idp_entity_id:
- raise Exception('Invalid issuer in the Logout Request')
-
- current_url = OneLogin_Saml2_Utils.get_self_url_no_query(request_data)
-
- # Check destination
- if self.document.documentElement.hasAttribute('Destination'):
- destination = self.document.documentElement.getAttribute('Destination')
- if destination is not None:
- if current_url not in destination:
- raise Exception('The LogoutRequest was received at $currentURL instead of $destination')
-
- if security['wantMessagesSigned']:
- if 'Signature' not in get_data:
- raise Exception('The Message of the Logout Response is not signed and the SP require it')
-
- if 'Signature' in get_data:
- if 'SigAlg' not in get_data:
- sign_alg = OneLogin_Saml2_Constants.RSA_SHA1
- else:
- sign_alg = get_data['SigAlg']
-
- if sign_alg != OneLogin_Saml2_Constants.RSA_SHA1:
- raise Exception('Invalid signAlg in the recieved Logout Response')
-
- signed_query = 'SAMLResponse=%s' % quote_plus(get_data['SAMLResponse'])
- if 'RelayState' in get_data:
- signed_query = '%s&RelayState=%s' % (signed_query, quote_plus(get_data['RelayState']))
- signed_query = '%s&SigAlg=%s' % (signed_query, quote_plus(sign_alg))
-
- if 'x509cert' not in idp_data or idp_data['x509cert'] is None:
- raise Exception('In order to validate the sign on the Logout Response, the x509cert of the IdP is required')
- cert = idp_data['x509cert']
-
- xmlsec.initialize()
- objkey = xmlsec.Key.load(cert, xmlsec.KeyDataFormatPem, None) # FIXME is this right?
-
- if not objkey.verifySignature(signed_query, b64decode(get_data['Signature'])):
- raise Exception('Signature validation failed. Logout Response rejected')
-
- return True
- except Exception as e:
- debug = self.__settings.is_debug_active()
- if debug:
- print(e.strerror)
- return False
-
- def __query(self, query):
- """
- Extracts a node from the DOMDocument (Logout Response Menssage)
- :param query: Xpath Expresion
- :type query: string
- :return: The queried node
- :rtype: DOMNodeList
- """
- # Switch to lxml for querying
- xml = self.document.toxml()
- return OneLogin_Saml2_Utils.query(etree.fromstring(xml), query)
-
-[docs] def build(self, in_response_to):
- """
- Creates a Logout Response object.
- :param in_response_to: InResponseTo value for the Logout Response.
- :type in_response_to: string
- """
- sp_data = self.__settings.get_sp_data()
- idp_data = self.__settings.get_idp_data()
-
- uid = OneLogin_Saml2_Utils.generate_unique_id()
- issue_instant = OneLogin_Saml2_Utils.parse_time_to_SAML(
- int(datetime.now().strftime("%s"))
- )
-
- logout_response = """<samlp:LogoutResponse xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
- xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
- ID="%(id)s"
- Version="2.0"
- IssueInstant="%(issue_instant)s"
- Destination="%(destination)s"
- InResponseTo="%(in_response_to)s"
->
- <saml:Issuer>%(entity_id)s</saml:Issuer>
- <samlp:Status>
- <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success" />
- </samlp:Status>
-</samlp:LogoutResponse>""" % {
- 'id': uid,
- 'issue_instant': issue_instant,
- 'destination': idp_data['singleLogoutService']['url'],
- 'in_response_to': in_response_to,
- 'entity_id': sp_data['entityId'],
- }
-
- self.__logout_response = logout_response
-
-[docs] def get_response(self):
- """
- Returns a Logout Response object.
- :return: Logout Response deflated and base64 encoded
- :rtype: string
- """
- return OneLogin_Saml2_Utils.deflate_and_base64_encode(self.__logout_response)
-
-# -*- 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()
-
-# -*- coding: utf-8 -*-
-
-# Copyright (c) 2010-2018 OneLogin, Inc.
-# MIT License
-
-from base64 import b64decode
-from copy import deepcopy
-from lxml import etree
-from os.path import basename
-from time import time
-import sys
-from xml.dom.minidom import Document
-
-import dm.xmlsec.binding as xmlsec
-
-from saml2.constants import OneLogin_Saml2_Constants
-from saml2.utils import OneLogin_Saml2_Utils
-
-
-[docs]class OneLogin_Saml2_Response(object):
-
- def __init__(self, settings, response):
- """
- Constructs the response object.
-
- :param settings: The setting info
- :type settings: OneLogin_Saml2_Setting object
-
- :param response: The base64 encoded, XML string containing the samlp:Response
- :type response: string
- """
- self.__settings = settings
- self.response = b64decode(response)
- self.document = etree.fromstring(self.response)
- self.decrypted_document = None
- self.encrypted = None
-
- # Quick check for the presence of EncryptedAssertion
- encrypted_assertion_nodes = self.__query('//saml:EncryptedAssertion')
- if encrypted_assertion_nodes:
- decrypted_document = deepcopy(self.document)
- self.encrypted = True
- self.decrypted_document = self.__decrypt_assertion(decrypted_document)
-
-[docs] def is_valid(self, request_data, request_id=None):
- """
- Constructs the response object.
-
- :param request_id: Optional argument. The ID of the AuthNRequest sent by this SP to the IdP
- :type request_id: string
-
- :returns: True if the SAML Response is valid, False if not
- :rtype: bool
- """
- try:
- # Checks SAML version
- if self.document.get('Version', None) != '2.0':
- raise Exception('Unsupported SAML version')
-
- # Checks that ID exists
- if self.document.get('ID', None) is None:
- raise Exception('Missing ID attribute on SAML Response')
-
- # Checks that the response only has one assertion
- if not self.validate_num_assertions():
- raise Exception('Multiple assertions are not supported')
-
- # Checks that the response has the SUCCESS status
- self.check_status()
-
- idp_data = self.__settings.get_idp_data()
- idp_entityid = idp_data.get('entityId', '')
- sp_data = self.__settings.get_sp_data()
- sp_entityid = sp_data.get('entityId', '')
-
- sign_nodes = self.__query('//ds:Signature')
-
- signed_elements = []
- for sign_node in sign_nodes:
- signed_elements.append(sign_node.getparent().tag)
-
- if self.__settings.is_strict():
- res = OneLogin_Saml2_Utils.validate_xml(etree.tostring(self.document), 'saml-schema-protocol-2.0.xsd', self.__settings.is_debug_active())
- if not isinstance(res, Document):
- raise Exception('Invalid SAML Response. Not match the saml-schema-protocol-2.0.xsd')
-
- security = self.__settings.get_security_data()
- current_url = OneLogin_Saml2_Utils.get_self_url_no_query(request_data)
-
- # Check if the InResponseTo of the Response matchs the ID of the AuthNRequest (requestId) if provided
- in_response_to = self.document.get('InResponseTo', None)
- if in_response_to and request_id:
- if in_response_to != request_id:
- raise Exception('The InResponseTo of the Response: %s, does not match the ID of the AuthNRequest sent by the SP: %s' % (in_response_to, request_id))
-
- if not self.encrypted and security.get('wantAssertionsEncrypted', False):
- raise Exception('The assertion of the Response is not encrypted and the SP require it')
-
- if security.get('wantNameIdEncrypted', False):
- encrypted_nameid_nodes = self.__query_assertion('/saml:Subject/saml:EncryptedID/xenc:EncryptedData')
- if not encrypted_nameid_nodes:
- raise Exception('The NameID of the Response is not encrypted and the SP require it')
-
- # Checks that there is at least one AttributeStatement
- attribute_statement_nodes = self.__query_assertion('/saml:AttributeStatement')
- if not attribute_statement_nodes:
- raise Exception('There is no AttributeStatement on the Response')
-
- # Validates Asserion timestamps
- if not self.validate_timestamps():
- raise Exception('Timing issues (please check your clock settings)')
-
- encrypted_attributes_nodes = self.__query_assertion('/saml:AttributeStatement/saml:EncryptedAttribute')
- if encrypted_attributes_nodes:
- raise Exception('There is an EncryptedAttribute in the Response and this SP not support them')
-
- # Checks destination
- destination = self.document.get('Destination', None)
- if destination:
- if destination not in current_url:
- raise Exception('The response was received at %s instead of %s' % (current_url, destination))
-
- # Checks audience
- valid_audiences = self.get_audiences()
- if valid_audiences and sp_entityid not in valid_audiences:
- raise Exception('%s is not a valid audience for this Response' % sp_entityid)
-
- # Checks the issuers
- issuers = self.get_issuers()
- for issuer in issuers:
- if not issuer or issuer != idp_entityid:
- raise Exception('Invalid issuer in the Assertion/Response')
-
- # Checks the session Expiration
- session_expiration = self.get_session_not_on_or_after()
- if not session_expiration and session_expiration <= time():
- raise Exception('The attributes have expired, based on the SessionNotOnOrAfter of the AttributeStatement of this Response')
-
- # Checks the SubjectConfirmation, at least one SubjectConfirmation must be valid
- any_subject_confirmation = False
- subject_confirmation_nodes = self.__query_assertion('/saml:Subject/saml:SubjectConfirmation')
-
- for scn in subject_confirmation_nodes:
- method = scn.get('Method', None)
- if method and method != OneLogin_Saml2_Constants.CM_BEARER:
- continue
- scData = scn.find('saml:SubjectConfirmationData', namespaces=OneLogin_Saml2_Constants.NSMAP)
- if scData is None:
- continue
- else:
- irt = scData.get('InResponseTo', None)
- if irt != in_response_to:
- continue
- recipient = scData.get('Recipient', None)
- if recipient not in current_url:
- continue
- nooa = scData.get('NotOnOrAfter', None)
- if nooa:
- parsed_nooa = OneLogin_Saml2_Utils.parse_SAML_to_time(nooa)
- if parsed_nooa <= time():
- continue
- nb = scData.get('NotBefore', None)
- if nb:
- parsed_nb = OneLogin_Saml2_Utils.parse_SAML_to_time(nb)
- if (parsed_nb > time()):
- continue
- any_subject_confirmation = True
- break
-
- if not any_subject_confirmation:
- raise Exception('A valid SubjectConfirmation was not found on this Response')
-
- if security.get('wantAssertionsSigned', False) and 'saml:Assertion' not in signed_elements:
- raise Exception('The Assertion of the Response is not signed and the SP require it')
-
- if security.get('wantMessagesSigned', False) and 'samlp:Response' not in signed_elements:
- raise Exception('The Message of the Response is not signed and the SP require it')
-
- document_to_validate = None
- if len(signed_elements) > 0:
- cert = idp_data.get('x509cert', None)
- fingerprint = idp_data.get('certFingerprint', None)
-
- # Only validates the first sign found
- if 'samlp:Response' in signed_elements:
- document_to_validate = self.document
- else:
- if self.encrypted:
- document_to_validate = self.decrypted_document
- else:
- document_to_validate = self.document
-
- if document_to_validate is not None:
- if not OneLogin_Saml2_Utils.validate_sign(document_to_validate, cert, fingerprint):
- raise Exception('Signature validation failed. SAML Response rejected')
- return True
- except:
- debug = self.__settings.is_debug_active()
- if debug:
- print sys.exc_info()[0]
- return False
-
-[docs] def check_status(self):
- """
- Check if the status of the response is success or not
-
- :raises: Exception. If the status is not success
- """
- status = OneLogin_Saml2_Utils.get_status(self.document)
- code = status.get('code', None)
- if code and code != OneLogin_Saml2_Constants.STATUS_SUCCESS:
- splited_code = code.split(':')
- printable_code = splited_code.pop()
- status_exception_msg = 'The status code of the Response was not Success, was %s' % printable_code
- status_msg = status.get('msg', None)
- if status_msg:
- status_exception_msg += ' -> ' + status_msg
- raise Exception(status_exception_msg)
-
-[docs] def get_audiences(self):
- """
- Gets the audiences
-
- :returns: The valid audiences for the SAML Response
- :rtype: list
- """
- audiences = []
-
- audience_nodes = self.__query_assertion('/saml:Conditions/saml:AudienceRestriction/saml:Audience')
- for audience_node in audience_nodes:
- audiences.append(audience_node.text)
-
-[docs] def get_issuers(self):
- """
- Gets the issuers (from message and from assertion)
-
- :returns: The issuers
- :rtype: list
- """
- issuers = []
-
- message_issuer_nodes = self.__query('/samlp:Response/saml:Issuer')
- if message_issuer_nodes:
- issuers.append(message_issuer_nodes[0].text)
-
- assertion_issuer_nodes = self.__query_assertion('/saml:Issuer')
- if assertion_issuer_nodes:
- issuers.append(assertion_issuer_nodes[0].text)
-
- return list(set(issuers))
-
-[docs] def get_nameid_data(self):
- """
- Gets the NameID Data provided by the SAML Response from the IdP
-
- :returns: Name ID Data (Value, Format, NameQualifier, SPNameQualifier)
- :rtype: dict
- """
- nameid = None
- encrypted_id_data_nodes = self.__query_assertion('/saml:Subject/saml:EncryptedID/xenc:EncryptedData')
- if encrypted_id_data_nodes:
- encrypted_data = encrypted_id_data_nodes[0]
-
- xmlsec.initialize()
-
- # Load the key into the xmlsec context
- key = self.__settings.get_sp_key()
- file_key = OneLogin_Saml2_Utils.write_temp_file(key) # FIXME avoid writing a file
- enc_key = xmlsec.Key.load(file_key.name, xmlsec.KeyDataFormatPem, None)
- enc_key.name = basename(file_key.name)
- file_key.close()
- enc_ctx = xmlsec.EncCtx()
- enc_ctx.encKey = enc_key
-
- nameid = OneLogin_Saml2_Utils.decrypt_element(encrypted_data, enc_ctx)
- else:
- nameid_nodes = self.__query_assertion('/saml:Subject/saml:NameID')
- if nameid_nodes:
- nameid = nameid_nodes[0]
- if nameid is None:
- raise Exception('Not NameID found in the assertion of the Response')
-
- nameid_data = {'Value': nameid.text}
- for attr in ['Format', 'SPNameQualifier', 'NameQualifier']:
- value = nameid.get(attr, None)
- if value:
- nameid_data[attr] = value
- return nameid_data
-
-[docs] def get_nameid(self):
- """
- Gets the NameID provided by the SAML Response from the IdP
-
- :returns: NameID (value)
- :rtype: string
- """
- nameid_data = self.get_nameid_data()
- return nameid_data['Value']
-
-[docs] def get_session_not_on_or_after(self):
- """
- Gets the SessionNotOnOrAfter from the AuthnStatement
- Could be used to set the local session expiration
-
- :returns: The SessionNotOnOrAfter value
- :rtype: time|None
- """
- not_on_or_after = None
- authn_statement_nodes = self.__query_assertion('/saml:AuthnStatement[@SessionNotOnOrAfter]')
- if authn_statement_nodes:
- not_on_or_after = OneLogin_Saml2_Utils.parse_SAML_to_time(authn_statement_nodes[0].get('SessionNotOnOrAfter'))
- return not_on_or_after
-
-[docs] def get_session_index(self):
- """
- Gets the SessionIndex from the AuthnStatement
- Could be used to be stored in the local session in order
- to be used in a future Logout Request that the SP could
- send to the SP, to set what specific session must be deleted
-
- :returns: The SessionIndex value
- :rtype: string|None
- """
- session_index = None
- authn_statement_nodes = self.__query_assertion('/saml:AuthnStatement[@SessionIndex]')
- if authn_statement_nodes:
- session_index = authn_statement_nodes[0].get('SessionIndex')
- return session_index
-
-[docs] def get_attributes(self):
- """
- Gets the Attributes from the AttributeStatement element.
- EncryptedAttributes are not supported
- """
- attributes = {}
- attribute_nodes = self.__query_assertion('/saml:AttributeStatement/saml:Attribute')
- for attribute_node in attribute_nodes:
- attr_name = attribute_node.get('Name')
- values = []
- for attr in attribute_node.iterchildren('{%s}AttributeValue' % OneLogin_Saml2_Constants.NSMAP['saml']):
- values.append(attr.text)
- attributes[attr_name] = values
- return attributes
-
-[docs] def validate_num_assertions(self):
- """
- Verifies that the document only contains a single Assertion (encrypted or not)
-
- :returns: True if only 1 assertion encrypted or not
- :rtype: bool
- """
- encrypted_assertion_nodes = self.__query('//saml:EncryptedAssertion')
- assertion_nodes = self.__query('//saml:Assertion')
- return (len(encrypted_assertion_nodes) + len(assertion_nodes)) == 1
-
-[docs] def validate_timestamps(self):
- """
- Verifies that the document is valid according to Conditions Element
-
- :returns: True if the condition is valid, False otherwise
- :rtype: bool
- """
- conditions_nodes = self.__query('//saml:Conditions')
- for conditions_node in conditions_nodes:
- nb_attr = conditions_node.get('NotBefore')
- nooa_attr = conditions_node.get('NotOnOrAfter')
- if nb_attr and OneLogin_Saml2_Utils.parse_SAML_to_time(nb_attr) > time() + OneLogin_Saml2_Constants.ALOWED_CLOCK_DRIFT:
- return False
- if nooa_attr and OneLogin_Saml2_Utils.parse_SAML_to_time(nooa_attr) + OneLogin_Saml2_Constants.ALOWED_CLOCK_DRIFT <= time():
- return False
- return True
-
- def __query_assertion(self, xpath_expr):
- """
- Extracts nodes that match the query from the Assertion
-
- :param query: Xpath Expresion
- :type query: String
-
- :returns: The queried nodes
- :rtype: list
- """
- if self.encrypted:
- assertion_expr = '/saml:EncryptedAssertion/saml:Assertion'
- else:
- assertion_expr = '/saml:Assertion'
- signature_expr = '/ds:Signature/ds:SignedInfo/ds:Reference'
- signed_assertion_query = '/samlp:Response' + assertion_expr + signature_expr
- assertion_reference_nodes = self.__query(signed_assertion_query)
-
- if not assertion_reference_nodes:
- # Check if the message is signed
- signed_message_query = '/samlp:Response' + signature_expr
- message_reference_nodes = self.__query(signed_message_query)
- if message_reference_nodes:
- id = message_reference_nodes[0].get('URI')
- final_query = "/samlp:Response[@ID='%s']/" % id[1:]
- else:
- final_query = "/samlp:Response/"
- final_query += assertion_expr
- else:
- id = assertion_reference_nodes[0].get('URI')
- final_query = '/samlp:Response' + assertion_expr + "[@ID='%s']" % id[1:]
- final_query += xpath_expr
- return self.__query(final_query)
-
- def __query(self, query):
- """
- Extracts nodes that match the query from the Response
-
- :param query: Xpath Expresion
- :type query: String
-
- :returns: The queried nodes
- :rtype: list
- """
- if self.encrypted:
- document = self.decrypted_document
- else:
- document = self.document
- return OneLogin_Saml2_Utils.query(document, query)
-
- def __decrypt_assertion(self, dom):
- """
- Decrypts the Assertion
-
- :raises: Exception if no private key available
- :param dom: Encrypted Assertion
- :type dom: Element
- :returns: Decrypted Assertion
- :rtype: Element
- """
- key = self.__settings.get_sp_key()
-
- if not key:
- raise Exception('No private key available, check settings')
-
- # TODO Study how decrypt assertion
-
-# -*- coding: utf-8 -*-
-
-# Copyright (c) 2010-2018 OneLogin, Inc.
-# MIT License
-
-from datetime import datetime
-import json
-import re
-from os.path import dirname, exists, join, sep
-from xml.dom.minidom import Document
-
-from saml2.constants import OneLogin_Saml2_Constants
-from saml2.errors import OneLogin_Saml2_Error
-from saml2.metadata import OneLogin_Saml2_Metadata
-from saml2.utils import OneLogin_Saml2_Utils
-
-
-# Regex from Django Software Foundation and individual contributors.
-# Released under a BSD 3-Clause License
-url_regex = re.compile(
- r'^(?:[a-z0-9\.\-]*)://' # scheme is validated separately
- r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' # domain...
- r'localhost|' # localhost...
- r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|' # ...or ipv4
- r'\[?[A-F0-9]*:[A-F0-9:]+\]?)' # ...or ipv6
- r'(?::\d+)?' # optional port
- r'(?:/?|[/?]\S+)$', re.IGNORECASE)
-url_schemes = ['http', 'https', 'ftp', 'ftps']
-
-
-[docs]def validate_url(url):
- scheme = url.split('://')[0].lower()
- if scheme not in url_schemes:
- return False
- if not bool(url_regex.search(url)):
- return False
- return True
-
-
-[docs]class OneLogin_Saml2_Settings:
-
- def __init__(self, settings=None, custom_base_path=None):
- """
- Initializes the settings:
- - Sets the paths of the different folders
- - Loads settings info from settings file or array/object provided
-
- :param settings: SAML Toolkit Settings
- :type settings: dict|object
- """
- self.__paths = {}
- self.__strict = False
- self.__debug = False
- self.__sp = {}
- self.__idp = {}
- self.__contacts = {}
- self.__organization = {}
- self.__errors = []
-
- self.__load_paths(base_path=custom_base_path)
- self.__update_paths(settings)
-
- if settings is None:
- if not self.__load_settings_from_file():
- raise OneLogin_Saml2_Error(
- 'Invalid file settings: %s',
- OneLogin_Saml2_Error.SETTINGS_INVALID,
- ','.join(self.__errors)
- )
- self.__add_default_values()
- elif isinstance(settings, dict):
- if not self.__load_settings_from_dict(settings):
- raise OneLogin_Saml2_Error(
- 'Invalid dict settings: %s',
- OneLogin_Saml2_Error.SETTINGS_INVALID,
- ','.join(self.__errors)
- )
- else:
- raise Exception('Unsupported settings object')
-
- self.format_idp_cert()
-
- def __load_paths(self, base_path=None):
- """
- Sets the paths of the different folders
- """
- if base_path is None:
- base_path = dirname(dirname(dirname(__file__)))
- base_path += sep
- self.__paths = {
- 'base': base_path,
- 'cert': base_path + 'certs' + sep,
- 'lib': base_path + 'lib' + sep,
- 'extlib': base_path + 'extlib' + sep,
- }
-
- def __update_paths(self, settings):
- """
- Set custom paths if necessary
- """
- if not isinstance(settings, dict):
- return
-
- if 'custom_base_path' in settings:
- base_path = settings['custom_base_path']
- base_path = join(dirname(__file__), base_path)
- self.__load_paths(base_path)
-
-[docs] def get_base_path(self):
- """
- Returns base path
-
- :return: The base toolkit folder path
- :rtype: string
- """
- return self.__paths['base']
-
-[docs] def get_cert_path(self):
- """
- Returns cert path
-
- :return: The cert folder path
- :rtype: string
- """
- return self.__paths['cert']
-
-[docs] def get_lib_path(self):
- """
- Returns lib path
-
- :return: The library folder path
- :rtype: string
- """
- return self.__paths['lib']
-
-[docs] def get_ext_lib_path(self):
- """
- Returns external lib path
-
- :return: The external library folder path
- :rtype: string
- """
- return self.__paths['extlib']
-
-[docs] def get_schemas_path(self):
- """
- Returns schema path
-
- :return: The schema folder path
- :rtype: string
- """
- return self.__paths['lib'] + 'schemas/'
-
- def __load_settings_from_dict(self, settings):
- """
- Loads settings info from a settings Dict
-
- :param settings: SAML Toolkit Settings
- :type settings: dict
-
- :returns: True if the settings info is valid
- :rtype: boolean
- """
- errors = self.check_settings(settings)
- if len(errors) == 0:
- self.__errors = []
- self.__sp = settings['sp']
- self.__idp = settings['idp']
-
- if 'strict' in settings:
- self.__strict = settings['strict']
- if 'debug' in settings:
- self.__debug = settings['debug']
- if 'security' in settings:
- self.__security = settings['security']
- if 'contactPerson' in settings:
- self.__contacts = settings['contactPerson']
- if 'organization' in settings:
- self.__organization = settings['organization']
-
- self.__add_default_values()
- return True
-
- self.__errors = errors
- return False
-
- def __load_settings_from_file(self):
- """
- Loads settings info from the settings json file
-
- :returns: True if the settings info is valid
- :rtype: boolean
- """
- filename = self.get_base_path() + 'settings.json'
-
- if not exists(filename):
- raise OneLogin_Saml2_Error(
- 'Settings file not found: %s',
- OneLogin_Saml2_Error.SETTINGS_FILE_NOT_FOUND,
- filename
- )
-
- # In the php toolkit instead of being a json file it is a php file and
- # it is directly included
- json_data = open(filename, 'r')
- settings = json.load(json_data)
- json_data.close()
-
- advanced_filename = self.get_base_path() + 'advanced_settings.json'
- if exists(advanced_filename):
- json_data = open(advanced_filename, 'r')
- settings.update(json.load(json_data)) # Merge settings
- json_data.close()
-
- return self.__load_settings_from_dict(settings)
-
- def __add_default_values(self):
- """
- Add default values if the settings info is not complete
- """
- if 'binding' not in self.__sp['assertionConsumerService']:
- self.__sp['assertionConsumerService']['binding'] = OneLogin_Saml2_Constants.BINDING_HTTP_POST
- if 'singleLogoutService' in self.__sp and 'binding' not in self.__sp['singleLogoutService']:
- self.__sp['singleLogoutService']['binding'] = OneLogin_Saml2_Constants.BINDING_HTTP_REDIRECT
-
- # Related to nameID
- if 'NameIDFormat' not in self.__sp:
- self.__sp['NameIDFormat'] = OneLogin_Saml2_Constants.NAMEID_PERSISTENT
- if 'nameIdEncrypted' not in self.__security:
- self.__security['nameIdEncrypted'] = False
-
- # Sign provided
- if 'authnRequestsSigned' not in self.__security:
- self.__security['authnRequestsSigned'] = False
- if 'logoutRequestSigned' not in self.__security:
- self.__security['logoutRequestSigned'] = False
- if 'logoutResponseSigned' not in self.__security:
- self.__security['logoutResponseSigned'] = False
- if 'signMetadata' not in self.__security:
- self.__security['signMetadata'] = False
-
- # Sign expected
- if 'wantMessagesSigned' not in self.__security:
- self.__security['wantMessagesSigned'] = False
- if 'wantAssertionsSigned' not in self.__security:
- self.__security['wantAssertionsSigned'] = False
-
- # Encrypt expected
- if 'wantAssertionsEncrypted' not in self.__security:
- self.__security['wantAssertionsEncrypted'] = False
- if 'wantNameIdEncrypted' not in self.__security:
- self.__security['wantNameIdEncrypted'] = False
-
- if 'x509cert' not in self.__idp:
- self.__idp['x509cert'] = ''
- if 'certFingerprint' not in self.__idp:
- self.__idp['certFingerprint'] = ''
-
-[docs] def check_settings(self, settings):
- """
- Checks the settings info.
-
- :param settings: Dict with settings data
- :type settings: dict
-
- :returns: Errors found on the settings data
- :rtype: list
- """
- assert isinstance(settings, dict)
-
- errors = []
- if not isinstance(settings, dict) or len(settings) == 0:
- errors.append('invalid_syntax')
- return errors
-
- if 'idp' not in settings or len(settings['idp']) == 0:
- errors.append('idp_not_found')
- else:
- idp = settings['idp']
- if 'entityId' not in idp or len(idp['entityId']) == 0:
- errors.append('idp_entityId_not_found')
-
- if ('singleSignOnService' not in idp or
- 'url' not in idp['singleSignOnService'] or
- len(idp['singleSignOnService']['url']) == 0):
- errors.append('idp_sso_not_found')
- elif not validate_url(idp['singleSignOnService']['url']):
- errors.append('idp_sso_url_invalid')
-
- if ('singleLogoutService' in idp and
- 'url' in idp['singleLogoutService'] and
- len(idp['singleLogoutService']['url']) > 0 and
- not validate_url(idp['singleLogoutService']['url'])):
- errors.append('idp_slo_url_invalid')
-
- if 'sp' not in settings or len(settings['sp']) == 0:
- errors.append('sp_not_found')
- else:
- sp = settings['sp']
- security = {}
- if 'security' in settings:
- security = settings['security']
-
- if 'entityId' not in sp or len(sp['entityId']) == 0:
- errors.append('sp_entityId_not_found')
-
- if ('assertionConsumerService' not in sp or
- 'url' not in sp['assertionConsumerService'] or
- len(sp['assertionConsumerService']['url']) == 0):
- errors.append('sp_acs_not_found')
- elif not validate_url(sp['assertionConsumerService']['url']):
- errors.append('sp_acs_url_invalid')
-
- if ('singleLogoutService' in sp and
- 'url' in sp['singleLogoutService'] and
- len(sp['singleLogoutService']['url']) > 0 and
- not validate_url(sp['singleLogoutService']['url'])):
- errors.append('sp_sls_url_invalid')
-
- if 'signMetadata' in security and isinstance(security['signMetadata'], dict):
- if ('keyFileName' not in security['signMetadata'] or
- 'certFileName' not in security['signMetadata']):
- errors.append('sp_signMetadata_invalid')
-
- if ((('authnRequestsSigned' in security and security['authnRequestsSigned']) or
- ('logoutRequestSigned' in security and security['logoutRequestSigned']) or
- ('logoutResponseSigned' in security and security['logoutResponseSigned']) or
- ('wantAssertionsEncrypted' in security and security['wantAssertionsEncrypted']) or
- ('wantNameIdEncrypted' in security and security['wantNameIdEncrypted'])) and
- not self.check_sp_certs()):
- errors.append('sp_cert_not_found_and_required')
-
- exists_X509 = ('idp' in settings and
- 'x509cert' in settings['idp'] and
- len(settings['idp']['x509cert']) > 0)
- exists_fingerprint = ('idp' in settings and
- 'certFingerprint' in settings['idp'] and
- len(settings['idp']['certFingerprint']) > 0)
- if ((('wantAssertionsSigned' in security and security['wantAssertionsSigned']) or
- ('wantMessagesSigned' in security and security['wantMessagesSigned'])) and
- not(exists_X509 or exists_fingerprint)):
- errors.append('idp_cert_or_fingerprint_not_found_and_required')
- if ('nameIdEncrypted' in security and security['nameIdEncrypted']) and not exists_X509:
- errors.append('idp_cert_not_found_and_required')
-
- if 'contactPerson' in settings:
- types = settings['contactPerson'].keys()
- valid_types = ['technical', 'support', 'administrative', 'billing', 'other']
- for t in types:
- if t not in valid_types:
- errors.append('contact_type_invalid')
- break
-
- for t in settings['contactPerson']:
- contact = settings['contactPerson'][t]
- if (('givenName' not in contact or len(contact['givenName']) == 0) or
- ('emailAddress' not in contact or len(contact['emailAddress']) == 0)):
- errors.append('contact_not_enough_data')
- break
-
- if 'organization' in settings:
- for o in settings['organization']:
- organization = settings['organization'][o]
- if (('name' not in organization or len(organization['name']) == 0) or
- ('displayname' not in organization or len(organization['displayname']) == 0) or
- ('url' not in organization or len(organization['url']) == 0)):
- errors.append('organization_not_enough_data')
- break
-
- return errors
-
-[docs] def check_sp_certs(self):
- """
- Checks if the x509 certs of the SP exists and are valid.
-
- :returns: If the x509 certs of the SP exists and are valid
- :rtype: boolean
- """
- key = self.get_sp_key()
- cert = self.get_sp_cert()
- return key is not None and cert is not None
-
-[docs] def get_sp_key(self):
- """
- Returns the x509 private key of the SP.
-
- :returns: SP private key
- :rtype: string
- """
- key = None
- key_file = self.__paths['cert'] + 'sp.key'
-
- if exists(key_file):
- f = open(key_file, 'r')
- key = f.read()
- f.close()
-
- return key
-
-[docs] def get_sp_cert(self):
- """
- Returns the x509 public cert of the SP.
-
- :returns: SP public cert
- :rtype: string
- """
- cert = None
- cert_file = self.__paths['cert'] + 'sp.crt'
-
- if exists(cert_file):
- f = open(cert_file, 'r')
- cert = f.read()
- f.close()
-
- return cert
-
-[docs] def get_idp_data(self):
- """
- Gets the IdP data.
-
- :returns: IdP info
- :rtype: dict
- """
- return self.__idp
-
-[docs] def get_sp_data(self):
- """
- Gets the SP data.
-
- :returns: SP info
- :rtype: dict
- """
- return self.__sp
-
-[docs] def get_security_data(self):
- """
- Gets security data.
-
- :returns: Security info
- :rtype: dict
- """
- return self.__security
-
-[docs] def get_contacts(self):
- """
- Gets contact data.
-
- :returns: Contacts info
- :rtype: dict
- """
- return self.__contacts
-
-[docs] def get_organization(self):
- """
- Gets organization data.
-
- :returns: Organization info
- :rtype: dict
- """
- return self.__organization
-
-[docs] def get_sp_metadata(self):
- """
- Gets the SP metadata. The XML representation.
-
- :returns: SP metadata (xml)
- :rtype: string
- """
- metadata = OneLogin_Saml2_Metadata.builder(
- self.__sp, self.__security['authnRequestsSigned'],
- self.__security['wantAssertionsSigned'], None, None,
- self.get_contacts(), self.get_organization()
- )
- cert = self.get_sp_cert()
- if cert is not None:
- metadata = OneLogin_Saml2_Metadata.add_x509_key_descriptors(metadata, cert)
-
- # Sign metadata
- if 'signMetadata' in self.__security and self.__security['signMetadata'] is not False:
- if self.__security['signMetadata'] is True:
- key_file_name = 'sp.key'
- cert_file_name = 'sp.crt'
- else:
- if ('keyFileName' not in self.__security['signMetadata'] or
- 'certFileName' not in self.__security['signMetadata']):
- raise OneLogin_Saml2_Error(
- 'Invalid Setting: signMetadata value of the sp is not valid',
- OneLogin_Saml2_Error.SETTINGS_INVALID_SYNTAX
- )
- key_file_name = self.__security['signMetadata']['keyFileName']
- cert_file_name = self.__security['signMetadata']['certFileName']
- key_metadata_file = self.__paths['cert'] + key_file_name
- cert_metadata_file = self.__paths['cert'] + cert_file_name
-
- if not exists(key_metadata_file):
- raise OneLogin_Saml2_Error(
- 'Private key file not found: %s',
- OneLogin_Saml2_Error.PRIVATE_KEY_FILE_NOT_FOUND,
- key_metadata_file
- )
-
- if not exists(cert_metadata_file):
- raise OneLogin_Saml2_Error(
- 'Public cert file not found: %s',
- OneLogin_Saml2_Error.PUBLIC_CERT_FILE_NOT_FOUND,
- cert_metadata_file
- )
-
- f = open(key_metadata_file, 'r')
- key_metadata = f.read()
- f.close()
- f = open(cert_metadata_file, 'r')
- cert_metadata = f.read()
- f.close()
- metadata = OneLogin_Saml2_Metadata.sign_metadata(metadata, key_metadata, cert_metadata)
-
- return metadata
-
-[docs] def validate_metadata(self, xml):
- """
- Validates an XML SP Metadata.
-
- :param xml: Metadata's XML that will be validate
- :type xml: string
-
- :returns: The list of found errors
- :rtype: list
- """
-
- assert isinstance(xml, basestring)
-
- if len(xml) == 0:
- raise Exception('Empty string supplied as input')
-
- errors = []
- res = OneLogin_Saml2_Utils.validate_xml(xml, 'saml-schema-metadata-2.0.xsd', self.__debug)
- if not isinstance(res, Document):
- errors.append(res)
- else:
- dom = res
- element = dom.documentElement
- if element.tagName != 'md:EntityDescriptor':
- errors.append('noEntityDescriptor_xml')
- else:
- valid_until = cache_duration = expire_time = None
-
- if element.hasAttribute('validUntil'):
- valid_until = OneLogin_Saml2_Utils.parse_SAML_to_time(element.getAttribute('validUntil'))
- if element.hasAttribute('cacheDuration'):
- cache_duration = element.getAttribute('cacheDuration')
-
- expire_time = OneLogin_Saml2_Utils.get_expire_time(cache_duration, valid_until)
- if expire_time is not None and int(datetime.now().strftime('%s')) > int(expire_time):
- errors.append('expired_xml')
-
- return errors
-
-[docs] def format_idp_cert(self):
- """
- Formats the IdP cert.
- """
- if self.__idp['x509cert'] is not None:
- self.__idp['x509cert'] = OneLogin_Saml2_Utils.format_cert(self.__idp['x509cert'])
-
-[docs] def get_errors(self):
- """
- Returns an array with the errors, the array is empty when the settings is ok.
-
- :returns: Errors
- :rtype: list
- """
- return self.__errors
-
-[docs] def set_strict(self, value):
- """
- Activates or deactivates the strict mode.
-
- :param xml: Strict parameter
- :type xml: boolean
- """
- assert isinstance(value, bool)
-
- self.__strict = value
-
-[docs] def is_strict(self):
- """
- Returns if the 'strict' mode is active.
-
- :returns: Strict parameter
- :rtype: boolean
- """
- return self.__strict
-
-[docs] def is_debug_active(self):
- """
- Returns if the debug is active.
-
- :returns: Debug parameter
- :rtype: boolean
- """
- return self.__debug
-
-# -*- coding: utf-8 -*-
-
-# Copyright (c) 2010-2018 OneLogin, Inc.
-# MIT License
-
-import base64
-from datetime import datetime
-import calendar
-from hashlib import sha1
-from isodate import parse_duration as duration_parser
-from lxml import etree
-from lxml.etree import ElementBase
-from os.path import basename, dirname, join
-import re
-from sys import stderr
-from tempfile import NamedTemporaryFile
-from textwrap import wrap
-from urllib import quote_plus
-from uuid import uuid4
-from xml.dom.minidom import Document, parseString, Element
-from xml.etree.ElementTree import tostring
-import zlib
-
-import dm.xmlsec.binding as xmlsec
-from dm.xmlsec.binding.tmpl import EncData, Signature
-from M2Crypto import X509
-
-from saml2.constants import OneLogin_Saml2_Constants
-from saml2.errors import OneLogin_Saml2_Error
-
-
-def _(msg):
- # Fixme Add i18n support
- return msg
-
-
-[docs]class OneLogin_Saml2_Utils:
-
- @staticmethod
-[docs] def decode_base64_and_inflate(value):
- """ base64 decodes and then inflates according to RFC1951
- :param value: a deflated and encoded string
- :return: the string after decoding and inflating
- """
-
- return zlib.decompress(base64.b64decode(value), -15)
-
- @staticmethod
-[docs] def deflate_and_base64_encode(value):
- """
- Deflates and the base64 encodes a string
- :param value: The string to deflate and encode
- :return: The deflated and encoded string
- """
- return base64.b64encode(zlib.compress(value)[2:-4])
-
- @staticmethod
-[docs] def validate_xml(xml, schema, debug=False):
- """
- """
- assert (isinstance(xml, basestring) or isinstance(xml, Document))
- assert isinstance(schema, basestring)
-
- if isinstance(xml, Document):
- xml = xml.toxml()
-
- # Switch to lxml for schema validation
- try:
- dom = etree.fromstring(xml)
- except Exception:
- return 'unloaded_xml'
-
- schema_file = join(dirname(__file__), 'schemas', schema)
- f = open(schema_file, 'r')
- schema_doc = etree.parse(f)
- f.close()
- xmlschema = etree.XMLSchema(schema_doc)
-
- if not xmlschema.validate(dom):
- xml_errors = [xmlschema.error_log]
- if debug:
- stderr.write('Errors validating the metadata')
- stderr.write(':\n\n')
- for error in xml_errors:
- stderr.write('%s\n' % error.message)
-
- return 'invalid_xml'
-
- return parseString(etree.tostring(dom))
-
- @staticmethod
-[docs] def format_cert(cert, heads=True):
- """
- Returns a x509 cert (adding header & footer if required).
-
- :param cert: A x509 unformated cert
- :type: string
-
- :param heads: True if we want to include head and footer
- :type: boolean
-
- :returns: Formated cert
- :rtype: string
- """
- x509_cert = cert.replace('\x0D', '')
- x509_cert = x509_cert.replace('\r', '')
- x509_cert = x509_cert.replace('\n', '')
- if len(x509_cert) > 0:
- x509_cert = x509_cert.replace('-----BEGIN CERTIFICATE-----', '')
- x509_cert = x509_cert.replace('-----END CERTIFICATE-----', '')
- x509_cert = x509_cert.replace(' ', '')
-
- if heads:
- x509_cert = '-----BEGIN CERTIFICATE-----\n' + '\n'.join(wrap(x509_cert, 64)) + '\n-----END CERTIFICATE-----\n'
-
- return x509_cert
-
- @staticmethod
-[docs] def redirect(url, parameters={}, request_data={}):
- """
- Executes a redirection to the provided url (or return the target url).
-
- :param url: The target url
- :type: string
-
- :param parameters: Extra parameters to be passed as part of the url
- :type: dict
-
- :param request_data: The request as a dict
- :type: dict
-
- :returns: Url
- :rtype: string
- """
- assert isinstance(url, basestring)
- assert isinstance(parameters, dict)
-
- if url.startswith('/'):
- url = '%s%s' % (OneLogin_Saml2_Utils.get_self_url_host(request_data), url)
-
- # Verify that the URL is to a http or https site.
- if re.search('^https?://', url) is None:
- raise OneLogin_Saml2_Error(
- 'Redirect to invalid URL: ' + url,
- OneLogin_Saml2_Error.REDIRECT_INVALID_URL
- )
-
- # Add encoded parameters
- if url.find('?') < 0:
- param_prefix = '?'
- else:
- param_prefix = '&'
-
- for name, value in parameters.items():
-
- if value is None:
- param = urlencode(name)
- elif isinstance(value, list):
- param = ''
- for val in value:
- param += quote_plus(name) + '[]=' + quote_plus(val) + '&'
- if len(param) > 0:
- param = param[0:-1]
- else:
- param = quote_plus(name) + '=' + quote_plus(value)
-
- url += param_prefix + param
- param_prefix = '&'
-
- return url
-
- @staticmethod
-[docs] def get_self_url_host(request_data):
- """
- Returns the protocol + the current host + the port (if different than
- common ports).
-
- :param request_data: The request as a dict
- :type: dict
-
- :return: Url
- :rtype: string
- """
- current_host = OneLogin_Saml2_Utils.get_self_host(request_data)
- port = ''
- if OneLogin_Saml2_Utils.is_https(request_data):
- protocol = 'https'
- else:
- protocol = 'http'
-
- if 'server_port' in request_data:
- port_number = request_data['server_port']
- port = ':' + port_number
-
- if protocol == 'http' and port_number == '80':
- port = ''
- elif protocol == 'https' and port_number == '443':
- port = ''
-
- return '%s://%s%s' % (protocol, current_host, port)
-
- @staticmethod
-[docs] def get_self_host(request_data):
- """
- Returns the current host.
-
- :param request_data: The request as a dict
- :type: dict
-
- :return: The current host
- :rtype: string
- """
- if 'http_host' in request_data:
- current_host = request_data['http_host']
- elif 'server_name' in request_data:
- current_host = request_data['server_name']
- else:
- raise Exception('No hostname defined')
-
- if ':' in current_host:
- current_host_data = current_host.split(':')
- possible_port = current_host_data[-1]
- try:
- possible_port = float(possible_port)
- current_host = current_host_data[0]
- except ValueError:
- current_host = ':'.join(current_host_data)
-
- return current_host
-
- @staticmethod
-[docs] def is_https(request_data):
- """
- Checks if https or http.
-
- :param request_data: The request as a dict
- :type: dict
-
- :return: False if https is not active
- :rtype: boolean
- """
- is_https = 'https' in request_data and request_data['https'] != 'off'
- is_https = is_https or ('server_port' in request_data and request_data['server_port'] == '443')
- return is_https
-
- @staticmethod
-[docs] def get_self_url_no_query(request_data):
- """
- Returns the URL of the current host + current view.
-
- :param request_data: The request as a dict
- :type: dict
-
- :return: The url of current host + current view
- :rtype: string
- """
- self_url_host = OneLogin_Saml2_Utils.get_self_url_host(request_data)
- script_name = request_data['script_name']
- if script_name[0] != '/':
- script_name = '/' + script_name
- self_url_host += script_name
- if 'path_info' in request_data:
- self_url_host += request_data['path_info']
-
- return self_url_host
-
- @staticmethod
-[docs] def get_self_url(request_data):
- """
- Returns the URL of the current host + current view + query.
-
- :param request_data: The request as a dict
- :type: dict
-
- :return: The url of current host + current view + query
- :rtype: string
- """
- self_url_host = OneLogin_Saml2_Utils.get_self_url_host(request_data)
-
- request_uri = ''
- if 'request_uri' in request_data:
- request_uri = request_data['request_uri']
- if not request_uri.startswith('/'):
- match = re.search('^https?://[^/]*(/.*)', request_uri)
- if match is not None:
- request_uri = match.groups()[0]
-
- return self_url_host + request_uri
-
- @staticmethod
-[docs] def generate_unique_id():
- """
- Generates an unique string (used for example as ID for assertions).
-
- :return: A unique string
- :rtype: string
- """
- return 'ONELOGIN_%s' % sha1(uuid4().hex).hexdigest()
-
- @staticmethod
-[docs] def parse_time_to_SAML(time):
- """
- Converts a UNIX timestamp to SAML2 timestamp on the form
- yyyy-mm-ddThh:mm:ss(\.s+)?Z.
-
- :param time: The time we should convert (DateTime).
- :type: string
-
- :return: SAML2 timestamp.
- :rtype: string
- """
- data = datetime.utcfromtimestamp(float(time))
- return data.strftime('%Y-%m-%dT%H:%M:%SZ')
-
- @staticmethod
-[docs] def parse_SAML_to_time(timestr):
- """
- Converts a SAML2 timestamp on the form yyyy-mm-ddThh:mm:ss(\.s+)?Z
- to a UNIX timestamp. The sub-second part is ignored.
-
- :param time: The time we should convert (SAML Timestamp).
- :type: string
-
- :return: Converted to a unix timestamp.
- :rtype: int
- """
- try:
- data = datetime.strptime(timestr, '%Y-%m-%dT%H:%M:%SZ')
- except ValueError:
- data = datetime.strptime(timestr, '%Y-%m-%dT%H:%M:%S.%fZ')
- return calendar.timegm(data.utctimetuple())
-
- @staticmethod
-[docs] def parse_duration(duration, timestamp=None):
- """
- Interprets a ISO8601 duration value relative to a given timestamp.
-
- :param duration: The duration, as a string.
- :type: string
-
- :param timestamp: The unix timestamp we should apply the duration to.
- Optional, default to the current time.
- :type: string
-
- :return: The new timestamp, after the duration is applied.
- :rtype: int
- """
- assert isinstance(duration, basestring)
- assert (timestamp is None or isinstance(timestamp, int))
-
- timedelta = duration_parser(duration)
- if timestamp is None:
- data = datetime.utcnow() + timedelta
- else:
- data = datetime.utcfromtimestamp(timestamp) + timedelta
- return calendar.timegm(data.utctimetuple())
-
- @staticmethod
-[docs] def get_expire_time(cache_duration=None, valid_until=None):
- """
- Compares 2 dates and returns the earliest.
-
- :param cache_duration: The duration, as a string.
- :type: string
-
- :param valid_until: The valid until date, as a string or as a timestamp
- :type: string
-
- :return: The expiration time.
- :rtype: int
- """
- expire_time = None
-
- if cache_duration is not None:
- expire_time = OneLogin_Saml2_Utils.parse_duration(cache_duration)
-
- if valid_until is not None:
- if isinstance(valid_until, int):
- valid_until_time = valid_until
- else:
- valid_until_time = OneLogin_Saml2_Utils.parse_SAML_to_time(valid_until)
- if expire_time is None or expire_time > valid_until_time:
- expire_time = valid_until_time
-
- if expire_time is not None:
- return '%d' % expire_time
- return None
-
- @staticmethod
-[docs] def query(dom, query, context=None):
- """
- Extracts nodes that match the query from the Element
-
- :param dom: The root of the lxml objet
- :type: Element
-
- :param query: Xpath Expresion
- :type: string
-
- :param context: Context Node
- :type: DOMElement
-
- :returns: The queried nodes
- :rtype: list
- """
- if context is None:
- return dom.xpath(query, namespaces=OneLogin_Saml2_Constants.NSMAP)
- else:
- return context.xpath(query, namespaces=OneLogin_Saml2_Constants.NSMAP)
-
- @staticmethod
-[docs] def delete_local_session(callback=None):
- """
- Deletes the local session.
- """
-
- if callback is not None:
- callback()
-
- @staticmethod
-[docs] def calculate_x509_fingerprint(x509_cert):
- """
- Calculates the fingerprint of a x509cert.
-
- :param x509_cert: x509 cert
- :type: string
-
- :returns: Formated fingerprint
- :rtype: string
- """
- assert isinstance(x509_cert, basestring)
-
- lines = x509_cert.split('\n')
- data = ''
-
- for line in lines:
- # Remove '\r' from end of line if present.
- line = line.rstrip()
- if line == '-----BEGIN CERTIFICATE-----':
- # Delete junk from before the certificate.
- data = ''
- elif line == '-----END CERTIFICATE-----':
- # Ignore data after the certificate.
- break
- elif line == '-----BEGIN PUBLIC KEY-----' or line == '-----BEGIN RSA PRIVATE KEY-----':
- # This isn't an X509 certificate.
- return None
- else:
- # Append the current line to the certificate data.
- data += line
- # "data" now contains the certificate as a base64-encoded string. The
- # fingerprint of the certificate is the sha1-hash of the certificate.
- return sha1(base64.b64decode(data)).hexdigest().lower()
-
- @staticmethod
-[docs] def format_finger_print(fingerprint):
- """
- Formates a fingerprint.
-
- :param fingerprint: fingerprint
- :type: string
-
- :returns: Formated fingerprint
- :rtype: string
- """
- formated_fingerprint = fingerprint.replace(':', '')
- return formated_fingerprint.lower()
-
- @staticmethod
-[docs] def generate_name_id(value, sp_nq, sp_format, key=None):
- """
- Generates a nameID.
-
- :param value: fingerprint
- :type: string
-
- :param sp_nq: SP Name Qualifier
- :type: string
-
- :param sp_format: SP Format
- :type: string
-
- :param key: SP Key to encrypt the nameID
- :type: string
-
- :returns: DOMElement | XMLSec nameID
- :rtype: string
- """
- doc = Document()
-
- name_id = doc.createElement('saml:NameID')
- name_id.setAttribute('SPNameQualifier', sp_nq)
- name_id.setAttribute('Format', sp_format)
- name_id.appendChild(doc.createTextNode(value))
-
- doc.appendChild(name_id)
-
- if key is not None:
- xmlsec.initialize()
-
- # Load the private key
- mngr = xmlsec.KeysMngr()
- key = OneLogin_Saml2_Utils.format_cert(key, heads=False)
- file_key = OneLogin_Saml2_Utils.write_temp_file(key)
- key_data = xmlsec.Key.load(file_key.name, xmlsec.KeyDataFormatPem, None)
- key_data.name = key_name = basename(file_key.name)
- mngr.addKey(key_data)
- file_key.close()
-
- # Prepare for encryption
- enc_data = EncData(xmlsec.TransformAes128Cbc, type=xmlsec.TypeEncElement)
- enc_data.ensureCipherValue()
- key_info = enc_data.ensureKeyInfo()
- enc_key = key_info.addEncryptedKey(xmlsec.TransformRsaPkcs1)
- enc_key.ensureCipherValue()
- enc_key_info = enc_key.ensureKeyInfo()
- enc_key_info.addKeyName(key_name)
-
- # Encrypt!
- enc_ctx = xmlsec.EncCtx(mngr)
- enc_ctx.enc_key = xmlsec.Key.generate(xmlsec.KeyDataAes, 128, xmlsec.KeyDataTypeSession)
- ed = enc_ctx.encryptXml(enc_data, doc.getroot())
-
- # Build XML with encrypted data
- newdoc = Document()
- encrypted_id = newdoc.createElement('saml:EncryptedID')
- newdoc.appendChild(encrypted_id)
- encrypted_id.appendChild(encrypted_id.ownerDocument.importNode(ed, True))
-
- return newdoc.saveXML(encrypted_id)
- else:
- return doc.saveXML(name_id)
-
- @staticmethod
-[docs] def get_status(dom):
- """
- Gets Status from a Response.
-
- :param dom: The Response as XML
- :type: Document
-
- :returns: The Status, an array with the code and a message.
- :rtype: dict
- """
- status = {}
-
- status_entry = OneLogin_Saml2_Utils.query(dom, '/samlp:Response/samlp:Status')
- if len(status_entry) == 0:
- raise Exception('Missing Status on response')
-
- code_entry = OneLogin_Saml2_Utils.query(dom, '/samlp:Response/samlp:Status/samlp:StatusCode', status_entry[0])
- if len(code_entry) == 0:
- raise Exception('Missing Status Code on response')
- code = code_entry[0].values()[0]
- status['code'] = code
-
- message_entry = OneLogin_Saml2_Utils.query(dom, '/samlp:Response/samlp:Status/samlp:StatusMessage', status_entry[0])
- if len(message_entry) == 0:
- status['msg'] = ''
- else:
- status['msg'] = message_entry[0].text
-
- return status
-
- @staticmethod
-[docs] def decrypt_element(encrypted_data, enc_ctx):
- """
- Decrypts an encrypted element.
-
- :param encrypted_data: The encrypted data.
- :type: DOMElement
-
- :param enc_ctx: The encryption context.
- :type: Encryption Context
-
- :returns: The decrypted element.
- :rtype: DOMElement
- """
- if isinstance(encrypted_data, Element):
- # Minidom element
- encrypted_data = etree.fromstring(encrypted_data.toxml())
-
- decrypted = enc_ctx.decrypt(encrypted_data)
- if isinstance(decrypted, ElementBase):
- # lxml element, decrypted xml data
- return tostring(decrypted.getroottree())
- else:
- # decrypted binary data
- return decrypted
-
- @staticmethod
-[docs] def write_temp_file(content):
- """
- Writes some content into a temporary file and returns it.
-
- :param content: The file content
- :type: string
-
- :returns: The temporary file
- :rtype: file-like object
- """
- f = NamedTemporaryFile(delete=True)
- f.file.write(content)
- f.file.flush()
- return f
-
- @staticmethod
-[docs] def add_sign(xml, key, cert):
- """
- Adds signature key and senders certificate to an element (Message or
- Assertion).
-
- :param xml: The element we should sign
- :type: string | Document
-
- :param key: The private key
- :type: string
-
- :param cert: The public
- :type: string
- """
- if isinstance(xml, Document):
- dom = xml
- else:
- if xml == '':
- raise Exception('Empty string supplied as input')
-
- try:
- dom = parseString(xml)
- except Exception:
- raise Exception('Error parsing xml string')
-
- xmlsec.initialize()
-
- # TODO the key and cert could be file descriptors instead
- # Load the private key.
- file_key = OneLogin_Saml2_Utils.write_temp_file(key)
- sign_key = xmlsec.Key.load(file_key.name, xmlsec.KeyDataFormatPem, None)
- file_key.close()
- # Add the certificate to the signature.
- file_cert = OneLogin_Saml2_Utils.write_temp_file(cert)
- sign_key.loadCert(file_cert.name, xmlsec.KeyDataFormatPem)
- file_cert.close()
-
- # Get the EntityDescriptor node we should sign.
- root_node = dom.firstChild
-
- # Sign the metadata with our private key.
- signature = Signature(xmlsec.TransformExclC14N, xmlsec.TransformRsaSha1)
- ref = signature.addReference(xmlsec.TransformSha1)
- ref.addTransform(xmlsec.TransformEnveloped)
-
- key_info = signature.ensureKeyInfo()
- key_info.addX509Data()
-
- dsig_ctx = xmlsec.DSigCtx()
- dsig_ctx.signKey = sign_key
- dsig_ctx.sign(signature)
-
- signature = tostring(signature).replace('ns0:', 'ds:').replace(':ns0', ':ds')
- signature = parseString(signature).firstChild
-
- insert_before = root_node.getElementsByTagName('saml:Issuer')
- if len(insert_before) > 0:
- insert_before = insert_before[0].nextSibling
- else:
- insert_before = root_node.firstChild.nextSibling.nextSibling
- root_node.insertBefore(signature, insert_before)
-
- return dom.toxml()
-
- @staticmethod
-[docs] def validate_sign(xml, cert=None, fingerprint=None):
- """
- Validates a signature (Message or Assertion).
-
- :param xml: The element we should validate
- :type: string | Document
-
- :param cert: The pubic cert
- :type: string
-
- :param fingerprint: The fingerprint of the public cert
- :type: string
- """
- if isinstance(xml, Document):
- dom = etree.fromstring(xml.toxml())
- else:
- if xml == '':
- raise Exception('Empty string supplied as input')
-
- try:
- dom = etree.fromstring(xml)
- except Exception:
- raise Exception('Error parsing xml string')
-
- xmlsec.initialize()
-
- # Find signature in the dom
- signature_node = OneLogin_Saml2_Utils.query(dom, 'ds:Signature')[0]
-
- # Prepare context and load cert into it
- dsig_ctx = xmlsec.DSigCtx()
- sign_cert = X509.load_cert_string(str(cert), X509.FORMAT_PEM)
- pub_key = sign_cert.get_pubkey().get_rsa()
- sign_key = xmlsec.Key.loadMemory(pub_key.as_pem(cipher=None),
- xmlsec.KeyDataFormatPem)
- dsig_ctx.signKey = sign_key
-
- # Verify signature
- dsig_ctx.verify(signature_node)
-
' + _('Hide Search Matches') + '
') - .appendTo($('#searchbox')); - } - }, - - /** - * init the domain index toggle buttons - */ - initIndexTable : function() { - var togglers = $('img.toggler').click(function() { - var src = $(this).attr('src'); - var idnum = $(this).attr('id').substr(7); - $('tr.cg-' + idnum).toggle(); - if (src.substr(-9) == 'minus.png') - $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); - else - $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); - }).css('display', ''); - if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { - togglers.click(); - } - }, - - /** - * helper function to hide the search marks again - */ - hideSearchWords : function() { - $('#searchbox .highlight-link').fadeOut(300); - $('span.highlighted').removeMethod('highlighted'); - }, - - /** - * make the url absolute - */ - makeURL : function(relativeURL) { - return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; - }, - - /** - * get the current relative url - */ - getCurrentURL : function() { - var path = document.location.pathname; - var parts = path.split(/\//); - $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { - if (this == '..') - parts.pop(); - }); - var url = parts.join('/'); - return path.substring(url.lastIndexOf('/') + 1, path.length - 1); - } -}; - -// quick alias for translations -_ = Documentation.gettext; - -$(document).ready(function() { - Documentation.init(); -}); diff --git a/docs/saml2/_static/down-pressed.png b/docs/saml2/_static/down-pressed.png deleted file mode 100644 index 6f7ad782..00000000 Binary files a/docs/saml2/_static/down-pressed.png and /dev/null differ diff --git a/docs/saml2/_static/down.png b/docs/saml2/_static/down.png deleted file mode 100644 index 3003a887..00000000 Binary files a/docs/saml2/_static/down.png and /dev/null differ diff --git a/docs/saml2/_static/file.png b/docs/saml2/_static/file.png deleted file mode 100644 index d18082e3..00000000 Binary files a/docs/saml2/_static/file.png and /dev/null differ diff --git a/docs/saml2/_static/jquery.js b/docs/saml2/_static/jquery.js deleted file mode 100644 index dc2f3dac..00000000 --- a/docs/saml2/_static/jquery.js +++ /dev/null @@ -1,9404 +0,0 @@ -/*! - * jQuery JavaScript Library v1.7.2 - * http://jquery.com/ - * - * Copyright 2011, John Resig - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * Includes Sizzle.js - * http://sizzlejs.com/ - * Copyright 2011, The Dojo Foundation - * Released under the MIT, BSD, and GPL Licenses. - * - * Date: Fri Jul 5 14:07:58 UTC 2013 - */ -(function( window, undefined ) { - -// Use the correct document accordingly with window argument (sandbox) -var document = window.document, - navigator = window.navigator, - location = window.location; -var jQuery = (function() { - -// Define a local copy of jQuery -var jQuery = function( selector, context ) { - // The jQuery object is actually just the init constructor 'enhanced' - return new jQuery.fn.init( selector, context, rootjQuery ); - }, - - // Map over jQuery in case of overwrite - _jQuery = window.jQuery, - - // Map over the $ in case of overwrite - _$ = window.$, - - // A central reference to the root jQuery(document) - rootjQuery, - - // A simple way to check for HTML strings or ID strings - // Prioritize #id overt |
- | - |
- | - |
- | - |
- | - |
- |
- | - |
- |
- | - |
- | - |
- | - |
- |