Menu

#1939 UserLDAP auto create User

Unassigned
not-a-bug
None
Data model
Critical
2.7.0
defect
2021-02-23
2021-01-22
No

OK, I have more than 30.000 ldap users and it's a non sense to import all of them because probably only 5% will ever open a ticket for support. So I manged to implement auto creation of ldap users. The first attempt was to make a new external authentication extension. But I'm not familiar with PHP and I've not an IDE installed. So I managed on modifying some files. Probably not the best solution but it's functional, so.....

Here are the files modified. I've tested on env-production but they must be in the datamodel, of course.
It requires some new config:

'authent-ldap' => array (
        'host' => 'ldap server',
        'port' => 389,
        'default_user' => 'ldap search user',
        'default_pwd' => 'password',
        'base_dn' => 'base dn',
        'user_query' => '(&(objectCategory=person)(objectClass=user)(sAMAccountName=%1$s))',
        'options' => array (
          17 => 3,
          8 => 0,
        ),
        'start_tls' => true,
        'create_user' => true,
        'default_organization' => 'default organization name',
        'default_profile' => array (
          0 => 'Portal user',
        ),
        'debug' => false,
    ),

and here the snip of modification made:

/var/www/html/application# vi loginform.class.inc.php

    protected function OnCheckCredentials(&$iErrorCode)
        {
                if ($_SESSION['login_mode'] == 'form')
                {
                        $sAuthUser = utils::ReadPostedParam('auth_user', '', 'raw_data');
                        $sAuthPwd = utils::ReadPostedParam('auth_pwd', null, 'raw_data');
                        if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, $_SESSION['login_mode'], 'internal'))
                        {
                                IssueLog::Error("QUAT '$sAuthUser'");
                                if (!UserLDAP::CheckCredentialsAndCreateUser($sAuthUser, $sAuthPwd, $_SESSION['login_mode'], 'internal'))
                                {
                                        $iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
                                        return LoginWebPage::LOGIN_FSM_ERROR;
                                }
                        }
                }
                return LoginWebPage::LOGIN_FSM_CONTINUE;
        }


-------------------------------------------------------------------------------------
/var/www/html/env-production/authent-ldap# vi model.authent-ldap.php


class UserLDAP extends UserInternal implements iSelfRegister
{

....

