From 57d405fe3b49517c7b0844a7b6ea8959b8ed572c Mon Sep 17 00:00:00 2001 From: Deon George Date: Sun, 12 Jul 2009 12:01:59 +1000 Subject: [PATCH] SF Feature #2073323 - Using Single Sign On authentication --- doc/phpldapadmin-demo.conf | 2 + lib/HTMLTree.php | 17 ++--- lib/ds.php | 38 +++++++++-- lib/ds_ldap.php | 131 ++++++++++++++++++++++++++++++++++++- 4 files changed, 167 insertions(+), 21 deletions(-) diff --git a/doc/phpldapadmin-demo.conf b/doc/phpldapadmin-demo.conf index 3abc83a..91f8915 100644 --- a/doc/phpldapadmin-demo.conf +++ b/doc/phpldapadmin-demo.conf @@ -21,6 +21,8 @@ access to dn.regex="o=Simpsons$" access to * by * read +authz-policy any + database bdb suffix "dc=example.com" rootdn "cn=Manager,dc=example.com" diff --git a/lib/HTMLTree.php b/lib/HTMLTree.php index 193c062..c3f65cc 100644 --- a/lib/HTMLTree.php +++ b/lib/HTMLTree.php @@ -128,6 +128,10 @@ class HTMLTree extends Tree { $this->draw_login_link(); break; + case 'config': + case 'proxy': + break; + default: die(sprintf('Error: %s hasnt been configured for auth_type %s',__METHOD__,$server->getAuthType())); } @@ -294,7 +298,7 @@ class HTMLTree extends Tree { break; case 'logout': - if (in_array($server->getAuthType(),array('config','http'))) + if (in_array($server->getAuthType(),array('config','http','proxy'))) return ''; $href = sprintf('cmd.php?cmd=logout&server_id=%s',$server->getIndex()); @@ -514,17 +518,6 @@ class HTMLTree extends Tree { $this->getDepth()+3-1,_('(Session timed out. Automatically logged out.)')); } - /** - * Draw out link - */ - protected function draw_logout_link() { - $server = $this->getServer(); - - if (! in_array($server->getAuthType(),array('config','http'))) - printf('%s', - $this->getDepth()+3-1,get_custom_file($server->getIndex(),'logout',''),$server->getIndex(),_('logout')); - } - /** * If there is javascript, draw it */ diff --git a/lib/ds.php b/lib/ds.php index 3033eb8..706c4f2 100644 --- a/lib/ds.php +++ b/lib/ds.php @@ -128,6 +128,7 @@ abstract class DS { switch ($this->getValue('login','auth_type')) { case 'config': case 'http': + case 'proxy': case 'session': return $this->getValue('login','auth_type'); @@ -145,7 +146,7 @@ abstract class DS { public function getLogin($method=null) { $method = $this->getMethod($method); - if ($method == 'unauth') + if ($method == 'anon') return ''; switch ($this->getAuthType()) { @@ -155,6 +156,12 @@ abstract class DS { else return blowfish_decrypt($_SESSION['USER'][$this->index][$method]['name']); + case 'proxy': + if (! isset($_SESSION['USER'][$this->index][$method]['proxy'])) + return $this->getValue('login','bind_id'); + else + return blowfish_decrypt($_SESSION['USER'][$this->index][$method]['proxy']); + case 'http': case 'session': if (! isset($_SESSION['USER'][$this->index][$method]['name'])) @@ -175,6 +182,12 @@ abstract class DS { switch ($this->getAuthType()) { case 'config': + return true; + + case 'proxy': + if (isset($_SESSION['USER'][$this->index][$method]['proxy'])) + unset($_SESSION['USER'][$this->index][$method]['proxy']); + case 'http': case 'session': $_SESSION['USER'][$this->index][$method]['name'] = blowfish_encrypt($user); @@ -193,11 +206,12 @@ abstract class DS { protected function getPassword($method=null) { $method = $this->getMethod($method); - if ($method == 'unauth') + if ($method == 'anon') return ''; switch ($this->getAuthType()) { case 'config': + case 'proxy': if (! isset($_SESSION['USER'][$this->index][$method]['pass'])) return $this->getValue('login','bind_pass'); else @@ -230,6 +244,15 @@ abstract class DS { # For some authentication types, we need to do the login here switch ($this->getAuthType()) { + case 'config': + if (! $CACHE[$this->index] = $this->login($this->getLogin($method),$this->getPassword($method),$method)) + system_message(array( + 'title'=>_('Unable to login.'), + 'body'=>_('Your configuration file has authentication set to CONFIG based authentication, however, the userid/password failed to login'), + 'type'=>'error')); + + break; + case 'http': # If our auth vars are not set, throw up a login box. if (! isset($_SERVER['PHP_AUTH_USER'])) { @@ -272,6 +295,11 @@ abstract class DS { break; + case 'proxy': + $CACHE[$this->index] = $this->login($this->getValue('login','bind_id'),$this->getValue('login','bind_pass'),$method); + + break; + default: $CACHE[$this->index] = is_null($this->getLogin($method)) ? false : true; } @@ -287,14 +315,10 @@ abstract class DS { switch ($this->getAuthType()) { case 'config': - if (isset($_SESSION['USER'][$this->index][$method])) - unset($_SESSION['USER'][$this->index][$method]); - return true; case 'http': - return true; - + case 'proxy': case 'session': if (isset($_SESSION['USER'][$this->index][$method])) unset($_SESSION['USER'][$this->index][$method]); diff --git a/lib/ds_ldap.php b/lib/ds_ldap.php index 2534ac8..9f8103e 100644 --- a/lib/ds_ldap.php +++ b/lib/ds_ldap.php @@ -79,6 +79,10 @@ class ldap extends DS { 'desc'=>'Limit logins to users who match any of the following LDAP filters', 'default'=>array()); + $this->default->proxy['attr'] = array( + 'desc'=>'Attribute to use to find the users DN for proxy based authentication', + 'default'=>array()); + # SASL configuration $this->default->server['sasl'] = array( 'desc'=>'Use SASL authentication when binding LDAP server', @@ -209,15 +213,24 @@ class ldap extends DS { debug_log('Leaving with FALSE, bind FAILed',16,__FILE__,__LINE__,__METHOD__); $this->noconnect = true; + $CACHE[$this->index][$method] = null; 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 + } else { $this->noconnect = false; + # If this is a proxy session, we need to switch to the proxy user + if ($this->isProxyEnabled() && $bind['id'] && $method != 'anon') + if (! $this->startProxy($resource,$method)) { + $this->noconnect = true; + $CACHE[$this->index][$method] = null; + } + } + if (function_exists('run_hook')) run_hook('post_connect',array('server_id'=>$this->index,'method'=>$method,'id'=>$bind['id'])); @@ -243,7 +256,7 @@ class ldap extends DS { if ($this->getValue('login','attr') == 'dn') $userDN = $user; else - $userDN = $this->getLoginID($user,'unauth'); + $userDN = $this->getLoginID($user,'anon'); if (! $userDN) return false; @@ -596,6 +609,106 @@ class ldap extends DS { $this->getValue('sasl','props')); } + /** + * Fetches whether PROXY AUTH has been configured for use with a certain server. + * + * Users may configure phpLDAPadmin to use PROXY AUTH in config,php thus: + * + * $servers->setValue('login','auth_type','proxy'); + * + * + * @return boolean + */ + private function isProxyEnabled() { + return $this->getValue('login','auth_type') == 'proxy' ? true : false; + } + + /** + * If PROXY AUTH is configured, then start it + */ + private function startProxy($resource,$method) { + $rootdse = $this->getRootDSE(); + + if (! (isset($rootdse['supportedcontrol']) && in_array('2.16.840.1.113730.3.4.18',$rootdse['supportedcontrol']))) { + system_message(array( + 'title'=>sprintf('%s %s',_('Unable to start proxy connection'),$this->getName()), + 'body'=>sprintf('%s: %s',_('Error'),_('Your LDAP server doesnt seem to support this control')), + 'type'=>'error')); + + return false; + } + + $filter = '(&'; + $dn = ''; + + $missing = false; + foreach ($this->getValue('proxy','attr') as $attr => $var) { + if (! isset($_SERVER[$var])) { + system_message(array( + 'title'=>sprintf('%s %s',_('Unable to start proxy connection'),$this->getName()), + 'body'=>sprintf('%s: %s (%s)',_('Error'),_('Attribute doesnt exist'),$var), + 'type'=>'error')); + + $missing = true; + + } else { + if ($attr == 'dn') { + $dn = $var; + + break; + + } else + $filter .= sprintf('(%s=%s)',$attr,$_SERVER[$var]); + } + } + + if ($missing) + return false; + + $filter .= ')'; + + if (! $dn) { + $query['filter'] = $filter; + + foreach ($this->getBaseDN() as $base) { + $query['base'] = $base; + + if ($search = $this->query($query,$method)) + break; + } + + if (count($search) != 1) { + system_message(array( + 'title'=>sprintf('%s %s',_('Unable to start proxy connection'),$this->getName()), + 'body'=>sprintf('%s: %s (%s)',_('Error'),_('Search for DN returned the incorrect number of results'),count($search)), + 'type'=>'error')); + + return false; + } + + $search = array_pop($search); + $dn = $search['dn']; + } + + $ctrl = array( + 'oid'=>'2.16.840.1.113730.3.4.18', + 'value'=>sprintf('dn:%s',$dn), + 'iscritical' => true); + + if (! ldap_set_option($resource,LDAP_OPT_SERVER_CONTROLS,array($ctrl))) { + system_message(array( + 'title'=>sprintf('%s %s',_('Unable to start proxy connection'),$this->getName()), + 'body'=>sprintf('%s: %s (%s) for %s',_('Error'),$this->getErrorMessage($method),$this->getErrorNum($method),$method), + 'type'=>'error')); + + return false; + } + + $_SESSION['USER'][$this->index][$method]['proxy'] = blowfish_encrypt($dn); + + return true; + } + /** * Modify attributes of a DN */ @@ -946,6 +1059,20 @@ class ldap extends DS { return preg_replace('/\\\([0-9A-Fa-f]{2})/e',"''.chr(hexdec('\\1')).''",$dn); } + public function getRootDSE($method=null) { + $query = array(); + $query['base'] = ''; + $query['scope'] = 'base'; + $query['attrs'] = $this->getValue('server','root_dse_attributes'); + $query['baseok'] = true; + $results = $this->query($query,$method); + + if (is_array($results)) + return array_change_key_case(array_pop($results)); + else + return array(); + } + /** Schema Methods **/ /** * This function will query the ldap server and request the subSchemaSubEntry which should be the Schema DN.