From 24b41b5447e823af1603ff4a0fd84992d5c5a263 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 29 Sep 2012 17:56:28 -0400 Subject: [PATCH 1/4] Added an optional LDAP modual for authentication. Added Auth_Ldap for authentication. To enable authentication update config/stikked.php and config/auth_ldap.php --- htdocs/application/config/auth_ldap.php | 59 ++++ htdocs/application/config/stikked.php | 9 + htdocs/application/controllers/auth.php | 85 ++++++ htdocs/application/controllers/main.php | 20 +- htdocs/application/libraries/auth_ldap.php | 265 ++++++++++++++++++ htdocs/application/views/auth/login_form.php | 23 ++ htdocs/application/views/auth/logout_view.php | 15 + htdocs/static/styles/main.css | 5 + 8 files changed, 480 insertions(+), 1 deletion(-) create mode 100644 htdocs/application/config/auth_ldap.php create mode 100644 htdocs/application/controllers/auth.php create mode 100644 htdocs/application/libraries/auth_ldap.php create mode 100644 htdocs/application/views/auth/login_form.php create mode 100644 htdocs/application/views/auth/logout_view.php diff --git a/htdocs/application/config/auth_ldap.php b/htdocs/application/config/auth_ldap.php new file mode 100644 index 0000000..d1b1c1d --- /dev/null +++ b/htdocs/application/config/auth_ldap.php @@ -0,0 +1,59 @@ +. + * + */ + +/** + * @author Greg Wojtak + * @copyright Copyright © 2010,2011 by Greg Wojtak + * @package Auth_Ldap + * @subpackage configuration + * @license GNU Lesser General Public License + */ + +/** + * Array Index - Usage + * hosts - Array of ldap servers to try to authenticate against + * ports - The remote port on the ldap server to connect to + * basedn - The base dn of your ldap data store + * login_attribute - LDAP attribute used to check usernames against + * proxy_user - Distinguised name of a proxy user if your LDAP server does not allow anonymous binds + * proxy pass - Password to use with above + * roles - An array of role names to use within your app. The values are arbitrary. + * The keys themselves represent the + * "security level," ie + * if( $security_level >= 3 ) { + * // Is a power user + * echo display_info_for_power_users_or_admins(); + * } + * member_attribute - Attribute to search to determine allowance after successful authentication + * auditlog - Location to log auditable events. Needs to be writeable + * by the web server + */ + +$config['hosts'] = array('ldap.mycompany.com'); +$config['ports'] = array(389); +$config['basedn'] = 'dc=mycompany,dc=com'; +$config['login_attribute'] = 'uid'; +$config['proxy_user'] = ''; +$config['proxy_pass'] = ''; +$config['roles'] = array(1 => 'User', + 3 => 'Power User', + 5 => 'Administrator'); +$config['member_attribute'] = 'memberUid'; +$config['auditlog'] = 'application/logs/audit.log'; // Some place to log attempted logins (separate from message log) +?> diff --git a/htdocs/application/config/stikked.php b/htdocs/application/config/stikked.php index ec7e3ac..6379ec7 100755 --- a/htdocs/application/config/stikked.php +++ b/htdocs/application/config/stikked.php @@ -104,6 +104,15 @@ $config['unknown_poster'] = 'random'; **/ $config['unknown_title'] = 'Untitled'; +/** + * To require LDAP authentication or not. + * + * Weather to require LDAP authenticaiton or not. + * Set to either 'true' to require authentication or 'false' not to. + * NOTE: if changed, set LDAP settings in auth_ldap.php +**/ +$config['require_auth'] = false; + /** * * diff --git a/htdocs/application/controllers/auth.php b/htdocs/application/controllers/auth.php new file mode 100644 index 0000000..ba256ba --- /dev/null +++ b/htdocs/application/controllers/auth.php @@ -0,0 +1,85 @@ +. + * + */ + +/** + * @author Greg Wojtak + * @copyright Copyright © 2010,2011 by Greg Wojtak + * @package Auth_Ldap + * @subpackage auth demo + * @license GNU Lesser General Public License + */ +class Auth extends CI_Controller { + function __construct() { + parent::__construct(); + + $this->load->helper('form'); + $this->load->library('Form_validation'); + $this->load->library('auth_ldap'); + $this->load->helper('url'); + $this->load->library('table'); + } + + function index() { + $this->db_session->keep_flashdata('tried_to'); + $this->login(); + } + + function login($errorMsg = NULL){ + $this->db_session->keep_flashdata('tried_to'); + if(!$this->auth_ldap->is_authenticated()) { + // Set up rules for form validation + $rules = $this->form_validation; + $rules->set_rules('username', 'Username', 'required|alpha_dash'); + $rules->set_rules('password', 'Password', 'required'); + + // Do the login... + if($rules->run() && $this->auth_ldap->login( + $rules->set_value('username'), + $rules->set_value('password'))) { + // Login WIN! + if($this->db_session->flashdata('tried_to')) { + redirect($this->db_session->flashdata('tried_to')); + }else { + redirect('/'); + } + }else { + // Login FAIL + $this->load->view('auth/login_form', array('login_fail_msg' + => 'Error with LDAP authentication.')); + } + }else { + // Already logged in... + redirect('/'); + } + } + + function logout() { + if($this->db_session->userdata('logged_in')) { + $data['name'] = $this->db_session->userdata('cn'); + $data['username'] = $this->db_session->userdata('username'); + $data['logged_in'] = TRUE; + $this->auth_ldap->logout(); + } else { + $data['logged_in'] = FALSE; + } + $this->load->view('auth/logout_view', $data); + } +} + +?> diff --git a/htdocs/application/controllers/main.php b/htdocs/application/controllers/main.php index 732f271..e89310b 100755 --- a/htdocs/application/controllers/main.php +++ b/htdocs/application/controllers/main.php @@ -17,6 +17,7 @@ * - _valid_lang() * - _valid_captcha() * - _valid_ip() + * - _valid_authentication() * - get_cm_js() * - error_404() * Classes list: @@ -30,6 +31,7 @@ class Main extends CI_Controller { parent::__construct(); $this->load->model('languages'); + $this->load->library('auth_ldap'); if (!$this->db->table_exists('ci_sessions')) { @@ -235,6 +237,7 @@ class Main extends CI_Controller function index() { + $this->_valid_authentication(); $this->load->helper('json'); if (!$this->input->post('submit')) @@ -307,6 +310,7 @@ class Main extends CI_Controller function raw() { + $this->_valid_authentication(); $this->load->model('pastes'); $check = $this->pastes->checkPaste(3); @@ -323,6 +327,7 @@ class Main extends CI_Controller function rss() { + $this->_valid_authentication(); $this->load->model('pastes'); $check = $this->pastes->checkPaste(3); @@ -343,6 +348,7 @@ class Main extends CI_Controller function embed() { + $this->_valid_authentication(); $this->load->model('pastes'); $check = $this->pastes->checkPaste(3); @@ -359,6 +365,7 @@ class Main extends CI_Controller function download() { + $this->_valid_authentication(); $this->load->model('pastes'); $check = $this->pastes->checkPaste(3); @@ -375,7 +382,7 @@ class Main extends CI_Controller function lists() { - + $this->_valid_authentication(); if ($this->config->item('private_only')) { show_404(); @@ -403,6 +410,7 @@ class Main extends CI_Controller function view() { + $this->_valid_authentication(); $this->load->helper('json'); $this->load->model('pastes'); $check = $this->pastes->checkPaste(2); @@ -528,6 +536,16 @@ class Main extends CI_Controller } } + function _valid_authentication() + { + if ($this->config->item('require_auth') ){ + if (!$this->auth_ldap->is_authenticated()){ + $this->db_session->set_flashdata('tried_to', "/" . $this->uri->uri_string()); + redirect('/auth'); + } + } + } + function get_cm_js() { $lang = $this->uri->segment(3); diff --git a/htdocs/application/libraries/auth_ldap.php b/htdocs/application/libraries/auth_ldap.php new file mode 100644 index 0000000..fb162b5 --- /dev/null +++ b/htdocs/application/libraries/auth_ldap.php @@ -0,0 +1,265 @@ +. + * + */ +/** + * Auth_Ldap Class + * + * Simple LDAP Authentication library for Code Igniter. + * + * @package Auth_Ldap + * @author Greg Wojtak + * @version 0.6 + * @link http://www.techrockdo.com/projects/auth_ldap + * @license GNU Lesser General Public License (LGPL) + * @copyright Copyright © 2010,2011 by Greg Wojtak + * @todo Allow for privileges in groups of groups in AD + * @todo Rework roles system a little bit to a "auth level" paradigm + */ +class Auth_Ldap { + function __construct() { + $this->ci =& get_instance(); + + log_message('debug', 'Auth_Ldap initialization commencing...'); + + // Load the session library + $this->ci->load->library('db_session'); + + // Load the configuration + $this->ci->load->config('auth_ldap'); + + // Load the language file + // $this->ci->lang->load('auth_ldap'); + + $this->_init(); + } + + + /** + * @access private + * @return void + */ + private function _init() { + + // Verify that the LDAP extension has been loaded/built-in + // No sense continuing if we can't + if (! function_exists('ldap_connect')) { + show_error('LDAP functionality not present. Either load the module ldap php module or use a php with ldap support compiled in.'); + log_message('error', 'LDAP functionality not present in php.'); + } + + $this->hosts = $this->ci->config->item('hosts'); + $this->ports = $this->ci->config->item('ports'); + $this->basedn = $this->ci->config->item('basedn'); + $this->account_ou = $this->ci->config->item('account_ou'); + $this->login_attribute = $this->ci->config->item('login_attribute'); + $this->use_ad = $this->ci->config->item('use_ad'); + $this->ad_domain = $this->ci->config->item('ad_domain'); + $this->proxy_user = $this->ci->config->item('proxy_user'); + $this->proxy_pass = $this->ci->config->item('proxy_pass'); + $this->roles = $this->ci->config->item('roles'); + $this->auditlog = $this->ci->config->item('auditlog'); + $this->member_attribute = $this->ci->config->item('member_attribute'); + } + + /** + * @access public + * @param string $username + * @param string $password + * @return bool + */ + function login($username, $password) { + /* + * For now just pass this along to _authenticate. We could do + * something else here before hand in the future. + */ + + $user_info = $this->_authenticate($username,$password); + if(empty($user_info['role'])) { + log_message('info', $username." has no role to play."); + show_error($username.' succssfully authenticated, but is not allowed because the username was not found in an allowed access group.'); + } + // Record the login + $this->_audit("Successful login: ".$user_info['cn']."(".$username.") from ".$this->ci->input->ip_address()); + + // Set the session data + $customdata = array('username' => $username, + 'cn' => $user_info['cn'], + 'role' => $user_info['role'], + 'logged_in' => TRUE); + + $this->ci->db_session->set_userdata($customdata); + return TRUE; + } + + /** + * @access public + * @return bool + */ + function is_authenticated() { + if($this->ci->db_session->userdata('logged_in')) { + return TRUE; + } else { + return FALSE; + } + } + + /** + * @access public + */ + function logout() { + // Just set logged_in to FALSE and then destroy everything for good measure + $this->ci->session->set_userdata(array('logged_in' => FALSE)); + $this->ci->session->sess_destroy(); + } + + /** + * @access private + * @param string $msg + * @return bool + */ + private function _audit($msg){ + $date = date('Y/m/d H:i:s'); + if( ! file_put_contents($this->auditlog, $date.": ".$msg."\n",FILE_APPEND)) { + log_message('info', 'Error opening audit log '.$this->auditlog); + return FALSE; + } + return TRUE; + } + + /** + * @access private + * @param string $username + * @param string $password + * @return array + */ + private function _authenticate($username, $password) { + $needed_attrs = array('dn', 'cn', $this->login_attribute); + + foreach($this->hosts as $host) { + $this->ldapconn = ldap_connect($host); + if($this->ldapconn) { + break; + }else { + log_message('info', 'Error connecting to '.$uri); + } + } + // At this point, $this->ldapconn should be set. If not... DOOM! + if(! $this->ldapconn) { + log_message('error', "Couldn't connect to any LDAP servers. Bailing..."); + show_error('Error connecting to your LDAP server(s). Please check the connection and try again.'); + } + + // We've connected, now we can attempt the login... + + // These to ldap_set_options are needed for binding to AD properly + // They should also work with any modern LDAP service. + ldap_set_option($this->ldapconn, LDAP_OPT_REFERRALS, 0); + ldap_set_option($this->ldapconn, LDAP_OPT_PROTOCOL_VERSION, 3); + + // Find the DN of the user we are binding as + // If proxy_user and proxy_pass are set, use those, else bind anonymously + if($this->proxy_user) { + $bind = ldap_bind($this->ldapconn, $this->proxy_user, $this->proxy_pass); + }else { + $bind = ldap_bind($this->ldapconn); + } + + if(!$bind){ + log_message('error', 'Unable to perform anonymous/proxy bind'); + show_error('Unable to bind for user id lookup'); + } + + log_message('debug', 'Successfully bound to directory. Performing dn lookup for '.$username); + $filter = '('.$this->login_attribute.'='.$username.')'; + $search = ldap_search($this->ldapconn, $this->basedn, $filter, + array('dn', $this->login_attribute, 'cn')); + $entries = ldap_get_entries($this->ldapconn, $search); + $binddn = $entries[0]['dn']; + + // Now actually try to bind as the user + $bind = ldap_bind($this->ldapconn, $binddn, $password); + if(! $bind) { + $this->_audit("Failed login attempt: ".$username." from ".$_SERVER['REMOTE_ADDR']); + return FALSE; + } + $cn = $entries[0]['cn'][0]; + $dn = stripslashes($entries[0]['dn']); + $id = $entries[0][$this->login_attribute][0]; + + $get_role_arg = $id; + + return array('cn' => $cn, 'dn' => $dn, 'id' => $id, + 'role' => $this->_get_role($get_role_arg)); + } + + /** + * @access private + * @param string $str + * @param bool $for_dn + * @return string + */ + private function ldap_escape($str, $for_dn = false) { + /** + * This function courtesy of douglass_davis at earthlink dot net + * Posted in comments at + * http://php.net/manual/en/function.ldap-search.php on 2009/04/08 + */ + // see: + // RFC2254 + // http://msdn.microsoft.com/en-us/library/ms675768(VS.85).aspx + // http://www-03.ibm.com/systems/i/software/ldap/underdn.html + + if ($for_dn) + $metaChars = array(',','=', '+', '<','>',';', '\\', '"', '#'); + else + $metaChars = array('*', '(', ')', '\\', chr(0)); + + $quotedMetaChars = array(); + foreach ($metaChars as $key => $value) $quotedMetaChars[$key] = '\\'.str_pad(dechex(ord($value)), 2, '0'); + $str=str_replace($metaChars,$quotedMetaChars,$str); //replace them + return ($str); + } + + /** + * @access private + * @param string $username + * @return int + */ + private function _get_role($username) { + + $filter = '('.$this->member_attribute.'='.$username.')'; + $search = ldap_search($this->ldapconn, $this->basedn, $filter, array('cn')); + if(! $search ) { + log_message('error', "Error searching for group:".ldap_error($this->ldapconn)); + show_error('Couldn\'t find groups: '.ldap_error($this->ldapconn)); + } + $results = ldap_get_entries($this->ldapconn, $search); + if($results['count'] != 0) { + for($i = 0; $i < $results['count']; $i++) { + $role = array_search($results[$i]['cn'][0], $this->roles); + if($role !== FALSE) { + return $role; + } + } + } + return false; + } +} + +?> diff --git a/htdocs/application/views/auth/login_form.php b/htdocs/application/views/auth/login_form.php new file mode 100644 index 0000000..f2251b0 --- /dev/null +++ b/htdocs/application/views/auth/login_form.php @@ -0,0 +1,23 @@ +load->view('defaults/header'); ?> + + +load->view('defaults/footer'); ?> diff --git a/htdocs/application/views/auth/logout_view.php b/htdocs/application/views/auth/logout_view.php new file mode 100644 index 0000000..7aeffca --- /dev/null +++ b/htdocs/application/views/auth/logout_view.php @@ -0,0 +1,15 @@ + + + + Logout + + + +

