/**
  * Query the LDAP directory with the given filter.
  *
  * @param string $filter The string to filter by, e.g. (objectClass=user)
  * @param null|string $baseDn The DN to search from. Default is the baseDn option in the connection if not given
  * @param int $scope The scope to perform the search. Zend_Ldap::SEARCH_SCOPE_ONE, Zend_LDAP::SEARCH_SCOPE_BASE. Default is Zend_Ldap::SEARCH_SCOPE_SUB
  * @param array $attributes Restrict to specific AD attributes. An empty array will return all attributes
  * @param string $sort Sort results by this attribute if given
  * @return array
  */
 protected function search($filter, $baseDn = null, $scope = Zend\Ldap\Ldap::SEARCH_SCOPE_SUB, $attributes = array(), $sort = '')
 {
     $records = $this->ldap->search($filter, $baseDn, $scope, $attributes, $sort);
     $results = array();
     foreach ($records as $record) {
         foreach ($record as $attribute => $value) {
             // if the value is an array with a single value, e.g. 'samaccountname' => array(0 => 'myusername')
             // then make sure it's just set in the results as 'samaccountname' => 'myusername' so that it
             // can be used directly by ArrayData
             if (is_array($value) && count($value) == 1) {
                 $value = $value[0];
             }
             // ObjectGUID and ObjectSID attributes are in binary, we need to convert those to strings
             if ($attribute == 'objectguid') {
                 $value = LDAPUtil::bin_to_str_guid($value);
             }
             if ($attribute == 'objectsid') {
                 $value = LDAPUtil::bin_to_str_sid($value);
             }
             $record[$attribute] = $value;
         }
         $results[] = $record;
     }
     return $results;
 }
 /**
  * Assertion Consumer Service
  *
  * The user gets sent back here after authenticating with the IdP, off-site.
  * The earlier redirection to the IdP can be found in the SAMLAuthenticator::authenticate.
  *
  * After this handler completes, we end up with a rudimentary Member record (which will be created on-the-fly
  * if not existent), with the user already logged in. Login triggers memberLoggedIn hooks, which allows
  * LDAP side of this module to finish off loading Member data.
  *
  * @throws OneLogin_Saml2_Error
  */
 public function acs()
 {
     $auth = Injector::inst()->get('SAMLHelper')->getSAMLAuth();
     $auth->processResponse();
     $error = $auth->getLastErrorReason();
     if (!empty($error)) {
         SS_Log::log($error, SS_Log::ERR);
         Form::messageForForm("SAMLLoginForm_LoginForm", "Authentication error: '{$error}'", 'bad');
         Session::save();
         return $this->getRedirect();
     }
     if (!$auth->isAuthenticated()) {
         Form::messageForForm("SAMLLoginForm_LoginForm", _t('Member.ERRORWRONGCRED'), 'bad');
         Session::save();
         return $this->getRedirect();
     }
     $decodedNameId = base64_decode($auth->getNameId());
     // check that the NameID is a binary string (which signals that it is a guid
     if (ctype_print($decodedNameId)) {
         Form::messageForForm("SAMLLoginForm_LoginForm", "Name ID provided by IdP is not a binary GUID.", 'bad');
         Session::save();
         return $this->getRedirect();
     }
     // transform the NameId to guid
     $guid = LDAPUtil::bin_to_str_guid($decodedNameId);
     if (!LDAPUtil::validGuid($guid)) {
         $errorMessage = "Not a valid GUID '{$guid}' recieved from server.";
         SS_Log::log($errorMessage, SS_Log::ERR);
         Form::messageForForm("SAMLLoginForm_LoginForm", $errorMessage, 'bad');
         Session::save();
         return $this->getRedirect();
     }
     // Write a rudimentary member with basic fields on every login, so that we at least have something
     // if LDAP synchronisation fails.
     $member = Member::get()->filter('GUID', $guid)->limit(1)->first();
     if (!($member && $member->exists())) {
         $member = new Member();
         $member->GUID = $guid;
     }
     $attributes = $auth->getAttributes();
     foreach ($member->config()->claims_field_mappings as $claim => $field) {
         if (!isset($attributes[$claim][0])) {
             SS_Log::log(sprintf('Claim rule \'%s\' configured in LDAPMember.claims_field_mappings, but wasn\'t passed through. Please check IdP claim rules.', $claim), SS_Log::WARN);
             continue;
         }
         $member->{$field} = $attributes[$claim][0];
     }
     $member->SAMLSessionIndex = $auth->getSessionIndex();
     // This will throw an exception if there are two distinct GUIDs with the same email address.
     // We are happy with a raw 500 here at this stage.
     $member->write();
     // This will trigger LDAP update through LDAPMemberExtension::memberLoggedIn.
     // Both SAML and LDAP identify Members by the GUID field.
     $member->logIn();
     return $this->getRedirect();
 }