From d2aecdec004b24d53f80784d23284eba54a70095 Mon Sep 17 00:00:00 2001
From: Olav Morken <olav.morken@uninett.no>
Date: Mon, 6 Jul 2009 09:12:00 +0000
Subject: [PATCH] Auth/LDAP: Change the error handling code.

git-svn-id: https://simplesamlphp.googlecode.com/svn/trunk@1551 44740490-163a-0410-bde0-09ae8108e29a
---
 lib/SimpleSAML/Auth/LDAP.php                  | 632 +++++++++++-------
 lib/SimpleSAML/Auth/LDAPException.php         |  38 --
 lib/SimpleSAML/Error/AuthSource.php           |  14 +
 lib/SimpleSAML/Error/BadUserInnput.php        |  14 +
 lib/SimpleSAML/Error/Exception.php            |  48 ++
 lib/SimpleSAML/Error/InvalidCredential.php    |  14 +
 lib/SimpleSAML/Error/User.php                 |  16 +
 lib/SimpleSAML/Error/UserNotFound.php         |  15 +
 .../Auth/Backend/Test/StandardLDAPTest.php    |   2 +-
 9 files changed, 500 insertions(+), 293 deletions(-)
 delete mode 100644 lib/SimpleSAML/Auth/LDAPException.php
 create mode 100644 lib/SimpleSAML/Error/AuthSource.php
 create mode 100644 lib/SimpleSAML/Error/BadUserInnput.php
 create mode 100644 lib/SimpleSAML/Error/Exception.php
 create mode 100644 lib/SimpleSAML/Error/InvalidCredential.php
 create mode 100644 lib/SimpleSAML/Error/User.php
 create mode 100644 lib/SimpleSAML/Error/UserNotFound.php

diff --git a/lib/SimpleSAML/Auth/LDAP.php b/lib/SimpleSAML/Auth/LDAP.php
index 04cd9e31d..32ce1e804 100644
--- a/lib/SimpleSAML/Auth/LDAP.php
+++ b/lib/SimpleSAML/Auth/LDAP.php
@@ -1,5 +1,16 @@
 <?php
 
+/**
+ * Constants defining possible errors
+ */
+define('ERR_INTERNAL', 1);
+define('ERR_NO_USER', 2);
+define('ERR_WRONG_PW', 3);
+define('ERR_AS_DATA_INCONSIST', 4);
+define('ERR_AS_INTERNAL', 5);
+define('ERR_AS_ATTRIBUTE', 6);
+
+
 /**
  * The LDAP class holds helper functions to access an LDAP database.
  *
@@ -12,257 +23,380 @@ class SimpleSAML_Auth_LDAP {
 
 
 	/**
-	 * LDAP link
+	 * LDAP link identifier.
+	 *
+	 * @var resource
 	 */
 	private $ldap = null;
-	
+
 	/**
-	 * private constructor restricts instantiaton to getInstance()
+	 * Timeout value, in seconds.
+	 *
+	 * @var int
 	 */
+	private $timeout = 0;
+
+	/**
+	 * Private constructor restricts instantiation to getInstance().
+	 *
+	 * @param string $hostname
+	 * @param bool $enable_tls
+	 * @param bool $debug
+	 * @param int $timeout
+	 */
+	// TODO: Flesh out documentation.
 	public function __construct($hostname, $enable_tls = TRUE, $debug = FALSE, $timeout = 0) {
 
+		// Debug.
 		SimpleSAML_Logger::debug('Library - LDAP __construct(): Setup LDAP with ' .
-			'host "' . $hostname .
-			', tls=' . var_export($enable_tls, TRUE) .
-			', debug=' . var_export($debug, TRUE) .
-		    ', timeout=' . var_export($timeout, true));
+			'host=\'' . $hostname .
+			'\', tls=' . var_export($enable_tls, true) .
+			', debug=' . var_export($debug, true) .
+			', timeout=' . var_export($timeout, true));
+
+		// Set debug level and protocol version, if supported.
+		// (OpenLDAP 2.x.x or Netscape Directory SDK x.x needed).
+		if ($debug && !ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, 7))
+			SimpleSAML_Logger::warning('Library - LDAP __construct(): Unable to set debug level (LDAP_OPT_DEBUG_LEVEL) to 7');
+		if (!@ldap_set_option($this->ldap, LDAP_OPT_PROTOCOL_VERSION, 3))
+			// TODO: Should this be a warning instead?
+			throw $this->makeException('Library - LDAP __construct(): Failed to set LDAP Protocol version (LDAP_OPT_PROTOCOL_VERSION) to 3', ERR_INTERNAL);
 
-		if ($debug) ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, 7);
+		// Connect.
 		$this->ldap = @ldap_connect($hostname);
-		
-		// Set timeouts, if supported...
+		if ($this->ldap == FALSE)
+			throw new $this->makeException('Library - LDAP __construct(): Unable to connect to \'' . $hostname . '\'', ERR_INTERNAL);
+
+		// Set timeouts, if supported.
 		// (OpenLDAP 2.x.x or Netscape Directory SDK x.x needed).
