<?php 
 
/** 
 * Assertion consumer service handler for SAML 2.0 SP authentication client. 
 */ 
 
$sourceId = substr($_SERVER['PATH_INFO'], 1); 
$source = SimpleSAML_Auth_Source::getById($sourceId, 'sspmod_saml_Auth_Source_SP'); 
$spMetadata = $source->getMetadata(); 
 
$b = SAML2_Binding::getCurrentBinding(); 
if ($b instanceof SAML2_HTTPArtifact) { 
    $b->setSPMetadata($spMetadata); 
} 
 
$response = $b->receive(); 
if (!($response instanceof SAML2_Response)) { 
    throw new SimpleSAML_Error_BadRequest('Invalid message received to AssertionConsumerService endpoint.'); 
} 
 
$idp = $response->getIssuer(); 
if ($idp === NULL) { 
    /* No Issuer in the response. Look for an unencrypted assertion with an issuer. */ 
    foreach ($response->getAssertions() as $a) { 
        if ($a instanceof SAML2_Assertion) { 
            /* We found an unencrypted assertion - there should be an issuer here. */ 
            $idp = $a->getIssuer(); 
            break; 
        } 
    } 
    if ($idp === NULL) { 
        /* No issuer found in the assertions. */ 
        throw new Exception('Missing <saml:Issuer> in message delivered to AssertionConsumerService.'); 
    } 
} 
 
$session = SimpleSAML_Session::getSessionFromRequest(); 
$prevAuth = $session->getAuthData($sourceId, 'saml:sp:prevAuth'); 
if ($prevAuth !== NULL && $prevAuth['id'] === $response->getId() && $prevAuth['issuer'] === $idp) { 
    /* OK, it looks like this message has the same issuer 
     * and ID as the SP session we already have active. We 
     * therefore assume that the user has somehow triggered 
     * a resend of the message. 
     * In that case we may as well just redo the previous redirect 
     * instead of displaying a confusing error message. 
     */ 
    SimpleSAML_Logger::info('Duplicate SAML 2 response detected - ignoring the response and redirecting the user to the correct page.'); 
    SimpleSAML_Utilities::redirectTrustedURL($prevAuth['redirect']); 
} 
 
$idpMetadata = array(); 
 
$stateId = $response->getInResponseTo(); 
if (!empty($stateId)) { 
 
    // sanitize the input 
    $sid = SimpleSAML_Utilities::parseStateID($stateId); 
    if (!is_null($sid['url'])) { 
        SimpleSAML_Utilities::checkURLAllowed($sid['url']); 
    } 
 
    /* This is a response to a request we sent earlier. */ 
    $state = SimpleSAML_Auth_State::loadState($stateId, 'saml:sp:sso'); 
 
    /* Check that the authentication source is correct. */ 
    assert('array_key_exists("saml:sp:AuthId", $state)'); 
    if ($state['saml:sp:AuthId'] !== $sourceId) { 
        throw new SimpleSAML_Error_Exception('The authentication source id in the URL does not match the authentication source which sent the request.'); 
    } 
 
    /* Check that the issuer is the one we are expecting. */ 
    assert('array_key_exists("ExpectedIssuer", $state)'); 
    if ($state['ExpectedIssuer'] !== $idp) { 
        $idpMetadata = $source->getIdPMetadata($idp); 
        $idplist = $idpMetadata->getArrayize('IDPList', array()); 
        if (!in_array($state['ExpectedIssuer'], $idplist)) { 
            throw new SimpleSAML_Error_Exception('The issuer of the response does not match to the identity provider we sent the request to.'); 
        } 
    } 
} else { 
    /* This is an unsolicited response. */ 
    $state = array( 
        'saml:sp:isUnsolicited' => TRUE, 
        'saml:sp:AuthId' => $sourceId, 
        'saml:sp:RelayState' => SimpleSAML_Utilities::checkURLAllowed($response->getRelayState()), 
    ); 
} 
 
SimpleSAML_Logger::debug('Received SAML2 Response from ' . var_export($idp, TRUE) . '.'); 
 
if (empty($idpMetadata)) { 
    $idpMetadata = $source->getIdPmetadata($idp); 
} 
 
