From e0846210829d1a4a1272cbd45459a1fa939f95f4 Mon Sep 17 00:00:00 2001 From: Deon George Date: Tue, 5 Jun 2012 13:50:21 +1000 Subject: [PATCH] Initial application work --- application/classes/auth/ldap.php | 4 + application/classes/block.php | 4 + application/classes/config.php | 4 + application/classes/controller/login.php | 4 + application/classes/controller/logout.php | 4 + application/classes/controller/media.php | 4 + application/classes/controller/template.php | 4 + application/classes/database/ldap.php | 4 + application/classes/database/ldap/search.php | 4 + .../database/ldap/search/builder/query.php | 4 + application/classes/htmlrender.php | 4 + application/classes/meta.php | 4 + application/classes/pla/auth/ldap.php | 79 +++++ application/classes/pla/block.php | 81 ++++++ application/classes/pla/config.php | 105 +++++++ application/classes/pla/controller/login.php | 200 +++++++++++++ application/classes/pla/controller/logout.php | 26 ++ application/classes/pla/controller/media.php | 59 ++++ .../classes/pla/controller/template.php | 124 ++++++++ application/classes/pla/database/ldap.php | 186 ++++++++++++ .../classes/pla/database/ldap/search.php | 270 ++++++++++++++++++ .../database/ldap/search/builder/query.php | 214 ++++++++++++++ application/classes/pla/htmlrender.php | 92 ++++++ application/classes/pla/meta.php | 34 +++ application/classes/pla/script.php | 53 ++++ application/classes/pla/style.php | 54 ++++ application/classes/script.php | 4 + application/classes/style.php | 4 + application/config/auth.php | 21 ++ application/config/config.php | 24 ++ application/config/database.php | 70 +++++ 31 files changed, 1748 insertions(+) create mode 100644 application/classes/auth/ldap.php create mode 100644 application/classes/block.php create mode 100644 application/classes/config.php create mode 100644 application/classes/controller/login.php create mode 100644 application/classes/controller/logout.php create mode 100644 application/classes/controller/media.php create mode 100644 application/classes/controller/template.php create mode 100644 application/classes/database/ldap.php create mode 100644 application/classes/database/ldap/search.php create mode 100644 application/classes/database/ldap/search/builder/query.php create mode 100644 application/classes/htmlrender.php create mode 100644 application/classes/meta.php create mode 100644 application/classes/pla/auth/ldap.php create mode 100644 application/classes/pla/block.php create mode 100644 application/classes/pla/config.php create mode 100644 application/classes/pla/controller/login.php create mode 100644 application/classes/pla/controller/logout.php create mode 100644 application/classes/pla/controller/media.php create mode 100644 application/classes/pla/controller/template.php create mode 100644 application/classes/pla/database/ldap.php create mode 100644 application/classes/pla/database/ldap/search.php create mode 100644 application/classes/pla/database/ldap/search/builder/query.php create mode 100644 application/classes/pla/htmlrender.php create mode 100644 application/classes/pla/meta.php create mode 100644 application/classes/pla/script.php create mode 100644 application/classes/pla/style.php create mode 100644 application/classes/script.php create mode 100644 application/classes/style.php create mode 100644 application/config/auth.php create mode 100644 application/config/config.php create mode 100644 application/config/database.php diff --git a/application/classes/auth/ldap.php b/application/classes/auth/ldap.php new file mode 100644 index 0000000..f6eced6 --- /dev/null +++ b/application/classes/auth/ldap.php @@ -0,0 +1,4 @@ + diff --git a/application/classes/block.php b/application/classes/block.php new file mode 100644 index 0000000..bde7470 --- /dev/null +++ b/application/classes/block.php @@ -0,0 +1,4 @@ + diff --git a/application/classes/config.php b/application/classes/config.php new file mode 100644 index 0000000..65daaae --- /dev/null +++ b/application/classes/config.php @@ -0,0 +1,4 @@ + diff --git a/application/classes/controller/login.php b/application/classes/controller/login.php new file mode 100644 index 0000000..f0c1b01 --- /dev/null +++ b/application/classes/controller/login.php @@ -0,0 +1,4 @@ + diff --git a/application/classes/controller/logout.php b/application/classes/controller/logout.php new file mode 100644 index 0000000..b60b0ee --- /dev/null +++ b/application/classes/controller/logout.php @@ -0,0 +1,4 @@ + diff --git a/application/classes/controller/media.php b/application/classes/controller/media.php new file mode 100644 index 0000000..53bdf41 --- /dev/null +++ b/application/classes/controller/media.php @@ -0,0 +1,4 @@ + diff --git a/application/classes/controller/template.php b/application/classes/controller/template.php new file mode 100644 index 0000000..1caabd9 --- /dev/null +++ b/application/classes/controller/template.php @@ -0,0 +1,4 @@ + diff --git a/application/classes/database/ldap.php b/application/classes/database/ldap.php new file mode 100644 index 0000000..d9b3b1a --- /dev/null +++ b/application/classes/database/ldap.php @@ -0,0 +1,4 @@ + diff --git a/application/classes/database/ldap/search.php b/application/classes/database/ldap/search.php new file mode 100644 index 0000000..4a28484 --- /dev/null +++ b/application/classes/database/ldap/search.php @@ -0,0 +1,4 @@ + diff --git a/application/classes/database/ldap/search/builder/query.php b/application/classes/database/ldap/search/builder/query.php new file mode 100644 index 0000000..6931b05 --- /dev/null +++ b/application/classes/database/ldap/search/builder/query.php @@ -0,0 +1,4 @@ + diff --git a/application/classes/htmlrender.php b/application/classes/htmlrender.php new file mode 100644 index 0000000..ed9c6a0 --- /dev/null +++ b/application/classes/htmlrender.php @@ -0,0 +1,4 @@ + diff --git a/application/classes/meta.php b/application/classes/meta.php new file mode 100644 index 0000000..8750207 --- /dev/null +++ b/application/classes/meta.php @@ -0,0 +1,4 @@ + diff --git a/application/classes/pla/auth/ldap.php b/application/classes/pla/auth/ldap.php new file mode 100644 index 0000000..0b64e03 --- /dev/null +++ b/application/classes/pla/auth/ldap.php @@ -0,0 +1,79 @@ +_config['hash_key']) + return $str; + else + return parent::hash($str); + } + + /** + * Logs a user in. + * + * @param string username + * @param string password + * @param boolean enable autologin (not supported) + * @return boolean + */ + protected function _login($user, $password, $remember) { + if ( ! is_object($user)) { + $username = $user; + + // Load the user + // @todo Get the server ID + $sid = 'default'; + + $user = Database_LDAP::instance($sid)->select_db('user')->connect(); + $user->bind($username,$password); + } + + // @todo Implement conditional logging based on memberships to groups or other criteria. + // @todo This check of user being logged in needs to be better + if (! $user->noconnect) { + /* + // @todo To implement + if ($remember === TRUE) { + // Token data + $data = array( + 'user_id'=>$user->id, + 'expires'=>time()+$this->_config['lifetime'], + 'user_agent'=>sha1(Request::$user_agent), + ); + + // Create a new autologin token + $token = ORM::factory('user_token') + ->values($data) + ->create(); + + // Set the autologin cookie + Cookie::set('authautologin', $token->token, $this->_config['lifetime']); + } + */ + + // Finish the login + $this->complete_login($user); + + return TRUE; + } + + // Login failed + return FALSE; + } +} +?> diff --git a/application/classes/pla/block.php b/application/classes/pla/block.php new file mode 100644 index 0000000..dc9e8d2 --- /dev/null +++ b/application/classes/pla/block.php @@ -0,0 +1,81 @@ + '; + protected static $_required_keys = array('body'); + + /** + * Add a block to be rendered + * + * @param array Block attributes + */ + public static function add($block,$prepend=FALSE) { + parent::add($block); + + // Detect any style sheets. + if (! empty($block['style']) && is_array($block['style'])) + foreach ($block['style'] as $data=>$media) + Style::add(array( + 'type'=>'file', + 'data'=>$data, + 'media'=>$media, + )); + } + + /** + * Return an instance of this class + * + * @return Block + */ + public static function factory() { + return new Block; + } + + /** + * Render this block + * + * @see HTMLRender::render() + */ + protected function render() { + $output = ''; + $styles = array(); + + $i = 0; + foreach (static::$_data as $value) { + if ($i++) + $output .= static::$_spacer; + + $output .= ''; + + if (! empty($value['title'])) + $output .= sprintf('',$value['title']); + + if (! empty($value['subtitle'])) + $output .= sprintf('',$value['subtitle']); + + $output .= sprintf('',$value['body']); + + if (! empty($value['footer'])) + $output .= sprintf('',$value['footer']); + + $output .= '
%s
%s
%s
'; + } + + return $output; + } +} +?> diff --git a/application/classes/pla/config.php b/application/classes/pla/config.php new file mode 100644 index 0000000..2c0e5b4 --- /dev/null +++ b/application/classes/pla/config.php @@ -0,0 +1,105 @@ +'Production', + Kohana::STAGING=>'Staging', + Kohana::TESTING=>'Testing', + Kohana::DEVELOPMENT=>'Development', + ); + + return (! isset($modes[static::sitemode()])) ? 'Unknown' : $modes[static::sitemode()]; + } + + public static function submode() { + $submode = Kohana::Config('config.debug.submode'); + + return (isset($submode[Request::$client_ip])) ? $submode[Request::$client_ip] : FALSE; + } + + public static function sitename() { + return Kohana::Config('config.site.name'); + } + + // Called in Invoice/Emailing to embed the file. + public static function logo_file() { + list ($path,$suffix) = explode('.',static::$logo); + return Kohana::find_file(sprintf('media/%s',Config::siteid()),$path,$suffix); + } + + public static function logo_uri() { + list ($path,$suffix) = explode('.',static::$logo); + return URL::site(Route::get('default/media')->uri(array('file'=>$path.'.'.$suffix),array('alt'=>static::sitename())),'http'); + } + + public static function logo() { + return HTML::image(static::logo_uri(),array('class'=>'headlogo','alt'=>_('Logo'))); + } + + public static function login_uri() { + return ($ao = Auth::instance()->get_user()) ? $ao->name() : HTML::anchor('login',_('Login')); + } + + public static function copywrite() { + return '(c) phpLDAPadmin Development Team'; + } + + /** + * Return our caching mechanism + */ + public static function cachetype() { + return is_null(Kohana::config('config.cache_type')) ? 'file' : Kohana::config('config.cache_type'); + } +} +?> diff --git a/application/classes/pla/controller/login.php b/application/classes/pla/controller/login.php new file mode 100644 index 0000000..a361c7b --- /dev/null +++ b/application/classes/pla/controller/login.php @@ -0,0 +1,200 @@ +logged_in()!= 0) { + // Redirect to the user account + Request::current()->redirect('user/welcome'); + } + + // If there is a post and $_POST is not empty + if ($_POST) { +//echo debug::vars(array('p'=>$_POST,'ai'=>Auth::instance()));die(); + // Store our details in a session key + Session::instance()->set('login',$_POST['username']); + Session::instance()->set('password',$_POST['password']); + + // If the post data validates using the rules setup in the user model + if (Auth::instance()->login($_POST['username'],$_POST['password'])) { + // Redirect to the user account + if ($redir = Session::instance()->get('afterlogin')) { + Session::instance()->delete('afterlogin'); + Request::current()->redirect($redir); + + } else + Request::current()->redirect('user/welcome'); + + } else { + SystemMessage::add(array( + 'title'=>_('Invalid username or password'), + 'type'=>'error', + 'body'=>_('The username or password was invalid.') + )); + } + } + + Block::add(array( + 'title'=>_('Login to server'), + 'body'=>View::factory('login'), + 'style'=>array('css/login.css'=>'screen'), + )); + + Script::add(array('type'=>'stdin','data'=>' + $(document).ready(function() { + $("#ajxbody").click(function() {$("#ajBODY").load("'.$this->request->uri().'/"); return false;}); + });' + )); + } + + public function action_register() { + // If user already signed-in + if (Auth::instance()->logged_in()!= 0) { + // Redirect to the user account + Request::current()->redirect('welcome/index'); + } + + // Instantiate a new user + $account = ORM::factory('account'); + + // If there is a post and $_POST is not empty + if ($_POST) { + // Check Auth + $status = $account->values($_POST)->check(); + + if (! $status) { + foreach ($account->validation()->errors('form/register') as $f => $r) { + // $r[0] has our reason for validation failure + switch ($r[0]) { + // Generic validation reason + default: + SystemMessage::add(array( + 'title'=>_('Validation failed'), + 'type'=>'error', + 'body'=>sprintf(_('The defaults on your submission were not valid for field %s (%s).'),$f,$r) + )); + } + } + } + + $ido = ORM::factory('module') + ->where('name','=','account') + ->find(); + + $account->id = $ido->record_id->next_id($ido->id); + // Save the user details + if ($account->save()) {} + + } + + SystemMessage::add(array( + 'title'=>_('Already have an account?'), + 'type'=>'info', + 'body'=>_('If you already have an account, please login..') + )); + + Block::add(array( + 'title'=>_('Register'), + 'body'=>View::factory('bregister') + ->set('account',$account) + ->set('errors',$account->validation()->errors('form/register')), + )); + + $this->template->left = HTML::anchor('login','Login').'...'; + } + + /** + * Enable user password reset + */ + public function action_reset() { + // If user already signed-in + if (Auth::instance()->logged_in()!= 0) { + // Redirect to the user account + Request::current()->redirect('welcome/index'); + } + + // If the user posted their details to reset their password + if ($_POST) { + // If the email address is correct, create a method token + if (! empty($_POST['email']) AND ($ao=ORM::factory('account',array('email'=>$_POST['email']))) AND $ao->loaded()) { + $mt = ORM::factory('module_method_token'); + + // Find out our password reset method id + // @todo move this to a more generic method, so that it can be called by other methods + $mo = ORM::factory('module',array('name'=>'account')); + $mmo = ORM::factory('module_method',array('name'=>'user_resetpassword','module_id'=>$mo->id)); + + // Check to see if there is already a token, if so, do nothing. + if ($mt->where('account_id','=',$ao->id)->and_where('method_id','=',$mmo->id)->find()) { + if ($mt->date_expire < time()) { + $mt->delete(); + $mt->clear(); + } + } + + if (! $mt->loaded()) { + $mt->account_id = $ao->id; + $mt->method_id = $mmo->id; + $mt->date_expire = time() + 15*3600; + $mt->token = md5(sprintf('%s:%s:%s',$mt->account_id,$mt->method_id,$mt->date_expire)); + $mt->save(); + + // Send our email with the token + $et = EmailTemplate::instance('account_reset_password'); + $et->to = array($mt->account->email=>sprintf('%s %s',$mt->account->first_name,$mt->account->last_name)); + $et->variables = array( + 'SITE'=>URL::base(TRUE,TRUE), + 'SITE_ADMIN'=>Config::sitename(), + 'SITE_NAME'=>Config::sitename(), + 'TOKEN'=>$mt->token, + 'USER_NAME'=>sprintf('%s %s',$mt->account->first_name,$mt->account->last_name), + ); + $et->send(); + } + + // Redirect to our password reset, the Auth will validate the token. + } elseif (! empty($_REQUEST['token'])) { + Request::current()->redirect(sprintf('user/account/resetpassword?token=%s',$_REQUEST['token'])); + } + + // Show our token screen even if the email was invalid. + if (isset($_POST['email'])) + Block::add(array( + 'title'=>_('Reset your password'), + 'body'=>View::factory('login_reset_sent'), + 'style'=>array('css/login.css'=>'screen'), + )); + else + Request::current()->redirect('login'); + + } else { + Block::add(array( + 'title'=>_('Reset your password'), + 'body'=>View::factory('login_reset'), + 'style'=>array('css/login.css'=>'screen'), + )); + } + } + + public function action_noaccess() { + SystemMessage::add(array( + 'title'=>_('No access to requested resource'), + 'type'=>'error', + 'body'=>_('You do not have access to the requested resource, please contact your administrator.') + )); + } +} +?> diff --git a/application/classes/pla/controller/logout.php b/application/classes/pla/controller/logout.php new file mode 100644 index 0000000..ded76b5 --- /dev/null +++ b/application/classes/pla/controller/logout.php @@ -0,0 +1,26 @@ +logged_in()!= 0) { + Auth::instance()->logout(); + + Request::current()->redirect('login'); + } + + Request::current()->redirect('welcome/index'); + } +} +?> diff --git a/application/classes/pla/controller/media.php b/application/classes/pla/controller/media.php new file mode 100644 index 0000000..9413a08 --- /dev/null +++ b/application/classes/pla/controller/media.php @@ -0,0 +1,59 @@ +request->param('file'); + + // Find the file extension + $ext = pathinfo($file,PATHINFO_EXTENSION); + + // Remove the extension from the filename + $file = substr($file,0,-(strlen($ext)+1)); + $f = ''; + + // If our file is pathed with session, our file is in our session. + if ($fd = Session::instance()->get_once($this->request->param('file'))) { + $this->response->body($fd); + + // If not found try a default media file + } elseif ($f = Kohana::find_file('media/'.Kohana::Config('config.theme'),$file,$ext)) { + // Send the file content as the response + $this->response->body(file_get_contents($f)); + + // If not found try a default media file + } elseif ($f = Kohana::find_file('media',$file,$ext)) { + // Send the file content as the response + $this->response->body(file_get_contents($f)); + + } else { + // Return a 404 status + $this->response->status(404); + } + + // Generate and check the ETag for this file + if (Kohana::$environment === Kohana::PRODUCTION) + $this->response->check_cache(NULL,$this->request); + + // Set the proper headers to allow caching + $this->response->headers('Content-Type',File::mime_by_ext($ext)); + $this->response->headers('Content-Length',(string)$this->response->content_length()); + $this->response->headers('Last-Modified',date('r', $f ? filemtime($f) : time())); + } +} +?> diff --git a/application/classes/pla/controller/template.php b/application/classes/pla/controller/template.php new file mode 100644 index 0000000..4b189b1 --- /dev/null +++ b/application/classes/pla/controller/template.php @@ -0,0 +1,124 @@ +template = Kohana::Config('config.theme'); + + return parent::__construct($request,$response); + } + + public function before() { + // Do not template media files + if ($this->request->action() === 'media') { + $this->auto_render = FALSE; + return; + } + + parent::before(); + + // For AJAX calls, we dont need to render the complete page. + if ($this->request->is_ajax()) { + $this->auto_render = FALSE; + return; + } + + $this->template->content = ''; + + // Setup the page template + $this->meta = new meta; + View::bind_global('meta',$this->meta); + } + + public function after() { + if ($this->auto_render === TRUE) { + // Application Title + $this->meta->title = Kohana::Config('config.appname'); + + // Language + // @todo + $this->meta->language = ''; + + // Description + $this->meta->description = sprintf('%s::%s',$this->request->controller(),$this->request->action()); + + // Control Line + // @todo + $this->template->control = ''; + + // System Messages line + // @todo + $this->template->sysmsg = ''; + + // Left Item + // @todo + $this->template->left = ''; + $this->template->right = ''; + $this->template->center = ''; + + if (! $this->response->body()) + $this->response->body((string)Block::factory()); + + if (empty($this->template->content)) + $this->template->content = $this->response->body(); + + // Footer + // @todo + $this->template->footer = ''; + + // Our default script(s) + foreach (array('file'=>array_reverse(array( + 'js/jquery-1.6.4.min.js', + ))) as $type => $datas) { + + foreach ($datas as $data) { + Script::add(array( + 'type'=>$type, + 'data'=>$data, + ),TRUE); + } + } + + // Add our logo + Style::add(array( + 'type'=>'stdin', + 'data'=>'h1 span{background:url('.Config::logo_uri().') no-repeat;}', + )); + + // For any ajax rendered actions, we'll need to capture the content and put it in the response + // @todo + } elseif ($this->request->is_ajax() && isset($this->template->content) && ! $this->response->body()) { + // @todo move this formatting to a view? + if ($s = $this->_sysmsg() AND (string)$s) + $this->response->body(sprintf('
%s
',$s)); + + // Since we are ajax, we should re-render the breadcrumb + Session::instance()->set('breadcrumb',(string)Breadcrumb::factory()); + $this->response->bodyadd(Script::add(array('type'=>'stdin','data'=>'$().ready($("#ajCONTROL").load("'.URL::site('welcome/breadcrumb').'",null,function(x,s,r) {}));'))); + + // In case there any javascript for this render. + $this->response->bodyadd(Script::factory()); + + // Get the response body + $this->response->bodyadd(sprintf('
%s
',$this->template->content)); + } + + parent::after(); + + // Generate and check the ETag for this file + if (Kohana::$environment === Kohana::PRODUCTION) + $this->response->check_cache(NULL,$this->request); + } +} diff --git a/application/classes/pla/database/ldap.php b/application/classes/pla/database/ldap.php new file mode 100644 index 0000000..e127284 --- /dev/null +++ b/application/classes/pla/database/ldap.php @@ -0,0 +1,186 @@ +index); + */ + + /* + // @todo To implement + if (function_exists('run_hook')) + run_hook('pre_connect',array('server_id'=>$this->index,'method'=>$method)); + */ + + if (! empty($this->_config['port'])) + $r = ldap_connect($this->_config['connection']['hostname'],$this->_config['port']); + else + $r = ldap_connect($this->_config['connection']['hostname']); + + /* + // @todo To implement + if (DEBUG_ENABLED) + debug_log('LDAP Resource [%s], Host [%s], Port [%s]',16,0,__FILE__,__LINE__,__METHOD__, + $this->_r,$this->getValue('server','host'),$this->getValue('server','port')); + */ + + if (! is_resource($r)) + throw Kohana_Exception('UNHANDLED, $r is not a resource'); + + // Go with LDAP version 3 if possible (needed for renaming and Novell schema fetching) + ldap_set_option($r,LDAP_OPT_PROTOCOL_VERSION,3); + + /* Disabling this makes it possible to browse the tree for Active Directory, and seems + * to not affect other LDAP servers (tested with OpenLDAP) as phpLDAPadmin explicitly + * specifies deref behavior for each ldap_search operation. */ + ldap_set_option($r,LDAP_OPT_REFERRALS,0); + + /* + // @todo To implement + # Try to fire up TLS is specified in the config + if ($this->isTLSEnabled()) + $this->startTLS($this->_r); + */ + + return $r; + } + + private function _bind($r,$u,$p) { + if (! is_resource($r)) + throw Kohana_Exception('UNHANDLED, $r is not a resource'); + + /* + // @todo To implement + # If SASL has been configured for binding, then start it now. + if ($this->isSASLEnabled()) + $br = $this->startSASL($this->_r,$method); + + # Normal bind... + else + */ + $br = @ldap_bind($r,$u,$p); + + /* + if ($debug) + debug_dump(array('method'=>$method,'bind'=>$bind,'USER'=>$_SESSION['USER'])); + + if (DEBUG_ENABLED) + debug_log('Resource [%s], Bind Result [%s]',16,0,__FILE__,__LINE__,__METHOD__,$this->_r,$bind); + */ + + if (! $br) { + /* + if (DEBUG_ENABLED) + debug_log('Leaving with FALSE, bind FAILed',16,0,__FILE__,__LINE__,__METHOD__); + */ + + $this->noconnect = true; + + /* + // @todo To implement + system_message(array( + 'title'=>sprintf('%s %s',_('Unable to connect to LDAP server'),$this->getName()), + 'body'=>sprintf('%s: %s (%s) for %s',_('Error'),$this->getErrorMessage($method),$this->getErrorNum($method),$method), + 'type'=>'error')); + */ + + } else { + $this->noconnect = false; + + /* + // @todo To implement + # If this is a proxy session, we need to switch to the proxy user + if ($this->isProxyEnabled() && $bind['id'] && $method != 'anon') + if (! $this->startProxy($this->_r,$method)) { + $this->noconnect = true; + $CACHE[$this->index][$method] = null; + } + */ + } + + /* + // @todo To implement + if (function_exists('run_hook')) + run_hook('post_connect',array('server_id'=>$this->index,'method'=>$method,'id'=>$bind['id'])); + */ + + /* + // @todo To implement + if ($debug) + debug_dump(array($method=>$CACHE[$this->index][$method])); + */ + + return $br; + } + + public function connect() { + if ($this->_r = $this->_connect()) + return $this; + else + throw Kohana_Exception('Unable to connect to LDAP Server?'); + } + + public function bind($user,$pass) { + // If this is an anon query, then we return + + // Do we need to do an anon search to find the DN + if (! empty($this->_config['login_attr']) AND strtoupper($this->_config['login_attr']) != 'DN') { + $u = $this->search() + ->scope('sub') + ->where($this->_config['login_attr'],'=',$user) + ->run(); + + if (! $u) + throw new Kohana_Exception('Unable to find user :user',array(':user'=>$user)); + + $u = array_shift($u); + $user = $u['dn']; + } + + // Bind + if ($this->_bind($this->_r,$user,$pass)) + return $this; + else + throw new Kohana_Exception('Unable to bind'); + } + + public function search() { + return new Database_LDAP_Search($this->_r); + } +} +?> diff --git a/application/classes/pla/database/ldap/search.php b/application/classes/pla/database/ldap/search.php new file mode 100644 index 0000000..04ac955 --- /dev/null +++ b/application/classes/pla/database/ldap/search.php @@ -0,0 +1,270 @@ +_r = $resource; + } + + /** + * Handles pass-through to database methods. Calls to query methods + * (query, get, insert, update) are not allowed. Query builder methods + * are chainable. + * + * @param string $method Method name + * @param array $args Method arguments + * @return mixed + */ + public function __call($method,array $args) { + if (in_array($method,Database_LDAP_Search::$_properties)) { + /* + // @todo To Implement + if ($method === 'validation') + { + if ( ! isset($this->_validation)) + { + // Initialize the validation object + $this->_validation(); + } + } + */ + + // Return the property + return $this->{'_'.$method}; + } + elseif (in_array($method,Database_LDAP_Search::$_db_methods)) + { + // Add pending database call which is executed after query type is determined + $this->_db_pending[] = array('name' => $method,'args' => $args); + + return $this; + } + else + { + throw new Kohana_Exception('Invalid method :method called in :class', + array(':method' => $method,':class' => get_class($this))); + } + } + + private function _build() { + $s = Database_LDAP_Search::Search(); + + // Process pending database method calls + foreach ($this->_db_pending as $method) { + $name = $method['name']; + $args = $method['args']; + + $this->_db_applied[$name] = $name; + + call_user_func_array(array($s,$name),$args); + } + + return $s; + } + + public static function Search($columns = NULL) { + return new Database_LDAP_Search_Builder_Query(func_get_args()); + } + + public static function instance($resource) { + return new Database_LDAP_Search($resource); + } + + public function scope($val) { + switch ($val) { + case 'base': + case 'sub': + case 'one': $this->_scope = $val; + break; + + default: + throw new Kohana_Exception('Unknown search scope :scope',array(':scope',$val)); + } + + return $this; + } + + /** + * Search the LDAP database + */ + public function run() { + $query = array(); + + // Compile our query + if ($this->_db_pending) + $this->_filter = $this->_build(); + + /* + if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS')) + debug_log('Entered (%%)',17,0,__FILE__,__LINE__,__METHOD__,$fargs); + */ + + $attrs_only = 0; + + $this->_base = 'o=Simpsons'; + /* + // @todo To implement + if (! isset($query['base'])) { + $bases = $this->getBaseDN(); + $query['base'] = array_shift($bases); + } + */ + + /* + // @todo To implement + if (! isset($query['deref'])) + $query['deref'] = $_SESSION[APPCONFIG]->getValue('deref','search'); + */ + if (! isset($query['size_limit'])) + $query['size_limit'] = 0; + if (! isset($query['time_limit'])) + $query['time_limit'] = 0; + + /* + if ($query['scope'] == 'base' && ! isset($query['baseok'])) + system_message(array( + 'title'=>sprintf('Dont call %s',__METHOD__), + 'body'=>sprintf('Use getDNAttrValues for base queries [%s]',$query['base']), + 'type'=>'info')); + */ + + /* + if (is_array($query['base'])) { + system_message(array( + 'title'=>_('Invalid BASE for query'), + 'body'=>_('The query was cancelled because of an invalid base.'), + 'type'=>'error')); + + return array(); + } + */ + + /* + if (DEBUG_ENABLED) + debug_log('%s search PREPARE.',16,0,__FILE__,__LINE__,__METHOD__,$query['scope']); + */ + + /* + if ($debug) + debug_dump(array('query'=>$query,'server'=>$this->getIndex(),'con'=>$this->connect($method))); + */ + + //$resource = $this->connect($method,$debug); + + switch ($this->_scope) { + case 'base': + $search = @ldap_read($this->_r,$this->_base,$this->_filter,$this->_attrs,$attrs_only,$query['size_limit'],$query['time_limit'],$query['deref']); + break; + + case 'one': + $search = @ldap_list($this->_r,$this->_base,$this->_filter,$this->_attrs,$attrs_only,$query['size_limit'],$query['time_limit'],$query['deref']); + break; + + case 'sub': + default: + $search = @ldap_search($this->_r,$this->_base,$this->_filter,$this->_attrs,$attrs_only,$query['size_limit'],$query['time_limit'],$query['deref']); + break; + } + + /* + if ($debug) + debug_dump(array('method'=>$method,'search'=>$search,'error'=>$this->getErrorMessage())); + */ + + /* + if (DEBUG_ENABLED) + debug_log('Search scope [%s] base [%s] filter [%s] attrs [%s] COMPLETE (%s).',16,0,__FILE__,__LINE__,__METHOD__, + $query['scope'],$query['base'],$query['filter'],$query['attrs'],is_null($search)); + */ + + if (! $search) + return array(); + + $return = array(); + + // Get the first entry identifier + if ($entries = ldap_get_entries($this->_r,$search)) { + # Remove the count + if (isset($entries['count'])) + unset($entries['count']); + + // Iterate over the entries + foreach ($entries as $a => $entry) { + /* + if (! isset($entry['dn'])) + debug_dump_backtrace('No DN?',1); + */ + + // 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; + $return[$dn] = $entry; + } + + // Sort our results + foreach ($return as $key => $values) + ksort($return[$key]); + } + + /* + if (DEBUG_ENABLED) + debug_log('Returning (%s)',17,0,__FILE__,__LINE__,__METHOD__,$return); + */ + + return $return; + } +} +?> diff --git a/application/classes/pla/database/ldap/search/builder/query.php b/application/classes/pla/database/ldap/search/builder/query.php new file mode 100644 index 0000000..63c1c21 --- /dev/null +++ b/application/classes/pla/database/ldap/search/builder/query.php @@ -0,0 +1,214 @@ +and_where($column,$op,$value); + } + + /** + * Creates a new "AND WHERE" condition for the query. + * + * @param mixed column name or array($column,$alias) or object + * @param string logic operator + * @param mixed column value + * @return $this + */ + public function and_where($column,$op,$value) { + $this->_where[] = array('AND' => array($column,$op,$value)); + + return $this; + } + + /** + * Creates a new "OR WHERE" condition for the query. + * + * @param mixed column name or array($column,$alias) or object + * @param string logic operator + * @param mixed column value + * @return $this + */ + public function or_where($column,$op,$value) { + $this->_where[] = array('OR' => array($column,$op,$value)); + + return $this; + } + + /** + * Alias of and_where_open() + * + * @return $this + */ + public function where_open() { + return $this->and_where_open(); + } + + /** + * Opens a new "AND WHERE (...)" grouping. + * + * @return $this + */ + public function and_where_open() { + $this->_where[] = array('AND' => '('); + + return $this; + } + + /** + * Opens a new "OR WHERE (...)" grouping. + * + * @return $this + */ + public function or_where_open() { + $this->_where[] = array('OR' => '('); + + return $this; + } + + public function compile(Database $db) { + $filter = ''; + + return $this->_compile_conditions($db,$this->_where); + } + + /** + * Closes an open "AND WHERE (...)" grouping. + * + * @return $this + */ + public function where_close() { + return $this->and_where_close(); + } + + /** + * Closes an open "AND WHERE (...)" grouping. + * + * @return $this + */ + public function and_where_close() { + $this->_where[] = array('AND' => ')'); + + return $this; + } + + /** + * Closes an open "OR WHERE (...)" grouping. + * + * @return $this + */ + public function or_where_close() { + $this->_where[] = array('OR' => ')'); + + return $this; + } + + /** + * Compiles an array of conditions into an LDAP filter. + * + * @param object Database instance + * @param array condition statements + * @return string + */ + protected function _compile_conditions(Database $db,array $conditions,$index=0) { + $current_condition = $last_condition = NULL; + + $filter = ''; + $sub = 0; + foreach ($conditions as $key => $group) { + // If we have been called again, we need to skip ahead, or skip what has been processed + if ($key < $index OR $sub) + continue; + + // Process groups of conditions + foreach ($group as $logic => $condition) { + if ($condition === '(') { + $filter .= $this->_compile_conditions($db,$conditions,$key+1); + $sub = 1; + + } elseif ($condition === ')') { + if ($index) { + // As we return, we'll include our condition + switch ($current_condition) { + case 'AND': + return '(&'.$filter.')'; + + case 'OR': + return '(|'.$filter.')'; + + default: + throw new Kohana_Exception('Condition :condition not handled.',array(':condition'=>$condition)); + } + } + + $sub = 0; + + } else { + // We currently cant handle when a condition changes, without brackets. + if ($filter AND $current_condition AND $current_condition != $logic) + throw new Kohana_Exception('Condition changed without brackets'); + + $current_condition = $logic; + + // Split the condition + list($column,$op,$value) = $condition; + + // Database operators are always uppercase + $op = strtoupper($op); + + if ((is_string($value) AND array_key_exists($value,$this->_parameters)) === FALSE) { + // Quote the value, it is not a parameter + $value = $db->quote($value); + } + + if ($column) { + // Apply proper quoting to the column + $column = $db->quote_column($column); + } + + // Append the statement to the query + $filter .= trim('('.$column.$op.$value.')'); + } + + $last_condition = $condition; + } + } + + switch ($current_condition) { + case 'AND': + return '(&'.$filter.')'; + + case 'OR': + return '(|'.$filter.')'; + + default: + throw new Kohana_Exception('Condition :condition not handled.',array(':condition'=>$condition)); + } + } +} +?> diff --git a/application/classes/pla/htmlrender.php b/application/classes/pla/htmlrender.php new file mode 100644 index 0000000..5bf2100 --- /dev/null +++ b/application/classes/pla/htmlrender.php @@ -0,0 +1,92 @@ +get_called_class())); + } + + /** + * Add an item to be rendered + * + * @param array Item to be added + */ + public static function add($item,$prepend=FALSE) { + foreach (static::$_required_keys as $key) + if (! isset($item[$key])) + throw new Kohana_Exception('Missing key :key for image',array(':key'=>$key)); + + // Check for unique keys + if (static::$_unique_vals) + foreach (static::$_unique_vals as $v=>$u) + foreach (static::$_data as $d) + if (isset($d[$u]) && $d['data'] == $item['data']) + return; + + if ($prepend) + array_unshift(static::$_data,$item); + else + array_push(static::$_data,$item); + } + + /** + * Set the space used between rendering output + */ + public static function setSpacer($spacer) { + static::$_spacer = $spacer; + } + + /** + * Set the Kohana Media Path, used to determine where to find additional + * HTML content required for rendering. + */ + public static function setMediaPath($path) { + static::$_media_path = $path; + } + + /** + * Factory instance method must be declared by the child class + */ + public static function factory() { + throw new Kohana_Exception(':class is calling :method, when it should have its own method', + array(':class'=>get_called_class(),':method'=>__METHOD__)); + } + + /** + * Return the HTML to render the header images + */ + public function __toString() { + try { + return static::render(); + } + + // Display the exception message + catch (Exception $e) { + Kohana_Exception::handler($e); + } + } + + /** + * Rendering must be declared by the child class + */ + protected function render() { + throw new Kohana_Exception(':class is calling :method, when it should have its own method', + array(':class'=>get_called_class(),':method'=>__METHOD__)); + } +} +?> diff --git a/application/classes/pla/meta.php b/application/classes/pla/meta.php new file mode 100644 index 0000000..f87c496 --- /dev/null +++ b/application/classes/pla/meta.php @@ -0,0 +1,34 @@ +_array_keys) && empty($this->_data[$key])) + return array(); + + if (empty($this->_data[$key])) + return null; + else + return $this->_data[$key]; + } + + public function __set($key,$value) { + if (in_array($key,$this->_array_keys) && ! is_array($value)) + throw new Kohana_Exception('Key :key must be an array',array(':key'=>$key)); + + $this->_data[$key] = $value; + } +} +?> diff --git a/application/classes/pla/script.php b/application/classes/pla/script.php new file mode 100644 index 0000000..00d8839 --- /dev/null +++ b/application/classes/pla/script.php @@ -0,0 +1,53 @@ +'type'); + + /** + * Return an instance of this class + * + * @return Script + */ + public static function factory() { + return new Script; + } + + /** + * Render the script tag + * + * @see HTMLRender::render() + */ + protected function render() { + $foutput = $soutput = ''; + $mediapath = Route::get(static::$_media_path); + + foreach (static::$_data as $value) { + switch ($value['type']) { + case 'file': + $foutput .= HTML::script($mediapath->uri(array('file'=>$value['data']))); + break; + case 'stdin': + $soutput .= sprintf("",$value['data']); + break; + default: + throw new Kohana_Exception('Unknown style type :type',array(':type'=>$value['type'])); + } + } + + return $foutput.static::$_spacer.$soutput; + } +} +?> diff --git a/application/classes/pla/style.php b/application/classes/pla/style.php new file mode 100644 index 0000000..29abd5e --- /dev/null +++ b/application/classes/pla/style.php @@ -0,0 +1,54 @@ +'type'); + + /** + * Return an instance of this class + * + * @return Style + */ + public static function factory() { + return new Style; + } + + /** + * Render the style tag + * + * @see HTMLRender::render() + */ + protected function render() { + $foutput = $soutput = ''; + $mediapath = Route::get(static::$_media_path); + + foreach (static::$_data as $value) { + switch ($value['type']) { + case 'file': + $foutput .= HTML::style($mediapath->uri(array('file'=>$value['data'])), + array('media'=>(! empty($value['media'])) ? $value['media'] : 'screen'),TRUE); + break; + case 'stdin': + $soutput .= sprintf("",$value['data']); + break; + default: + throw new Kohana_Exception('Unknown style type :type',array(':type'=>$value['type'])); + } + } + + return $foutput.static::$_spacer.$soutput; + } +} +?> diff --git a/application/classes/script.php b/application/classes/script.php new file mode 100644 index 0000000..676a71a --- /dev/null +++ b/application/classes/script.php @@ -0,0 +1,4 @@ + diff --git a/application/classes/style.php b/application/classes/style.php new file mode 100644 index 0000000..e5a03bf --- /dev/null +++ b/application/classes/style.php @@ -0,0 +1,4 @@ + diff --git a/application/config/auth.php b/application/config/auth.php new file mode 100644 index 0000000..6bc0cd8 --- /dev/null +++ b/application/config/auth.php @@ -0,0 +1,21 @@ + 'LDAP', +// 'hash_method' => '', + 'hash_key' => '', // LDAP passwords should be cleartext + 'lifetime' => 1209600, +// 'session_key' => 'auth_user', +// 'forced_key' => 'auth_forced', +); +?> diff --git a/application/config/config.php b/application/config/config.php new file mode 100644 index 0000000..034e13b --- /dev/null +++ b/application/config/config.php @@ -0,0 +1,24 @@ + 'phpLDAPadmin - LDAP Administration', + // Our mode level (PRODUCTION, STAGING, TESTING, DEVELOPMENT) - see [Kohana] + 'mode' => Kohana::PRODUCTION, + 'site' => array( + 'name'=>'phpLDAPadmin', + ), + // Our custom theme + 'theme' => 'original', +); +?> diff --git a/application/config/database.php b/application/config/database.php new file mode 100644 index 0000000..13d1068 --- /dev/null +++ b/application/config/database.php @@ -0,0 +1,70 @@ + array + ( + 'type' => 'ldap', + 'connection' => array( + /** + * The following options are available for MySQL: + * + * string hostname server hostname, or socket + * string database database name + * string username database username + * string password database password + * boolean persistent use persistent connections? + * + * Ports and sockets may be appended to the hostname. + */ + 'hostname' => 'localhost', + 'database' => 'kohana', + 'username' => FALSE, + 'password' => FALSE, + 'persistent' => FALSE, + ), + 'table_prefix' => '', + 'charset' => 'utf8', + 'caching' => FALSE, + 'profiling' => TRUE, + + 'login_attr'=>'uid', + ), + 'alternate' => array( + 'type' => 'pdo', + 'connection' => array( + /** + * The following options are available for PDO: + * + * string dsn Data Source Name + * string username database username + * string password database password + * boolean persistent use persistent connections? + */ + 'dsn' => 'mysql:host=localhost;dbname=kohana', + 'username' => 'root', + 'password' => 'r00tdb', + 'persistent' => FALSE, + ), + /** + * The following extra options are available for PDO: + * + * string identifier set the escaping identifier + */ + 'table_prefix' => '', + 'charset' => 'utf8', + 'caching' => FALSE, + 'profiling' => TRUE, + ), +);