    public function CheckCredentials($sPassword)
        {
                $sLDAPHost = MetaModel::GetModuleSetting('authent-ldap', 'host', 'localhost');
                $iLDAPPort = MetaModel::GetModuleSetting('authent-ldap', 'port', 389);

                $sDefaultLDAPUser = MetaModel::GetModuleSetting('authent-ldap', 'default_user', '');
                $sDefaultLDAPPwd = MetaModel::GetModuleSetting('authent-ldap', 'default_pwd', '');
                $bLDAPStartTLS = MetaModel::GetModuleSetting('authent-ldap', 'start_tls', false);
                $bCreateUser = MetaModel::GetModuleSetting('authent-ldap', 'create_user', false);
                $sDefaultOrganization = MetaModel::GetModuleSetting('authent-ldap', 'default_organization', '');
                $aRequestedProfiles = MetaModel::GetModuleSetting('authent-ldap', 'default_profile', '');

                $aOptions = MetaModel::GetModuleSetting('authent-ldap', 'options', array());
                if (array_key_exists(LDAP_OPT_DEBUG_LEVEL, $aOptions))
                {
                        // Set debug level before trying to connect, so that debug info appear in the PHP error log if ldap_connect goes wrong
                        $bRet = ldap_set_option($hDS, LDAP_OPT_DEBUG_LEVEL, $aOptions[LDAP_OPT_DEBUG_LEVEL]);
                        $this->LogMessage("ldap_set_option('$name', '$value') returned ".($bRet ? 'true' : 'false'));
                }
                $hDS = @ldap_connect($sLDAPHost, $iLDAPPort);
                if ($hDS === false)
                {
                        $this->LogMessage("ldap_authentication: can not connect to the LDAP server '$sLDAPHost' (port: $iLDAPPort). Check the configuration file config-itop.php.");
                        return false;
                }
                foreach($aOptions as $name => $value)
                {
                        $bRet = ldap_set_option($hDS, $name, $value);
                        $this->LogMessage("ldap_set_option('$name', '$value') returned ".($bRet ? 'true' : 'false'));
                }
                if ($bLDAPStartTLS)
                {
                        $this->LogMessage("ldap_authentication: start tls required.");
                        $hStartTLS = ldap_start_tls($hDS);
                        //$this->LogMessage("ldap_authentication: hStartTLS = '$hStartTLS'");
                if (!$hStartTLS)
                        {
                                $this->LogMessage("ldap_authentication: start tls failed.");
                                return false;
                        }
                }

                if ($bind = @ldap_bind($hDS, $sDefaultLDAPUser, $sDefaultLDAPPwd))
                {
                        // Search for the person, using the specified query expression
                        $sLDAPUserQuery = MetaModel::GetModuleSetting('authent-ldap', 'user_query', '');
                        $sBaseDN = MetaModel::GetModuleSetting('authent-ldap', 'base_dn', '');

                        $sLogin = $this->Get('login');
                        $iContactId = $this->Get('contactid');
                        $sFirstName = '';
                        $sLastName = '';
                        $sEMail = '';
                        if ($iContactId > 0)
                        {
                                $oPerson = MetaModel::GetObject('Person', $iContactId);
                                if (is_object($oPerson))
                                {
                                        $sFirstName = $oPerson->Get('first_name');
                                        $sLastName = $oPerson->Get('name');
                                        $sEMail = $oPerson->Get('email');
                                }
                        }
                        // %1$s => login
                        // %2$s => first name
                        // %3$s => last name
                        // %4$s => email
                        $sQuery = sprintf($sLDAPUserQuery, $sLogin, $sFirstName, $sLastName, $sEMail);
                        $hSearchResult = @ldap_search($hDS, $sBaseDN, $sQuery);

                        $iCountEntries = ($hSearchResult !== false) ? @ldap_count_entries($hDS, $hSearchResult) : 0;
                        switch($iCountEntries)
            {
                                case 1:
                                // Exactly one entry found, let's check the password by trying to bind with this user
                                $aEntry = ldap_get_entries($hDS, $hSearchResult);
                                $sUserDN = $aEntry[0]['dn'];
                                $bUserBind =  @ldap_bind($hDS, $sUserDN, $sPassword);

                                if (($bUserBind !== false) && !empty($sPassword))
                                {
                                        $sFirstName = $aEntry[0]['sn'][0];
                                        $sLastName = $aEntry[0]['givenname'][0];
                                        $sEMail = $aEntry[0]['mail'][0];
                                        ldap_unbind($hDS);
                                        $sAuthentication = 'internal';
                                        $oUser = LoginWebPage::FindUser($sLogin, true, ucfirst(strtolower($sAuthentication)));
                                        if($bCreateUser && is_null($oUser))
                                        {
                                                $oPerson=LoginWebPage::ProvisionPerson($sFirstName, $sLastName, $sEMail, $sDefaultOrganization);
                                                LoginWebPage::ProvisionUser($sLogin, $oPerson, $aRequestedProfiles, 'UserLDAP');
                                        }
                                        return true; // Password Ok
                                }
                                $this->LogMessage("ldap_authentication: wrong password for user: '$sUserDN'.");
                                return false; // Wrong password
                                break;

                                case 0:
                                // User not found...
                                $this->LogMessage("ldap_authentication: no entry found with the query '$sQuery', base_dn = '$sBaseDN'. User not found in LDAP.");
                                break;

                                default:
                                // More than one entry... maybe the query is not specific enough...
                                $this->LogMessage("ldap_authentication: several (".ldap_count_entries($hDS, $hSearchResult).") entries match the query '$sQuery', base_dn = '$sBaseDN', check that the query defined in config-itop.php is specific enough.");
                        }
            return false;
                }
                else
                {
                        // Trace: invalid default user for LDAP initial binding
                        $this->LogMessage("ldap_authentication: can not bind to the LDAP server '$sLDAPHost' (port: $iLDAPPort), user='$sDefaultLDAPUser', pwd='$sDefaultLDAPPwd'. Error: '".ldap_error($hDS)."'. Check the configuration file config-itop.php.");
                        return false;
                }
        }


        /**
         * Called when no user is found in iTop for the corresponding 'name'. This method
         * can create/synchronize the User in iTop with an external source (such as AD/LDAP) on the fly
         * @param string $sName The typed-in user name
         * @param string $sPassword The typed-in password
         * @param string $sLoginMode The login method used (cas|form|basic|url)
         * @param string $sAuthentication The authentication method used (any|internal|external)
         * @return bool true if the user is a valid one, false otherwise
         */
        public static function CheckCredentialsAndCreateUser($sName, $sPassword, $sLoginMode, $sAuthentication)
        {
                IssueLog::Error("TRE '$sName'");
                $oUser = MetaModel::NewObject('UserLDAP');
                $oUser->Set('login', $sName);
                $oUser->Set('language', MetaModel::GetConfig()->GetDefaultLanguage());
                return $oUser->CheckCredentials($sPassword);
        }


    /**
         * Called after the user has been authenticated and found in iTop. This method can
         * Update the user's definition on the fly (profiles...) to keep it in sync with an external source
         * @param User $oUser The user to update/synchronize
         * @param string $sLoginMode The login mode used (cas|form|basic|url)
         * @param string $sAuthentication The authentication method used
         * @return void
         */
        public static function UpdateUser(User $oUser, $sLoginMode, $sAuthentication)
        {
                return false;
        }       

-------------------------------------------------------------------------------------------------------------------------------
/var/www/html/application# vi loginwebpage.class.inc.php

