rakoitde / ci4-shield-ldap

LDAP Authentication addon for CodeIgniter 4 Shield
2 stars 1 forks source link

NO samaccountname in our LDAP #5

Open xloouch opened 1 week ago

xloouch commented 1 week ago

As mentioned in the title, we have this variable "samaccountname" not in our LDAP.

We identify by "uid" ..

So, is there a possibility tho add a "user_login_attribute" = "uid" , if not set -> user_login_attribute = samaccountname in the .env..

So in the rakoitde/shieldldap/src/Autentication/LDAPManager.php , on Line 139, the filter will me more dynamically?

$filter = "({$user_login_attribute}={$samaccountname})";

?

(So i don't neet to change your source-code, for our needs) Thanks in Advance

Xloouch

rakoitde commented 6 days ago

If I understood you correctly, the user name is stored in the uid attribute in your LDAP. I have built the function into the username_ldap_attribute branch. You can now make the settings in the .env: authldap.username_ldap_attribute = uid Please test whether it meets your requirements.

xloouch commented 4 days ago

Hi @rakoitde

I've walked through it. I had additionally a lot of changes done. Because it's ment to connect to an AD-LDAP and not a "normal" LDAP.

I haven't done the changes in a "beautifull" way.. if you want, i can send you the changed sources

regards

Xloouch

rakoitde commented 4 days ago

hi @xloouch If I can help with a manageable effort so that the code does not have to be adjusted manually, please send me the desired changes. Otherwise please close the issue.

xloouch commented 3 days ago

hi @rakoitde

i've had to do a lot of "changes" .. beacuse, it seems, your library is ment to be for "AD-LDAP"..

Firstly: If you have any question, about my changed. please don't hesitate to ask.

Now lets start with the attribute-differences

AD

/**
     * The ldap attributes
     *
     * @var list<string>
     */
    public array $attributes = [
        'objectSID', 'distinguishedname', 'displayName', 'title', 'description','givenName', 'sn', 'mail', 'co', 'telephoneNumber', 'mobile', 'company', 'department', 'l', 'postalCode', 'streetAddress', 'displayName', 'samaccountname', 'thumbnailPhoto', 'userAccountControl'];

LDAP Settings:

/**
     * The ldap attributes
     *
     * @var list<string>
     */
    public array $attributes = [
        'dn','uid', 'displayname', 'cn', 'givenname', 'sn', 'mail', 'o', 'telephonenumber', 'mobile', 'roomnumber', 'departmentnumber', 'l', 'postalcode', 'street','c','uidnumber'];

i left the "jpegphoto" out. because I never got it to work.. (instead of "thumbnailPhoto"

ENV settings extended

i needed to extend the ".env" variables with the following:

authldap.ldap_type      = ldap
authldap.login_attribute    = uid
authldap.use_proxyuser      = true // if you need a proxy-user to access the ldap
authldap.proxy_username         =     // full dn of the proxy user 
authldap.proxy_password         =     // password of the proxy user

File src/Commands/CheckCommand.php

public function showConfig() - extended:

CLI::write('  attributes:             ' . CLI::color(implode(', ', $ldapConfig->attributes), 'white'), 'green');
//extension:
        CLI::write('  ldap_type:              ' . CLI::color($ldapConfig->ldap_type, 'white'), 'green');
        CLI::write('  login_attribute:        ' . CLI::color($ldapConfig->login_attribute, 'white'), 'green');
        CLI::write('  use_proxyuser:          ' . CLI::color($ldapConfig->use_proxyuser ? 'true' : 'false', 'white'), 'green');
        CLI::write('  proxy_username:         ' . CLI::color($ldapConfig->proxy_username, 'white'), 'green');
        CLI::write('  proxy_password:         ' . CLI::color($ldapConfig->proxy_password, 'white'), 'green');
        CLI::newLine();

public function bind() - extended

Here I extended it with the ldap-switch and the proxy-user

public function bind()
    {
        if(config('AuthLDAP')->ldap_type=="ad"){
            $ldap_domain = config('AuthLDAP')->ldap_domain;
            $ldap_user   = $ldap_domain . '\\' . $this->username;
        }else{
            $ldap_user = $this->username;
        }

        if(config('AuthLDAP')->use_proxyuser){
            CLI::write('  proxy_username:       ' . CLI::color(config('AuthLDAP')->proxy_username, 'white'), 'green');
            CLI::write('  proxy_password:       ' . CLI::color(config('AuthLDAP')->proxy_password, 'white'), 'green');    
        }
        CLI::write('  bind user:       ' . CLI::color($this->username, 'white'), 'green');

        if(config('AuthLDAP')->ldap_type=="ad"){
            CLI::write('  bind domain:     ' . CLI::color($ldap_domain, 'white'), 'green');
            CLI::write('  bind ldap_user:  ' . CLI::color($ldap_user, 'white'), 'green');
        }else{
            CLI::write('  bind ldap_user:  ' . CLI::color($this->username, 'white'), 'green');
        }

        ldap_set_option($this->connection, LDAP_OPT_PROTOCOL_VERSION, 3);
        ldap_set_option($this->connection, LDAP_OPT_REFERRALS, 0);

        if(config('AuthLDAP')->use_proxyuser){
            #$bind = @ldap_bind($this->connection, config('AuthLDAP')->proxy_username, config('AuthLDAP')->proxy_password);
            try{$bind_proxy = ldap_bind($this->connection, config('AuthLDAP')->proxy_username, config('AuthLDAP')->proxy_password);}
            catch (Exception $e){echo 'Exception in function _authenticate: '. $e->getMessage().'\n';}
            $filter = '('.config('AuthLDAP')->login_attribute.'='.$this->username.')';
            CLI::write('  ldap filter:     ' . CLI::color($filter, 'white'), 'green');

            $search = ldap_search($this->connection, config('AuthLDAP')->search_base, $filter, config('AuthLDAP')->attributes);
            $entries = ldap_get_entries($this->connection, $search);

            $binddn = $entries[0]['dn'];

            CLI::write('  full dn:         ' . CLI::color($binddn, 'white'), 'green');

            $bind = @ldap_bind($this->connection, $binddn, $this->password);
        }else{
            $bind = @ldap_bind($this->connection, $ldap_user, $this->password);
        }

        if ($bind !== false) {
            CLI::write('  isAuthenticated: ' . CLI::color('true', 'white'), 'green');
            CLI::write('  Ldap_connect:    ' . CLI::color(ldap_error($this->connection), 'white'), 'green');
        } else {
            CLI::write('  isAuthenticated: ' . CLI::color('false', 'red'), 'green');
            CLI::write('  Ldap_connect:    ' . CLI::color(ldap_error($this->connection), 'red'), 'green');
        }

        return $bind;
    }

File src/Authentication/LDAPManager.php

now, to the "bigger" changes, which i had to do.. (but they aren't very beautifull

public function auth() //partial rewritten

/**
     *  Authenticate user against LDAP host
     */
    public function auth()
    {
        ldap_set_option($this->connection, LDAP_OPT_PROTOCOL_VERSION, 3);
        ldap_set_option($this->connection, LDAP_OPT_REFERRALS, 0);

        $ldapAttributes ??= $this->config->attributes;
        log_message('debug', 'Starting Auth');
        if($this->config->ldap_type == "ad"){
            log_message('debug', 'LDAPManager.php - auth(): Using AD connector');
            $ldap_domain = config('AuthLDAP')->ldap_domain;
            $ldap_user   = $ldap_domain . '\\' . $this->username;

            $ldap_user   = $this->username;
            $this->bind = @ldap_bind($this->connection, $ldap_user, $this->password);

        }elseif($this->config->ldap_type == "ldap"){
            log_message('debug', 'LDAPManager.php - auth(): Using LDAP connector');
            if($this->config->use_proxyuser){
                try{
                    $this->bind = ldap_bind($this->connection, $this->config->proxy_username, $this->config->proxy_password);
                    log_message('debug', 'LDAPManager.php - auth(): Using Proxy User for pre-authentication');
                }
                catch (Exception $e){log_message('error', 'LDAPManager.php - auth(): Exception in function auth(): '. $e->getMessage().'\n');}
            }else{
                try{$this->bind = ldap_bind($this->connection, $this->username, $this->password);}
                catch (Exception $e){log_message('error', 'LDAPManager.php - auth(): Exception in function auth(): '. $e->getMessage().'\n');}
            }

            if(!$this->bind){
                log_message('error', 'LDAPManager.php - auth(): Unable to perform anonymous/proxy bind');
                die();
            }
            log_message('debug', 'LDAPManager.php - auth(): Successfully bound to directory.  Performing dn lookup for '.$this->username);
            $filter = '('.$this->config->login_attribute.'='.$this->username.')';
            $search = ldap_search($this->connection, $this->config->search_base, $filter, $ldapAttributes);
            $entries = ldap_get_entries($this->connection, $search);

            $binddn = $entries[0]['dn'];
            $this->bind = @ldap_bind($this->connection, $binddn, $this->password);
            if(! $this->bind) {

                log_message('error',"LDAPManager.php - auth(): Failed login attempt: ".$this->username." from ".$_SERVER['REMOTE_ADDR']);
                return FALSE;
            }

        }else{
            log_message('error', json_encode(ldap_error($this->connection)));
        }

        log_message('error', json_encode(ldap_error($this->connection)));
    }

In the following method, i still get errors, when i try to "load" the jpegphoto from the LDAP. Additionally i've lect the part with the AD commented out:

public function loadAttributes() //partial rewritten

    /**
     * Get user attributes
     */
    public function loadAttributes(?string $username = null, ?array $ldapAttributes = null): ?array
    {
        $samaccountname = $username ?? $this->username;
        //log_message('debug',"LDAPManager.php - loadAttributes(): ".$samaccountname." - ".$username." - ".$this->username."\n");
        $ldapAttributes ??= $this->config->attributes;
        //log_message('debug',"LDAPManager.php - loadAttributes(): ".json_encode($ldapAttributes)."\n");

        if($this->config->ldap_type == "ad"){
            $filter = "(samaccountname={$samaccountname})";
        }elseif($this->config->ldap_type == "ldap"){
            $filter = '('.$this->config->login_attribute.'='.$this->username.')';
        }else{
            log_message('error', json_encode(ldap_error($this->connection)));    
        }
        //log_message('debug',"LDAPManager.php - loadAttributes() - this->config: ".print_r($this->config,true)."\n");

        $result = ldap_search($this->connection, $this->config->search_base, $filter, $ldapAttributes);

        if ($result === false) {
            $this->ldap_error = ldap_error($this->connection);

            ldap_get_option($this->connection, LDAP_OPT_DIAGNOSTIC_MESSAGE, $err);
            $this->ldap_diagnostic_message = $err;

            return [];
        }

        $entries = ldap_get_entries($this->connection, $result);

        $countEntries = (int) $entries['count'];
        /*
        $aduser = ldap_first_entry($this->connection, $result);

        if ($aduser === false) {
            return null;
        }

        $adattributes = ldap_get_attributes($this->connection, $aduser);

        $dn       = ldap_get_dn($this->connection, $aduser);
        $this->dn = $dn;

        $adCount    = (int) $adattributes['count'];
        */
        $attributes = [];

        foreach($ldapAttributes as $key){
            //log_message('debug',"LDAPManager.php - loadAttributes() - key: ".$key."\n");
            if(!isset($entries[0][$key])){
                $attributes[$key] = 0;
            }elseif($key == "dn"){
                $attributes[$key] = $entries[0][$key];
            }elseif($key == "jpegphoto"){
                $attributes[$key] = base64_encode($entries[0][$key][0]);
            } else{
                if($entries[0][$key]['count']==1){
                    $attributes[$key] = $entries[0][$key][0];
                }else{
                    unset($entries[0][$key]['count']);
                    foreach($entries[0][$key] as $value){
                        $attributes[$key][]=$value;
                    }
                }
            }
        }

        //log_message('debug',"LDAPManager.php - loadAttributes() - attributes: ".json_encode($attributes)."\n");
        /*
        for ($i = 0; $i < $countEntries; $i++) {
            $key = $adattributes[$i];

            switch ($key) {
                case 'objectSid':
                    $value = $this->sid_decode($adattributes[$key][0]);
                    break;

                case 'jpegPhoto':
                    $value = base64_encode($adattributes[$key][0]);
                    break;

                default:
                    $value = $adattributes[$key][0];
                    break;
            }
            $attributes[$key] = $value;
        }
        */
        $this->attributes = $attributes;

        return $attributes;
    }

I tried this Method, but never used it

public function getThumbnailImage() //partial rewritten -> unused by me

public function getThumbnailImage()
    {
        if (! isset($this->attributes['jpegphoto'])) {
            log_message('debug',"LDAPManager.php - getThumbnailImage(): no jpegphoto in attributes\n");
            return '';
        }
        log_message('debug',"LDAPManager.php - getThumbnailImage(): ".json_encode($this->attributes['jpegphoto'])."\n");

        return '<img src="data:image/jpeg;base64,' . $this->attributes['jpegphoto'] . '">';
    }

I've only partionally implemented the loadTokengroups, cause I currently don't need id:

public function loadTokengroups

public function loadTokengroups(bool $return_group_sids = true)
    {
        if (! isset($this->dn)) {
            return [];
        }
        if($this->config->ldap_type == "ad"){
            $result = ldap_read($this->connection, $this->dn, 'CN=*', ['tokengroups']);
        }else{
            $result = ldap_read($this->connection, $this->dn, 'CN=*', ['memberOf']);
        }
        $tokegroups = ldap_get_entries($this->connection, $result);
        log_message('debug', "LDAPManager.php - loadTokengroups(): ".json_encode($tokegroups));

        $groups = [];

        if ((int) $tokegroups['count'] > 0) {
            if($this->config->ldap_type == "ad"){
                $groups = $tokegroups[0]['tokengroups'];
            }else{
                $groups = $tokegroups[0]['memberof'];
            }
            unset($groups['count']);

            /*
            foreach ($groups as $i => &$sid) {
                $sid = $this->sid_decode($sid);

                if ($return_group_sids) {
                    $groups[$i] = $sid;

                    continue;
                }

                $sid_dn = ldap_read($this->connection, "<SID={$sid}>", 'CN=*', ['dn']);
                if ($sid_dn !== false) {
                    $group      = ldap_get_entries($this->connection, $sid_dn);
                    $group      = $group['count'] === 1 ? $group[0]['dn'] : null;
                    $groups[$i] = $group;
                }
            }*/
        }

        return $groups;
    }

File src/Authentication/Authenticators/LDAP.php

i needed to partially update the Function "check()"

if ($ldapManager->isAuthenticated()) {
            // Update user entity with ldap attributes and group sids
            $ldapAttributes        = $ldapManager->getAttributes();
            //log_message('debug', "LDAP.php - check(): ".json_encode($ldapAttributes)."\n");

            $user->mail            = $ldapAttributes['mail'];
            if(config('AuthLDAP')->ldap_type=="ad"){
                $user->dn              = $ldapAttributes['distinguishedName'];
                $user->object_sid      = $ldapAttributes['objectSid'];
            }else{
                $user->dn              = $ldapAttributes['dn'];
            }

            $user->ldap_attributes = json_encode($ldapAttributes);
            $user->ldap_group_sids = json_encode($ldapManager->getGroupSids());
            $this->provider->update($user->id, $user);

            if (config('AuthLDAP')->storePasswordInSession) {
                $encrypter = Services::encrypter();
                session()->set('password', $encrypter->encrypt($givenPassword));
            }

            return new Result([
                'success'   => true,
                'extraInfo' => $user,
            ]);
        }