try { 
    $assertions = sspmod_saml_Message::processResponse($spMetadata, $idpMetadata, $response); 
} catch (sspmod_saml_Error $e) { 
    /* The status of the response wasn't "success". */ 
    $e = $e->toException(); 
    SimpleSAML_Auth_State::throwException($state, $e); 
} 
 
 
$authenticatingAuthority = NULL; 
$nameId = NULL; 
$sessionIndex = NULL; 
$expire = NULL; 
$attributes = array(); 
$foundAuthnStatement = FALSE; 
foreach ($assertions as $assertion) { 
 
    /* Check for duplicate assertion (replay attack). */ 
    $store = SimpleSAML_Store::getInstance(); 
    if ($store !== FALSE) { 
        $aID = $assertion->getId(); 
        if ($store->get('saml.AssertionReceived', $aID) !== NULL) { 
            $e = new SimpleSAML_Error_Exception('Received duplicate assertion.'); 
            SimpleSAML_Auth_State::throwException($state, $e); 
        } 
 
        $notOnOrAfter = $assertion->getNotOnOrAfter(); 
        if ($notOnOrAfter === NULL) { 
            $notOnOrAfter = time() + 24*60*60; 
        } else { 
            $notOnOrAfter += 60; /* We allow 60 seconds clock skew, so add it here also. */ 
        } 
 
        $store->set('saml.AssertionReceived', $aID, TRUE, $notOnOrAfter); 
    } 
 
 
    if ($authenticatingAuthority === NULL) { 
        $authenticatingAuthority = $assertion->getAuthenticatingAuthority(); 
    } 
    if ($nameId === NULL) { 
        $nameId = $assertion->getNameId(); 
    } 
    if ($sessionIndex === NULL) { 
        $sessionIndex = $assertion->getSessionIndex(); 
    } 
    if ($expire === NULL) { 
        $expire = $assertion->getSessionNotOnOrAfter(); 
    } 
 
    $attributes = array_merge($attributes, $assertion->getAttributes()); 
 
    if ($assertion->getAuthnInstant() !== NULL) { 
        /* Assertion contains AuthnStatement, since AuthnInstant is a required attribute. */ 
        $foundAuthnStatement = TRUE; 
    } 
} 
 
if (!$foundAuthnStatement) { 
    $e = new SimpleSAML_Error_Exception('No AuthnStatement found in assertion(s).'); 
    SimpleSAML_Auth_State::throwException($state, $e); 
} 
 
if ($expire !== NULL) { 
    $logoutExpire = $expire; 
} else { 
    /* Just expire the logout associtaion 24 hours into the future. */ 
    $logoutExpire = time() + 24*60*60; 
} 
 
/* Register this session in the logout store. */ 
sspmod_saml_SP_LogoutStore::addSession($sourceId, $nameId, $sessionIndex, $logoutExpire); 
 
/* We need to save the NameID and SessionIndex for logout. */ 
$logoutState = array( 
    'saml:logout:Type' => 'saml2', 
    'saml:logout:IdP' => $idp, 
    'saml:logout:NameID' => $nameId, 
    'saml:logout:SessionIndex' => $sessionIndex, 
    ); 
$state['LogoutState'] = $logoutState; 
$state['saml:AuthenticatingAuthority'] = $authenticatingAuthority; 
$state['saml:AuthenticatingAuthority'][] = $idp; 
$state['PersistentAuthData'][] = 'saml:AuthenticatingAuthority'; 
 
$state['saml:sp:NameID'] = $nameId; 
$state['PersistentAuthData'][] = 'saml:sp:NameID'; 
$state['saml:sp:SessionIndex'] = $sessionIndex; 
$state['PersistentAuthData'][] = 'saml:sp:SessionIndex'; 
$state['saml:sp:AuthnContext'] = $assertion->getAuthnContext(); 
$state['PersistentAuthData'][] = 'saml:sp:AuthnContext'; 
 
if ($expire !== NULL) { 
    $state['Expire'] = $expire; 
} 
 
if (isset($state['SimpleSAML_Auth_Default.ReturnURL'])) { 
    /* Just note some information about the authentication, in case we receive the 
     * same response again. 
     */ 
    $state['saml:sp:prevAuth'] = array( 
        'id' => $response->getId(), 
        'issuer' => $idp, 
        'redirect' => $state['SimpleSAML_Auth_Default.ReturnURL'], 
    ); 
    $state['PersistentAuthData'][] = 'saml:sp:prevAuth'; 
} 
 
$source->handleResponse($state, $idp, $attributes); 
assert('FALSE'); 
 
 |