    /**
         * Provisioning API: Create or update a User
         *
         * @api
         *
         * @param string $sAuthUser
         * @param Person $oPerson
         * @param array $aRequestedProfiles profiles to add to the new user
         *
         * @return \UserExternal|\UserLdap|null
         */
        public static function ProvisionUser($sAuthUser, $oPerson, $aRequestedProfiles, $sUserType = 'UserExternal')
        {
                if (!MetaModel::IsValidClass('URP_Profiles'))
                {
                        IssueLog::Error("URP_Profiles is not a valid class. Automatic creation of Users is not supported in this context, sorry.");
                        return null;
                }

                /** @var UserExternal $oUser */
                $oUser = null;
                try
                {
                        CMDBObject::SetTrackOrigin('custom-extension');
                        $sInfo = 'External User provisioning';
                        if (isset($_SESSION['login_mode']))
                        {
                                $sInfo .= " ({$_SESSION['login_mode']})";
                        }
                        CMDBObject::SetTrackInfo($sInfo);

                        $oUser = MetaModel::GetObjectByName($sUserType, $sAuthUser, false);
                        if (is_null($oUser))
                        {
                                $oUser = MetaModel::NewObject($sUserType);
                $oUser->Set('login', $sAuthUser);
                                $oUser->Set('contactid', $oPerson->GetKey());
                                $oUser->Set('language', MetaModel::GetConfig()->GetDefaultLanguage());
                        }

                        // read all the existing profiles
                        $oProfilesSearch = new DBObjectSearch('URP_Profiles');
                        $oProfilesSet = new DBObjectSet($oProfilesSearch);
                        $aAllProfiles = array();
                        while ($oProfile = $oProfilesSet->Fetch())
                        {
                                $aAllProfiles[strtolower($oProfile->GetName())] = $oProfile->GetKey();
                        }

                        $aProfiles = array();
                        foreach ($aRequestedProfiles as $sRequestedProfile)
                        {
                                $sRequestedProfile = strtolower($sRequestedProfile);
                                if (isset($aAllProfiles[$sRequestedProfile]))
                                {
                                        $aProfiles[] = $aAllProfiles[$sRequestedProfile];
                                }
                        }

                        if (empty($aProfiles))
                        {
                                throw new Exception(Dict::S('UI:Login:Error:NoValidProfiles'));
                        }

                        // Now synchronize the profiles
                        $oProfilesSet = DBObjectSet::FromScratch('URP_UserProfile');
                        $sOrigin = 'External User provisioning';
                        if (isset($_SESSION['login_mode']))
                        {
                                $sOrigin .= " ({$_SESSION['login_mode']})";
                        }
            foreach ($aProfiles as $iProfileId)
                        {
                                $oLink = new URP_UserProfile();
                                $oLink->Set('profileid', $iProfileId);
                                $oLink->Set('reason', $sOrigin);
                                $oProfilesSet->AddObject($oLink);
                        }
                        $oUser->Set('profile_list', $oProfilesSet);
                        if ($oUser->IsModified())
                        {
                                $oUser->DBWrite();
                        }
                }
                catch (Exception $e)
                {
                        IssueLog::Error($e->getMessage());
                }

                return $oUser;
        }

----------------------------------------------------------------------------------------------------------

Discussion

  • Pierre Goiffon

    Pierre Goiffon - 2021-01-25

    Hello Romano,

    Thanks for sharing !
    But... Are you asking something ?

    Next time please use the forums ("discussion" tab, then "new topic" button on the top left) : we prefer tickets to be used only for qualified bug reports and enhancements requests.
    In consequence I'm closing this.

    Also, we strongly advise against modifying iTop core code : that will complicate a lot iTop upgrades, cause you'll have to report all of those modifications each times !
    iTop provides a complete customization system that should be used instead !
    About user provisionning, all is explained here : Login and User Provisioning API
    This custom code can be added to iTop using modules, see Customizing iTop [iTop Documentation]

     
  • Pierre Goiffon

    Pierre Goiffon - 2021-01-25
    • status: new --> not-a-bug
    • assigned_to: Pierre Goiffon
     
  • Romano Trampus

    Romano Trampus - 2021-01-25
     

    Last edit: Romano Trampus 2021-01-25
  • Pierre Goiffon

    Pierre Goiffon - 2021-02-23

    Hello Romano, I've received an email with your answer but when opening this ticket your message is blank ?!??

    In the email I received you were asking if it's ok to contribute and yes of course it is ! See the CONTRIBUTING.md files in our public repos.

     

    Last edit: Pierre Goiffon 2021-02-24
    • Romano Trampus

      Romano Trampus - 2021-02-23

      Hi Pierre, I moved to a discussion, and probably it's still there :-) Thank
      you,
      romano

       
      👍
      1

      Last edit: Pierre Goiffon 2021-02-24

Log in to post a comment.