/** * Apply this filter. * * @param array &$request The current request */ public function process(&$request) { assert('is_array($request)'); assert('array_key_exists("Attributes", $request)'); $attributes =& $request['Attributes']; if (!isset($attributes[$this->sourceAttribute])) { return; } // will not overwrite existing attribute if (isset($attributes[$this->targetAttribute])) { return; } $sourceAttrVal = $attributes[$this->sourceAttribute][0]; /* the last position of an @ is usually the beginning of the scope * string */ $scopeIndex = strrpos($sourceAttrVal, '@'); if ($scopeIndex !== FALSE) { $attributes[$this->targetAttribute] = array(); $scope = substr($sourceAttrVal, $scopeIndex + 1); $attributes[$this->targetAttribute][] = $scope; SimpleSAML\Logger::debug('ScopeFromAttribute: Inserted new attribute ' . $this->targetAttribute . ', with scope ' . $scope); } else { SimpleSAML\Logger::warning('ScopeFromAttribute: The configured source attribute ' . $this->sourceAttribute . ' does not have a scope. Did not add attribute ' . $this->targetAttribute . '.'); } }
/** * Attempt to log in using the given username and password. * * On a successful login, this function should return the username as 'uid' attribute, * and merged attributes from the configuration file. * On failure, it should throw an exception. A SimpleSAML_Error_Error('WRONGUSERPASS') * should be thrown in case of a wrong username OR a wrong password, to prevent the * enumeration of usernames. * * @param string $username The username the user wrote. * @param string $password The password the user wrote. * @return array Associative array with the users attributes. */ protected function login($username, $password) { assert('is_string($username)'); assert('is_string($password)'); foreach ($this->users as $userpass) { $matches = explode(':', $userpass, 2); if ($matches[0] == $username) { $crypted = $matches[1]; // This is about the only attribute we can add $attributes = array_merge(array('uid' => array($username)), $this->attributes); // Traditional crypt(3) if (crypt($password, $crypted) == $crypted) { SimpleSAML\Logger::debug('User ' . $username . ' authenticated successfully'); return $attributes; } // Apache's custom MD5 if (APR1_MD5::check($crypted, $password)) { SimpleSAML\Logger::debug('User ' . $username . ' authenticated successfully'); return $attributes; } // SHA1 or plain-text if (SimpleSAML\Utils\Crypto::pwValid($crypted, $password)) { SimpleSAML\Logger::debug('User ' . $username . ' authenticated successfully'); return $attributes; } throw new SimpleSAML_Error_Error('WRONGUSERPASS'); } } throw new SimpleSAML_Error_Error('WRONGUSERPASS'); }
public function finalStep(&$state) { $requestToken = $state['authtwitter:authdata:requestToken']; $parameters = array(); if (!isset($_REQUEST['oauth_token'])) { throw new SimpleSAML_Error_BadRequest("Missing oauth_token parameter."); } if ($requestToken->key !== (string) $_REQUEST['oauth_token']) { throw new SimpleSAML_Error_BadRequest("Invalid oauth_token parameter."); } if (!isset($_REQUEST['oauth_verifier'])) { throw new SimpleSAML_Error_BadRequest("Missing oauth_verifier parameter."); } $parameters['oauth_verifier'] = (string) $_REQUEST['oauth_verifier']; $consumer = new sspmod_oauth_Consumer($this->key, $this->secret); SimpleSAML\Logger::debug("oauth: Using this request token [" . $requestToken->key . "] with the secret [" . $requestToken->secret . "]"); // Replace the request token with an access token $accessToken = $consumer->getAccessToken('https://api.twitter.com/oauth/access_token', $requestToken, $parameters); SimpleSAML\Logger::debug("Got an access token from the OAuth service provider [" . $accessToken->key . "] with the secret [" . $accessToken->secret . "]"); $userdata = $consumer->getUserInfo('https://api.twitter.com/1.1/account/verify_credentials.json', $accessToken); if (!isset($userdata['id_str']) || !isset($userdata['screen_name'])) { throw new SimpleSAML_Error_AuthSource($this->authId, 'Authentication error: id_str and screen_name not set.'); } $attributes = array(); foreach ($userdata as $key => $value) { if (is_string($value)) { $attributes['twitter.' . $key] = array((string) $value); } } $attributes['twitter_at_screen_name'] = array('@' . $userdata['screen_name']); $attributes['twitter_screen_n_realm'] = array($userdata['screen_name'] . '@twitter.com'); $attributes['twitter_targetedID'] = array('http://twitter.com!' . $userdata['id_str']); $state['Attributes'] = $attributes; }
/** * Apply filter to add groups attribute. * * @param array &$request The current request */ public function process(&$request) { assert('is_array($request)'); assert('array_key_exists("Attributes", $request)'); $groups = array(); $attributes =& $request['Attributes']; $realm = self::getRealm($attributes); if ($realm !== NULL) { $groups[] = 'realm-' . $realm; } foreach ($this->generateGroupsFrom as $name) { if (!array_key_exists($name, $attributes)) { SimpleSAML\Logger::debug('GenerateGroups - attribute \'' . $name . '\' not found.'); /* Attribute not present. */ continue; } foreach ($attributes[$name] as $value) { $value = self::escapeIllegalChars($value); $groups[] = $name . '-' . $value; if ($realm !== NULL) { $groups[] = $name . '-' . $realm . '-' . $value; } } } if (count($groups) > 0) { $attributes['groups'] = $groups; } }
/** * Clean the logout table of expired entries. * * @param SimpleSAML_Store_SQL $store The datastore. */ private static function cleanLogoutStore(SimpleSAML_Store_SQL $store) { SimpleSAML\Logger::debug('saml.LogoutStore: Cleaning logout store.'); $query = 'DELETE FROM ' . $store->prefix . '_saml_LogoutStore WHERE _expire < :now'; $params = array('now' => gmdate('Y-m-d H:i:s')); $query = $store->pdo->prepare($query); $query->execute($params); }
/** * Check that the user has access to the statistics. * * If the user doesn't have access, send the user to the login page. */ public static function checkAccess(SimpleSAML_Configuration $statconfig) { $protected = $statconfig->getBoolean('protected', FALSE); $authsource = $statconfig->getString('auth', NULL); $allowedusers = $statconfig->getValue('allowedUsers', NULL); $useridattr = $statconfig->getString('useridattr', 'eduPersonPrincipalName'); $acl = $statconfig->getValue('acl', NULL); if ($acl !== NULL && !is_string($acl) && !is_array($acl)) { throw new SimpleSAML_Error_Exception('Invalid value for \'acl\'-option. Should be an array or a string.'); } if (!$protected) { return; } if (SimpleSAML\Utils\Auth::isAdmin()) { // User logged in as admin. OK. SimpleSAML\Logger::debug('Statistics auth - logged in as admin, access granted'); return; } if (!isset($authsource)) { // If authsource is not defined, init admin login. SimpleSAML\Utils\Auth::requireAdmin(); } // We are using an authsource for login. $as = new SimpleSAML_Auth_Simple($authsource); $as->requireAuth(); // User logged in with auth source. SimpleSAML\Logger::debug('Statistics auth - valid login with auth source [' . $authsource . ']'); // Retrieving attributes $attributes = $as->getAttributes(); if (!empty($allowedusers)) { // Check if userid exists if (!isset($attributes[$useridattr][0])) { throw new Exception('User ID is missing'); } // Check if userid is allowed access.. if (in_array($attributes[$useridattr][0], $allowedusers)) { SimpleSAML\Logger::debug('Statistics auth - User granted access by user ID [' . $attributes[$useridattr][0] . ']'); return; } SimpleSAML\Logger::debug('Statistics auth - User denied access by user ID [' . $attributes[$useridattr][0] . ']'); } else { SimpleSAML\Logger::debug('Statistics auth - no allowedUsers list.'); } if (!is_null($acl)) { $acl = new sspmod_core_ACL($acl); if ($acl->allows($attributes)) { SimpleSAML\Logger::debug('Statistics auth - allowed access by ACL.'); return; } SimpleSAML\Logger::debug('Statistics auth - denied access by ACL.'); } else { SimpleSAML\Logger::debug('Statistics auth - no ACL configured.'); } throw new SimpleSAML_Error_Exception('Access denied to the current user.'); }
/** * Returns the name of the transform class based on a given URI * * @throws Exception * @param string $uri The transform URI * @return string The transform implementation class name */ protected function _findClassbyURI($uri) { switch ($uri) { case 'http://www.w3.org/2000/09/xmldsig#enveloped-signature': return 'Zend_InfoCard_Xml_Security_Transform_EnvelopedSignature'; case 'http://www.w3.org/2001/10/xml-exc-c14n#': return 'Zend_InfoCard_Xml_Security_Transform_XmlExcC14N'; default: SimpleSAML\Logger::debug("Unknown or Unsupported Transformation Requested"); } }
/** * Transforms the XML Document according to the EnvelopedSignature Transform * * @throws Exception * @param string $strXMLData The input XML data * @return string the transformed XML data */ public function transform($strXMLData) { $sxe = simplexml_load_string($strXMLData); $sxe->registerXPathNamespace('ds', 'http://www.w3.org/2000/09/xmldsig#'); list($signature) = $sxe->xpath("//ds:Signature"); if (!isset($signature)) { SimpleSAML\Logger::debug("Unable to locate Signature Block for EnvelopedSignature Transform"); } $transformed_xml = str_replace($signature->asXML(), "", $sxe->asXML()); return $transformed_xml; }
/** * Save consent. * * Called when the user asks for the consent to be saved. If consent information for the given user and destination * already exists, it should be overwritten. * * @param string $userId The hash identifying the user at an IdP. * @param string $destinationId A string which identifies the destination. * @param string $attributeSet A hash which identifies the attributes. * * @return void */ public function saveConsent($userId, $destinationId, $attributeSet) { assert('is_string($userId)'); assert('is_string($destinationId)'); assert('is_string($attributeSet)'); $name = self::_getCookieName($userId, $destinationId); $value = $userId . ':' . $attributeSet . ':' . $destinationId; SimpleSAML\Logger::debug('Consent cookie - Set [' . $value . ']'); $value = self::_sign($value); $this->_setConsentCookie($name, $value); }
/** * Filter out YubiKey 'otp' attribute and replace it with * a 'yubiPrefix' attribute that leaves out the dynamic part. * * @param array &$state The state we should update. */ public function process(&$state) { assert('is_array($state)'); assert('array_key_exists("Attributes", $state)'); $attributes = $state['Attributes']; SimpleSAML\Logger::debug('OTP2YubiPrefix: enter with attributes: ' . implode(',', array_keys($attributes))); $otps = $attributes['otp']; $otp = $otps['0']; $token_size = 32; $identity = substr($otp, 0, strlen($otp) - $token_size); $attributes['yubiPrefix'] = array($identity); SimpleSAML\Logger::info('OTP2YubiPrefix: otp: ' . $otp . ' identity: ' . $identity . ' (otp keys: ' . implode(',', array_keys($otps)) . ')'); unset($attributes['otp']); SimpleSAML\Logger::debug('OTP2YubiPrefix: leaving with attributes: ' . implode(',', array_keys($attributes))); }
/** * Transform the input XML based on C14n XML Exclusive Canonicalization rules * * @throws Exception * @param string $strXMLData The input XML * @return string The output XML */ public function transform($strXMLData) { $dom = new DOMDocument(); $dom->loadXML($strXMLData); if ($strXMLData == NULL) { SimpleSAML\Logger::debug("NOXML: " . $dom->saveXML()); } else { SimpleSAML\Logger::debug("XMLcan: " . $dom->saveXML()); } if (method_exists($dom, 'C14N')) { return $dom->C14N(true, false); } SimpleSAML\Logger::debug("This transform requires the C14N() method to exist in the DOM extension"); throw new Exception('This transform requires the C14N() method to exist in the DOM extension'); }
/** * Attempt to log in using the given username and password. * * On a successful login, this function should return the users attributes. On failure, * it should throw an exception. If the error was caused by the user entering the wrong * username OR password, a SimpleSAML_Error_Error('WRONGUSERPASS') should be thrown. * * The username is UTF-8 encoded, and the hash is base64 encoded. * * @param string $username The username the user wrote. * @param string $password The password the user wrote. * @return array Associative array with the users attributes. */ protected function login($username, $password) { assert('is_string($username)'); assert('is_string($password)'); foreach ($this->users as $userpass => $attrs) { $matches = explode(':', $userpass, 2); if ($matches[0] === $username) { if (SimpleSAML\Utils\Crypto::pwValid($matches[1], $password)) { return $this->users[$userpass]; } else { SimpleSAML\Logger::debug('Incorrect password "' . $password . '" for user ' . $username); } } } throw new SimpleSAML_Error_Error('WRONGUSERPASS'); }
public static function handleLogin($authStateId, $xmlToken) { assert('is_string($authStateId)'); $config = SimpleSAML_Configuration::getInstance(); $autoconfig = $config->copyFromBase('logininfocard', 'config-login-infocard.php'); $idp_key = $autoconfig->getValue('idp_key'); $idp_pass = $autoconfig->getValue('idp_key_pass', NULL); $sts_crt = $autoconfig->getValue('sts_crt'); $Infocard = $autoconfig->getValue('InfoCard'); $infocard = new sspmod_InfoCard_RP_InfoCard(); $infocard->addIDPKey($idp_key, $idp_pass); $infocard->addSTSCertificate($sts_crt); if (!$xmlToken) { SimpleSAML\Logger::debug("XMLtoken: " . $xmlToken); } else { SimpleSAML\Logger::debug("NOXMLtoken: " . $xmlToken); } $claims = $infocard->process($xmlToken); if ($claims->isValid()) { $attributes = array(); foreach ($Infocard['requiredClaims'] as $claim => $data) { $attributes[$claim] = array($claims->{$claim}); } foreach ($Infocard['optionalClaims'] as $claim => $data) { $attributes[$claim] = array($claims->{$claim}); } // Retrieve the authentication state $state = SimpleSAML_Auth_State::loadState($authStateId, self::STAGEID); // Find authentication source assert('array_key_exists(self::AUTHID, $state)'); $source = SimpleSAML_Auth_Source::getById($state[self::AUTHID]); if ($source === NULL) { throw new Exception('Could not find authentication source with id ' . $state[self::AUTHID]); } $state['Attributes'] = $attributes; unset($infocard); unset($claims); SimpleSAML_Auth_Source::completeAuth($state); } else { unset($infocard); unset($claims); return 'wrong_IC'; } }
/** * Initialize an authentication processing chain for the given service provider * and identity provider. * * @param array $idpMetadata The metadata for the IdP. * @param array $spMetadata The metadata for the SP. */ public function __construct($idpMetadata, $spMetadata, $mode = 'idp') { assert('is_array($idpMetadata)'); assert('is_array($spMetadata)'); $this->filters = array(); $config = SimpleSAML_Configuration::getInstance(); $configauthproc = $config->getArray('authproc.' . $mode, NULL); if (!empty($configauthproc)) { $configfilters = self::parseFilterList($configauthproc); self::addFilters($this->filters, $configfilters); } if (array_key_exists('authproc', $idpMetadata)) { $idpFilters = self::parseFilterList($idpMetadata['authproc']); self::addFilters($this->filters, $idpFilters); } if (array_key_exists('authproc', $spMetadata)) { $spFilters = self::parseFilterList($spMetadata['authproc']); self::addFilters($this->filters, $spFilters); } SimpleSAML\Logger::debug('Filter config for ' . $idpMetadata['entityid'] . '->' . $spMetadata['entityid'] . ': ' . str_replace("\n", '', var_export($this->filters, TRUE))); }
/** * Apply filter to add or replace attributes. * * Add or replace existing attributes with the configured values. * * @param array &$request The current request */ public function process(&$request) { assert('is_array($request)'); assert('array_key_exists("Attributes", $request)'); $attributes =& $request['Attributes']; $attrlang = NULL; if (array_key_exists($this->langattr, $attributes)) { $attrlang = $attributes[$this->langattr][0]; } $lang = SimpleSAML\Locale\Language::getLanguageCookie(); if (isset($attrlang)) { SimpleSAML\Logger::debug('LanguageAdaptor: Language in attribute was set [' . $attrlang . ']'); } if (isset($lang)) { SimpleSAML\Logger::debug('LanguageAdaptor: Language in session was set [' . $lang . ']'); } if (isset($attrlang) && !isset($lang)) { // Language set in attribute but not in cookie - update cookie SimpleSAML\Locale\Language::setLanguageCookie($attrlang); } elseif (!isset($attrlang) && isset($lang)) { // Language set in cookie, but not in attribute. Update attribute $request['Attributes'][$this->langattr] = array($lang); } }
/** * Getter for the LDAP connection object. Created this getter * rather than setting in the constructor to avoid unnecessarily * connecting to LDAP when it might not be needed. * * @return sspmod_ldap_LdapConnection */ protected function getLdap() { // Check if already connected if ($this->ldap) { return $this->ldap; } // Get the connection specific options $hostname = $this->config->getString('ldap.hostname'); $port = $this->config->getInteger('ldap.port', 389); $enable_tls = $this->config->getBoolean('ldap.enable_tls', false); $debug = $this->config->getBoolean('ldap.debug', false); $timeout = $this->config->getInteger('ldap.timeout', 0); $username = $this->config->getString('ldap.username', null); $password = $this->config->getString('ldap.password', null); // Log the LDAP connection SimpleSAML\Logger::debug($this->title . 'Connecting to LDAP server;' . ' Hostname: ' . $hostname . ' Port: ' . $port . ' Enable TLS: ' . ($enable_tls ? 'Yes' : 'No') . ' Debug: ' . ($debug ? 'Yes' : 'No') . ' Timeout: ' . $timeout . ' Username: '******' Password: '******'*', strlen($password))); // Connect to the LDAP server to be queried during processing $this->ldap = new SimpleSAML_Auth_LDAP($hostname, $enable_tls, $debug, $timeout, $port); $this->ldap->bind($username, $password); // All done return $this->ldap; }
/** * This function checks if this EntityDescriptor was signed with a certificate with the * given fingerprint. * * @param string $fingerprint Fingerprint of the certificate which should have been used to sign this * EntityDescriptor. * * @return boolean True if it was signed with the certificate with the given fingerprint, false otherwise. */ public function validateFingerprint($fingerprint) { assert('is_string($fingerprint)'); $fingerprint = strtolower(str_replace(":", "", $fingerprint)); $candidates = array(); foreach ($this->validators as $validator) { foreach ($validator->getValidatingCertificates() as $cert) { $fp = strtolower(sha1(base64_decode($cert))); $candidates[] = $fp; if ($fp === $fingerprint) { return true; } } } SimpleSAML\Logger::debug('Fingerprint was [' . $fingerprint . '] not one of [' . join(', ', $candidates) . ']'); return false; }
/** * Retrieve the metadata file. * * This function will check its cached copy, to see whether it can be used. * * @return SAML2_XML_md_EntityDescriptor|SAML2_XML_md_EntitiesDescriptor|NULL The downloaded metadata. */ public function getMetadata() { if ($this->metadata !== NULL) { /* We have already downloaded the metdata. */ return $this->metadata; } if (!$this->aggregator->isCacheValid($this->cacheId, $this->cacheTag)) { $this->updateCache(); if ($this->metadata !== NULL) { return $this->metadata; } /* We were unable to update the cache - use cached metadata. */ } $cacheFile = $this->aggregator->getCacheFile($this->cacheId); if (!file_exists($cacheFile)) { SimpleSAML\Logger::error($this->logLoc . 'No cached metadata available.'); return NULL; } SimpleSAML\Logger::debug($this->logLoc . 'Using cached metadata from ' . var_export($cacheFile, TRUE)); $metadata = file_get_contents($cacheFile); if ($metadata !== NULL) { $this->metadata = unserialize($metadata); return $this->metadata; } return NULL; }
/** * Send a logout response. * * @param SimpleSAML_IdP $idp The IdP we are sending a logout request from. * @param array &$state The logout state array. */ public static function sendLogoutResponse(SimpleSAML_IdP $idp, array $state) { assert('isset($state["saml:SPEntityId"])'); assert('isset($state["saml:RequestId"])'); assert('array_key_exists("saml:RelayState", $state)'); // Can be NULL. $spEntityId = $state['saml:SPEntityId']; $metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler(); $idpMetadata = $idp->getConfig(); $spMetadata = $metadata->getMetaDataConfig($spEntityId, 'saml20-sp-remote'); $lr = sspmod_saml_Message::buildLogoutResponse($idpMetadata, $spMetadata); $lr->setInResponseTo($state['saml:RequestId']); $lr->setRelayState($state['saml:RelayState']); if (isset($state['core:Failed']) && $state['core:Failed']) { $partial = TRUE; $lr->setStatus(array('Code' => SAML2_Const::STATUS_SUCCESS, 'SubCode' => SAML2_Const::STATUS_PARTIAL_LOGOUT)); SimpleSAML\Logger::info('Sending logout response for partial logout to SP ' . var_export($spEntityId, TRUE)); } else { $partial = FALSE; SimpleSAML\Logger::debug('Sending logout response to SP ' . var_export($spEntityId, TRUE)); } SimpleSAML_Stats::log('saml:idp:LogoutResponse:sent', array('spEntityID' => $spEntityId, 'idpEntityID' => $idpMetadata->getString('entityid'), 'partial' => $partial)); $dst = $spMetadata->getEndpointPrioritizedByBinding('SingleLogoutService', array(SAML2_Const::BINDING_HTTP_REDIRECT, SAML2_Const::BINDING_HTTP_POST)); $binding = SAML2_Binding::getBinding($dst['Binding']); if (isset($dst['ResponseLocation'])) { $dst = $dst['ResponseLocation']; } else { $dst = $dst['Location']; } $lr->setDestination($dst); $binding->send($lr); }
/** * Send a SAML2 SSO request to an IdP. * * @param SimpleSAML_Configuration $idpMetadata The metadata of the IdP. * @param array $state The state array for the current authentication. */ private function startSSO2(SimpleSAML_Configuration $idpMetadata, array $state) { if (isset($state['saml:ProxyCount']) && $state['saml:ProxyCount'] < 0) { SimpleSAML_Auth_State::throwException($state, new SimpleSAML_Error_ProxyCountExceeded("ProxyCountExceeded")); } $ar = sspmod_saml_Message::buildAuthnRequest($this->metadata, $idpMetadata); $ar->setAssertionConsumerServiceURL(SimpleSAML\Module::getModuleURL('saml/sp/saml2-acs.php/' . $this->authId)); if (isset($state['SimpleSAML_Auth_Source.ReturnURL'])) { $ar->setRelayState($state['SimpleSAML_Auth_Source.ReturnURL']); } if (isset($state['saml:AuthnContextClassRef'])) { $accr = SimpleSAML\Utils\Arrays::arrayize($state['saml:AuthnContextClassRef']); $ar->setRequestedAuthnContext(array('AuthnContextClassRef' => $accr)); } if (isset($state['ForceAuthn'])) { $ar->setForceAuthn((bool) $state['ForceAuthn']); } if (isset($state['isPassive'])) { $ar->setIsPassive((bool) $state['isPassive']); } if (isset($state['saml:NameIDPolicy'])) { if (is_string($state['saml:NameIDPolicy'])) { $policy = array('Format' => (string) $state['saml:NameIDPolicy'], 'AllowCreate' => TRUE); } elseif (is_array($state['saml:NameIDPolicy'])) { $policy = $state['saml:NameIDPolicy']; } else { throw new SimpleSAML_Error_Exception('Invalid value of $state[\'saml:NameIDPolicy\'].'); } $ar->setNameIdPolicy($policy); } if (isset($state['saml:IDPList'])) { $IDPList = $state['saml:IDPList']; } else { $IDPList = array(); } $ar->setIDPList(array_unique(array_merge($this->metadata->getArray('IDPList', array()), $idpMetadata->getArray('IDPList', array()), (array) $IDPList))); if (isset($state['saml:ProxyCount']) && $state['saml:ProxyCount'] !== null) { $ar->setProxyCount($state['saml:ProxyCount']); } elseif ($idpMetadata->getInteger('ProxyCount', null) !== null) { $ar->setProxyCount($idpMetadata->getInteger('ProxyCount', null)); } elseif ($this->metadata->getInteger('ProxyCount', null) !== null) { $ar->setProxyCount($this->metadata->getInteger('ProxyCount', null)); } $requesterID = array(); if (isset($state['saml:RequesterID'])) { $requesterID = $state['saml:RequesterID']; } if (isset($state['core:SP'])) { $requesterID[] = $state['core:SP']; } $ar->setRequesterID($requesterID); if (isset($state['saml:Extensions'])) { $ar->setExtensions($state['saml:Extensions']); } // save IdP entity ID as part of the state $state['ExpectedIssuer'] = $idpMetadata->getString('entityid'); $id = SimpleSAML_Auth_State::saveState($state, 'saml:sp:sso', TRUE); $ar->setId($id); SimpleSAML\Logger::debug('Sending SAML 2 AuthnRequest to ' . var_export($idpMetadata->getString('entityid'), TRUE)); /* Select appropriate SSO endpoint */ if ($ar->getProtocolBinding() === SAML2_Const::BINDING_HOK_SSO) { $dst = $idpMetadata->getDefaultEndpoint('SingleSignOnService', array(SAML2_Const::BINDING_HOK_SSO)); } else { $dst = $idpMetadata->getDefaultEndpoint('SingleSignOnService', array(SAML2_Const::BINDING_HTTP_REDIRECT, SAML2_Const::BINDING_HTTP_POST)); } $ar->setDestination($dst['Location']); $b = SAML2_Binding::getBinding($dst['Binding']); $this->sendSAML2AuthnRequest($state, $b, $ar); assert('FALSE'); }
/** * Save a metadata entry. * * @param string $entityId The entityId of the metadata entry. * @param string $set The metadata set this metadata entry belongs to. * @param array $metadata The metadata. * * @return boolean True if successfully saved, false otherwise. */ public function saveMetadata($entityId, $set, $metadata) { assert('is_string($entityId)'); assert('is_string($set)'); assert('is_array($metadata)'); $filePath = $this->getMetadataPath($entityId, $set); $newPath = $filePath . '.new'; $dir = dirname($filePath); if (!is_dir($dir)) { SimpleSAML\Logger::info('Creating directory: ' . $dir); $res = @mkdir($dir, 0777, true); if ($res === false) { $error = error_get_last(); SimpleSAML\Logger::error('Failed to create directory ' . $dir . ': ' . $error['message']); return false; } } $data = serialize($metadata); SimpleSAML\Logger::debug('Writing: ' . $newPath); $res = file_put_contents($newPath, $data); if ($res === false) { $error = error_get_last(); SimpleSAML\Logger::error('Error saving file ' . $newPath . ': ' . $error['message']); return false; } $res = rename($newPath, $filePath); if ($res === false) { $error = error_get_last(); SimpleSAML\Logger::error('Error renaming ' . $newPath . ' to ' . $filePath . ': ' . $error['message']); return false; } return true; }
function send() { if ($this->to == NULL) { throw new Exception('EMail field [to] is required and not set.'); } if ($this->subject == NULL) { throw new Exception('EMail field [subject] is required and not set.'); } if ($this->body == NULL) { throw new Exception('EMail field [body] is required and not set.'); } $random_hash = bin2hex(openssl_random_pseudo_bytes(16)); if (isset($this->from)) { $this->headers[] = 'From: ' . $this->from; } if (isset($this->replyto)) { $this->headers[] = 'Reply-To: ' . $this->replyto; } $this->headers[] = 'Content-Type: multipart/alternative; boundary="simplesamlphp-' . $random_hash . '"'; $message = ' --simplesamlphp-' . $random_hash . ' Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 8bit ' . strip_tags(html_entity_decode($this->body)) . ' --simplesamlphp-' . $random_hash . ' Content-Type: text/html; charset="utf-8" Content-Transfer-Encoding: 8bit ' . $this->getHTML($this->body) . ' --simplesamlphp-' . $random_hash . '-- '; $headers = implode("\n", $this->headers); $mail_sent = @mail($this->to, $this->subject, $message, $headers); SimpleSAML\Logger::debug('Email: Sending e-mail to [' . $this->to . '] : ' . ($mail_sent ? 'OK' : 'Failed')); if (!$mail_sent) { throw new Exception('Error when sending e-mail'); } }
/** * Post-logout handler for re-authentication. * * This method will never return. * * @param SimpleSAML_IdP $idp The IdP we are logging out from. * @param array &$state The state array with the state during logout. */ public static function reauthPostLogout(SimpleSAML_IdP $idp, array $state) { assert('isset($state["saml:sp:AuthId"])'); SimpleSAML\Logger::debug('Proxy: logout completed.'); if (isset($state['saml:proxy:reauthLogout:PrevResponder'])) { $state['Responder'] = $state['saml:proxy:reauthLogout:PrevResponder']; } $sp = SimpleSAML_Auth_Source::getById($state['saml:sp:AuthId'], 'sspmod_saml_Auth_Source_SP'); /** @var sspmod_saml_Auth_Source_SP $authSource */ SimpleSAML\Logger::debug('Proxy: logging in again.'); $sp->authenticate($state); assert('false'); }
/** * Delete all consents. * * @param string $userId The hash identifying the user at an IdP. * * @return int Number of consents deleted */ public function deleteAllConsents($userId) { assert('is_string($userId)'); $st = $this->_execute('DELETE FROM ' . $this->_table . ' WHERE hashed_user_id = ?', array($userId)); if ($st === false) { return; } if ($st->rowCount() > 0) { SimpleSAML\Logger::debug('consent:Database - Deleted (' . $st->rowCount() . ') consent(s).'); return $st->rowCount(); } else { SimpleSAML\Logger::warning('consent:Database - Attempted to delete nonexistent consent'); } }
/** * Is the session representing an authenticated user, and is the session still alive. * This function will return false after the user has timed out. * * @param string $authority The authentication source that the user should be authenticated with. * * @return true if the user has a valid session, false if not. */ public function isValid($authority) { assert('is_string($authority)'); if (!isset($this->authData[$authority])) { SimpleSAML\Logger::debug('Session: ' . var_export($authority, true) . ' not valid because we are not authenticated.'); return false; } if ($this->authData[$authority]['Expire'] <= time()) { SimpleSAML\Logger::debug('Session: ' . var_export($authority, true) . ' not valid because it is expired.'); return false; } SimpleSAML\Logger::debug('Session: Valid session found with ' . var_export($authority, true) . '.'); return true; }
/** * Process this filter * * Logic is largely the same as (and lifted from) sqlauth:sql * @param mixed &$request * @throws SimpleSAML_Error_Exception */ public function process(&$request) { assert('is_array($request)'); assert('array_key_exists("Attributes", $request)'); assert('array_key_exists("entityid", $request["Destination"])'); $attributes =& $request['Attributes']; if (!array_key_exists($this->attribute, $attributes)) { SimpleSAML\Logger::info('AttributeFromSQL: attribute \'' . $this->attribute . '\' not set, declining'); return; } $db = $this->connect(); try { $sth = $db->prepare('SELECT attribute,value FROM ' . $this->table . ' WHERE uid=? AND (sp=\'%\' OR sp=?);'); } catch (PDOException $e) { throw new SimpleSAML_Error_Exception('AttributeFromSQL: prepare() failed: ' . $e->getMessage()); } try { $res = $sth->execute(array($attributes[$this->attribute][0], $request["Destination"]["entityid"])); } catch (PDOException $e) { throw new SimpleSAML_Error_Exception('AttributeFromSQL: execute(' . $attributes[$this->attribute][0] . ', ' . $request["Destination"]["entityid"] . ') failed: ' . $e->getMessage()); } try { $data = $sth->fetchAll(PDO::FETCH_ASSOC); } catch (PDOException $e) { throw new SimpleSAML_Error_Exception('AttributeFromSQL: fetchAll() failed: ' . $e->getMessage()); } if (count($data) === 0) { SimpleSAML\Logger::info('AttributeFromSQL: no additional attributes for ' . $this->attribute . '=\'' . $attributes[$this->attribute][0] . '\''); return; } /* Extract attributes from the SQL datasource, and then merge them into * the existing attribute set. If $replace is set, overwrite any existing * attribute of the same name; otherwise add it as a multi-valued attribute */ foreach ($data as $row) { if (empty($row['attribute']) || $row['value'] === null) { SimpleSAML\Logger::debug('AttributeFromSQL: skipping invalid attribute/value tuple: ' . var_export($row, true)); continue; } $name = (string) $row['attribute']; $value = (string) $row['value']; /* Limit the attribute set returned */ if ($this->limit !== null && !in_array($name, $this->limit, true)) { SimpleSAML\Logger::notice('AttributeFromSQL: skipping unwanted attribute ' . $name . ' [limited to: ' . var_export($this->limit, true) . ']'); continue; } if (!array_key_exists($name, $attributes) || $this->replace === true) { $attributes[$name] = array(); } if (in_array($value, $attributes[$name], true)) { /* Value already exists in attribute. */ SimpleSAML\Logger::debug('AttributeFromSQL: skipping duplicate attribute/value tuple ' . $name . '=\'' . $value . '\''); continue; } $attributes[$name][] = $value; } }
/** * Process an assertion in a response. * * Will throw an exception if it is invalid. * * @param SimpleSAML_Configuration $spMetadata The metadata of the service provider. * @param SimpleSAML_Configuration $idpMetadata The metadata of the identity provider. * @param \SAML2\Response $response The response containing the assertion. * @param \SAML2\Assertion|\SAML2\EncryptedAssertion $assertion The assertion. * @param bool $responseSigned Whether the response is signed. * @return \SAML2\Assertion The assertion, if it is valid. */ private static function processAssertion(SimpleSAML_Configuration $spMetadata, SimpleSAML_Configuration $idpMetadata, \SAML2\Response $response, $assertion, $responseSigned) { assert('$assertion instanceof \\SAML2\\Assertion || $assertion instanceof \\SAML2\\EncryptedAssertion'); assert('is_bool($responseSigned)'); $assertion = self::decryptAssertion($idpMetadata, $spMetadata, $assertion); if (!self::checkSign($idpMetadata, $assertion)) { if (!$responseSigned) { throw new SimpleSAML_Error_Exception('Neither the assertion nor the response was signed.'); } } /* At least one valid signature found. */ $currentURL = \SimpleSAML\Utils\HTTP::getSelfURLNoQuery(); /* Check various properties of the assertion. */ $notBefore = $assertion->getNotBefore(); if ($notBefore !== NULL && $notBefore > time() + 60) { throw new SimpleSAML_Error_Exception('Received an assertion that is valid in the future. Check clock synchronization on IdP and SP.'); } $notOnOrAfter = $assertion->getNotOnOrAfter(); if ($notOnOrAfter !== NULL && $notOnOrAfter <= time() - 60) { throw new SimpleSAML_Error_Exception('Received an assertion that has expired. Check clock synchronization on IdP and SP.'); } $sessionNotOnOrAfter = $assertion->getSessionNotOnOrAfter(); if ($sessionNotOnOrAfter !== NULL && $sessionNotOnOrAfter <= time() - 60) { throw new SimpleSAML_Error_Exception('Received an assertion with a session that has expired. Check clock synchronization on IdP and SP.'); } $validAudiences = $assertion->getValidAudiences(); if ($validAudiences !== NULL) { $spEntityId = $spMetadata->getString('entityid'); if (!in_array($spEntityId, $validAudiences, TRUE)) { $candidates = '[' . implode('], [', $validAudiences) . ']'; throw new SimpleSAML_Error_Exception('This SP [' . $spEntityId . '] is not a valid audience for the assertion. Candidates were: ' . $candidates); } } $found = FALSE; $lastError = 'No SubjectConfirmation element in Subject.'; $validSCMethods = array(\SAML2\Constants::CM_BEARER, \SAML2\Constants::CM_HOK, \SAML2\Constants::CM_VOUCHES); foreach ($assertion->getSubjectConfirmation() as $sc) { if (!in_array($sc->Method, $validSCMethods)) { $lastError = 'Invalid Method on SubjectConfirmation: ' . var_export($sc->Method, TRUE); continue; } /* Is SSO with HoK enabled? IdP remote metadata overwrites SP metadata configuration. */ $hok = $idpMetadata->getBoolean('saml20.hok.assertion', NULL); if ($hok === NULL) { $hok = $spMetadata->getBoolean('saml20.hok.assertion', FALSE); } if ($sc->Method === \SAML2\Constants::CM_BEARER && $hok) { $lastError = 'Bearer SubjectConfirmation received, but Holder-of-Key SubjectConfirmation needed'; continue; } if ($sc->Method === \SAML2\Constants::CM_HOK && !$hok) { $lastError = 'Holder-of-Key SubjectConfirmation received, but the Holder-of-Key profile is not enabled.'; continue; } $scd = $sc->SubjectConfirmationData; if ($sc->Method === \SAML2\Constants::CM_HOK) { /* Check HoK Assertion */ if (\SimpleSAML\Utils\HTTP::isHTTPS() === FALSE) { $lastError = 'No HTTPS connection, but required for Holder-of-Key SSO'; continue; } if (isset($_SERVER['SSL_CLIENT_CERT']) && empty($_SERVER['SSL_CLIENT_CERT'])) { $lastError = 'No client certificate provided during TLS Handshake with SP'; continue; } /* Extract certificate data (if this is a certificate). */ $clientCert = $_SERVER['SSL_CLIENT_CERT']; $pattern = '/^-----BEGIN CERTIFICATE-----([^-]*)^-----END CERTIFICATE-----/m'; if (!preg_match($pattern, $clientCert, $matches)) { $lastError = 'Error while looking for client certificate during TLS handshake with SP, the client certificate does not ' . 'have the expected structure'; continue; } /* We have a valid client certificate from the browser. */ $clientCert = str_replace(array("\r", "\n", " "), '', $matches[1]); foreach ($scd->info as $thing) { if ($thing instanceof \SAML2\XML\ds\KeyInfo) { $keyInfo[] = $thing; } } if (count($keyInfo) != 1) { $lastError = 'Error validating Holder-of-Key assertion: Only one <ds:KeyInfo> element in <SubjectConfirmationData> allowed'; continue; } foreach ($keyInfo[0]->info as $thing) { if ($thing instanceof \SAML2\XML\ds\X509Data) { $x509data[] = $thing; } } if (count($x509data) != 1) { $lastError = 'Error validating Holder-of-Key assertion: Only one <ds:X509Data> element in <ds:KeyInfo> within <SubjectConfirmationData> allowed'; continue; } foreach ($x509data[0]->data as $thing) { if ($thing instanceof \SAML2\XML\ds\X509Certificate) { $x509cert[] = $thing; } } if (count($x509cert) != 1) { $lastError = 'Error validating Holder-of-Key assertion: Only one <ds:X509Certificate> element in <ds:X509Data> within <SubjectConfirmationData> allowed'; continue; } $HoKCertificate = $x509cert[0]->certificate; if ($HoKCertificate !== $clientCert) { $lastError = 'Provided client certificate does not match the certificate bound to the Holder-of-Key assertion'; continue; } } if ($scd->NotBefore && $scd->NotBefore > time() + 60) { $lastError = 'NotBefore in SubjectConfirmationData is in the future: ' . $scd->NotBefore; continue; } if ($scd->NotOnOrAfter && $scd->NotOnOrAfter <= time() - 60) { $lastError = 'NotOnOrAfter in SubjectConfirmationData is in the past: ' . $scd->NotOnOrAfter; continue; } if ($scd->Recipient !== NULL && $scd->Recipient !== $currentURL) { $lastError = 'Recipient in SubjectConfirmationData does not match the current URL. Recipient is ' . var_export($scd->Recipient, TRUE) . ', current URL is ' . var_export($currentURL, TRUE) . '.'; continue; } if ($scd->InResponseTo !== NULL && $response->getInResponseTo() !== NULL && $scd->InResponseTo !== $response->getInResponseTo()) { $lastError = 'InResponseTo in SubjectConfirmationData does not match the Response. Response has ' . var_export($response->getInResponseTo(), TRUE) . ', SubjectConfirmationData has ' . var_export($scd->InResponseTo, TRUE) . '.'; continue; } $found = TRUE; break; } if (!$found) { throw new SimpleSAML_Error_Exception('Error validating SubjectConfirmation in Assertion: ' . $lastError); } /* As far as we can tell, the assertion is valid. */ /* Maybe we need to base64 decode the attributes in the assertion? */ if ($idpMetadata->getBoolean('base64attributes', FALSE)) { $attributes = $assertion->getAttributes(); $newAttributes = array(); foreach ($attributes as $name => $values) { $newAttributes[$name] = array(); foreach ($values as $value) { foreach (explode('_', $value) as $v) { $newAttributes[$name][] = base64_decode($v); } } } $assertion->setAttributes($newAttributes); } /* Decrypt the NameID element if it is encrypted. */ if ($assertion->isNameIdEncrypted()) { try { $keys = self::getDecryptionKeys($idpMetadata, $spMetadata); } catch (Exception $e) { throw new SimpleSAML_Error_Exception('Error decrypting NameID: ' . $e->getMessage()); } $blacklist = self::getBlacklistedAlgorithms($idpMetadata, $spMetadata); $lastException = NULL; foreach ($keys as $i => $key) { try { $assertion->decryptNameId($key, $blacklist); SimpleSAML\Logger::debug('Decryption with key #' . $i . ' succeeded.'); $lastException = NULL; break; } catch (Exception $e) { SimpleSAML\Logger::debug('Decryption with key #' . $i . ' failed with exception: ' . $e->getMessage()); $lastException = $e; } } if ($lastException !== NULL) { throw $lastException; } } return $assertion; }
/** * Delete a key-value pair from the memcache servers. * * @param string $key The key we should delete. */ public static function delete($key) { assert('is_string($key)'); SimpleSAML\Logger::debug("deleting key {$key} from memcache"); // store this object to all groups of memcache servers foreach (self::getMemcacheServers() as $server) { $server->delete($key); } }
public function finalStep(&$state) { $requestToken = $state['authlinkedin:requestToken']; $consumer = new sspmod_oauth_Consumer($this->key, $this->secret); SimpleSAML\Logger::debug("oauth: Using this request token [" . $requestToken->key . "] with the secret [" . $requestToken->secret . "]"); // Replace the request token with an access token (via GET method) $accessToken = $consumer->getAccessToken('https://api.linkedin.com/uas/oauth/accessToken', $requestToken, array('oauth_verifier' => $state['authlinkedin:oauth_verifier'])); SimpleSAML\Logger::debug("Got an access token from the OAuth service provider [" . $accessToken->key . "] with the secret [" . $accessToken->secret . "]"); // TODO: configure attributes (http://developer.linkedin.com/docs/DOC-1061) from config? Limited options via LinkedIn $userdata = $consumer->getUserInfo('https://api.linkedin.com/v1/people/~:(id,first-name,last-name,headline,summary,specialties,picture-url)', $accessToken, array('http' => array('header' => 'x-li-format: json'))); $attributes = array(); foreach ($userdata as $key => $value) { if (is_string($value)) { $attributes['linkedin.' . $key] = array((string) $value); } } // TODO: pass accessToken: key, secret + expiry as attributes? if (array_key_exists('id', $userdata)) { $attributes['linkedin_targetedID'] = array('http://linkedin.com!' . $userdata['id']); $attributes['linkedin_user'] = array($userdata['id'] . '@linkedin.com'); } SimpleSAML\Logger::debug('LinkedIn Returned Attributes: ' . implode(", ", array_keys($attributes))); $state['Attributes'] = $attributes; }
} $idpMetadata = $metadata->getMetadataConfig($idpEntityId, 'saml20-idp-hosted'); $spMetadata = $metadata->getMetaDataConfig($spEntityId, 'saml20-sp-remote'); // The endpoint we should deliver the message to $endpoint = $spMetadata->getString('testAttributeEndpoint'); // The attributes we will return $attributes = array('name' => array('value1', 'value2', 'value3'), 'test' => array('test')); /* The name format of the attributes. */ $attributeNameFormat = SAML2_Const::NAMEFORMAT_UNSPECIFIED; /* Determine which attributes we will return. */ $returnAttributes = array_keys($query->getAttributes()); if (count($returnAttributes) === 0) { SimpleSAML\Logger::debug('No attributes requested - return all attributes.'); $returnAttributes = $attributes; } elseif ($query->getAttributeNameFormat() !== $attributeNameFormat) { SimpleSAML\Logger::debug('Requested attributes with wrong NameFormat - no attributes returned.'); $returnAttributes = array(); } else { foreach ($returnAttributes as $name => $values) { if (!array_key_exists($name, $attributes)) { /* We don't have this attribute. */ unset($returnAttributes[$name]); continue; } if (count($values) === 0) { /* Return all attributes. */ $returnAttributes[$name] = $attributes[$name]; continue; } /* Filter which attribute values we should return. */ $returnAttributes[$name] = array_intersect($values, $attributes[$name]);