File indexing completed on 2024-12-22 05:33:26

0001 <?php
0002 
0003 /**
0004  *  ocs-webserver
0005  *
0006  *  Copyright 2016 by pling GmbH.
0007  *
0008  *    This file is part of ocs-webserver.
0009  *
0010  *    This program is free software: you can redistribute it and/or modify
0011  *    it under the terms of the GNU Affero General Public License as
0012  *    published by the Free Software Foundation, either version 3 of the
0013  *    License, or (at your option) any later version.
0014  *
0015  *    This program is distributed in the hope that it will be useful,
0016  *    but WITHOUT ANY WARRANTY; without even the implied warranty of
0017  *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0018  *    GNU Affero General Public License for more details.
0019  *
0020  *    You should have received a copy of the GNU Affero General Public License
0021  *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
0022  *
0023  * Created: 08.05.2017
0024  */
0025 class Backend_CldapController extends Local_Controller_Action_CliAbstract
0026 {
0027 
0028     const filename = "members";
0029 
0030     protected $domain;
0031     protected $tld;
0032     /** @var Zend_Config */
0033     protected $config;
0034     protected $log;
0035 
0036     /**
0037      * @inheritDoc
0038      */
0039     public function __construct(
0040         Zend_Controller_Request_Abstract $request,
0041         Zend_Controller_Response_Abstract $response,
0042         array $invokeArgs = array()
0043     ) {
0044         parent::__construct($request, $response, $invokeArgs);
0045         $this->config = Zend_Registry::get('config')->settings->server->ldap;
0046         $this->log = self::initLog();
0047         $this->_helper->viewRenderer->setNoRender(false);
0048     }
0049 
0050     /**
0051      * @return Zend_Log
0052      * @throws Zend_Log_Exception
0053      */
0054     private static function initLog()
0055     {
0056         $writer = new Zend_Log_Writer_Stream(APPLICATION_DATA . '/logs/ldap-' . date("Ymd-His") . '.log');
0057         $logger = new Zend_Log($writer);
0058 
0059         return $logger;
0060     }
0061 
0062     /**
0063      * @throws Default_Model_Ocs_Exception
0064      * @throws Zend_Db_Statement_Exception
0065      * @throws Zend_Exception
0066      */
0067     public function runAction()
0068     {
0069         ini_set('memory_limit', '1024M');
0070 
0071         $force = (boolean)$this->getParam('force', false);
0072         $method = $this->getParam('method', 'create');
0073 
0074         $this->log->info("METHOD: {$method}\n--------------\n");
0075         $this->log->info(print_r($this->config->toArray(), true));
0076 
0077         if ($this->hasParam('member_id')) {
0078             $member_id = $this->getParam('member_id');
0079             $operator = $this->getParam('op', null);
0080             $members = $this->getMemberList($member_id, $operator);
0081         } else {
0082             $members = $this->getMemberList();
0083         }
0084 
0085         if ('create' == $method) {
0086             $this->exportMembers($members, $force);
0087 
0088             return;
0089         }
0090         if ('update' == $method) {
0091             $this->updateMembers($members);
0092 
0093             return;
0094         }
0095         if ('validate' == $method) {
0096             $this->validateMembers($members, $force);
0097 
0098             return;
0099         }
0100         if ('updateAvatar' == $method) {
0101             $this->updateAvatar($members);
0102 
0103             return;
0104         }
0105         if ('updatePassword' == $method) {
0106             $this->updatePassword($members);
0107 
0108             return;
0109         }
0110     }
0111 
0112     /**
0113      * @param int|null $memberId
0114      * @param string   $operator
0115      *
0116      * @return Zend_Db_Statement_Interface
0117      * @throws Zend_Db_Statement_Exception
0118      */
0119     private function getMemberList($memberId = null, $operator = "=")
0120     {
0121         $filter = "";
0122         if (empty($operator)) {
0123             $operator = "=";
0124         }
0125         if ($operator == "gt") {
0126             $operator = ">";
0127         }
0128         if ($operator == "lt") {
0129             $operator = "<";
0130         }
0131         if (isset($memberId)) {
0132             $filter = " AND `m`.`member_id` {$operator} " . $memberId;
0133         }
0134 
0135         $sql = "
0136             SELECT `mei`.`external_id`,`m`.`member_id`, `m`.`username`, `me`.`email_address`, `m`.`password`, `m`.`roleId`, `m`.`firstname`, `m`.`lastname`, `m`.`profile_image_url`, `m`.`created_at`, `m`.`changed_at`, `m`.`source_id`
0137             FROM `member` AS `m`
0138             LEFT JOIN `member_email` AS `me` ON `me`.`email_member_id` = `m`.`member_id` AND `me`.`email_primary` = 1
0139             LEFT JOIN `member_external_id` AS `mei` ON `mei`.`member_id` = `m`.`member_id`
0140             WHERE `m`.`is_active` = 1 
0141               AND `m`.`is_deleted` = 0 
0142               AND `me`.`email_checked` IS NOT NULL 
0143               AND `me`.`email_deleted` = 0
0144               AND LOCATE('_double', `m`.`username`) = 0 
0145               AND LOCATE('_double', `me`.`email_address`) = 0
0146             {$filter}
0147             ORDER BY `m`.`member_id`
0148             # LIMIT 100
0149         ";
0150 
0151         $result = Zend_Db_Table::getDefaultAdapter()->query($sql);
0152 
0153         $this->log->info("Load : " . $result->rowCount() . " members...");
0154 
0155         return $result;
0156     }
0157 
0158     /**
0159      * @param Zend_Db_Statement_Interface $members
0160      *
0161      * @param bool                        $force
0162      *
0163      * @return bool
0164      * @throws Zend_Db_Statement_Exception
0165      * @throws Zend_Exception
0166      */
0167     private function exportMembers($members, $force = false)
0168     {
0169         $modelOcsLdap = new Default_Model_Ocs_Ldap();
0170 
0171         while ($member = $members->fetch()) {
0172             try {
0173                 $ldapUserData = $modelOcsLdap->addUserFromArray($member, $force);
0174             } catch (Zend_Ldap_Exception $e) {
0175                 $this->log->info("process " . Zend_Json::encode($member));
0176                 $this->log->err($e->getMessage() . PHP_EOL . mb_split("\n",$e->getTraceAsString())[0]);
0177 
0178                 continue;
0179             }
0180             $messages = $modelOcsLdap->getMessages();
0181             if (isset($messages[0]) AND $messages[0] != Default_Model_Ocs_Ldap::LDAP_SUCCESS) {
0182                 $this->log->info("process " . Zend_Json::encode($member));
0183                 $this->log->info("messages " . Zend_Json::encode($messages));
0184             }
0185         }
0186 
0187         return true;
0188     }
0189 
0190     /**
0191      * @param int    $member_id
0192      * @param string $status_msg
0193      * @param string $response_msg
0194      * @param string $ldap_new
0195      * @param string $ldap_old
0196      *
0197      * CREATE TABLE `log_ldap` (
0198      * `id` BINARY(16) NOT NULL,
0199      * `member_id` int(11) NOT NULL,
0200      * `status` varchar(45) NOT NULL,
0201      * `response_msg` varchar(255) DEFAULT NULL,
0202      * `json_ldap_old` text DEFAULT NULL,
0203      * `json_ldap_new` text DEFAULT NULL,
0204      * `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
0205      * PRIMARY KEY (`id`),
0206      * KEY `idx_member_id` (`member_id`)
0207      * ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
0208      *
0209      */
0210     private function dbLog($member_id, $status_msg, $response_msg, $ldap_new, $ldap_old)
0211     {
0212         $sql = "INSERT INTO `log_ldap` 
0213                (`id`,`member_id`, `status`, `response_msg`, `json_ldap_old`, `json_ldap_new`) 
0214                VALUES 
0215                (UNHEX(REPLACE(UUID(),'-','')),:memberId, :statusVal, :msgVal, :dbVal, :ldapVal)";
0216         $db = Zend_Db_Table::getDefaultAdapter();
0217         $db->query($sql,
0218             array(
0219                 'memberId'  => $member_id,
0220                 'statusVal' => $status_msg,
0221                 'msgVal'    => $response_msg,
0222                 'ldapVal'   => $ldap_new,
0223                 'dbVal'     => $ldap_old
0224             ));
0225     }
0226 
0227     /**
0228      * @param Zend_Db_Statement_Interface $members
0229      *
0230      * @return bool
0231      * @throws Zend_Db_Statement_Exception
0232      * @throws Zend_Exception
0233      */
0234     private function updateMembers($members)
0235     {
0236         $model_Ocs_Ldap = new Default_Model_Ocs_Ldap();
0237         // create an org unit for backup user data
0238         $ou = "member-bkp-" . date("Ymd-Hi");
0239         $entry_ou_dn = $this->createBackupTree($model_Ocs_Ldap, $ou);
0240 
0241         while ($member = $members->fetch()) {
0242             try {
0243                 $model_Ocs_Ldap->resetMessages();
0244                 $ldapUser = $model_Ocs_Ldap->getLdapUserByMemberId($member['member_id']);
0245                 if (empty($ldapUser)) {
0246                     throw new Exception('user not found');
0247                 }
0248 
0249                 $model_Ocs_Ldap->updateUserFromArray($member);
0250                 $ldapUser = $this->storeBackupEntry($model_Ocs_Ldap, $member, $entry_ou_dn, $ldapUser);
0251             } catch (Exception $e) {
0252                 $this->log->info("process " . json_encode($member));
0253                 $this->log->info($e->getMessage() . PHP_EOL . $e->getTraceAsString());
0254 
0255                 continue;
0256             }
0257             $messages = $model_Ocs_Ldap->getMessages();
0258             if (isset($messages[0]) AND $messages[0] != "Success") {
0259                 $this->log->info("process " . Zend_Json::encode($member));
0260                 $this->log->info("messages " . Zend_Json::encode($messages));
0261             }
0262         }
0263 
0264         return true;
0265     }
0266 
0267     /**
0268      * @param Default_Model_Ocs_Ldap $modelOcsLdap
0269      * @param                        $ou
0270      * @return string
0271      * @throws Zend_Exception
0272      */
0273     private function createBackupTree(Default_Model_Ocs_Ldap $modelOcsLdap, $ou)
0274     {
0275         $entry_ou = $modelOcsLdap->createEntryOrgUnit($ou);
0276         $entry_ou_dn = $modelOcsLdap->addOrgUnit($entry_ou, $ou);
0277         $this->log->info("name for backup in ldap tree: {$entry_ou_dn}");
0278 
0279         return $entry_ou_dn;
0280     }
0281 
0282     /**
0283      * @param Default_Model_Ocs_Ldap $modelOcsLdap
0284      * @param array                  $member
0285      * @param string                 $entry_ou_dn
0286      * @param array                  $ldapUser
0287      * @return array
0288      */
0289     private function storeBackupEntry(
0290         Default_Model_Ocs_Ldap $modelOcsLdap,
0291         array $member,
0292         $entry_ou_dn,
0293         array $ldapUser
0294     ) {
0295         // backup old entry
0296         $cn = Zend_Ldap_Attribute::getAttribute($ldapUser, 'cn', 0);
0297         $dnBackup = $modelOcsLdap->getDnForUser($cn, $entry_ou_dn, false);
0298         unset($ldapUser['dn']);
0299         $modelOcsLdap->addEntry($ldapUser, $dnBackup);
0300 
0301         return $ldapUser;
0302     }
0303 
0304     /**
0305      * @param      $members
0306      * @param bool $force
0307      * @return bool
0308      *
0309      * @throws Default_Model_Ocs_Exception
0310      * @throws Zend_Exception
0311      */
0312     private function validateMembers($members, $force = false)
0313     {
0314         $model_Ocs_Ldap = new Default_Model_Ocs_Ldap();
0315         if ($force) {
0316             // create an org unit for backup user data
0317             $ou = "member-bkp-" . date("Ymd-Hi");
0318             $entry_ou_dn = $this->createBackupTree($model_Ocs_Ldap, $ou);
0319         }
0320 
0321         while ($member = $members->fetch()) {
0322             $model_Ocs_Ldap->resetMessages();
0323             try {
0324                 $ldapEntry = $model_Ocs_Ldap->getLdapUser($member);
0325                 if (empty($ldapEntry)) {
0326                     $this->log->info('user not exist (' . $member['member_id'] . ', ' . $member['username'] . ')');
0327 
0328                     continue;
0329                 }
0330                 $result = $this->validateEntry($member, $ldapEntry);
0331                 if (isset($result)) {
0332                     $this->log->info('member (' . $member['member_id'] . ', ' . $member['username'] . ') unequal: '
0333                                      . PHP_EOL
0334                                      . implode("<=>", $result)
0335                                      . ' '
0336                                      . $member[$result[0]] . '<=>' . Zend_Ldap_Attribute::getAttribute($ldapEntry,
0337                             $result[1], 0));
0338                     if ($force) {
0339                         $update = $model_Ocs_Ldap->createEntryForUser($member);
0340                         $update['dn'] = $model_Ocs_Ldap->getDnForUser($member['username']);
0341                         $model_Ocs_Ldap->updateLdapEntry($update);
0342                         $ldapUser = $this->storeBackupEntry($model_Ocs_Ldap, $member, $entry_ou_dn, $ldapEntry);
0343                     }
0344                 }
0345             } catch (Zend_Ldap_Exception $e) {
0346                 $this->log->info($e->getMessage() . PHP_EOL . mb_split("\n",$e->getTraceAsString())[0]);
0347                 $this->log->info("member: " . Zend_Json::encode($member));
0348                 $this->log->info("ldap entry: " . print_r($update, true));
0349             }
0350             $messages = $model_Ocs_Ldap->getMessages();
0351             if (false == empty($messages)) {
0352                 $this->log->info(json_encode($messages));
0353             }
0354         }
0355 
0356         return true;
0357     }
0358 
0359     /**
0360      * @param array $member
0361      * @param array $ldapEntry
0362      * @return array|null
0363      */
0364     private function validateEntry(array $member, array $ldapEntry)
0365     {
0366         $enc = mb_detect_encoding($member['username']) ? mb_detect_encoding($member['username']) : 'UTF-8';
0367         $lower_username = mb_strtolower($member['username'], $enc);
0368 
0369         $enc = mb_detect_encoding($member['email_address']) ? mb_detect_encoding($member['email_address']) : 'UTF-8';
0370         $lower_mail = mb_strtolower($member['email_address'], $enc);
0371 
0372         $attr = Zend_Ldap_Attribute::getAttribute($ldapEntry, 'uidNumber', 0);
0373         if ($member['member_id'] != $attr) {
0374             return array('member_id', 'uidNumber');
0375         }
0376         $attr = Zend_Ldap_Attribute::getAttribute($ldapEntry, 'memberUid', 0);
0377         if ($member['external_id'] != $attr) {
0378             return array('external_id', 'memberUid');
0379         }
0380         $attr = Zend_Ldap_Attribute::getAttribute($ldapEntry, 'cn', 0);
0381         $enc = mb_detect_encoding($attr) ? mb_detect_encoding($attr) : 'UTF-8';
0382         if ($lower_username != mb_strtolower($attr, $enc)) {
0383             return array('username', 'cn');
0384         }
0385         $attr = Zend_Ldap_Attribute::getAttribute($ldapEntry, 'email', 0);
0386         $enc = mb_detect_encoding($attr) ? mb_detect_encoding($attr) : 'UTF-8';
0387         if ($lower_mail != mb_strtolower($attr, $enc)) {
0388             return array('email_address', 'email');
0389         }
0390         $attr = Zend_Ldap_Attribute::getAttribute($ldapEntry, 'userPassword', 0);
0391         $password = '{MD5}' . base64_encode(pack("H*", $member['password']));
0392         if ($password != $attr) {
0393             return array('password', 'userPassword');
0394         }
0395 
0396         if(false == array_key_exists('jpegphoto', $ldapEntry)) {
0397             return array('profile_image_url','jpegPhoto');
0398         }
0399 
0400         return null;
0401     }
0402 
0403     private function updateAvatar($members)
0404     {
0405         $model_Ocs_Ldap = new Default_Model_Ocs_Ldap();
0406         // create an org unit for backup user data
0407         $ou = "member-bkp-" . date("Ymd-Hi");
0408         $backupTree = $this->createBackupTree($model_Ocs_Ldap, $ou);
0409 
0410         while ($member = $members->fetch()) {
0411             try {
0412                 $model_Ocs_Ldap->resetMessages();
0413                 $ldapUser = $model_Ocs_Ldap->getLdapUserByMemberId($member['member_id']);
0414                 if (empty($ldapUser)) {
0415                     throw new Exception('user not found');
0416                 }
0417                 $avatar = $model_Ocs_Ldap->createJpegPhoto($member['member_id'], $member['profile_image_url']);
0418                 if (false === $avatar) {
0419                     throw new Exception('update avatar failed');
0420                 }
0421                 $ldapUserNew = $model_Ocs_Ldap->updateLdapAttrib($ldapUser, $avatar,Default_Model_Ocs_Ldap::JPEG_PHOTO);
0422                 $model_Ocs_Ldap->updateLdapEntry($ldapUserNew);
0423                 $ldapUser = $this->storeBackupEntry($model_Ocs_Ldap, $member, $backupTree, $ldapUser);
0424             } catch (Exception $e) {
0425                 $this->log->info("process " . Zend_Json::encode($member));
0426                 $this->log->err($e->getMessage() . PHP_EOL . $e->getTraceAsString());
0427 
0428                 continue;
0429             }
0430             $messages = $model_Ocs_Ldap->getMessages();
0431             if (isset($messages[0]) AND $messages[0] != Default_Model_Ocs_Ldap::LDAP_SUCCESS) {
0432                 $this->log->info("process " . Zend_Json::encode($member));
0433                 $this->log->info("messages " . Zend_Json::encode($messages));
0434             }
0435         }
0436 
0437         return true;
0438     }
0439 
0440     private function updatePassword($members)
0441     {
0442         $model_Ocs_Ldap = new Default_Model_Ocs_Ldap();
0443         // create an org unit for backup user data
0444         $ou = "member-bkp-" . date("Ymd-Hi");
0445         $backupTree = $this->createBackupTree($model_Ocs_Ldap, $ou);
0446 
0447         while ($member = $members->fetch()) {
0448             try {
0449                 $model_Ocs_Ldap->resetMessages();
0450                 $ldapUser = $model_Ocs_Ldap->getLdapUserByMemberId($member['member_id']);
0451                 if (empty($ldapUser)) {
0452                     throw new Exception('user not found');
0453                 }
0454                 $attribValue = Zend_Ldap_Attribute::getAttribute($ldapUser, Default_Model_Ocs_Ldap::USER_PASSWORD, 0);
0455                 $passwordFromHash = $model_Ocs_Ldap->createPasswordFromHash($member['password']);
0456                 if (false === $passwordFromHash) {
0457                     throw new Exception('update password failed');
0458                 }
0459                 if ($attribValue != $passwordFromHash) {
0460                     $ldapUserNew = $model_Ocs_Ldap->updateLdapAttrib($ldapUser, $passwordFromHash,Default_Model_Ocs_Ldap::USER_PASSWORD);
0461                     $model_Ocs_Ldap->updateLdapEntry($ldapUserNew);
0462                     $ldapUser = $this->storeBackupEntry($model_Ocs_Ldap, $member, $backupTree, $ldapUser);
0463                 }
0464             } catch (Exception $e) {
0465                 $this->log->info("process " . Zend_Json::encode($member));
0466                 $this->log->err($e->getMessage() . PHP_EOL . $e->getTraceAsString());
0467 
0468                 continue;
0469             }
0470             $messages = $model_Ocs_Ldap->getMessages();
0471             if (isset($messages[0]) AND $messages[0] != Default_Model_Ocs_Ldap::LDAP_SUCCESS) {
0472                 $this->log->info("process " . Zend_Json::encode($member));
0473                 $this->log->info("messages " . Zend_Json::encode($messages));
0474             }
0475         }
0476 
0477         return true;
0478     }
0479 
0480     /**
0481      * @param Zend_Db_Statement_Interface $members
0482      * @param                             $file
0483      * @param                             $errorfile
0484      *
0485      * @return string
0486      * @throws Zend_Db_Statement_Exception
0487      * @throws Zend_Exception
0488      */
0489     private function renderLdif($members, $file, $errorfile)
0490     {
0491         $usernameValidChars = new Local_Validate_UsernameValid();
0492 
0493         while ($member = $members->fetch()) {
0494             $ldif = $this->renderElement($member);
0495             if (false === $usernameValidChars->isValid($member['username'])) {
0496                 file_put_contents($errorfile, $ldif, FILE_APPEND);
0497                 continue;
0498             }
0499             file_put_contents($file, $ldif, FILE_APPEND);
0500         }
0501 
0502         return true;
0503     }
0504 
0505     /**
0506      * @param $member
0507      *
0508      * @return string
0509      */
0510     private function renderElement($member)
0511     {
0512         $username = strtolower($member['username']);
0513         $password = base64_encode(pack("H*", $member['password']));
0514 
0515         return "
0516 dn: cn={$username},ou=member,dc={$this->domain},dc={$this->tld}
0517 objectClass: top
0518 objectClass: account
0519 objectClass: extensibleObject
0520 uid: {$username}
0521 uid: {$member['email_address']}
0522 userPassword: {MD5}{$password}
0523 cn: {$member['username']}
0524 email: {$member['email_address']}\n"
0525                . (empty(trim($member['firstname'])) ? "" : "gn: {$member['firstname']}\n")
0526                . (empty(trim($member['lastname'])) ? "" : "sn: {$member['lastname']}\n")
0527                . "uidNumber: {$member['member_id']}
0528 gidNumber: {$member['roleId']}
0529 memberUid: {$member['external_id']}
0530 ";
0531     }
0532 
0533 }