197 lines
6.3 KiB
Groovy
197 lines
6.3 KiB
Groovy
import groovy.xml.XmlSlurper
|
|
import groovy.xml.slurpersupport.GPathResult
|
|
import groovy.xml.slurpersupport.NodeChild
|
|
|
|
import java.util.zip.Inflater
|
|
import java.util.zip.InflaterInputStream
|
|
|
|
/**
|
|
* Gets the value of the Referer header.
|
|
* If the header is missing the fallback is returned
|
|
*
|
|
* This method is used when SAML IDP / Dispatch Error Redirect is not set
|
|
*
|
|
* @param fallback - value to return if the Referer header is missing
|
|
* @return value of header or fallback
|
|
*/
|
|
def getReferer(String fallback) {
|
|
return request.getHttpHeader('Referer') ?: fallback
|
|
}
|
|
|
|
def redirect(String url) {
|
|
outargs.put('nevis.transfer.type', 'redirect')
|
|
outargs.put('nevis.transfer.destination', url)
|
|
}
|
|
|
|
String getNormalisedSamlMessage(String parameter) {
|
|
if (parameter == null) {
|
|
return
|
|
}
|
|
String text
|
|
byte[] decoded
|
|
|
|
// if parameter is raw xml then continue otherwise try to parse the base64 encoding
|
|
if (parameter.startsWith("<")) {
|
|
text = new String(parameter)
|
|
}
|
|
else {
|
|
decoded = parameter.decodeBase64()
|
|
text = new String(decoded)
|
|
}
|
|
return text
|
|
}
|
|
|
|
|
|
String getNodeText(GPathResult xml, String nodeName) {
|
|
return xml.depthFirst().find { GPathResult node -> {
|
|
node.name().endsWith(":${nodeName}") || node.name().equalsIgnoreCase(nodeName)
|
|
}
|
|
}?.text()?.trim()
|
|
}
|
|
|
|
String getAttribute(GPathResult xml, String attributeName) {
|
|
return xml.depthFirst().find { GPathResult node -> {
|
|
node.attributes().containsKey(attributeName)
|
|
}
|
|
}?.attributes()?.get(attributeName)
|
|
}
|
|
|
|
String getNodeText(String parameter, String nodeName) {
|
|
String samlMessage = getNormalisedSamlMessage(parameter)
|
|
if (samlMessage == null) {
|
|
return
|
|
}
|
|
def parser = new XmlSlurper()
|
|
def xml = parser.parseText(samlMessage)
|
|
return getNodeText(xml, nodeName)
|
|
}
|
|
|
|
String getAttribute(String parameter, String attributeName) {
|
|
String samlMessage = getNormalisedSamlMessage(parameter)
|
|
if (samlMessage == null) {
|
|
return
|
|
}
|
|
def parser = new XmlSlurper()
|
|
def xml = parser.parseText(samlMessage)
|
|
return getAttribute(xml, attributeName)
|
|
}
|
|
|
|
String getIssuer(String value) {
|
|
return getNodeText(value, 'Issuer')
|
|
}
|
|
|
|
String getAttributeConsumingServiceIndex(String value) {
|
|
return getAttribute(value, 'AttributeConsumingServiceIndex')
|
|
}
|
|
|
|
String getProtocolBinding(String value) {
|
|
return getAttribute(value, 'ProtocolBinding')
|
|
}
|
|
|
|
def dispatchIssuer(i2s, String issuer, boolean secureMode) {
|
|
def result = i2s.get(issuer)
|
|
if (result == null) {
|
|
LOG.info("No SP found for issuer '$issuer'. Hint: check SAML SP Connector patterns.")
|
|
}
|
|
|
|
// dispatch different idp if artifact binding is enabled
|
|
if(parameters.get('epdMode') == 'artifact' && result == 'epd'){
|
|
LOG.debug("EPD: Artifact mode")
|
|
result = result + "_artifact"
|
|
} else if (result == 'main' && secureMode) {
|
|
LOG.debug("AGOV: Secure mode requested")
|
|
result = result + "_secure"
|
|
}
|
|
response.setResult(result)
|
|
session.put('saml.inbound.issuer', issuer)
|
|
session.put('saml.idp.result', result) // remember decision for sub-sequent requests without a SAML message
|
|
|
|
}
|
|
|
|
def dispatchIssuer(i2s, String issuer) {
|
|
dispatchIssuer(i2s, issuer, false)
|
|
}
|
|
|
|
def dispatchMessage(i2s, String message) {
|
|
def issuer = getIssuer(message)
|
|
def secureMode = (getAttributeConsumingServiceIndex(message) == '10101')
|
|
def useArtifact = ('urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact' == getProtocolBinding(message))
|
|
|
|
LOG.info("secureMode requested: ${secureMode}")
|
|
|
|
if (issuer == null) {
|
|
LOG.info("No issuer found in incoming SAML message. Giving up.")
|
|
}
|
|
session.put('saml.inbound.issuer', issuer)
|
|
session.put('agov.idp.use.artifact', '' + useArtifact)
|
|
dispatchIssuer(i2s, issuer, secureMode)
|
|
}
|
|
|
|
if (parameters.get('logoutConfirmation') == 'true' && "stepup" == request.getMethod()) {
|
|
String url = request.currentResource
|
|
def path = new URL(url).getPath()
|
|
if (path.endsWith("/logout")) {
|
|
// next AuthState will show a logout confirmation GUI
|
|
response.setResult('confirm')
|
|
return
|
|
}
|
|
}
|
|
|
|
// ensure session exists
|
|
if (request.getSession(false) == null) {
|
|
session = request.getSession(true).getData()
|
|
}
|
|
|
|
// issuer (any case) -> ResultCond name
|
|
def i2s = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER)
|
|
|
|
|
|
i2s.put(parameters.get('atb'), 'main')
|
|
i2s.put(parameters.get('epd_atb'), 'epd')
|
|
|
|
if (parameters.get('spInitiated') == 'true' && inargs.containsKey('SAMLRequest')) { // SP-initiated authentication
|
|
LOG.debug("found SAMLRequest parameter for SP-initiated authentication")
|
|
String message = inargs.get('SAMLRequest')
|
|
dispatchMessage(i2s, message)
|
|
return
|
|
}
|
|
|
|
if (inargs.containsKey('SAMLResponse')) { // response to IDP-initiated SAML Logout
|
|
LOG.debug("found SAMLResponse parameter")
|
|
String message = inargs.get('SAMLResponse')
|
|
dispatchMessage(i2s, message)
|
|
return
|
|
}
|
|
|
|
if (parameters.get('spInitiated') == 'true' && inargs.containsKey('soapheader')) { // SP-initiated SOAP with soapheader
|
|
LOG.debug("found soapheader parameter for SP-initiated")
|
|
String message = inargs.get('soapheader')
|
|
dispatchMessage(i2s, message)
|
|
return
|
|
}
|
|
|
|
if (parameters.get('spInitiated') == 'true' && inargs.containsKey('')) { // SP-initiated SOAP with empty
|
|
LOG.debug("found empty parameter for SP-initiated SOAP message")
|
|
String message = inargs.get('')
|
|
dispatchMessage(i2s, message)
|
|
return
|
|
}
|
|
|
|
String issuer = inargs['Issuer'] ?: inargs['issuer']
|
|
if (parameters.get('idpInitiated') == 'true' && issuer != null) { // IDP-initiated authentication
|
|
LOG.debug("found Issuer parameter for IDP-initiated authentication")
|
|
dispatchIssuer(i2s, issuer)
|
|
return
|
|
}
|
|
|
|
// used as fallback in case of ?logout (we need an IdentityProviderState)
|
|
if (inargs.containsKey("logout") && session.containsKey('saml.idp.result')) {
|
|
def result = session.get('saml.idp.result')
|
|
LOG.debug("dispatching to last used ResultCond: $result")
|
|
response.setResult(result)
|
|
return
|
|
}
|
|
|
|
def location = getReferer('/')
|
|
LOG.info("Unable to dispatch request. Giving up and redirecting (back) to $location")
|
|
redirect(location) |