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 }