|
| 1 | +<?php |
| 2 | +/** |
| 3 | + * ADFS PRP IDP protocol support for simpleSAMLphp. |
| 4 | + * |
| 5 | + * @author Hans Zandbelt, SURFnet BV. <hans.zandbelt@surfnet.nl> |
| 6 | + * @package simpleSAMLphp |
| 7 | + * @version $Id$ |
| 8 | + */ |
| 9 | + |
| 10 | +$config = SimpleSAML_Configuration::getInstance(); |
| 11 | +$adfsconfig = SimpleSAML_Configuration::getConfig('adfs-idp-hosted.php'); |
| 12 | +$session = SimpleSAML_Session::getInstance(); |
| 13 | + |
| 14 | +SimpleSAML_Logger::info('ADFS - IdP.SSOService: Accessing ADFS IdP endpoint SSOService'); |
| 15 | + |
| 16 | +try { |
| 17 | + if (array_key_exists('entityId', $config)) { |
| 18 | + $idpentityid = $config['entityId']; |
| 19 | + } else { |
| 20 | + $idpentityid = 'urn:federation:' . SimpleSAML_Utilities::getSelfHost() . ':idp'; |
| 21 | + } |
| 22 | +} catch (Exception $exception) { |
| 23 | + SimpleSAML_Utilities::fatalError($session->getTrackID(), 'METADATA', $exception); |
| 24 | +} |
| 25 | + |
| 26 | +SimpleSAML_Logger::info('ADFS - IdP.SSOService: Accessing ADFS IdP endpoint SSOService'); |
| 27 | + |
| 28 | +function ADFS_GenerateResponse($issuer, $target, $nameid, $attributes) { |
| 29 | +# $nameid = 'hans@surfnet.nl'; |
| 30 | + $issueInstant = SimpleSAML_Utilities::generateTimestamp(); |
| 31 | + $notBefore = SimpleSAML_Utilities::generateTimestamp(time() - 30); |
| 32 | + $assertionExpire = SimpleSAML_Utilities::generateTimestamp(time() + 60 * 5); |
| 33 | + $assertionID = SimpleSAML_Utilities::generateID(); |
| 34 | + $nameidFormat = 'http://schemas.xmlsoap.org/claims/UPN'; |
| 35 | + $result = |
| 36 | +'<wst:RequestSecurityTokenResponse xmlns:wst="http://schemas.xmlsoap.org/ws/2005/02/trust"> |
| 37 | + <wst:RequestedSecurityToken> |
| 38 | + <saml:Assertion Issuer="' . $issuer . '" IssueInstant="' . $issueInstant . '" AssertionID="' . $assertionID . '" MinorVersion="1" MajorVersion="1" xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion"> |
| 39 | + <saml:Conditions NotOnOrAfter="' . $assertionExpire . '" NotBefore="' . $notBefore . '"> |
| 40 | + <saml:AudienceRestrictionCondition> |
| 41 | + <saml:Audience>' . $target .'</saml:Audience> |
| 42 | + </saml:AudienceRestrictionCondition> |
| 43 | + </saml:Conditions> |
| 44 | + <saml:AuthenticationStatement AuthenticationMethod="urn:oasis:names:tc:SAML:1.0:am:unspecified" AuthenticationInstant="' . $issueInstant . '"> |
| 45 | + <saml:Subject> |
| 46 | + <saml:NameIdentifier Format="' . $nameidFormat . '">' . $nameid . '</saml:NameIdentifier> |
| 47 | + </saml:Subject> |
| 48 | + </saml:AuthenticationStatement> |
| 49 | + <saml:AttributeStatement> |
| 50 | + <saml:Subject> |
| 51 | + <saml:NameIdentifier Format="' . $nameidFormat . '">' . $nameid . '</saml:NameIdentifier> |
| 52 | + </saml:Subject>'; |
| 53 | + foreach ($attributes as $name => $values) { |
| 54 | + $result .= '<saml:Attribute AttributeNamespace="http://schemas.xmlsoap.org/claims" AttributeName="' . $name .'">'; |
| 55 | + foreach ($values as $value) { |
| 56 | + $result .= '<saml:AttributeValue>' . $value . '</saml:AttributeValue>'; |
| 57 | + } |
| 58 | + $result .= '</saml:Attribute>'; |
| 59 | + } |
| 60 | + $result .= ' |
| 61 | + </saml:AttributeStatement> |
| 62 | + </saml:Assertion> |
| 63 | + </wst:RequestedSecurityToken> |
| 64 | + <wsp:AppliesTo xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"><wsa:EndpointReference xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing"> |
| 65 | + <wsa:Address>' . $target . '</wsa:Address> |
| 66 | + </wsa:EndpointReference></wsp:AppliesTo> |
| 67 | + </wst:RequestSecurityTokenResponse>'; |
| 68 | + return $result; |
| 69 | +} |
| 70 | + |
| 71 | +function ADFS_SignResponse($response, $key, $cert) { |
| 72 | + $objXMLSecDSig = new XMLSecurityDSig(); |
| 73 | + $objXMLSecDSig->idKeys = array('AssertionID'); |
| 74 | + $objXMLSecDSig->setCanonicalMethod(XMLSecurityDSig::EXC_C14N); |
| 75 | + $responsedom = new DOMDocument(); |
| 76 | + $responsedom->loadXML(str_replace ("\r", "", $response)); |
| 77 | + $firstassertionroot = $responsedom->getElementsByTagName('Assertion')->item(0); |
| 78 | + $objXMLSecDSig->addReferenceList(array($firstassertionroot), XMLSecurityDSig::SHA1, |
| 79 | + array('http://www.w3.org/2000/09/xmldsig#enveloped-signature', XMLSecurityDSig::EXC_C14N), |
| 80 | + array('id_name' => 'AssertionID')); |
| 81 | + $objKey = new XMLSecurityKey(XMLSecurityKey::RSA_SHA1, array('type'=>'private')); |
| 82 | + $objKey->loadKey($key, TRUE); |
| 83 | + $objXMLSecDSig->sign($objKey); |
| 84 | + if ($cert) { |
| 85 | + $public_cert = file_get_contents($cert); |
| 86 | + $objXMLSecDSig->add509Cert($public_cert, TRUE); |
| 87 | + } |
| 88 | + $newSig = $responsedom->importNode($objXMLSecDSig->sigNode, TRUE); |
| 89 | + $firstassertionroot->appendChild($newSig); |
| 90 | + return $responsedom->saveXML(); |
| 91 | +} |
| 92 | + |
| 93 | +function ADFS_PostResponse($url, $wresult, $wctx) { |
| 94 | + print ' |
| 95 | +<body onload="document.forms[0].submit()"><form method="post" action="' . $url . '"> |
| 96 | + <input type="hidden" name="wa" value="wsignin1.0"> |
| 97 | + <input type="hidden" name="wresult" value="' . htmlspecialchars($wresult) . '"> |
| 98 | + <input type="hidden" name="wctx" value="' . htmlspecialchars($wctx) . '"> |
| 99 | + <noscript><input type="submit" value="Continue"></noscript> |
| 100 | +</form></body>'; |
| 101 | + exit; |
| 102 | +} |
| 103 | + |
| 104 | +if (isset($_GET['wa'])) { |
| 105 | + |
| 106 | + if ($_GET['wa'] == 'wsignin1.0') { |
| 107 | + try { |
| 108 | + // accomodate for disfunctional $_GET "windows" slash decoding in PHP |
| 109 | + $wctx = $_GET['wctx']; |
| 110 | + foreach (split('&', $_SERVER['REQUEST_URI']) as $e) { |
| 111 | + $a = split('=', $e); |
| 112 | + if ($a[0] == 'wctx') $wctx = urldecode($a[1]); |
| 113 | + } |
| 114 | + $requestid = $wctx; |
| 115 | + $issuer = $_GET['wtrealm']; |
| 116 | + $requestcache = array( |
| 117 | + 'RequestID' => $requestid, |
| 118 | + 'Issuer' => $issuer, |
| 119 | + 'RelayState' => $requestid |
| 120 | + ); |
| 121 | + |
| 122 | + $spentityid = $requestcache['Issuer']; |
| 123 | + |
| 124 | + SimpleSAML_Logger::info('ADFS - IdP.SSOService: Incoming Authentication request: '.$issuer.' id '.$requestid); |
| 125 | + |
| 126 | + } catch(Exception $exception) { |
| 127 | + SimpleSAML_Utilities::fatalError($session->getTrackID(), 'PROCESSAUTHNREQUEST', $exception); |
| 128 | + } |
| 129 | + } |
| 130 | + |
| 131 | +} elseif(isset($_GET['RequestID'])) { |
| 132 | + |
| 133 | + try { |
| 134 | + |
| 135 | + SimpleSAML_Logger::info('ADFS - IdP.SSOService: Got incoming authentication ID'); |
| 136 | + |
| 137 | + $authId = $_GET['RequestID']; |
| 138 | + $requestcache = $session->getAuthnRequest('adfs', $authId); |
| 139 | + if (!$requestcache) { |
| 140 | + throw new Exception('Could not retrieve cached RequestID = ' . $authId); |
| 141 | + } |
| 142 | + |
| 143 | + } catch(Exception $exception) { |
| 144 | + SimpleSAML_Utilities::fatalError($session->getTrackID(), 'CACHEAUTHNREQUEST', $exception); |
| 145 | + } |
| 146 | + |
| 147 | +} elseif(isset($_REQUEST[SimpleSAML_Auth_ProcessingChain::AUTHPARAM])) { |
| 148 | + |
| 149 | + $authProcId = $_REQUEST[SimpleSAML_Auth_ProcessingChain::AUTHPARAM]; |
| 150 | + $authProcState = SimpleSAML_Auth_ProcessingChain::fetchProcessedState($authProcId); |
| 151 | + $requestcache = $authProcState['core:adfs-idp:requestcache']; |
| 152 | + |
| 153 | +} else { |
| 154 | + SimpleSAML_Utilities::fatalError($session->getTrackID(), 'SSOSERVICEPARAMS'); |
| 155 | +} |
| 156 | + |
| 157 | +if(SimpleSAML_Auth_Source::getById($adfsconfig->getValue('auth')) !== NULL) { |
| 158 | + $authSource = TRUE; |
| 159 | + $authority = $adfsconfig->getValue('auth'); |
| 160 | +} else { |
| 161 | + $authSource = FALSE; |
| 162 | + $authority = $adfsconfig->getValue('authority'); |
| 163 | +} |
| 164 | + |
| 165 | +if (!$session->isValid($authority) ) { |
| 166 | + |
| 167 | + SimpleSAML_Logger::info('ADFS - IdP.SSOService: Will go to authentication module ' . $adfsconfig->getValue('auth')); |
| 168 | + |
| 169 | + $authId = SimpleSAML_Utilities::generateID(); |
| 170 | + $session->setAuthnRequest('adfs', $authId, $requestcache); |
| 171 | + |
| 172 | + $redirectTo = SimpleSAML_Utilities::selfURLNoQuery() . '?RequestID=' . urlencode($authId); |
| 173 | + |
| 174 | + if($authSource) { |
| 175 | + |
| 176 | + SimpleSAML_Auth_Default::initLogin($adfsconfig->getValue('auth'), $redirectTo, NULL, NULL); |
| 177 | + } else { |
| 178 | + $authurl = '/' . $config->getBaseURL() . $adfsconfig->getValue('auth'); |
| 179 | + |
| 180 | + SimpleSAML_Utilities::redirect($authurl, array( |
| 181 | + 'RelayState' => $redirectTo, |
| 182 | + 'AuthId' => $authId, |
| 183 | + 'protocol' => 'adfs', |
| 184 | + )); |
| 185 | + } |
| 186 | + |
| 187 | +} else { |
| 188 | + |
| 189 | + try { |
| 190 | + |
| 191 | + $spentityid = $requestcache['Issuer']; |
| 192 | + $spmetadata = SimpleSAML_Configuration::getConfig('adfs-sp-remote.php'); |
| 193 | + $spmetadata = SimpleSAML_Configuration::loadFromArray($spmetadata->getValue($spentityid)); |
| 194 | + |
| 195 | + $sp_name = $spmetadata->getValue('name', $spentityid); |
| 196 | + |
| 197 | + SimpleSAML_Logger::info('ADFS - IdP.SSOService: Sending back AuthnResponse to ' . $spentityid); |
| 198 | + |
| 199 | + $attributes = $session->getAttributes(); |
| 200 | + |
| 201 | + if (!isset($authProcState)) { |
| 202 | + |
| 203 | + $idpap = $adfsconfig->getValue('authproc'); |
| 204 | + if ($idpap) $idpap = array('authproc' => $idpap); else $idpap = array(); |
| 205 | + |
| 206 | + $spap = $spmetadata->getValue('authproc'); |
| 207 | + if ($spap) $spap = array('authproc' => $spap); else $spap = array(); |
| 208 | + |
| 209 | + $pc = new SimpleSAML_Auth_ProcessingChain($idpap, $spap, 'idp'); |
| 210 | + |
| 211 | + $authProcState = array( |
| 212 | + 'core:adfs-idp:requestcache' => $requestcache, |
| 213 | + 'ReturnURL' => SimpleSAML_Utilities::selfURLNoQuery(), |
| 214 | + 'Attributes' => $attributes, |
| 215 | + 'Destination' => $spap, |
| 216 | + 'Source' => $idpap, |
| 217 | + 'isPassive' => false, |
| 218 | + ); |
| 219 | + |
| 220 | + $previousSSOTime = $session->getData('adfs-idp-ssotime', $spentityid); |
| 221 | + if ($previousSSOTime !== NULL) { |
| 222 | + $authProcState['PreviousSSOTimestamp'] = $previousSSOTime; |
| 223 | + } |
| 224 | + |
| 225 | + try { |
| 226 | + $pc->processState($authProcState); |
| 227 | + } catch (SimpleSAML_Error_NoPassive $e) { |
| 228 | + SimpleSAML_Utilities::fatalError($session->getTrackID(), 'GENERATEAUTHNRESPONSE', $exception); |
| 229 | + } |
| 230 | + |
| 231 | + $requestcache['AuthProcState'] = $authProcState; |
| 232 | + } |
| 233 | + |
| 234 | + $attributes = $authProcState['Attributes']; |
| 235 | + |
| 236 | + $session->setData('adfs-idp-ssotime', $spentityid, time(), |
| 237 | + SimpleSAML_Session::DATA_TIMEOUT_LOGOUT); |
| 238 | + |
| 239 | + $requestID = NULL; $relayState = NULL; |
| 240 | + if (array_key_exists('RequestID', $requestcache)) $requestID = $requestcache['RequestID']; |
| 241 | + if (array_key_exists('RelayState', $requestcache)) $relayState = $requestcache['RelayState']; |
| 242 | + |
| 243 | + $nameid = $session->getNameID(); |
| 244 | + |
| 245 | + $response = ADFS_GenerateResponse($idpentityid, $spentityid, $nameid['value'], $attributes); |
| 246 | + $wresult = ADFS_SignResponse($response, $config->getPathValue('certdir') . $adfsconfig->getValue('key'), $config->getPathValue('certdir') . $adfsconfig->getValue('cert')); |
| 247 | + |
| 248 | + ADFS_PostResponse($spmetadata->getValue('prp'), $wresult, $relayState); |
| 249 | + |
| 250 | + } catch(Exception $exception) { |
| 251 | + SimpleSAML_Utilities::fatalError($session->getTrackID(), 'GENERATEAUTHNRESPONSE', $exception); |
| 252 | + } |
| 253 | + |
| 254 | +} |
| 255 | + |
| 256 | +?> |
0 commit comments