Skip to content

Instantly share code, notes, and snippets.

Created December 10, 2014 18:30

Revisions

  1. @invalid-email-address Anonymous created this gist Dec 10, 2014.
    498 changes: 498 additions & 0 deletions gistfile1.txt
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,498 @@
    <?php if ( ! defined('EXT') ) exit('Invalid file request');
    /** FINAL
    * LDAP Authentication
    *
    * ### EE 2.1 version ###
    *
    * Based on: NCE LDAP
    * http://code.google.com/p/ee-ldap-extension/
    * License: "if you've used this module and found that it needed something then please hand it back so that it can be shared with the world"
    * Site: http://code.google.com/p/ee-ldap-extension/wiki/Introduction
    *
    * An ExpressionEngine Extension that allows the authentication of users via LDAP
    * LDAP details are copied to the EE database before standard MySQL authentication is performed
    * If user is not found on LDAP, MySQL authentication will still be performed (useful for EE users not in LDAP)
    *
    * Dependancy: iconv PHP module
    *
    * @package DesignByFront
    * @author Alistair Brown
    * @author Alex Glover
    * @link http://github.com/designbyfront/LDAP-Authentication-for-ExpressionEngine
    * @since Version 1.3
    *
    * LDAPS Instructions - http://github.com/designbyfront/LDAP-Authentication-for-ExpressionEngine/issues/closed#issue/1
    *
    * Enhancements to original:
    * - Upgraded to EE2
    * - Authentication against multiple LDAP servers
    * - Non-LDAP user login (remove restriction)
    * - Authentication even with LDAP server downtime (remove restriction)
    * - Use EE global classes (rather then PHP global variables)
    * - DB protection against injection (however unlikely)
    * - Better code structure using functions
    * - More settings control:
    * - Use of character encoding for sent data (and ability to change in settings)
    * PHP uses 'UTF-8' encoding; Windows server uses 'Windows-1252' encoding.
    * Using the iconv PHP module, settings data saved in 'UTF-8' is dynamically encoded to 'Windows-1252' when being sent.
    *
    */

    class Nce_ldap_ext {



    /*
    // PHP4 Constructor
    function Nce_ldap_ext($settings = '')
    {
    $this->EE =& get_instance();
    $this->settings = $settings;
    }
    */
    var $name = 'UNT LDAP auth (front ldap mod)';
    var $version = '1.4';
    var $description = 'Handles LDAP login / account creation. Modified by Blair';
    var $settings_exist = 'y';
    var $docs_url = 'http://github.com/designbyfront/LDAP-Authentication-for-ExpressionEngine/issues';
    var $settings = array();

    var $use_ldap_account_creation = 'yes';
    var $ldap_character_encode = 'Windows-1252';
    var $groupID_facultystaff = 9;
    var $groupID_student = 8;
    var $groupID_alumni = 4;
    var $debug = TRUE;


    function __construct()
    {
    $this->EE =& get_instance();
    $this->settings = $settings;
    }

    // ----------------------

    /**
    * EE method called when the extension is activated
    */
    function activate_extension ()
    {
    $settings['use_ldap_account_creation'] = $this->use_ldap_account_creation;
    $settings['ldap_character_encode'] = $this->ldap_character_encode;
    $settings['groupID_facultystaff'] = $this->groupID_facultystaff;
    $settings['groupID_student'] = $this->groupID_student;
    $settings['groupID_alumni'] = $this->groupID_alumni;


    $hooks = array(
    'login_authenticate_start' => 'login_authenticate_start',
    'member_member_login_start' => 'member_member_login_start'
    );

    foreach ($hooks as $hook => $method)
    {
    $this->EE->db->query($this->EE->db->insert_string('exp_extensions',
    array(
    'extension_id' => '',
    'class' => __CLASS__,
    'method' => $method,
    'hook' => $hook,
    'settings' => serialize($settings),
    'priority' => 10,
    'version' => $this->version,
    'enabled' => "y"
    )
    ));
    }
    }


    // ----------------------


    /**
    * EE method called when the extension is updated
    */
    function update_extension($current = '')
    {
    if ($current == '' OR $current == $this->version)
    return FALSE;

    $this->EE->db->query('UPDATE exp_extensions SET version = \''.$this->EE->db->escape_str($this->version).'\' WHERE class = \''.$this->EE->db->escape_str(__CLASS__).'\'');
    }


    // ----------------------


    /**
    * EE method called when the extension is disabled
    */
    function disable_extension()
    {
    $this->EE->db->query('DELETE FROM exp_extensions WHERE class = \''.$this->EE->db->escape_str(__CLASS__).'\'');
    }


    // ----------------------


    /**
    * Configuration for the extension settings page
    */
    function settings()
    {
    $settings['ldap_character_encode'] = $this->ldap_character_encode;
    $settings['groupID_facultystaff'] = $this->groupID_facultystaff;
    $settings['groupID_student'] = $this->groupID_student;
    $settings['groupID_alumni'] = $this->groupID_alumni;
    $settings['use_ldap_account_creation'] = array('r', array('yes' => 'yes_ldap_account_creation',
    'no' => 'no_ldap_account_creation'),
    'yes');

    return $settings;
    }


    // ----------------------


    /**
    * Called by the member_member_login_start hook
    */
    function member_member_login_start()
    {
    return $this->login_authenticate_start();
    }


    // ----------------------


    /**
    * Called by the login_authenticate_start hook
    */
    function login_authenticate_start()
    {

    $user_info = array();
    $user_info['username'] = ee()->input->post('username', TRUE);
    $user_info['password'] = ee()->input->post('password', TRUE);

    $result = $this->authenticate_user($user_info);

    $everything = array_merge($result, $user_info);

    if ($this->debug)
    {
    echo 'Dump of varible:';
    echo'<pre>';
    var_dump($everything);
    echo'</pre>';
    }

    if ($result['authenticated'])
    {
    $this->debug_print('Authenticated. Trying to sync \''.$user_info['username'].'\' with EE member system...');
    $this->sync_user_details($everything);
    }
    else
    {
    $this->debug_print('Could not authenticate username \''.$user_info['username'].'\' with LDAP');
    }
    //$this->close_connection($connection);

    if ($this->debug)
    exit();
    }


    // ----------------------


    function sync_user_details($user_info)
    {

    // Sync EE password to match LDAP (if account exists)
    $user_info['encrypted_password'] = hash('sha1',stripslashes($user_info['password']));

    // Get the password information from Auth
    $this->EE->load->library('auth');
    $user_info['hashed_password'] = $this->EE->auth->hash_password($user_info['password']);

    $sql = 'UPDATE exp_members SET password = \''.$this->EE->db->escape_str($user_info['encrypted_password']).'\' WHERE username = \''.$this->EE->db->escape_str($user_info['username']).'\'';
    $this->debug_print('Updating user with SQL: '.$sql);
    $this->EE->db->query($sql);

    // now we might want to do some EE account creation
    if ($this->settings['use_ldap_account_creation'] === 'yes')
    {
    $this->debug_print('Attempting to create EE user...');
    $this->create_ee_user($user_info);
    }

    /* Add a check if they are alumni or not */
    if( isset($user_info['edupersonaffiliation'][0]) OR $user_info['edupersonaffiliation'][0] != "")
    { /* Do nothing */

    } else {
    // Set the user group to be for former studnets/faculy/staff
    $sql = 'UPDATE exp_members SET group_id = 7 WHERE username = \''.$this->EE->db->escape_str($user_info['username']).'\'';
    $this->debug_print('Member affiliation could not be found, so user must not be active. Setting group. Updating using query: '.$sql);
    $this->EE->db->query($sql);
    }

    }


    // ---------------------- This section was heavily modified in order to handle the two directories. Outside the tree, this is the only way to do this.


    function create_ee_user($user_info)
    {
    $sql = 'SELECT \'username\' FROM exp_members WHERE username = \''.$this->EE->db->escape_str($user_info['username']).'\'';
    $this->debug_print('Checking for existing user with SQL: '.$sql);
    $query = $this->EE->db->query($sql);

    // user doesn't exist in exp_members table, so we will create an EE account
    if ($query->num_rows === 0)
    {
    $this->debug_print('Using LDAP for account creation...');

    $data['username'] = $user_info['username'];
    $data['password'] = $user_info['encrypted_password'];
    $data['email'] = $user_info['mail'][0];
    //$data['salt'] = $user_info['hashed_password'];
    $data['ip_address'] = $this->EE->input->ip_address();
    $data['unique_id'] = $this->EE->functions->random('encrypt');
    $data['crypt_key'] = $this->EE->functions->random('encrypt', 16);
    $data['join_date'] = $this->EE->localize->now;
    $data['language'] = $this->EE->config->item('deft_lang');
    $data['timezone'] = $this->EE->config->item('default_site_timezone');
    $data['time_format'] = $this->EE->config->item('time_format') ? $this->EE->config->item('time_format') : 'us';
    $data['screen_name'] = $user_info['givenname'][0]." ".$user_info['sn'][0];
    //$custom_data['a_field'] = $user_info['edupersonaffiliation'][0] . ',' . $user_info['edupersonaffiliation'][1];
    $custom_data['first_name'] = $user_info['givenname'][0];
    $custom_data['last_name'] = $user_info['sn'][0];


    // Eval if user is student or staff
    if ($user_info['i_am_a'] == 'student')
    {
    //ExpressionEngine group ID = Students
    $data['group_id'] = $this->$settings['groupID_student'];
    }
    if ($user_info['i_am_a'] == 'staff')
    {
    //ExpressionEngine group ID = staff
    $data['group_id'] = $this->$settings['groupID_facultystaff'];
    }
    if ($user_info['i_am_a'] == '')
    {
    //ExpressionEngine group ID = Guest
    $data['group_id'] = $this->$settings['groupID_alumni'];
    }

    // add in any optional data. Name of field => Value it needs to be. Set it ahead of time.
    $opt_data = array(
    //'m_field_id_14' => $custom_data['a_field'],
    'm_field_id_4' => $custom_data['first_name'],
    'm_field_id_5' => $custom_data['last_name'],
    );



    $this->debug_print('Inserting user with data:<pre> '.print_r($data, TRUE).'</pre><br /><pre>'.print_r($custom_data, TRUE).'</pre><br /><pre>'.print_r($opt_data, TRUE).'</pre>');

    $this->EE->load->model('member_model');
    $member_id = $this->EE->member_model->create_member($data, $opt_data);
    $this->debug_print('Member ID: '.$member_id);

    if ($member_id > 0) // update other relevant fields
    {
    //$sql = 'UPDATE exp_members SET photo_filename = \'photo_'.$member_id.'.jpg\', photo_width = \'90\', photo_height = \'120\'';
    //$query = $this->EE->db->query($sql);

    }
    else
    {
    exit('Could not create user account for '.$user_info['username'].'<br/>'."\n");
    }
    }
    $this->debug_print('Done with create ee user function, moving on.');
    }



    // ----------------------


    function authenticate_user($user_info)
    {

    $login_settings = array();

    // First try the students directory.
    $login_settings['ds'] = ldap_connect("students.ad.unt.edu", 389);
    $login_settings['dn'] = "dc=students,dc=ad,dc=unt,dc=edu";
    $login_settings['search_dir'] = 'student';

    $bind_result = ldap_bind($login_settings['ds'] , $user_info['username'].'@students.ad.unt.edu', $user_info['password']);
    $this->debug_print("Student bind result: $bind_result");

    //If the login was correct, assume they were a student with the correct login.
    if ($bind_result){

    return $this->greedy_ldap_grab($login_settings, $user_info);

    }


    // If it didn't work, then try the other directory.
    if (!$bind_result) {
    $this->debug_print('Bind not succesfull to student directory, moving to the faculty-staff directory.');

    // Connect to unt.ad.unt.edu
    $login_settings['ds'] = ldap_connect("unt.ad.unt.edu", 389);
    $login_settings['dn'] = "dc=unt,dc=ad,dc=unt,dc=edu";
    $login_settings['search_dir'] = 'staff';

    $bind_result = ldap_bind($login_settings['ds'] , $user_info['username'].'@unt.ad.unt.edu', $user_info['password']);
    $this->debug_print("UNT bind result: $bind_result");

    if ($bind_result)
    {
    return $this->greedy_ldap_grab($login_settings, $user_info);

    } else {
    $this->debug_print("Not able to bind to either directory.");
    }

    }
    }


    // ----------------------


    function greedy_ldap_grab($login_settings, $user_info)
    {
    $this->debug_print('Starting Greedy LDAP Grab...');

    $filter = "(cn={$user_info['username']})";
    $this->debug_print('Filter: ' . $filter);

    // The fields to pull from the directory entry.
    $attributes = array('givenName','mail','sn','cn','edupersonaffiliation');

    // Actually do the search of the user, and pull the above info.
    $result = ldap_search($login_settings['ds'], $login_settings['dn'], $filter);
    $this->debug_print("Result: {$result}");

    // If the search comes up empty, end and report the error.
    if (ldap_count_entries($login_settings['ds'], $result) != 1)
    {
    return array('authenticated' => false);
    }

    // If no error searching:

    // Get all the entries that match.
    $info = ldap_get_entries($login_settings['ds'], $result);

    // Since there could be more than one, only use the first entry it finds.
    $user_info = $info[0];
    $this->debug_print('Data for '.$info["count"].' items returned<br/>');


    // If everything goes well, then you are assigned a value.
    $user_info['i_am_a'] = $login_settings['search_dir'];
    $this->debug_print("You are a ".$login_settings['search_dir']);

    $user_info['authenticated'] = true;

    //Close Connection
    $this->debug_print('Closing connection...');
    ldap_close($login_settings['ds']) or
    die('Could not close the LDAP connection<br/>'."\n");


    $this->debug_print('Returning user info from Greedy.');
    return $user_info;
    }


    /** ORG
    function authenticate_user($conn, $username, $password, $ldap_username_attribute, $ldap_search_base)
    {
    $this->debug_print('Searching for attribute '.$ldap_username_attribute.'='.$username.' ...');
    // Search username entry
    $result = ldap_search($conn, $ldap_search_base, $ldap_username_attribute.'='.$username);
    $this->debug_print('Search result is: '.$result);

    // Search not successful (server down?), so do nothing - standard MySQL authentication can take over
    if ($result === FALSE)
    {
    $this->EE->session->userdata['ldap_message'] = $this->settings['no_ldap_login_message'];
    return array('authenticated' => false);
    }

    $this->debug_print('Number of entires returned is '.ldap_count_entries($conn, $result));
    // username not found, so do nothing - standard MySQL authentication can take over
    if (ldap_count_entries($conn, $result) < 1)
    {
    return array('authenticated' => false);
    }

    $this->debug_print('Getting entries for \''.$username.'\' ...');
    $info = ldap_get_entries($conn, $result); // entry for username found in directory, retrieve entries
    $user_info = $info[0];
    $this->debug_print('Data for '.$info["count"].' items returned<br/>');

    $user_info['username'] = $username;
    $user_info['password'] = $password;
    // Authenticate LDAP user against password submitted on login
    $dn = $user_info['dn'];
    $success = @ldap_bind($conn, $dn, $this->ldap_encode($password)); // bind with user credentials

    if (!$success)
    {
    $this->debug_print('Error binding with supplied password (dn: '.$dn.') ERROR: '.ldap_error($conn));
    }

    $user_info['authenticated'] = $success;
    return $user_info;
    }
    **/


    function debug_print($message, $br="<br/>\n")
    {
    if ($this->debug)
    {
    if (is_array($message))
    {
    print('<pre>');
    print_r($message);
    print('</pre>'.$br);
    }
    else
    {
    print($message.' '.$br);
    }
    }
    }


    function ldap_encode($text)
    {
    return iconv("UTF-8", $this->settings['ldap_character_encode'], $text);
    }


    }
    // END CLASS Nce_ldap

    /* End of file ext.nce_ldap.php */
    /* Location: ./system/expressionengine/third_party/nce_ldap/ext.nce_ldap.php */