+		// TODO: Should these be moved to before ldap_connect() above?
+		$this->timeout = $timeout;
 		if (!@ldap_set_option($this->ldap, LDAP_OPT_NETWORK_TIMEOUT, $timeout))
-		    SimpleSAML_Logger::warning('Library - LDAP __construct(): Unable to set timeouts [LDAP_OPT_NETWORK_TIMEOUT] to ' . var_export($timeout, true));
+			SimpleSAML_Logger::warning('Library - LDAP __construct(): Unable to set timeouts (LDAP_OPT_NETWORK_TIMEOUT) to ' . $timeout);
 		if (!@ldap_set_option($this->ldap, LDAP_OPT_TIMELIMIT, $timeout))
-		    SimpleSAML_Logger::warning('Library - LDAP __construct(): Unable to set timeouts [LDAP_OPT_TIMELIMIT] to ' . var_export($timeout, true));
+			SimpleSAML_Logger::warning('Library - LDAP __construct(): Unable to set timeouts (LDAP_OPT_TIMELIMIT) to ' . $timeout);
 
-		if (empty($this->ldap)) 
-			throw new Exception('Error initializing LDAP connection with PHP LDAP library.');
-		
-		$this->setV3();
-		
-		if (!preg_match("/ldaps:/i",$hostname) and $enable_tls) {
-			if (!@ldap_start_tls($this->ldap)) {
-				throw $this->getLDAPException('Could not force LDAP into TLS-session. Please verify certificates and configuration. Could also be that PHP the LDAP library cannot connect to the LDAP server [' . $hostname . ']: ');
-			}
-		}
+		// Enable TLS, if needed.
+		if (!preg_match("/ldaps:/i", $hostname) and $enable_tls)
+			if (!@ldap_start_tls($this->ldap))
+				throw $this->makeException('Library - LDAP __construct(): Unable to force TLS', ERR_INTERNAL);
 
 	}
 	
-	/**
-	 * 
-	 */
-	public function getLDAPException($message) {
-		if (ldap_errno($this->ldap) == 0) {
-			return new Exception($message);
-		}
-		return new SimpleSAML_Auth_LDAPException($message . ' [' . ldap_error($this->ldap) . ']', ldap_errno($this->ldap));
-	}
 	
 	/**
-	 * @DEPRECATED 2008-02-27
-	 */
-	public function getLastError() {
-		if (ldap_errno($this->ldap) == 0) return NULL;
-		return ldap_error($this->ldap);
-	}
-	
-	/**
-	 * Set LDAP version 3 option on the connection handler. Will throw an error if not possible.
+	 * Convenience method to create an LDAPException as well as log the
+	 * description.
+	 *
+	 * @param string $description
+	 * The exception's description
+	 * @return Exception
 	 */
