Files
spec/saml/logout.cfm
T
2025-06-02 16:16:51 +03:00

121 lines
6.6 KiB
Plaintext

<cfinclude template="app_saml_config.cfm"/>
<cfinclude template="saml.cfm"/>
<!---В отличие от логона, здесь тот же шаблон обрабатывает редирект от IDP. Возможно, правильнее делать было через sso.cfm--->
<!---Последовательность: шаблон принимает редирект от logout.cfm (к этому моменту usr_id в сессии уже сброшен), формирует подписанный запрос на разлогинивание и отправляет его браузеру с редиректом на IDP.
Заодно с ним идет RelayState, в который мы кладем относительный URL на страницу, которая инициировала логаут.
При успешном логауте IDP возвращает браузеру редирект на вот эту же страницу с подписанным ответом (мы его не читаем и подпись не проверяем) и RelayState.
Мы делаем редирект на относительный URL из RelayState --->
<!---*** Дублирование кода--->
<cfset ksFile = CreateObject("Java", "java.io.File").init(request.SAML.KEYSTORE_FILE_NAME) />
<cfset ksInputStream = CreateObject("Java", "java.io.FileInputStream").init(ksFile) />
<cfset keystore = CreateObject("Java" , "java.security.KeyStore").getInstance("JKS") />
<cfset keystore.load(ksInputStream, request.SAML.KEYSTORE_PASS.toCharArray()) />
<cfset certificate = keystore.getCertificate(request.SAML.CERTIFICATE_ALIAS) />
<cfset privateKey = keystore.getKey(request.SAML.CERTIFICATE_ALIAS, request.SAML.KEY_PASS.toCharArray()) />
<!---<cfset signingCertString = binaryEncode(certificate.getEncoded(),"base64") />
<cfset encryptionCertString = signingCertString/>--->
<cfset currentUTC = DateConvert("local2UTC", Now()) />
<cfparam name="SAMLResponse" default=""/>
<cfparam name="SAMLRequest" default=""/>
<cfparam name="target_page" default="ttrtrtrt"/> <!---пустое значение вызывает проблемы!--->
<cflock scope="session" type="exclusive" timeout="3">
<cfparam name="session.SAML_NameID" default=""/>
<cfparam name="session.SAML_SessionIndex" default=""/>
<cfset SAML_NameID=session.SAML_NameID/>
<cfset SAML_SessionIndex=session.SAML_SessionIndex/>
</cflock>
<cfif len(SAMLResponse)><!---Обработка ответа (редиректа) от IDP--->
<!---Мы не проверяем аутентичность ответа от IDP, полагаясь на то, что знаем, кому отправляем запрос на разлогинивание--->
<cfparam name="RelayState" default=""/>
<cflock scope="session" type="exclusive" timeout="3">
<cfset structdelete(session, "usr")>
<cfset structdelete(session, "SAML_NameID")>
<cfset structdelete(session, "SAML_SessionIndex")>
<cfset structdelete(session, "SAML_IdPCredentialsExpirationTime")>
<cfset structdelete(session, "usr_id")>
</cflock>
<cfset strUrl="../index.cfm"/>
<cfif len(RelayState) GT 0>
<cfif left(RelayState,1) EQ "/">
<cfset strUrl="#RelayState#"/>
<cfelse>
<cfset strUrl="../#RelayState#"/>
</cfif>
</cfif>
<!--- <cfset strUrl="req.cfm"/> --->
<cfelseif len(SAMLRequest)><!---Не знаю, когда используется этот режим--->
<cfset SAMLRequestStr = XMLParse(inflate(SAMLRequest))/>
<cfset SAMLRequestXml = XMLParse(SAMLRequestStr)/>
<cfset requestID=XMLSearch(SAMLRequestXml, "string(/*[name()='samlp:LogoutRequest']/@ID)")/>
<cfoutput><cfsavecontent variable="logoutResponseXml"><?xml version="1.0" encoding="UTF-8"?>
<samlp:LogoutResponse xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="id#CreateUUID()#" Version="2.0" IssueInstant="#DateFormat(currentUTC,"yyyy-mm-dd")#T#TimeFormat(currentUTC,"HH:MM:ss")#Z" Destination="https://sso.nubes.ru/adfs/ls/" InResponseTo="#requestID[1]#">
<saml:Issuer>#request.SAML.baseUrl#metadata.cfm</saml:Issuer>
<samlp:Status>
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
</samlp:Status>
</samlp:LogoutResponse>
</cfsavecontent></cfoutput>
<cfscript>
logout64 = (deflate2base64(logoutResponseXml));
logoutUrl = urlEncodeRfc2396(logout64);
logoutSigAlgUrl= urlEncodeRfc2396('http://www.w3.org/2001/04/xmldsig-more##rsa-sha256');
if (len(target_page)) { //empty RelayState would not be accepted
relayStateX = "&RelayState=#urlEncodeRfc2396(target_page)#";
} else {
relayStateX = "";
}
logoutRequest2Sign="SAMLRequest=#logoutUrl##relayStateX#&SigAlg=#logoutSigAlgUrl#"; //order matters
signedBytesUrl = urlEncodeRfc2396(sign2base64(logoutRequest2Sign, privateKey, 'SHA256withRSA'));
logoutRequest="#logoutRequest2Sign#&Signature=#signedBytesUrl#";
</cfscript>
<cflock scope="session" type="exclusive" timeout="3">
<cfset structClear(session)/>
<cfset structClear(cookie)/>
</cflock>
<cfset strUrl="#request.SAML.IDP_URL#?#logoutRequest#"/>
<cfelse><!---Формируем и отправляем IDP (редиректом) запрос на разлогинивание--->
<cfoutput>
<cfsavecontent variable="logoutRequestXml"><?xml version="1.0" encoding="UTF-8"?>
<samlp:LogoutRequest
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="id#CreateUUID()#" Version="2.0"
IssueInstant="#DateFormat(currentUTC,"yyyy-mm-dd")#T#TimeFormat(currentUTC,"HH:MM:ss")#Z"
Destination="#request.SAML.IDP_URL#">
<saml:Issuer>#request.SAML.baseUrl#metadata.cfm</saml:Issuer>
<saml:NameID>#SAML_NameID#</saml:NameID>
<samlp:SessionIndex>#SAML_SessionIndex#</samlp:SessionIndex>
</samlp:LogoutRequest>
</cfsavecontent>
</cfoutput>
<cfscript>
logout64 = (deflate2base64(logoutRequestXml));
logoutUrl = urlEncodeRfc2396(logout64);
logoutSigAlgUrl= urlEncodeRfc2396('http://www.w3.org/2001/04/xmldsig-more##rsa-sha256');
if (len(target_page)) { //empty RelayState would not be accepted
relayStateX = "&RelayState=#urlEncodeRfc2396(target_page)#";
} else {
relayStateX = "";
}
logoutRequest2Sign="SAMLRequest=#logoutUrl##relayStateX#&SigAlg=#logoutSigAlgUrl#"; //order matters
signedBytesUrl = urlEncodeRfc2396(sign2base64(logoutRequest2Sign,privateKey,'SHA256withRSA'));
logoutRequest="#logoutRequest2Sign#&Signature=#signedBytesUrl#";
</cfscript>
<cfset strUrl="#request.SAML.IDP_URL#?#logoutRequest#"/>
</cfif>
<cflocation url="#strUrl#" addtoken="no"/>