<?php

/*
 * Copyright 2005 - 2025 Centreon (https://www.centreon.com/)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * For more information : contact@centreon.com
 *
 */

use LDAP\Connection;

require_once __DIR__ . '/centreonAuth.class.php';
require_once __DIR__ . '/centreonLDAP.class.php';

/**
 * Class
 *
 * @class CentreonAuthLDAP
 * @description Class for Ldap authentication
 */
class CentreonAuthLDAP
{
    /** @var Connection|resource */
    public $ds;

    /** @var CentreonDB */
    protected $pearDB;

    /** @var CentreonLDAP */
    protected $ldap;

    /** @var CentreonUserLog */
    protected $CentreonLog;

    /** @var mixed[] */
    protected $contactInfos;

    /** @var string */
    protected $typePassword;

    /** @var int */
    protected $debug;

    /** @var bool */
    protected $firstCheck = true;

    /** @var int */
    protected $arId;

    /**
     * CentreonAuthLDAP constructor
     *
     * @param CentreonDB $pearDB Connection to centreon database
     * @param CentreonUserLog $CentreonLog Log event
     * @param string $login The username
     * @param string $password The user password
     * @param array $contactInfos
     * @param int $arId | Auth Ressource ID
     *
     * @throws PDOException
     */
    public function __construct($pearDB, $CentreonLog, $login, $password, $contactInfos, $arId)
    {
        $this->arId = $arId;
        $this->pearDB = $pearDB;

        $this->CentreonLog = $CentreonLog;

        $this->ldap = new CentreonLDAP($pearDB, $CentreonLog, $arId);
        $this->ldap->connect();
        $this->ds = $this->ldap->getDs();

        // Set contact Information
        $this->contactInfos = $contactInfos;
        if (! empty($this->contactInfos['contact_ldap_dn'])) {
            $this->contactInfos['contact_ldap_dn'] = html_entity_decode($this->contactInfos['contact_ldap_dn']);
        }

        // Keep password
        $this->typePassword = $password;

        $this->debug = $this->getLogFlag();
    }

    /**
     * Check the user pass
     */
    public function checkPassword()
    {
        if (empty($this->contactInfos['contact_ldap_dn'])) {
            $this->contactInfos['contact_ldap_dn'] = $this->ldap->findUserDn($this->contactInfos['contact_alias']);
        } elseif (
            ($userDn = $this->ldap->findUserDn($this->contactInfos['contact_alias']))
            && mb_strtolower($userDn, 'UTF-8') !== mb_strtolower($this->contactInfos['contact_ldap_dn'], 'UTF-8') // Ignore case for LDAP and database contact info comparison
        ) { // validate if user exists in this resource
            if (! $userDn) {
                // User resource error
                return CentreonAuth::PASSWORD_INVALID;
            }

            // User alias matches the contact alias but the user DN is different
            // We have to update the user DN
            $updateUserDnOK = $this->updateUserDn();
            if ($updateUserDnOK === false) {
                // LDAP fallback
                return CentreonAuth::PASSWORD_CANNOT_BE_VERIFIED;
            }
        }

        if (empty(trim($this->contactInfos['contact_ldap_dn']))) {
            return CentreonAuth::PASSWORD_CANNOT_BE_VERIFIED;
        }

        if ($this->debug) {
            $this->CentreonLog->insertLog(
                3,
                'LDAP AUTH : ' . $this->contactInfos['contact_ldap_dn'] . ' :: Authentication in progress'
            );
        }

        @ldap_bind($this->ds, $this->contactInfos['contact_ldap_dn'], $this->typePassword);

        if (empty($this->ds)) {
            if ($this->debug) {
                $this->CentreonLog->insertLog(3, 'DS empty');
            }

            return CentreonAuth::PASSWORD_CANNOT_BE_VERIFIED;
        }

        /*
         * In some case, we fallback to local Auth
         * 0 : Bind successful => Default case
         * 2 : Protocol error
         * -1 : Can't contact LDAP server (php4) => Fallback
         * 51 : Server is busy => Fallback
         * 52 : Server is unavailable => Fallback
         * 81 : Can't contact LDAP server (php5) => Fallback
         */
        switch (ldap_errno($this->ds)) {
            case 0:
                if ($this->debug) {
                    $this->CentreonLog->insertLog(3, 'LDAP AUTH : Success');
                }
                if ($this->updateUserDn() == false) {
                    return CentreonAuth::PASSWORD_INVALID;
                }

                return CentreonAuth::PASSWORD_VALID;
            case -1:
            case 2: // protocol error
            case 51: // busy
            case 52: // unavailable
            case 81: // server down
                if ($this->debug) {
                    $this->CentreonLog->insertLog(3, 'LDAP AUTH : ' . ldap_error($this->ds));
                }

                return CentreonAuth::PASSWORD_CANNOT_BE_VERIFIED;
            default:
                if ($this->debug) {
                    $this->CentreonLog->insertLog(3, 'LDAP AUTH : ' . ldap_error($this->ds));
                }

                return CentreonAuth::PASSWORD_INVALID;
        }
    }