-	private function setV3() {
-		// Give error if LDAPv3 is not supported
-		if (!@ldap_set_option($this->ldap, LDAP_OPT_PROTOCOL_VERSION, 3)) 
-			throw $this->getLDAPException('Failed to set LDAP Protocol version to 3');
+	private function makeException($description, $type = NULL) {
+		$errNo = 0x00;
+		
+		// Log LDAP code and description, if possible.
+		if (empty($this->ldap)){
+			SimpleSAML_Logger::error($description);
+		}else{
+			$errNo = @ldap_errno($this->ldap);
+		}
+		
+		// Decide exception type and return
+		if($type){
+			if($errNo !== 0){
+				// Only log real LDAP errors; not success.
+				SimpleSAML_Logger::error($description . '; cause: \'' . ldap_error($this->ldap) . '\' (0x' . dechex($errNo) . ')');
+			}else{
+				SimpleSAML_Logger::error($description);
+			}
+			
+			switch ($type){
+				case ERR_INTERNAL:// 1 - ExInternal
+					return new SimpleSAML_Error_Exception($description, $errNo);
+				case ERR_NO_USER:// 2 - ExUserNotFound
+					return new SimpleSAML_Error_UserNotFound($description, $errNo);
+				case ERR_WRONG_PW:// 3 - ExInvalidCredential
+					return new SimpleSAML_Error_InvalidCredential($description, $errNo);
+				case ERR_AS_DATA_INCONSIST:// 4 - ExAsDataInconsist
+					return new SimpleSAML_Error_AuthSource($description, $errNo);
+				case ERR_AS_INTERNAL:// 5 - ExAsInternal
+					return new SimpleSAML_Error_AuthSource($description, $errNo);
+			}
+		}else{
+			switch ($errNo){
+				case 0x20://LDAP_NO_SUCH_OBJECT
+					SimpleSAML_Logger::warning($description . '; cause: \'' . ldap_error($this->ldap) . '\' (0x' . dechex($errNo) . ')');
+					return new SimpleSAML_Error_UserNotFound($description, $errNo);
+				case 0x31://LDAP_INVALID_CREDENTIALS
+					SimpleSAML_Logger::info($description . '; cause: \'' . ldap_error($this->ldap) . '\' (0x' . dechex($errNo) . ')');
+					return new SimpleSAML_Error_InvalidCredential($description, $errNo);
+				case -1://NO_SERVER_CONNECTION
+					SimpleSAML_Logger::error($description . '; cause: \'' . ldap_error($this->ldap) . '\' (0x' . dechex($errNo) . ')');
+					return new SimpleSAML_Error_AuthSource($description, $errNo);
+				default:
+					SimpleSAML_Logger::error($description . '; cause: \'' . ldap_error($this->ldap) . '\' (0x' . dechex($errNo) . ')');
+					return new SimpleSAML_Error_AuthSource($description, $errNo);
+			}
+		}
 	}
-	
+
+
 	/**
-	 * Search for DN in one single base only
+	 * Search for DN from a single base.
 	 *
-	 * @param $allowZeroHits Default is false. If set to true it will return NULL instead
-	 * 			of throwing an exception if no results was found.
+	 * @param string $base
+	 * Indication of root of subtree to search
+	 * @param string|array $attribute
+	 * The attribute name(s) to search for.
+	 * @param string $value
+	 * The attribute value to search for.
+	 * @return string
+	 * The DN of the resulting found element.
+	 * @throws SimpleSAML_Error_Exception if:
+	 * - Attribute parameter is wrong type
+	 * @throws SimpleSAML_Error_AuthSource if:
+	 * - Not able to connect to LDAP server
+	 * - False search result
+	 * - Count return false
+	 * - Searche found more than one result
+	 * - Failed to get first entry from result
+	 * - Failed to get DN for entry
+	 * @throws SimpleSAML_Error_UserNotFound if:
+	 * - Zero entries was found
 	 */
-	public function searchfordnSingleBase($searchbase, $searchattr, $searchvalue, $allowZeroHits = FALSE) {
-
-		// Search for ePPN
-		$search = $this->generateSearchFilter($searchattr, $searchvalue);
-		SimpleSAML_Logger::debug('Library - LDAP: Search for DN base:' . $searchbase . ' search: ' . $search);
-		$search_result = @ldap_search($this->ldap, $searchbase, $search, array() );
+	private function search($base, $attribute, $value) {
 
-		if ($search_result === false)
-			throw $this->getLDAPException('Failed performing a LDAP search:' . $search);
+		// Create the search filter.
+		$attribute = self::escape_filter_value($attribute, FALSE);
+		$value = self::escape_filter_value($value);
+		if (is_array($attribute)) {
 
-		// Check number of entries. ePPN should be unique!
-		if (@ldap_count_entries($this->ldap, $search_result) > 1 )
-			throw $this->getLDAPException("Found multiple entries in LDAP search: " . $search . ' base(s): ' . $searchbase);
-	
-		if (@ldap_count_entries($this->ldap, $search_result) == 0) {
-			if ($allowZeroHits) {
-				return NULL;
-			} else {
-				throw $this->getLDAPException('LDAP search returned zero entries: ' . $search . ' base: ' . $searchbase);
+			// We have more than one attribute.
+			$filter = '';
+			foreach ($attribute AS $attr) {
+				$filter .= '(' . $attr . '=' . $value. ')';
 			}
+			$filter = '(|' . $filter . ')';
+
+		} elseif (is_string($attribute)) {
+
+			// We have only one attribute.
+			$filter = '(' . $attribute . '=' . $value. ')';
+
+		} else {
+			// We have an unknown attribute type...
+			throw $this->makeException('Library - LDAP search(): Search attribute must be an array or a string', ERR_INTERNAL);
 		}
 
-		// Authenticate user and fetch attributes
-		$entry = ldap_first_entry($this->ldap, $search_result);
-		
-		if (empty($entry))
-			throw $this->getLDAPException('Could not retrieve result of LDAP search: ' . $search);
+		// Search using generated filter.
+		SimpleSAML_Logger::debug('Library - LDAP search(): Searching base \'' . $base . '\' for \'' . $filter . '\'');
+		// TODO: Should aliases be dereferenced?
+		$result = @ldap_search($this->ldap, $base, $filter, array(), 0, 0, $this->timeout);
+		if ($result === FALSE){
+			throw $this->makeException('Library - LDAP search(): Failed search on base \'' . $base . '\' for \'' . $filter . '\'');
+		}
+
+		// Sanity checks on search results.
+		$count = @ldap_count_entries($this->ldap, $result);
+		if($count === FALSE){
+			throw $this->makeException('Library - LDAP search(): Failed to get number of entries returned');
+		}elseif($count > 1){
+			// More than one entry is found. External error
+			throw $this->makeException('Library - LDAP search(): Found ' . $count . ' entries searching base \'' . $base . '\' for \'' . $filter . '\'', ERR_AS_DATA_INCONSIST);
+		}elseif($count === 0){
+			// No entry is fond => wrong username is given (or not registered in the catalogue). User error
+			throw $this->makeException('Library - LDAP search(): Found no entries searching base \'' . $base . '\' for \'' . $filter . '\'', ERR_NO_USER);
+		}
 		
+
+		// Resolve the DN from the search result.
+		$entry = @ldap_first_entry($this->ldap, $result);
+		if ($entry === FALSE)
+			throw $this->makeException('Library - LDAP search(): Unable to retrieve result after searching base \'' . $base . '\' for \'' . $filter . '\'');
 		$dn = @ldap_get_dn($this->ldap, $entry);
-		
-		if (empty($dn))
-			throw $this->getLDAPException('Error retrieving DN from search result.');
-			
+		if ($dn === FALSE)
+			throw $this->makeException('Library - LDAP search(): Unable to get DN after searching base \'' . $base . '\' for \'' . $filter . '\'');
+		// FIXME: Are we now shure, if no excepton hawe been thrown, that we are returning av DN?
 		return $dn;
-		
 	}
 
-	
-	
+
 	/**
-	 * Search for a DN. You specify an attribute name and an attribute value
-	 * and the function will return the DN of the result of the search.
+	 * Search for a DN.
+	 *
+	 * @param string|array $base
+	 * The base, or bases, which to search from.
+	 * @param string|array $attribute
+	 * The attribute name(s) searched for.
+	 * @param string $value
+	 * The attribute value searched for.
+	 * @param bool $allowZeroHits
+	 * Determines if the method will throw an exception if no hits are found.
+	 * Defaults to FALSE.
+	 * @return string
+	 * The DN of the matching element, if found. If no element was found and
+	 * $allowZeroHits is set to FALSE, an exception will be thrown; otherwise
+	 * NULL will be returned.
+	 * @throws SimpleSAML_Error_AuthSource if:
+	 * - LDAP search encounter some problems when searching cataloge
+	 * - Not able to connect to LDAP server
+	 * @throws SimpleSAML_Error_UserNotFound if:
+	 * - $allowZeroHits er TRUE and no result is found
 	 *
-	 * @param $allowZeroHits Default is false. If set to true it will return NULL instead
-	 * 			of throwing an exception if no results was found.
 	 */
-	public function searchfordn($searchbase, $searchattr, $searchvalue, $allowZeroHits = FALSE) {
+	public function searchfordn($base, $attribute, $value, $allowZeroHits = FALSE) {
 
-		SimpleSAML_Logger::debug('Library - LDAP: searchfordn() Search for entries');
-		$searchbases = SimpleSAML_Utilities::arrayize($searchbase);
-
-		/**
-		 * Traverse all search bases. If DN was found, return the result.
-		 */
+		// Traverse all search bases, returning DN if found.
+		$bases = SimpleSAML_Utilities::arrayize($base);
 		$result = NULL;
-		foreach($searchbases AS $sbase) {
+		foreach ($bases AS $current) {
 			try {
-				$result = $this->searchfordnSingleBase($sbase, $searchattr, $searchvalue, TRUE);
-				if (!empty($result)) return $result;
-			
-			// If LDAP search failed, log errors, but continue to look in the other base DNs.
-			} catch(Exception $e) {
-				SimpleSAML_Logger::warning('Library - LDAP: Search for DN failed for base:' . $sbase . ' exception: ' . 
-					$e->getMessage());
+				// Single base search.
+				$result = $this->search($current, $attribute, $value);
+				// We don't hawe to look any futher if user is found
+				if (!empty($result))
+					return $result;
+			// If search failed, attempt the other base DNs.
+			}catch(SimpleSAML_Error_UserNotFound $e){
+				// Just continue searching
 			}
 		}
-		SimpleSAML_Logger::debug('Library - LDAP: searchfordn() Zero entries found');
-		
-		if ($allowZeroHits) {
+		// Decide what to do for zero entries.
+		SimpleSAML_Logger::debug('Library - LDAP searchfordn(): No entries found');
+		if($allowZeroHits){
+			// Zero hits allowed.
 			return NULL;
 		} else {
-			throw $this->getLDAPException('LDAP search returned zero entries: ' . $searchattr . '=' . $searchvalue . ' base(s): ' . 
-				join(' & ', $searchbases));
+			// Zero hits not allowed.
+			throw $this->makeException('Library - LDAP searchfordn(): LDAP search returned zero entries for filter \'(' . $attribute . ' = ' . $value . ')\' on base(s) \'(' . join(' & ', $bases) . ')\'', 2);
 		}
-
 	}
-	
+
+
 	/**
-	 * Generate a search filter for one or more attribute names to match
-	 * one attribute value.
+	 * Bind to LDAP with a specific DN and password. Simple wrapper around
+	 * ldap_bind() with some additional logging.
 	 *
-	 * @param $searchattr Can be either an array or a string. Attribute name.
-	 * @param $searchvalue Attribute value to match
-	 * @return A LDAP search filter.
-	 */
-	private function generateSearchFilter($searchattr, $searchvalue) {
-		$searchattr = self::escape_filter_value($searchattr, FALSE);
-		$searchvalue = self::escape_filter_value($searchvalue);
-		
-		if (is_array($searchattr)) {
-			
-			$search = '';
-			foreach ($searchattr AS $attr) {
-				$search .= '(' . $attr . '=' . $searchvalue. ')';
-			}
-			return '(|' . $search . ')';
-			
-		} elseif (is_string($searchattr)) {
-			return '(' . $searchattr . '=' . $searchvalue. ')';
-		} else {
-			throw $this->getLDAPException('Search attribute is required to be an array or a string.');
-		}
-	}
-	
-	
-	/**
-	 * Bind to LDAP with a specific DN and password.
+	 * @param string $dn
+	 * The DN used.
+	 * @param string $password
+	 * The password used.
+	 * @return bool
+	 * Returns TRUE if successful, FALSE if LDAP_INVALID_CREDENTIALS.
+	 * @throws SimpleSAML_Error_Exception on other errors
 	 */
 	public function bind($dn, $password) {
+
+		// Bind, with error handling.
 		if (@ldap_bind($this->ldap, $dn, $password)) {
-			SimpleSAML_Logger::debug('Library - LDAP: Bind successfull with ' . $dn);
+
+			// Good.
+			SimpleSAML_Logger::debug('Library - LDAP bind(): Bind successful with DN \'' . $dn . '\'');
 			return TRUE;
+
+		}
+
+		/* Handle invalid DN/password.
+		 * LDAP_INVALID_CREDENTIALS */
+		if (ldap_errno($this->ldap) === 49) {
+			return FALSE;
 		}
-		SimpleSAML_Logger::debug('Library - LDAP: Bind failed with [' . $dn . ']. LDAP error code [' . ldap_errno($this->ldap) . ']'); 
-		return FALSE;
+
+		// Bad.
+		throw $this->makeException('Library - LDAP bind(): Bind failed with DN \'' . $dn . '\'');
+
 	}
 
 
 	/**
-	 * Search DN for attributes, and return associative array.
+	 * Search a given DN for attributes, and return the resulting associative
+	 * array.
+	 *
+	 * @param string $dn
+	 * The DN of an element.
+	 * @param string|array $attributes
+	 * The names of the attribute(s) to retrieve. Defaults to NULL; that is,
+	 * all available attributes. Note that this is not very effective.
+	 * @param int $maxsize
+	 * The maximum size of any attribute's value(s). If exceeded, the attribute
+	 * will not be returned.
+	 * @return array
+	 * The array of attributes and their values.
+	 * @see http://no.php.net/manual/en/function.ldap-read.php
 	 */
 	public function getAttributes($dn, $attributes = NULL, $maxsize = NULL) {
-	
-		$searchtxt = (is_array($attributes) ? join(',', $attributes) : 'all attributes');
-		SimpleSAML_Logger::debug('Library - LDAP: Get attributes from ' . $dn . ' (' . $searchtxt . ')');
-		
-		if (is_array($attributes)) 
-			$sr = @ldap_read($this->ldap, $dn, 'objectClass=*', $attributes );
-		else 
-			$sr = @ldap_read($this->ldap, $dn, 'objectClass=*');
-			
-		if ($sr === false) 
-			throw $this->getLDAPException('Could not retrieve attributes for user');
-
-		$ldapEntry = @ldap_first_entry($this->ldap, $sr);
-		if ($ldapEntry === false) {
-			throw $this->getLDAPException('Could not retrieve attributes for user - could not select first entry');
-		}
 
-		$ldapAttributes = @ldap_get_attributes($this->ldap, $ldapEntry);
-		if ($ldapAttributes === false) {
-			throw $this->getLDAPException('Could not retrieve attributes for user - error fetching attributes for select first entry:');
-		}
+		// Preparations, including a pretty debug message...
+		$description = 'all attributes';
+		if (is_array($attributes)) {
+			$description = '\'' . join(',', $attributes) . '\'';
+		} else
+			// Get all attributes...
+			// TODO: Verify that this originally was the intended behaviour. Could $attributes be a string?
+			$attributes = array();
+		SimpleSAML_Logger::debug('Library - LDAP getAttributes(): Getting ' . $description . ' from DN \'' . $dn . '\'');
+
+		// Attempt to get attributes.
+		// TODO: Should aliases be dereferenced?
+		$result = @ldap_read($this->ldap, $dn, 'objectClass=*', $attributes, 0, 0, $this->timeout);
+		if ($result === false)
+			throw $this->makeException('Library - LDAP getAttributes(): Failed to get attributes from DN \'' . $dn . '\'');
+		$entry = @ldap_first_entry($this->ldap, $result);
+		if ($entry === false)
+			throw $this->makeException('Library - LDAP getAttributes(): Could not get first entry from DN \'' . $dn . '\'');
+		$attributes = @ldap_get_attributes($this->ldap, $entry);  // Recycling $attributes... Possibly bad practice.
+		if ($attributes === false)
+			throw $this->makeException('Library - LDAP getAttributes(): Could not get attributes of first entry from DN \'' . $dn . '\'');
+
+		// Parsing each found attribute into our result set.
+		$result = array();  // Recycling $result... Possibly bad practice.
+		for ($i = 0; $i < $attributes['count']; $i++) {
+
+			// Ignore attributes that exceed the maximum allowed size.
+			$name = $attributes[$i];
+			$attribute = $attributes[$name];
+
+			// Deciding whether to base64 encode.
+			$values = array();
+			for ($j = 0; $j < $attribute['count']; $j++) {
+				$value = $attribute[$j];
+
+				if (!empty($maxsize) && strlen($value) >= $maxsize) {
+					// Ignoring and warning.
+					SimpleSAML_Logger::warning('Library - LDAP getAttributes(): Attribute \'' .
+						$name . '\' exceeded maximum allowed size by ' + ($maxsize - strlen($value)));
+					continue;
+				}
 
-		$attributes = array();
-		for ($i = 0; $i < $ldapAttributes['count']; $i++) {
-			$attributeName = $ldapAttributes[$i];
+				// Base64 encode jpegPhoto.
+				if (strtolower($name) === 'jpegphoto') {
+					$values[] = base64_encode($value);
+				} else
+					$values[] = $value;
 
-			$base64encode = FALSE;
-			$include = FALSE;
-			
-			if (strtolower($attributeName) === 'jpegphoto') {
-				$base64encode = TRUE;
 			}
 
-			$attribute = $ldapAttributes[$attributeName];
-			$valueCount = $attribute['count'];
+			// Adding.
+			$result[$name] = $values;
 
-			$values = array();
-			for ($j = 0; $j < $valueCount; $j++) {
-				/*
-				SimpleSAML_Logger::debug('Library - attribute size of [' . $attributeName . '] (' . strlen($attribute[$j]) . ' of ' . 
-					(is_null($maxsize) ? 'NA'  : $maxsize) . ')');
-				*/
-				if (is_null($maxsize) or strlen($attribute[$j]) < $maxsize) {
-					$include = TRUE;
-					$values[] = ($base64encode ? base64_encode($attribute[$j]) : $attribute[$j] );
-				} else {
-					SimpleSAML_Logger::debug('Library - attribute size of [' . $attributeName . '] exceeded maximum limit of ' . $maxsize . ' - skipping attribute value');
-				}
-
-			}
-			if ($include) $attributes[$attributeName] = $values;
 		}
-		
-		SimpleSAML_Logger::debug('Library - LDAP: Found attributes (' . join(',', array_keys($attributes)) . ')');
-		return $attributes;
-	
+
+		// We're done.
+		SimpleSAML_Logger::debug('Library - LDAP getAttributes(): Found attributes \'(' . join(',', array_keys($result)) . ')\'');
+		return $result;
 	}
-	
+
+
+	/**
+	 * Enter description here...
+	 *
+	 * @param string $config
+	 * @param string $username
+	 * @param string $password
+	 * @return array|bool
+	 */
+	// TODO: Documentation; only cleared up exception/log messages.
 	public function validate($config, $username, $password = null) {
 
 		/* Escape any characters with a special meaning in LDAP. The following
@@ -272,19 +406,18 @@ class SimpleSAML_Auth_LDAP {
 		 */
 		$username = addcslashes($username, ',+"\\<>;*');
 		$password = addcslashes($password, ',+"\\<>;*');
-		
-		if (isset($config['priv_user_dn']) && !$this->bind($config['priv_user_dn'], $config['priv_user_pw']) ) {
-			throw $this->getLDAPException('Could not bind with system user: ' . $config['priv_user_dn']);
-		}
+
+		if (isset($config['priv_user_dn']))
+		    $this->bind($config['priv_user_dn'], $config['priv_user_pw']);
 		if (isset($config['dnpattern'])) {
 			$dn = str_replace('%username%', $username, $config['dnpattern']);
 		} else {
-			$dn = $this->searchfordn($config['searchbase'], $config['searchattributes'], $username);	
+			$dn = $this->searchfordn($config['searchbase'], $config['searchattributes'], $username);
 		}
 
 		if ($password != null) { /* checking users credentials ... assuming below that she may read her own attributes ... */
 			if (!$this->bind($dn, $password)) {
-				SimpleSAML_Logger::info('AUTH - ldap: '. $username . ' failed to authenticate. DN=' . $dn);
+				SimpleSAML_Logger::info('Library - LDAP validate(): Failed to authenticate \''. $username . '\' using DN \'' . $dn . '\'');
 				return FALSE;
 			}
 		}
@@ -294,79 +427,70 @@ class SimpleSAML_Auth_LDAP {
 		 */
 		$attributes = $this->getAttributes($dn, $config['attributes']);
 		return $attributes;
-		
-	}
-
-
-
 
+	}
 
 
-    /**
-     * Borrowed function from PEAR:LDAP.
-     *
-     * Escapes the given VALUES according to RFC 2254 so that they can be safely used in LDAP filters.
-     *
-     * Any control characters with an ACII code < 32 as well as the characters with special meaning in
-     * LDAP filters "*", "(", ")", and "\" (the backslash) are converted into the representation of a
-     * backslash followed by two hex digits representing the hexadecimal value of the character.
-     *
-     * @param array $values Array of values to escape
-     *
-     * @static
-     * @return array Array $values, but escaped
-     */
-    public static function escape_filter_value($values = array(), $singleValue = TRUE) {
-        // Parameter validation
-        if (!is_array($values)) {
-            $values = array($values);
-        }
+	/**
+	 * Borrowed function from PEAR:LDAP.
+	 *
+	 * Escapes the given VALUES according to RFC 2254 so that they can be safely used in LDAP filters.
+	 *
+	 * Any control characters with an ACII code < 32 as well as the characters with special meaning in
+	 * LDAP filters "*", "(", ")", and "\" (the backslash) are converted into the representation of a
+	 * backslash followed by two hex digits representing the hexadecimal value of the character.
+	 *
+	 * @static
+	 * @param array $values Array of values to escape
+	 * @return array Array $values, but escaped
+	 */
+	public static function escape_filter_value($values = array(), $singleValue = TRUE) {
+		// Parameter validation
+		if (!is_array($values)) {
+			$values = array($values);
+		}
 
-        foreach ($values as $key => $val) {
-            // Escaping of filter meta characters
-            $val = str_replace('\\', '\5c', $val);
-            $val = str_replace('*',  '\2a', $val);
-            $val = str_replace('(',  '\28', $val);
-            $val = str_replace(')',  '\29', $val);
+		foreach ($values as $key => $val) {
+			// Escaping of filter meta characters
+			$val = str_replace('\\', '\5c', $val);
+			$val = str_replace('*',  '\2a', $val);
+			$val = str_replace('(',  '\28', $val);
+			$val = str_replace(')',  '\29', $val);
 
-            // ASCII < 32 escaping
-            $val = self::asc2hex32($val);
+			// ASCII < 32 escaping
+			$val = self::asc2hex32($val);
 
-            if (null === $val) $val = '\0';  // apply escaped "null" if string is empty
+			if (null === $val) $val = '\0';  // apply escaped "null" if string is empty
 
-            $values[$key] = $val;
-        }
+			$values[$key] = $val;
+		}
 		if ($singleValue) return $values[0];
-        return $values;
-    }
-
-    /**
-     * Borrowed function from PEAR:LDAP.
-     *
-     * Converts all ASCII chars < 32 to "\HEX"
-     *
-     * @param string $string String to convert
-     *
-     * @static
-     * @return string
-     */
-    public static function asc2hex32($string)
-    {
-        for ($i = 0; $i < strlen($string); $i++) {
-            $char = substr($string, $i, 1);
-            if (ord($char) < 32) {
-                $hex = dechex(ord($char));
-                if (strlen($hex) == 1) $hex = '0'.$hex;
-                $string = str_replace($char, '\\'.$hex, $string);
-            }
-        }
-        return $string;
-    }
-
-
+		return $values;
+	}
 
 
+	/**
+	 * Borrowed function from PEAR:LDAP.
+	 *
+	 * Converts all ASCII chars < 32 to "\HEX"
+	 *
+	 * @param string $string String to convert
+	 *
+	 * @static
+	 * @return string
+	 */
+	public static function asc2hex32($string) {
+		for ($i = 0; $i < strlen($string); $i++) {
+			$char = substr($string, $i, 1);
+			if (ord($char) < 32) {
+				$hex = dechex(ord($char));
+				if (strlen($hex) == 1) $hex = '0'.$hex;
+				$string = str_replace($char, '\\'.$hex, $string);
+			}
+		}
+		return $string;
+	}
 
 }
 
-?>
+?>
\ No newline at end of file
diff --git a/lib/SimpleSAML/Auth/LDAPException.php b/lib/SimpleSAML/Auth/LDAPException.php
deleted file mode 100644
index 876ceeafb..000000000
--- a/lib/SimpleSAML/Auth/LDAPException.php
+++ /dev/null
@@ -1,38 +0,0 @@
-<?php
-
-/**
- * Exception related to LDAP.
- *
- * @author Andreas Ă…kre Solberg, UNINETT AS.
- * @package simpleSAMLphp
- * @version $Id$
- */
-class SimpleSAML_Auth_LDAPException extends Exception {
-
-
-	/**
-	 * LDAP Error code
-	 */
-	private $ldapErrorcode;
-
-	/**
-	 * Create a new NotFound error
-	 *
-	 * @param string $reason  Optional description of why the given page could not be found.
-	 */
-	public function __construct($message, $ldapErrorcode = NULL) {
-		parent::__construct($message . ' (' . $this->getErrorCode() . ')');
-		$this->ldapErrorcode = $ldapErrorcode;
-	}
-
-	/**
-	 * Return the error code from LDAP.
-	 *
-	 */
-	public function getErrorCode() {
-		return $this->ldapErrorcode;
-	}
-
-}
-
-?>
\ No newline at end of file
diff --git a/lib/SimpleSAML/Error/AuthSource.php b/lib/SimpleSAML/Error/AuthSource.php
new file mode 100644
index 000000000..6e353d53e
--- /dev/null
+++ b/lib/SimpleSAML/Error/AuthSource.php
@@ -0,0 +1,14 @@
+<?php
+/**
+ * Baseclass for auth source exceptions.
+ * 
+ * @author Thomas Graff <thomas.graff@uninett.no>
+ * @package simpleSAMLphp_base
+ * @version $Id$
+ *
+ */
+class SimpleSAML_Error_AuthSource extends SimpleSAML_Error_Exception{
+	
+}
+
+?>
\ No newline at end of file
diff --git a/lib/SimpleSAML/Error/BadUserInnput.php b/lib/SimpleSAML/Error/BadUserInnput.php
new file mode 100644
index 000000000..69bda7c26
--- /dev/null
+++ b/lib/SimpleSAML/Error/BadUserInnput.php
@@ -0,0 +1,14 @@
+<?php
+/**
+ * Exception indicating illegal innput from user.
+ * 
+ * @author Thomas Graff <thomas.graff@uninett.no>
+ * @package simpleSAMLphp_base
+ * @version $Id$
+ *
+ */
+class SimpleSAML_Error_BadUserInnput extends SimpleSAML_Error_User{
+	
+}
+
+?>
\ No newline at end of file
diff --git a/lib/SimpleSAML/Error/Exception.php b/lib/SimpleSAML/Error/Exception.php
new file mode 100644
index 000000000..9e1f656cd
--- /dev/null
+++ b/lib/SimpleSAML/Error/Exception.php
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * Baseclass for simpleSAML Exceptions
+ * 
+ * @author Thomas Graff <thomas.graff@uninett.no>
+ * @package simpleSAMLphp_base
+ * @version $Id$
+ */
+class SimpleSAML_Error_Exception extends Exception {
+	
+	/**
+	 * Constructor for this error.
+	 *
+	 * @param string $message Exception message
+	 * @param int $code Error code
+	 */
+	public function __construct($message, $code = 0) {
+		assert('is_string($message) || is_int($code)');
+		
+		parent::__construct($message, $code);
+	}
+	
+	
+	/**
+	 * Set the HTTP return code for this error.
+	 *
+	 * This should be overridden by subclasses who want a different return code than 500 Internal Server Error.
+	 */
+	protected function setHTTPCode() {
+		header('HTTP/1.0 500 Internal Server Error');
+	}
+	
+	
+	/**
+	 * Display this error.
+	 *
+	 * This method displays a standard simpleSAMLphp error page and exits.
+	 */
+	public function show() {
+		$this->setHTTPCode();
+		$session = SimpleSAML_Session::getInstance();
+		$e = $this;
+		SimpleSAML_Utilities::fatalError($session->getTrackID(), $this->errorCode, $e);
+	}
+}
+
+?>
\ No newline at end of file
diff --git a/lib/SimpleSAML/Error/InvalidCredential.php b/lib/SimpleSAML/Error/InvalidCredential.php
new file mode 100644
index 000000000..f8e2bfbf5
--- /dev/null
+++ b/lib/SimpleSAML/Error/InvalidCredential.php
@@ -0,0 +1,14 @@
+<?php
+/**
+ * Exception indicating wrong password given by user.
+ * 
+ * @author Thomas Graff <thomas.graff@uninett.no>
+ * @package simpleSAMLphp_base
+ * @version $Id$
+ *
+ */
+class SimpleSAML_Error_InvalidCredential extends SimpleSAML_Error_User{
+	
+}
+
+?>
\ No newline at end of file
diff --git a/lib/SimpleSAML/Error/User.php b/lib/SimpleSAML/Error/User.php
new file mode 100644
index 000000000..1c7c7a6c6
--- /dev/null
+++ b/lib/SimpleSAML/Error/User.php
@@ -0,0 +1,16 @@
+<?php
+
+/**
+ * Baseclass for user error exceptions
+ * 
+ * 
+ * @author Thomas Graff <thomas.graff@uninett.no>
+ * @package simpleSAMLphp_base
+ * @version $Id$
+ *
+ */
+class SimpleSAML_Error_User extends SimpleSAML_Error_Exception{
+	
+}
+
+?>
\ No newline at end of file
diff --git a/lib/SimpleSAML/Error/UserNotFound.php b/lib/SimpleSAML/Error/UserNotFound.php
new file mode 100644
index 000000000..85c09c763
--- /dev/null
+++ b/lib/SimpleSAML/Error/UserNotFound.php
@@ -0,0 +1,15 @@
+<?php
+
+/**
+ * Exception indicating user not found by authsource.
+ * 
+ * @author Thomas Graff <thomas.graff@uninett.no>
+ * @package simpleSAMLphp_base
+ * @version $Id$
+ *
+ */
+class SimpleSAML_Error_UserNotFound extends SimpleSAML_Error_User{
+	
+}
+
+?>
\ No newline at end of file
diff --git a/modules/ldapstatus/lib/Auth/Backend/Test/StandardLDAPTest.php b/modules/ldapstatus/lib/Auth/Backend/Test/StandardLDAPTest.php
index c33a0e1d8..104265f88 100755
--- a/modules/ldapstatus/lib/Auth/Backend/Test/StandardLDAPTest.php
+++ b/modules/ldapstatus/lib/Auth/Backend/Test/StandardLDAPTest.php
@@ -132,7 +132,7 @@ class sspmod_ldapstatus_Auth_Backend_Test_StandardLDAPTest extends sspmod_feide_
 			$result['ldapSearchBogus'] = array(TRUE,$tester->tack('ldapSearchBogus'));
 			$result['ldapSearchBogus']['time'] = $tester->tack('ldapSearchBogus', FALSE); 
 			
-		} catch (sspmod_feide_Exception_UserNotFound $e) {
+		} catch (SimpleSAML_Error_UserNotFound $e) {
 			$result['ldapSearchBogus'] = array(TRUE,$tester->tack('ldapSearchBogus'));
 			
 		} catch (Exception $e) {
-- 
GitLab