has been logged out.

+

Thanks for visiting

+ +

You need to before you log out...

+ + + diff --git a/htdocs/static/styles/main.css b/htdocs/static/styles/main.css index 98abf27..6076367 100644 --- a/htdocs/static/styles/main.css +++ b/htdocs/static/styles/main.css @@ -483,3 +483,8 @@ h4 { color: inherit; text-decoration: underline; } + +.login { + margin-left: 35%; + margin-right: auto; +} \ No newline at end of file From dc8b8eaa46db70fd86e58f3859bd1607d1532942 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sun, 30 Sep 2012 13:34:11 -0400 Subject: [PATCH 2/4] Only load the auth_ldap library if authentication is turend on --- htdocs/application/controllers/main.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/htdocs/application/controllers/main.php b/htdocs/application/controllers/main.php index e89310b..a30499b 100755 --- a/htdocs/application/controllers/main.php +++ b/htdocs/application/controllers/main.php @@ -31,7 +31,9 @@ class Main extends CI_Controller { parent::__construct(); $this->load->model('languages'); - $this->load->library('auth_ldap'); + if ($this->config->item('require_auth') ){ + $this->load->library('auth_ldap'); + } if (!$this->db->table_exists('ci_sessions')) { From aab3832e02ebe59a11865392a6d3c923a25fba15 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sun, 30 Sep 2012 14:10:41 -0400 Subject: [PATCH 3/4] Added a logout button Logout button will now appear in the header if authentication is enabled and the user is logged in. --- htdocs/application/controllers/auth.php | 2 +- htdocs/application/libraries/auth_ldap.php | 4 ++-- htdocs/application/views/auth/logout_view.php | 15 --------------- htdocs/application/views/defaults/header.php | 7 +++++++ 4 files changed, 10 insertions(+), 18 deletions(-) delete mode 100644 htdocs/application/views/auth/logout_view.php diff --git a/htdocs/application/controllers/auth.php b/htdocs/application/controllers/auth.php index ba256ba..002b916 100644 --- a/htdocs/application/controllers/auth.php +++ b/htdocs/application/controllers/auth.php @@ -78,7 +78,7 @@ class Auth extends CI_Controller { } else { $data['logged_in'] = FALSE; } - $this->load->view('auth/logout_view', $data); + redirect('/'); } } diff --git a/htdocs/application/libraries/auth_ldap.php b/htdocs/application/libraries/auth_ldap.php index fb162b5..c27d73b 100644 --- a/htdocs/application/libraries/auth_ldap.php +++ b/htdocs/application/libraries/auth_ldap.php @@ -124,8 +124,8 @@ class Auth_Ldap { */ function logout() { // Just set logged_in to FALSE and then destroy everything for good measure - $this->ci->session->set_userdata(array('logged_in' => FALSE)); - $this->ci->session->sess_destroy(); + $this->ci->db_session->set_userdata(array('logged_in' => FALSE)); + $this->ci->db_session->sess_destroy(); } /** diff --git a/htdocs/application/views/auth/logout_view.php b/htdocs/application/views/auth/logout_view.php deleted file mode 100644 index 7aeffca..0000000 --- a/htdocs/application/views/auth/logout_view.php +++ /dev/null @@ -1,15 +0,0 @@ - - - - Logout - - - -

has been logged out.

-

Thanks for visiting

- -

You need to before you log out...

- - - diff --git a/htdocs/application/views/defaults/header.php b/htdocs/application/views/defaults/header.php index 25085de..d89ad83 100755 --- a/htdocs/application/views/defaults/header.php +++ b/htdocs/application/views/defaults/header.php @@ -53,6 +53,13 @@ $this->carabiner->display('css');
  • href="" title="API">API
  • href="" title="About">About
  • + config->item('require_auth') ){ + if ($this->auth_ldap->is_authenticated()){ + echo "
  • Logout
  • '; + } + } + ?> From a3ed11ab283722522052ada5d141b2a37ae89fba Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 1 Oct 2012 11:41:44 -0400 Subject: [PATCH 4/4] Better error display for invalid logins Instead of showing an ugly error, failed logins now show the login page and a simple error about invalid username or password. --- htdocs/application/controllers/auth.php | 4 ++-- htdocs/application/libraries/auth_ldap.php | 10 +++++++++- htdocs/application/views/auth/login_form.php | 8 ++++++++ htdocs/static/styles/main.css | 10 ++++++++++ 4 files changed, 29 insertions(+), 3 deletions(-) diff --git a/htdocs/application/controllers/auth.php b/htdocs/application/controllers/auth.php index 002b916..447d7e5 100644 --- a/htdocs/application/controllers/auth.php +++ b/htdocs/application/controllers/auth.php @@ -60,8 +60,8 @@ class Auth extends CI_Controller { } }else { // Login FAIL - $this->load->view('auth/login_form', array('login_fail_msg' - => 'Error with LDAP authentication.')); + $this->db_session->set_flashdata('login_error', 'Incorrect username or password.'); + $this->load->view('auth/login_form'); } }else { // Already logged in... diff --git a/htdocs/application/libraries/auth_ldap.php b/htdocs/application/libraries/auth_ldap.php index c27d73b..62afddc 100644 --- a/htdocs/application/libraries/auth_ldap.php +++ b/htdocs/application/libraries/auth_ldap.php @@ -92,7 +92,8 @@ class Auth_Ldap { $user_info = $this->_authenticate($username,$password); if(empty($user_info['role'])) { log_message('info', $username." has no role to play."); - show_error($username.' succssfully authenticated, but is not allowed because the username was not found in an allowed access group.'); + //show_error($username.' succssfully authenticated, but is not allowed because the username was not found in an allowed access group.'); + return FALSE; } // Record the login $this->_audit("Successful login: ".$user_info['cn']."(".$username.") from ".$this->ci->input->ip_address()); @@ -190,6 +191,13 @@ class Auth_Ldap { $search = ldap_search($this->ldapconn, $this->basedn, $filter, array('dn', $this->login_attribute, 'cn')); $entries = ldap_get_entries($this->ldapconn, $search); + + if(!isset($entries[0])){ + //User either does not exist or has no permissions + $this->_audit("Failed login attempt: ".$username." from ".$_SERVER['REMOTE_ADDR']); + return FALSE; + } + $binddn = $entries[0]['dn']; // Now actually try to bind as the user diff --git a/htdocs/application/views/auth/login_form.php b/htdocs/application/views/auth/login_form.php index f2251b0..a233d6c 100644 --- a/htdocs/application/views/auth/login_form.php +++ b/htdocs/application/views/auth/login_form.php @@ -1,4 +1,12 @@ load->view('defaults/header'); ?> + db_session->flashdata('login_error'); + if ($message){ + echo ''; + } + ?>