Source code for onelogin.saml2.auth

# -*- coding: utf-8 -*-

""" OneLogin_Saml2_Auth class

MIT License

Main class of Python Toolkit.

Initializes the SP SAML instance

"""

from base64 import b64encode
from urllib import quote_plus

from onelogin.saml2.authn_request import OneLogin_Saml2_Authn_Request
from onelogin.saml2.constants import OneLogin_Saml2_Constants
from onelogin.saml2.errors import OneLogin_Saml2_Error
from onelogin.saml2.logout_response import OneLogin_Saml2_Logout_Response
from onelogin.saml2.logout_request import OneLogin_Saml2_Logout_Request
from onelogin.saml2.response import OneLogin_Saml2_Response
from onelogin.saml2.settings import OneLogin_Saml2_Settings
from onelogin.saml2.utils import OneLogin_Saml2_Utils, xmlsec
from onelogin.saml2.xmlparser import tostring


[docs]class OneLogin_Saml2_Auth(object): """ This class implements the SP SAML instance. Defines the methods that you can invoke in your application in order to add SAML support (initiates SSO, initiates SLO, processes a SAML Response, a Logout Request or a Logout Response). """ def __init__(self, request_data, old_settings=None, custom_base_path=None): """ Initializes the SP SAML instance. :param request_data: Request Data :type request_data: dict :param old_settings: Optional. SAML Toolkit Settings :type old_settings: dict :param custom_base_path: Optional. Path where are stored the settings file and the cert folder :type custom_base_path: string """ self.__request_data = request_data self.__settings = OneLogin_Saml2_Settings(old_settings, custom_base_path) self.__attributes = [] self.__friendlyname_attributes = [] self.__nameid = None self.__nameid_format = None self.__nameid_nq = None self.__nameid_spnq = None self.__session_index = None self.__session_expiration = None self.__authenticated = False self.__errors = [] self.__error_reason = None self.__last_request_id = None self.__last_message_id = None self.__last_assertion_id = None self.__last_assertion_not_on_or_after = None self.__last_authn_contexts = [] self.__last_request = None self.__last_response = None
[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 argument. 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 = [] self.__error_reason = None 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']) self.__last_response = response.get_xml_document() if response.is_valid(self.__request_data, request_id): self.__attributes = response.get_attributes() self.__friendlyname_attributes = response.get_friendlyname_attributes() self.__nameid = response.get_nameid() self.__nameid_format = response.get_nameid_format() self.__nameid_nq = response.get_nameid_nq() self.__nameid_spnq = response.get_nameid_spnq() self.__session_index = response.get_session_index() self.__session_expiration = response.get_session_not_on_or_after() self.__last_message_id = response.get_id() self.__last_assertion_id = response.get_assertion_id() self.__last_authn_contexts = response.get_authn_contexts() self.__last_assertion_not_on_or_after = response.get_assertion_not_on_or_after() self.__authenticated = True else: self.__errors.append('invalid_response') self.__error_reason = response.get_error() 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 = [] self.__error_reason = None 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']) self.__last_response = logout_response.get_xml() if not logout_response.is_valid(self.__request_data, request_id): self.__errors.append('invalid_logout_response') self.__error_reason = logout_response.get_error() elif logout_response.get_status() != OneLogin_Saml2_Constants.STATUS_SUCCESS: self.__errors.append('logout_not_success') else: self.__last_message_id = logout_response.id if 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']: logout_request = OneLogin_Saml2_Logout_Request(self.__settings, self.__request_data['get_data']['SAMLRequest']) self.__last_request = logout_request.get_xml() if not logout_request.is_valid(self.__request_data): self.__errors.append('invalid_logout_request') self.__error_reason = logout_request.get_error() else: if not keep_local_session: OneLogin_Saml2_Utils.delete_local_session(delete_session_cb) in_response_to = logout_request.id self.__last_message_id = logout_request.id response_builder = OneLogin_Saml2_Logout_Response(self.__settings) response_builder.build(in_response_to) self.__last_response = response_builder.get_xml() 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'] # else: # parameters['RelayState'] = OneLogin_Saml2_Utils.get_self_url_no_query(self.__request_data) security = self.__settings.get_security_data() if 'logoutResponseSigned' in security and security['logoutResponseSigned']: parameters['SigAlg'] = security['signatureAlgorithm'] parameters['Signature'] = self.build_response_signature(logout_response, parameters.get('RelayState', None), security['signatureAlgorithm']) 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 passed 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 get_last_authn_contexts(self): """ :returns: The list of authentication contexts sent in the last SAML Response. :rtype: list """ return self.__last_authn_contexts
[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_friendlyname_attributes(self): """ Returns the set of SAML attributes indexed by FiendlyName. :returns: SAML attributes :rtype: dict """ return self.__friendlyname_attributes
[docs] def get_nameid(self): """ Returns the nameID. :returns: NameID :rtype: string|None """ return self.__nameid
[docs] def get_nameid_format(self): """ Returns the nameID Format. :returns: NameID Format :rtype: string|None """ return self.__nameid_format
[docs] def get_nameid_nq(self): """ Returns the nameID NameQualifier of the Assertion. :returns: NameID NameQualifier :rtype: string|None """ return self.__nameid_nq
[docs] def get_nameid_spnq(self): """ Returns the nameID SP NameQualifier of the Assertion. :returns: NameID SP NameQualifier :rtype: string|None """ return self.__nameid_spnq
[docs] def get_session_index(self): """ Returns the SessionIndex from the AuthnStatement. :returns: The SessionIndex of the assertion :rtype: string """ return self.__session_index
[docs] def get_session_expiration(self): """ Returns the SessionNotOnOrAfter from the AuthnStatement. :returns: The SessionNotOnOrAfter of the assertion :rtype: unix/posix timestamp|None """ return self.__session_expiration
[docs] def get_last_assertion_not_on_or_after(self): """ The NotOnOrAfter value of the valid SubjectConfirmationData node (if any) of the last assertion processed """ return self.__last_assertion_not_on_or_after
[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_last_error_reason(self): """ Returns the reason for the last error :returns: Reason of the last error :rtype: None | string """ return self.__error_reason
[docs] def get_attribute(self, name): """ Returns the requested SAML attribute. :param name: Name of the attribute :type name: string :returns: Attribute value(s) if exists or None :rtype: list """ assert isinstance(name, basestring) value = None if self.__attributes and name in self.__attributes.keys(): value = self.__attributes[name] return value
[docs] def get_friendlyname_attribute(self, friendlyname): """ Returns the requested SAML attribute searched by FriendlyName. :param friendlyname: FriendlyName of the attribute :type friendlyname: string :returns: Attribute value(s) if exists or None :rtype: list """ assert isinstance(friendlyname, basestring) value = None if self.__friendlyname_attributes and friendlyname in self.__friendlyname_attributes.keys(): value = self.__friendlyname_attributes[friendlyname] return value
[docs] def get_last_request_id(self): """ :returns: The ID of the last Request SAML message generated. :rtype: string """ return self.__last_request_id
[docs] def get_last_message_id(self): """ :returns: The ID of the last Response SAML message processed. :rtype: string """ return self.__last_message_id
[docs] def get_last_assertion_id(self): """ :returns: The ID of the last assertion processed. :rtype: string """ return self.__last_assertion_id
[docs] def login(self, return_to=None, force_authn=False, is_passive=False, set_nameid_policy=True, name_id_value_req=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 :param force_authn: Optional argument. When true the AuthNRequest will set the ForceAuthn='true'. :type force_authn: bool :param is_passive: Optional argument. When true the AuthNRequest will set the Ispassive='true'. :type is_passive: bool :param set_nameid_policy: Optional argument. When true the AuthNRequest will set a nameIdPolicy element. :type set_nameid_policy: bool :param name_id_value_req: Optional argument. Indicates to the IdP the subject that should be authenticated :type name_id_value_req: string :returns: Redirection URL :rtype: string """ authn_request = OneLogin_Saml2_Authn_Request(self.__settings, force_authn, is_passive, set_nameid_policy, name_id_value_req) self.__last_request = authn_request.get_xml() self.__last_request_id = authn_request.get_id() 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'] = security['signatureAlgorithm'] parameters['Signature'] = self.build_request_signature(saml_request, parameters['RelayState'], security['signatureAlgorithm']) return self.redirect_to(self.get_sso_url(), parameters)
[docs] def logout(self, return_to=None, name_id=None, session_index=None, nq=None, name_id_format=None, spnq=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: The NameID that will be set in the LogoutRequest. :type name_id: string :param session_index: SessionIndex that identifies the session of the user. :type session_index: string :param nq: IDP Name Qualifier :type: string :param name_id_format: The NameID Format that will be set in the LogoutRequest. :type: string :param spnq: SP Name Qualifier :type: 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 ) if name_id is None and self.__nameid is not None: name_id = self.__nameid if name_id_format is None and self.__nameid_format is not None: name_id_format = self.__nameid_format logout_request = OneLogin_Saml2_Logout_Request( self.__settings, name_id=name_id, session_index=session_index, nq=nq, name_id_format=name_id_format, spnq=spnq ) self.__last_request = logout_request.get_xml() self.__last_request_id = logout_request.id 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'] = security['signatureAlgorithm'] parameters['Signature'] = self.build_request_signature(saml_request, parameters['RelayState'], security['signatureAlgorithm']) return self.redirect_to(slo_url, parameters)
[docs] def get_sso_url(self): """ Gets the IdP SSO URL. :returns: An URL, the SSO endpoint of the IdP :rtype: string """ return self.__settings.get_idp_sso_url()
[docs] def get_slo_url(self): """ Gets the IdP SLO URL. :returns: An URL, the SLO endpoint of the IdP :rtype: string """ return self.__settings.get_idp_slo_url()
[docs] def get_slo_response_url(self): """ Gets the SLO return URL for IdP-initiated logout. :returns: an URL, the SLO return endpoint of the IdP :rtype: string """ return self.__settings.get_idp_slo_response_url()
[docs] def build_request_signature(self, saml_request, relay_state, sign_algorithm=OneLogin_Saml2_Constants.RSA_SHA1): """ 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 :param sign_algorithm: Signature algorithm method :type sign_algorithm: string """ return self.__build_signature(saml_request, relay_state, 'SAMLRequest', sign_algorithm)
[docs] def build_response_signature(self, saml_response, relay_state, sign_algorithm=OneLogin_Saml2_Constants.RSA_SHA1): """ 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 :param sign_algorithm: Signature algorithm method :type sign_algorithm: string """ return self.__build_signature(saml_response, relay_state, 'SAMLResponse', sign_algorithm)
def __build_signature(self, saml_data, relay_state, saml_type, sign_algorithm=OneLogin_Saml2_Constants.RSA_SHA1): """ Builds the Signature :param saml_data: The SAML Data :type saml_data: string :param relay_state: The target URL the user should be redirected to :type relay_state: string :param saml_type: The target URL the user should be redirected to :type saml_type: string SAMLRequest | SAMLResponse :param sign_algorithm: Signature algorithm method :type sign_algorithm: string """ assert saml_type in ['SAMLRequest', 'SAMLResponse'] # Load the key into the xmlsec context key = self.__settings.get_sp_key() if not key: raise OneLogin_Saml2_Error( "Trying to sign the %s but can't load the SP private key" % saml_type, OneLogin_Saml2_Error.PRIVATE_KEY_NOT_FOUND ) dsig_ctx = xmlsec.DSigCtx() dsig_ctx.signKey = xmlsec.Key.loadMemory(key, xmlsec.KeyDataFormatPem, None) msg = '%s=%s' % (saml_type, quote_plus(saml_data)) if relay_state is not None: msg += '&RelayState=%s' % quote_plus(relay_state) msg += '&SigAlg=%s' % quote_plus(sign_algorithm) # Sign the metadata with our private key. sign_algorithm_transform_map = { OneLogin_Saml2_Constants.DSA_SHA1: xmlsec.TransformDsaSha1, OneLogin_Saml2_Constants.RSA_SHA1: xmlsec.TransformRsaSha1, OneLogin_Saml2_Constants.RSA_SHA256: xmlsec.TransformRsaSha256, OneLogin_Saml2_Constants.RSA_SHA384: xmlsec.TransformRsaSha384, OneLogin_Saml2_Constants.RSA_SHA512: xmlsec.TransformRsaSha512 } sign_algorithm_transform = sign_algorithm_transform_map.get(sign_algorithm, xmlsec.TransformRsaSha1) signature = dsig_ctx.signBinary(str(msg), sign_algorithm_transform) return b64encode(signature)
[docs] def get_last_response_xml(self, pretty_print_if_possible=False): """ Retrieves the raw XML (decrypted) of the last SAML response, or the last Logout Response generated or processed :returns: SAML response XML :rtype: string|None """ response = None if self.__last_response is not None: if isinstance(self.__last_response, basestring): response = self.__last_response else: response = tostring(self.__last_response, pretty_print=pretty_print_if_possible) return response
[docs] def get_last_request_xml(self): """ Retrieves the raw XML sent in the last SAML request :returns: SAML request XML :rtype: string|None """ return self.__last_request or None