    /**
     * Search and update the user dn at login
     *
     * @throws exception
     * @return bool If the DN is modified
     */
    public function updateUserDn(): bool
    {
        $contactAlias = html_entity_decode($this->contactInfos['contact_alias'], ENT_QUOTES, 'UTF-8');

        if ($this->ldap->rebind()) {
            $userDn = $this->ldap->findUserDn($contactAlias);
            if ($userDn === false) {
                $this->CentreonLog->insertLog(3, 'LDAP AUTH - Error : No DN for user ' . $contactAlias);

                return false;
            }

            // Get ldap user information
            $userInfos = $this->ldap->getEntry($userDn);
            $userDisplay = $userInfos[$this->ldap->getAttrName('user', 'name')];
            // Get the first if there are multiple entries
            if (is_array($userDisplay)) {
                $userDisplay = $userDisplay[0];
            }
            // Replace space by underscore
            $userDisplay = str_replace([' ', ','], '_', $userDisplay);
            // Delete parenthesis
            $userDisplay = str_replace(['(', ')'], '', $userDisplay);

            // getting user's email
            $userEmail = $this->contactInfos['contact_email'];
            $ldapEmailValue = $userInfos[$this->ldap->getAttrName('user', 'email')];
            if (isset($ldapEmailValue)) {
                $userEmail = (trim(is_array($ldapEmailValue) && $ldapEmailValue !== [] ? current($ldapEmailValue) : $ldapEmailValue));
            }

            if ($userEmail === '') {
                // empty email is not an error, we replace it with a dash
                $userEmail = '-';
            }
            // getting user's pager
            $userPager = $this->contactInfos['contact_pager'];
            $ldapUserPager = $userInfos[$this->ldap->getAttrName('user', 'pager')];
            if (isset($ldapUserPager)) {
                $userPager = (trim(is_array($ldapUserPager) && $ldapUserPager !== [] ? current($ldapUserPager) : $ldapUserPager));
            }

            /**
             * Searching if the user already exist in the DB and updating OR adding him
             */
            if (isset($this->contactInfos['contact_id'])) {
                try {
                    // checking if the LDAP synchronization on login is enabled or needed
                    if (! $this->ldap->isSyncNeededAtLogin($this->arId, $this->contactInfos['contact_id'])) {
                        // skipping the update
                        return true;
                    }

                    $this->CentreonLog->insertLog(
                        3,
                        'LDAP AUTH : Updating user DN of ' . $userDisplay
                    );
                    $stmt = $this->pearDB->prepare(
                        <<<'SQL'
                            UPDATE contact SET
                            contact_ldap_dn = :userDn,
                            contact_name = :userDisplay,
                            contact_email = :userEmail,
                            contact_pager = :userPager,
                            reach_api_rt = :reachApiRt,
                            ar_id = :arId
                            WHERE contact_id = :contactId
                            SQL
                    );
                    $stmt->bindValue(':userDn', $userDn, PDO::PARAM_STR);
                    $stmt->bindValue(':userDisplay', $userDisplay, PDO::PARAM_STR);
                    $stmt->bindValue(':userEmail', $userEmail, PDO::PARAM_STR);
                    $stmt->bindValue(':userPager', $userPager, PDO::PARAM_STR);
                    $stmt->bindValue(':reachApiRt', $this->contactInfos['reach_api_rt'], PDO::PARAM_INT);
                    $stmt->bindValue(':arId', $this->arId, PDO::PARAM_INT);
                    $stmt->bindValue(':contactId', $this->contactInfos['contact_id'], PDO::PARAM_INT);
                    $stmt->execute();
                } catch (PDOException $e) {
                    $this->CentreonLog->insertLog(
                        3,
                        'LDAP AUTH - Error : when trying to update user : ' . $userDisplay
                    );

                    return false;
                }
                $this->contactInfos['contact_ldap_dn'] = $userDn;

                // Updating user's contactgroup relations from LDAP
                try {
                    include_once realpath(__DIR__ . '/centreonContactgroup.class.php');
                    $cgs = new CentreonContactgroup($this->pearDB);
                    $cgs->syncWithLdap();
                } catch (Exception $e) {
                    $this->CentreonLog->insertLog(
                        3,
                        'LDAP AUTH - Error : when updating ' . $userDisplay . '\'s ldap contactgroups'
                    );
                }

                $this->ldap->setUserCurrentSyncTime($this->contactInfos);
                $this->CentreonLog->insertLog(
                    3,
                    'LDAP AUTH : User DN updated for ' . $userDisplay
                );

                return true;
            }
            /**
             * The current user wasn't found. Adding him to the DB
             * First, searching if a contact template has been specified in the LDAP parameters
             */
            $res = $this->pearDB->prepare(
                "SELECT ari_value FROM `auth_ressource_info` a, `contact` c
                    WHERE a.`ari_name` = 'ldap_contact_tmpl'
                    AND a.ar_id = :arId
                    AND a.ari_value = c.contact_id"
            );
            try {
                $res->bindValue(':arId', $this->arId, PDO::PARAM_INT);
                $res->execute();

                $row = $res->fetch();
                if (empty($row['ari_value'])) {
                    $this->CentreonLog->insertLog(3, 'LDAP AUTH - Error : No contact template defined.');

                    return false;
                }
                $tmplId = $row['ari_value'];
            } catch (PDOException $e) {
                $this->CentreonLog->insertLog(
                    3,
                    'LDAP AUTH - Error : when trying to get LDAP data for : ' . $userDisplay
                );

                return false;
            }

            // Inserting the new user in the database
            $stmt = $this->pearDB->prepare(
                <<<'SQL'
                    INSERT INTO contact
                        (contact_template_id, contact_alias, contact_name, contact_auth_type, contact_ldap_dn, ar_id,
                        contact_email, contact_pager, contact_oreon, reach_api_rt, contact_activate, contact_register,
                        contact_enable_notifications)
                    VALUES
                        (:templateId, :contactAlias, :userDisplay, 'ldap', :userDn, :arId, :userEmail, :userPager, '1',
                        1, '1', '1', '2')
                    SQL
            );
            try {
                $stmt->bindValue(':templateId', $tmplId, PDO::PARAM_INT);
                $stmt->bindValue(':contactAlias', $contactAlias, PDO::PARAM_STR);
                $stmt->bindValue(':userDisplay', $userDisplay, PDO::PARAM_STR);
                $stmt->bindValue(':userDn', $userDn, PDO::PARAM_STR);
                $stmt->bindValue(':arId', $this->arId, PDO::PARAM_INT);
                $stmt->bindValue(':userEmail', $userEmail, PDO::PARAM_STR);
                $stmt->bindValue(':userPager', $userPager, PDO::PARAM_STR);
                $stmt->execute();

                // Retrieving the created contact_id
                $res = $this->pearDB->prepare(
                    'SELECT contact_id FROM contact
                        WHERE contact_ldap_dn = :userDn'
                );
                $res->bindValue(':userDn', $userDn, PDO::PARAM_STR);
                $res->execute();
                $row = $res->fetch();
                $this->contactInfos['contact_id'] = $row['contact_id'];

                /**
                 * Searching the user's affiliated contactgroups in the LDAP
                 */
                $listGroup = $this->ldap->listGroupsForUser($userDn);
                $listGroupStr = '';
                foreach ($listGroup as $gName) {
                    if ($listGroupStr !== '') {
                        $listGroupStr .= ',';
                    }
                    $listGroupStr .= "'" . $gName . "'";
                }
                if ($listGroupStr === '') {
                    $listGroupStr = "''";
                }
                $res2 = $this->pearDB->query(
                    'SELECT cg_id FROM contactgroup
                        WHERE cg_name IN (' . $listGroupStr . ')'
                );

                // Inserting the relation between the LDAP's contactgroups and the user
                $stmt = $this->pearDB->prepare(
                    'INSERT INTO contactgroup_contact_relation
                        (contactgroup_cg_id, contact_contact_id)
                        VALUES (:ldapCg, :contactId)'
                );
                while ($row2 = $res2->fetch()) {
                    $stmt->bindValue(':ldapCg', $row2['cg_id'], PDO::PARAM_INT);
                    $stmt->bindValue(':contactId', $this->contactInfos['contact_id'], PDO::PARAM_INT);
                    $stmt->execute();
                }
                $this->ldap->setUserCurrentSyncTime($this->contactInfos);

                $this->CentreonLog->insertLog(3, 'LDAP AUTH - New user DN added : ' . $contactAlias);

                // Inserting the relation between the LDAP's default contactgroup and the user
                // @returns true if everything goes well
                return $this->ldap->addUserToLdapDefaultCg(
                    $this->arId,
                    $this->contactInfos['contact_id']
                );
            } catch (PDOException $e) {
                $this->CentreonLog->insertLog(
                    3,
                    'LDAP AUTH - Error : processing new user ' . $userDisplay . ' from ldap id : ' . $this->arId
                );
            }

        }

        return false;
    }

    /**
     * Is loging enable ?
     *
     * @throws PDOException
     * @return int 1 enable 0 disable
     */
    private function getLogFlag()
    {
        $res = $this->pearDB->query("SELECT value FROM options WHERE `key` = 'debug_ldap_import'");
        $data = $res->fetch();

        return $data['value'] ?? 0;
    }
}
