Returning result as object

This commit is contained in:
Deon George 2013-07-12 10:35:54 +10:00
parent 81aec2f376
commit a82edbf64c
10 changed files with 322 additions and 150 deletions

View File

@ -0,0 +1,4 @@
<?php defined('SYSPATH') or die('No direct access allowed.');
class Database_LDAP_Result extends Kohana_Database_LDAP_Result {}
?>

View File

@ -3,11 +3,10 @@
/** /**
* LDAP Auth driver. * LDAP Auth driver.
* *
* @package Kohana/LDAP * @package Auth/LDAP
* @subpackage Auth/LDAP
* @category Helpers * @category Helpers
* @author Deon George * @author Deon George
* @copyright (c) phpLDAPadmin Development Team * @copyright (c) 2013 phpLDAPadmin Development Team
* @license http://dev.phpldapadmin.org/license.html * @license http://dev.phpldapadmin.org/license.html
*/ */
class Kohana_Auth_LDAP extends Auth { class Kohana_Auth_LDAP extends Auth {
@ -38,7 +37,7 @@ class Kohana_Auth_LDAP extends Auth {
// Load the user // Load the user
// @todo Get the server ID // @todo Get the server ID
$user = Database_LDAP::factory('user')->bind($username,$password); $user = LDAP::factory('user')->bind($username,$password);
} }
// @todo Implement conditional logging based on memberships to groups or other criteria. // @todo Implement conditional logging based on memberships to groups or other criteria.
@ -76,7 +75,7 @@ class Kohana_Auth_LDAP extends Auth {
} }
public function logout($destroy=FALSE,$logout_all=FALSE) { public function logout($destroy=FALSE,$logout_all=FALSE) {
Database_LDAP::factory('user')->disconnect(); LDAP::factory('user')->disconnect();
if (PHP_SAPI !== 'cli') if (PHP_SAPI !== 'cli')
return parent::logout($destroy,$logout_all); return parent::logout($destroy,$logout_all);

View File

@ -6,42 +6,61 @@
* @package Kohana/LDAP * @package Kohana/LDAP
* @category Database/LDAP * @category Database/LDAP
* @author Deon George * @author Deon George
* @copyright (c) phpLDAPadmin Development Team * @copyright (c) 2013 phpLDAPadmin Development Team
* @license http://dev.phpldapadmin.org/license.html * @license http://dev.phpldapadmin.org/license.html
*/ */
abstract class Kohana_Database_LDAP extends Database { abstract class Kohana_Database_LDAP extends Kohana_LDAP {
// Our required abstract functions // LDAP doesnt use an identifier
public function set_charset($charset) {} protected $_identifier = '';
public function query($type, $sql, $as_object = FALSE, array $params = NULL) {}
public function begin($mode = NULL) {}
public function commit() {}
public function rollback() {}
public function list_tables($like = NULL) {}
public function list_columns($table, $like = NULL, $add_prefix = TRUE) {}
public function escape($value) { return $value;}
/** OVERRIDES **/
/** /**
* We override this parent function, since LDAP doesnt quote columns * @defunct This required abstruct function is defunct for LDAP
* @note Override */
public function begin($mode = NULL) { throw HTTP_Exception::factory(501,'We shouldnt be here: :method',array(':method'=>__METHOD__)); }
/**
* @defunct This required abstruct function is defunct for LDAP
*/
public function commit() { throw HTTP_Exception::factory(501,'We shouldnt be here: :method',array(':method'=>__METHOD__)); }
/**
* @defunct This required abstruct function is defunct for LDAP
*/
public function list_columns($table, $like = NULL, $add_prefix = TRUE) { throw HTTP_Exception::factory(501,'We shouldnt be here: :method',array(':method'=>__METHOD__)); }
/**
* @defunct This required abstruct function is defunct for LDAP
*/
public function list_tables($like = NULL) { throw HTTP_Exception::factory(501,'We shouldnt be here: :method',array(':method'=>__METHOD__)); }
/**
* @defunct This required abstruct function is defunct for LDAP
*/
public function query($type, $sql, $as_object = FALSE, array $params = NULL) { throw HTTP_Exception::factory(501,'We shouldnt be here: :method',array(':method'=>__METHOD__)); }
/**
* @defunct This required abstruct function is defunct for LDAP
*/
public function rollback() { throw HTTP_Exception::factory(501,'We shouldnt be here: :method',array(':method'=>__METHOD__)); }
/**
* @defunct This required abstruct function is defunct for LDAP
*/
public function set_charset($charset) { throw HTTP_Exception::factory(501,'We shouldnt be here: :method',array(':method'=>__METHOD__)); }
/** REQUIRED ABSTRACT FUNCTIONS **/
public function escape($value) { return $value;}
/**
* @override We override this parent function, since LDAP doesnt quote columns
*/ */
public function quote_column($column) { public function quote_column($column) {
return $column; return $column;
} }
/** Database_LDAP **/ /** LDAP **/
/** /**
* @var boolean Whether we are full connected connection & bind * Bind to the LDAP server with the creditials
*
* If we are successful, we return TRUE, if not FALSE
*
* @return boolean TRUE|FALSE
*/ */
protected $_connected = FALSE;
/**
* @var string Our default usage when connection
*/
public static $usage = 'default';
private function _bind($u,$p) { private function _bind($u,$p) {
/* /*
// @todo To implement // @todo To implement
@ -96,8 +115,12 @@ abstract class Kohana_Database_LDAP extends Database {
/** /**
* Bind to the LDAP server * Bind to the LDAP server
* *
* @param string User DN to connect with, or blank for anonymous * If we have been passed a login_attr that is not DN, we'll try and find the
* @param string Password for DN, or blank for anonymous * DN to bind with.
*
* @param string User attribute to connect with, or blank for anonymous
* @param string Bind password to use with a DN or blank for anonymous
* @return mixed $this|FALSE
*/ */
public function bind($user,$pass) { public function bind($user,$pass) {
// If we are already connected, no need to re-bind. // If we are already connected, no need to re-bind.
@ -125,17 +148,16 @@ abstract class Kohana_Database_LDAP extends Database {
if ($this->_instance == 'auth') if ($this->_instance == 'auth')
throw new Kohana_Exception('We shouldnt be authing an auth'); throw new Kohana_Exception('We shouldnt be authing an auth');
$config = array( $config = Arr::merge($this->_config,array(
'login_attr'=>'DN', 'login_attr'=>'DN',
'type'=>$this->_config['type'],
'connection'=>array( 'connection'=>array(
'hostname'=>$hostname, 'hostname'=>$hostname,
'port'=>$port, 'port'=>$port,
), ),
); ));
try { try {
$x = Database_LDAP::factory('auth',NULL,$config); $x = LDAP::factory('auth',NULL,$config);
// Our Auth Bind credentials are wrong // Our Auth Bind credentials are wrong
if (! $x->bind($username,$password)) if (! $x->bind($username,$password))
@ -158,11 +180,8 @@ abstract class Kohana_Database_LDAP extends Database {
foreach ($u as $base => $entries) foreach ($u as $base => $entries)
foreach ($entries as $dn => $details) foreach ($entries as $dn => $details)
if ($this->_bind($details['dn'],$pass)) { if ($this->_bind($dn,$pass))
// \xFF is a better delimiter, but the PHP driver uses underscore
$this->_connection_id = sha1($this->_instance.'_'.$details['dn']);
return $this; return $this;
}
// We didnt find an AUTH DN to bind with // We didnt find an AUTH DN to bind with
return FALSE; return FALSE;
@ -229,39 +248,31 @@ abstract class Kohana_Database_LDAP extends Database {
$this->_connection = $r; $this->_connection = $r;
} }
public function connected() { public function disconnect() {
return ($this->_connection AND $this->_connected); try {
} // Database is assumed disconnected
$status = TRUE;
/** if (is_resource($this->_connection)) {
* A wrapper for parent::instance(), so that we can create multiple connections if ($status = ldap_unbind($this->_connection)) {
* to the same LDAP server with different credentials/purposes. // Clear the connection
* $this->_connection = NULL;
* @param string A free form usage name, for this connection
* @param string A database configuration name, as per parent::instance()
* @param array An alternative database configuration to use for $name.
* @see Database::instance();
*/
public static function factory($usage=NULL,$name=NULL,array $config=NULL) {
// Use the default instance name
if ($usage === NULL)
$usage = Database_LDAP::$usage;
if (! isset(Database::$instances[$usage])) { // Clear the instance
// Use the default instance name parent::disconnect();
if ($name === NULL) }
$name = Database::$default; }
// Load the configuration for this database } catch (Exception $e) {
if ($config === NULL) // Database is probably not disconnected
$config = Kohana::$config->load('database')->$name; $status = ! is_resource($this->_connection);
} }
return parent::instance($usage,$config); return $status;
} }
public function search($base=array()) { public function search($base=array()) {
return new Database_LDAP_Search($this->_connection,$base); return new Database_LDAP_Search($this,$base);
} }
} }
?> ?>

View File

@ -0,0 +1,85 @@
<?php defined('SYSPATH') OR die('No direct script access.');
/**
* LDAP database result. See [Results](/database/results) for usage and examples.
*
* @package Kohana/LDAP
* @category Query/Result
* @author Deon George
* @copyright (c) 2013 phpLDAPadmin Development Team
* @license http://dev.phpldapadmin.org/license.html
*/
class Kohana_Database_LDAP_Result extends Database_Result {
private $_current_entry = NULL;
public function __construct($result, $sql, $as_object = FALSE, array $params = NULL) {
parent::__construct($result, $sql, $as_object, $params);
// Find the number of rows in the result
$this->_total_rows = ldap_count_entries($result['l'],$result['r']);
$this->_current_entry = ldap_first_entry($result['l'],$result['r']);
if (! $this->_total_rows)
$this->__destruct();
}
public function __destruct() {
if (is_resource($this->_result['r']))
ldap_free_result($this->_result['r']);
}
public function current() {
if ($this->_as_object === TRUE) {
// Return an stdClass
throw HTTP_Exception::factory(501,'Not implemented');
} elseif (is_string($this->_as_object)) {
// Return an object of given class name
throw HTTP_Exception::factory(501,'Not implemented');
} else {
// Return an array of the row
// @todo We could probably cache this for optimisation
$attrs = $vals = array();
array_push($attrs,ldap_first_attribute($this->_result['l'],$this->_current_entry));
while ($x = ldap_next_attribute($this->_result['l'],$this->_current_entry))
array_push($attrs,$x);
foreach ($attrs as $attr) {
$vals[strtolower($attr)] = ldap_get_values($this->_result['l'],$this->_current_entry,$attr);
// We dont need the count entry
unset($vals[strtolower($attr)]['count']);
}
return $vals;
}
}
/**
* Implements [Iterator::key], returns the dn.
*
* echo key($result);
*
* @return integer
*/
public function key() {
return ldap_get_dn($this->_result['l'],$this->_current_entry);
}
public function seek($dn) {
$this->_current_entry = ldap_first_entry($this->_result['l'],$this->_result['r']);
do {
if ($dn == $this->key())
return TRUE;
// Increment internal row for optimization assuming rows are fetched in order
$this->_current_entry = ldap_next_entry($this->_result['l'],$this->_current_entry);
} while ($this->_current_entry);
// We didnt find it
return FALSE;
}
} // End Database_LDAP_Result

View File

@ -4,14 +4,13 @@
* This class takes care of searching within LDAP * This class takes care of searching within LDAP
* *
* @package Kohana/LDAP * @package Kohana/LDAP
* @subpackage LDAP/Search
* @category Helpers * @category Helpers
* @author Deon George * @author Deon George
* @copyright (c) phpLDAPadmin Development Team * @copyright (c) 2013 phpLDAPadmin Development Team
* @license http://dev.phpldapadmin.org/license.html * @license http://dev.phpldapadmin.org/license.html
*/ */
abstract class Kohana_Database_LDAP_Search { abstract class Kohana_Database_LDAP_Search {
private $_connection; // Our LDAP Server to query private $_db; // Our LDAP Server to query
private $_attrs = array('*','+'); // LDAP Attributes to Return private $_attrs = array('*','+'); // LDAP Attributes to Return
private $_base = array(); // LDAP Search Base private $_base = array(); // LDAP Search Base
@ -22,6 +21,9 @@ abstract class Kohana_Database_LDAP_Search {
private $_time_limit = '60'; // LDAP Search Time Limit private $_time_limit = '60'; // LDAP Search Time Limit
private $_db_pending = array(); // LDAP Query Filter to compile private $_db_pending = array(); // LDAP Query Filter to compile
// Cache lifetime
protected $_lifetime = NULL;
/** /**
* Callable database methods * Callable database methods
* @var array * @var array
@ -38,8 +40,8 @@ abstract class Kohana_Database_LDAP_Search {
protected static $_properties = array( protected static $_properties = array(
); );
public function __construct($resource,$base=array()) { public function __construct(Database_LDAP $db,$base=array()) {
$this->_connection = $resource; $this->_db = $db;
$this->_base = is_null($base) ? $this->base() : $base; $this->_base = is_null($base) ? $this->base() : $base;
} }
@ -108,7 +110,7 @@ abstract class Kohana_Database_LDAP_Search {
if (! is_null($x=Kohana::$config->load('database.default.connection.database'))) if (! is_null($x=Kohana::$config->load('database.default.connection.database')))
return $x; return $x;
$x = Database_LDAP::factory('auth'); $x = LDAP::factory('auth');
$u = $x->search(array('')) $u = $x->search(array(''))
->scope('base') ->scope('base')
@ -116,13 +118,12 @@ abstract class Kohana_Database_LDAP_Search {
// Remove the '' base // Remove the '' base
$u = array_pop($u); $u = array_pop($u);
$u = array_pop($u);
return isset($u['namingcontexts']) ? $u['namingcontexts'] : array(); // Quick validation
} if ($u->count() > 1)
throw HTTP_Exception::factory(501,'We got more than 1 null DN?');
public static function instance($resource) { return isset($u['']['namingcontexts']) ? $u['']['namingcontexts'] : array();
return new Database_LDAP_Search($resource);
} }
public function deref($val) { public function deref($val) {
@ -134,7 +135,7 @@ abstract class Kohana_Database_LDAP_Search {
/** /**
* Search the LDAP database * Search the LDAP database
*/ */
public function run() { public function run($as_object=FALSE,$object_params=NULL) {
$query = array(); $query = array();
// Query Defaults // Query Defaults
@ -146,72 +147,38 @@ abstract class Kohana_Database_LDAP_Search {
$result = array(); $result = array();
foreach ($this->_base as $base) { foreach ($this->_base as $base) {
switch ($this->_scope) { $search = NULL;
case 'base':
$search = ldap_read($this->_connection,$base,$this->_filter,$this->_attrs,$attrs_only,$this->_size_limit,$this->_time_limit,$this->_deref);
break;
case 'one': if ($this->_lifetime !== NULL AND $this->_db->caching()) {
$search = ldap_list($this->_connection,$base,$this->_filter,$this->_attrs,$attrs_only,$this->_size_limit,$this->_time_limit,$this->_deref); // Set the cache key based on the database instance name and SQL
break; $cache_key = 'Database::query("'.$this->_db.'","'.$base.'","'.$this->_scope.'","'.$this->_filter.'")';
case 'sub': // Read the cache first to delete a possible hit with lifetime <= 0
default: if (($result = Kohana::cache($cache_key, NULL, $this->_lifetime)) !== NULL AND ! $this->_force_execute) {
$search = ldap_search($this->_connection,$base,$this->_filter,$this->_attrs,$attrs_only,$this->_size_limit,$this->_time_limit,$this->_deref); // Return a cached result
break; $search = new Database_Result_Cached($result, array('b'=>$base,'s'=>$this->_scope,'f'=>$this->_filter), $as_object, $object_params);
}
if (! $search) {
$result[$base] = array();
continue;
}
// Get the first entry identifier
if ($entries = ldap_get_entries($this->_connection,$search)) {
# Remove the count
if (isset($entries['count']))
unset($entries['count']);
// Iterate over the entries
foreach ($entries as $a => $entry) {
if (! isset($entry['dn']))
throw HTTP_Exception::factory(501,'No DN?');
// Remove the none entry references.
if (! is_array($entry)) {
unset($entries[$a]);
continue;
}
$dn = $entry['dn'];
unset($entry['dn']);
// Iterate over the attributes
foreach ($entry as $b => $attrs) {
// Remove the none entry references.
if (! is_array($attrs)) {
unset($entry[$b]);
continue;
}
// Remove the count
if (isset($entry[$b]['count']))
unset($entry[$b]['count']);
}
// Our queries always include the DN (the only value not an array).
$entry['dn'] = $dn;
$result[$base][$dn] = $entry;
} }
} }
// Sort our results // Search is not cached, OR caching is disabled, so we'll query
if (isset($result[$base])) if (! $search) {
foreach ($result[$base] as $key => $values) switch ($this->_scope) {
ksort($result[$base][$key]); case 'base':
$search = ldap_read($this->_db->connection(),$base,$this->_filter,$this->_attrs,$attrs_only,$this->_size_limit,$this->_time_limit,$this->_deref);
break;
case 'one':
$search = ldap_list($this->_db->connection(),$base,$this->_filter,$this->_attrs,$attrs_only,$this->_size_limit,$this->_time_limit,$this->_deref);
break;
case 'sub':
default:
$search = ldap_search($this->_db->connection(),$base,$this->_filter,$this->_attrs,$attrs_only,$this->_size_limit,$this->_time_limit,$this->_deref);
break;
}
$result[$base] = new Database_LDAP_Result(array('l'=>$this->_db->connection(),'r'=>$search),array('b'=>$base,'s'=>$this->_scope,'f'=>$this->_filter),$as_object,$object_params);
}
} }
return $result; return $result;

View File

@ -4,20 +4,21 @@
* This class takes care of building an LDAP filter query * This class takes care of building an LDAP filter query
* *
* @package Kohana/LDAP * @package Kohana/LDAP
* @subpackage LDAP/Search
* @category Helpers * @category Helpers
* @author Deon George * @author Deon George
* @copyright (c) phpLDAPadmin Development Team * @copyright (c) 2013 phpLDAPadmin Development Team
* @license http://dev.phpldapadmin.org/license.html * @license http://dev.phpldapadmin.org/license.html
*/ */
abstract class Kohana_Database_LDAP_Search_Builder_Query extends Database_Query_Builder { abstract class Kohana_Database_LDAP_Search_Builder_Query extends Database_Query_Builder {
protected $_where = array(); protected $_where = array();
// @todo Not implemented /**
* @defunct Not implemented
*/
public function reset() {} public function reset() {}
public function __construct() { public function __construct() {
parent::__construct(Database::SELECT,'ldap'); parent::__construct(Database::SELECT,'LDAP');
} }
/** /**

101
classes/Kohana/LDAP.php Normal file
View File

@ -0,0 +1,101 @@
<?php defined('SYSPATH') or die('No direct access allowed.');
/**
* This class takes care of communicating with LDAP
*
* @package Kohana/LDAP
* @category Database/LDAP
* @author Deon George
* @copyright (c) 2013 phpLDAPadmin Development Team
* @license http://dev.phpldapadmin.org/license.html
*/
abstract class Kohana_LDAP extends Database {
/** LDAP **/
/**
* @var boolean Whether we are fully connected (connection and bound)
*/
protected $_connected = FALSE;
/**
* @var string Our default usage when connection
*/
public static $usage = 'default';
/**
* Identifiy if caching is enabled for this LDAP connection
*
* @return boolean TRUE|FALSE
*/
public function caching() {
return $this->_config['caching'];
}
/**
* Have we got a bound connection to the LDAP server
*
* @return boolean TRUE|FALSE
*/
public function connected() {
return ($this->_connection AND $this->_connected);
}
/**
* Return the connection resource
*
* @return resource LDAP Resource Identifier
*/
public function connection() {
return $this->_connection;
}
/**
* A wrapper for parent::instance(), so that we can create multiple connections
* to the same LDAP server with different credentials/purposes.
*
* Get a singleton Database instance. If configuration is not specified,
* it will be loaded from the database configuration file using the same
* group as the name.
*
* // Load the default database
* $db = LDAP::factory();
*
* // Create a custom configured instance
* $db = LDAP:factory('auth','custom', $config);
*
* @param string A free form usage name, for this connection
* @param string A database configuration name, as per parent::instance()
* @param array An alternative database configuration to use for $name.
* @see Database::instance();
* @return LDAP::instance();
*
* @note We cant call this instance() like our parent, because of the additional parameter
* we need.
*/
public static function factory($usage=NULL,$name=NULL,array $config=NULL) {
// Use the default instance name
if ($usage === NULL)
$usage = LDAP::$usage;
if (! isset(Database::$instances[$usage])) {
// Use the default instance name
if ($name === NULL)
$name = Database::$default;
// Load the configuration for this database
if ($config === NULL)
$config = Kohana::$config->load('database')->$name;
}
return parent::instance($usage,$config);
}
/**
* @defunct This static function is defunct, you need to use factory() instead.
* @see LDAP::factory();
*/
public static function instance($name=NULL,array $config=NULL) {
throw Kohana_Exception('Sorry, you cant use instance(), you need to use factory()');
}
}
?>

4
classes/LDAP.php Normal file
View File

@ -0,0 +1,4 @@
<?php defined('SYSPATH') or die('No direct access allowed.');
abstract class LDAP extends Kohana_LDAP {}
?>

View File

@ -6,13 +6,13 @@
* @package Kohana/LDAP * @package Kohana/LDAP
* @category Configuration * @category Configuration
* @author Deon George * @author Deon George
* @copyright (c) phpLDAPadmin Development Team * @copyright (c) 2013 phpLDAPadmin Development Team
* @license http://dev.phpldapadmin.org/license.html * @license http://dev.phpldapadmin.org/license.html
*/ */
return array ( return array (
'default' => array ( 'default' => array (
'type' => 'ldap', 'type' => 'LDAP',
'connection' => array( 'connection' => array(
/** /**
* The following options are available for MySQL: * The following options are available for MySQL:
@ -34,7 +34,7 @@ return array (
), ),
'table_prefix' => NULL, 'table_prefix' => NULL,
'charset' => 'utf8', 'charset' => 'utf8',
'caching' => FALSE, 'caching' => FALSE, // If this LDAP server is enabled for cache TRUE|FALSE
'profiling' => TRUE, 'profiling' => TRUE,
'login_attr'=>'uid', 'login_attr'=>'uid',

View File

@ -28,11 +28,11 @@ Class LDAPConnection extends Unittest_TestCase {
*/ */
function testConnect($host,$port,$instance,$expect) { function testConnect($host,$port,$instance,$expect) {
$connection = array( $connection = array(
'type'=>'ldap', 'type'=>'LDAP',
'connection'=>array('hostname'=>$host,'port'=>$port), 'connection'=>array('hostname'=>$host,'port'=>$port),
); );
$x = Database_LDAP::factory($instance,NULL,$connection); $x = LDAP::factory($instance,NULL,$connection);
$x->connect(); $x->connect();
if ($expect) if ($expect)
@ -80,7 +80,7 @@ Class LDAPConnection extends Unittest_TestCase {
*/ */
function testAuthConfiguration($user,$password,$expect) { function testAuthConfiguration($user,$password,$expect) {
$connection = array( $connection = array(
'type'=>'ldap', 'type'=>'LDAP',
'login_attr'=>'uid', 'login_attr'=>'uid',
'connection'=>array( 'connection'=>array(
'hostname'=>'localhost', 'hostname'=>'localhost',
@ -91,10 +91,10 @@ Class LDAPConnection extends Unittest_TestCase {
); );
// Ensure we start with a clean auth connection. // Ensure we start with a clean auth connection.
Database_LDAP::factory('auth')->disconnect(); LDAP::factory('auth')->disconnect();
Database_LDAP::factory('default')->disconnect(); LDAP::factory('default')->disconnect();
$x = Database_LDAP::factory('default',NULL,$connection); $x = LDAP::factory('default',NULL,$connection);
$x->bind('bart','eatmyshorts'); $x->bind('bart','eatmyshorts');
if ($expect) if ($expect)