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)
}

/**
 * Extracts the content of the Issuer element from a parsed SAML message.
 * The Issuer is optional according to SAML specification but we need it for dispatching.
 *
 * @param xml - as parsed by Groovy XmlSlurper
 * @return text content of Issuer element converted or null
 */
String getIssuer(GPathResult xml) {
    return xml.depthFirst().find { GPathResult node -> {
        node.name().endsWith(":Issuer") || node.name().equalsIgnoreCase("Issuer")
      }
    }?.text()
}

String getIssuer(String value) {
    if (value == null) {
        return
    }
    String text
    byte[] decoded
    def parser = new XmlSlurper()
    // if value is raw xml then continue otherwise try to parse the base64 encoding
    if (value.startsWith("<")) {
        text = new String(value)
    }
    else {
        decoded = value.decodeBase64()
        text = new String(decoded)
        LOG.info("received SAML request $value")
    }

    // after decoded, if redirect binding, we need to parse string to xml
    if (text.startsWith("<")) {
        LOG.debug("assuming POST/SOAP binding")
        // plain String (POST/SOAP parameter)
        def xml = parser.parseText(text)
        return getIssuer(xml)
    }
    else {
        LOG.debug("assuming redirect binding")
        // should be deflate encoded (query parameter)
        def is = new InflaterInputStream(new ByteArrayInputStream(decoded), new Inflater(true))
        def xml = parser.parse(is)
        return getIssuer(xml)
    }
}

def dispatchIssuer(i2s, String issuer) {
    def result = i2s.get(issuer)
    if (result == null) {
        LOG.info("No SP found for issuer '$issuer'. Hint: check SAML SP Connector patterns.")
    }
    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 dispatchMessage(i2s, String message) {
    def issuer = getIssuer(message)
    if (issuer == null) {
        LOG.info("No issuer found in incoming SAML message. Giving up.")
    }
    session.put("saml.inbound.issuer", issuer)
    dispatchIssuer(i2s, issuer)
}

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('https://trustbroker.agov-d.azure.adnovum.net', 'state0')

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)