禪道開源版 Ldap認證外掛開發
阿新 • • 發佈:2021-08-12
禪道開源版-Ldap外掛開發
背景 由於開源版無法使用ldap認證,所以在此分享一下自己開發禪道的ldap開發過程,希望對你有所幫助。 簡單說一下這個外掛的功能: 1.跳過原有禪道認證,使用ldap認證 2.每次登入重新整理使用者關鍵資訊,如部門,郵箱,職位等(你也可以使用cron定時重新整理禪道底層資料 使用者表zt_user,部門表zt_dept,職位在zt_lang中,許可權表zt_group,使用者許可權表zt_usergroup) 3.ldap存在的員工,禪道不存在時,建立這個員工,給與預設許可權。 首先說一下為什麼編寫外掛,而不是修改原始碼跳過禪道本身的驗證。 因為當禪道釋出更新時,修改原始碼的地方將被覆蓋,而外掛並沒有直接修改原始碼,而是替換掉原本禪道的邏輯。這是直接修改原始碼不具備的優勢。
一、外掛檔案結構介紹
如圖所示 zh-cn.yaml:外掛資訊,可以放一些介紹,安裝介紹 ldap.class.php:ldap相關操作,ldap連線、驗證登入、查詢使用者、更新使用者 config/ldap.php:存放ldap相關配置,伺服器地址、埠、ldap密碼 model/ldap.php: 覆蓋原本禪道的邏輯,我的是/opt/zbox/app/zentao/module/user/model.php 你在此定義什麼函式,在model.php中就會替換什麼函式。 當然你也可以直接新增新的方法 public function fn(){}方式定義
二、程式碼介紹
1、ldap.class.php(位置:xxx/lib/ldap/ldap.class.php)實現ldap各種的業務邏輯
<? function myLog($msg) { global $config; // 引入的就是定義的config/ldap.php $logFilePath = $config->ldap->ldap_log_filePath; file_put_contents($logFilePath, $msg . PHP_EOL, FILE_APPEND); } // 讀配置檔案 function getLdapConfig($key, $default = '') { global $config; if (isset($config->ldap)) { return isset($config->ldap->$key) ? $config->ldap->$key : $default; } else { return ''; } } /** * LDAP 登入驗證 */ function my_ldap_login($uid, $password) { global $config; try { $baseDn = $config->ldap->ldap_bind_dn; // 基礎dn 可增加搜尋條件,直接查詢人員 $uidFiled = $config->ldap->ldap_uid_field; // uid欄位 $user = "$uidFiled=$uid,$baseDn"; $host = getLdapConfig('ldap_server'); $port = getLdapConfig('ldap_port', '389'); $version = getLdapConfig('ldap_version', 3); $referrals = getLdapConfig('ldap_referrals', 0); $conn = ldap_connect($host, $port); //不要寫成ldap_connect($host.':'.$port)的形式 if ($conn) { //設定引數 ldap_set_option($conn, LDAP_OPT_PROTOCOL_VERSION, $version); //宣告使用版本3 ldap_set_option($conn, LDAP_OPT_REFERRALS, $referrals); // Binding to ldap server $bd = ldap_bind($conn, $user, $password); ldap_close($conn); return $bd; } else { ldap_close($conn); return false; } } catch (Exception $e) { ldap_close($conn); myLog("ldap連線失敗"); myLog($e->getMessage()); return false; } } /** * LDAP 連線 host port 取得配置檔案 傳入引數沒效 */ function my_ldap_connect() { try { $host = getLdapConfig('ldap_server'); $port = getLdapConfig('ldap_port', '389'); $user = getLdapConfig('ldap_root_dn', 'cn=project,dc=datasw,dc=com'); $password = getLdapConfig('ldap_bind_passwd', 'O6sl8c7QV4K2dGQx'); $version = getLdapConfig('ldap_version', 3); $referrals = getLdapConfig('ldap_referrals', 0); $conn = ldap_connect($host, $port); //不要寫成ldap_connect($host.':'.$port)的形式 if ($conn) { //設定引數 ldap_set_option($conn, LDAP_OPT_PROTOCOL_VERSION, $version); //宣告使用版本3 ldap_set_option($conn, LDAP_OPT_REFERRALS, $referrals); // Binding to ldap server $bd = ldap_bind($conn, $user, $password); return $conn; } else { return false; } } catch (Exception $e) { myLog("ldap連線失敗"); myLog($e->getMessage()); return false; } } // 查詢ldap使用者 function queryLdapUser($account) { global $config; $user_info = []; // 使用者資訊 // 1.獲取員工 try { $baseDn = $config->ldap->ldap_bind_dn; // 基礎dn 可增加搜尋條件,直接查詢人員 $uidFiled = $config->ldap->ldap_uid_field; // uid欄位 $uid = $account; // 使用者uid $user_info = []; // 使用者資訊 // 連線ldap $conn = my_ldap_connect(); $dn = "$uidFiled=$uid,$baseDn"; // ===========讀取=========== $search_filter = "($uidFiled=$uid)"; //設定uid過濾 // $justthese = array('dn', 'o'); //設定輸出屬性 , 不傳查詢所有 $search_dn = $baseDn; $search_id = ldap_search($conn, $search_dn, $search_filter); $res = ldap_get_entries($conn, $search_id); //結果 if (!!$res[0]) { $user_info = $res["0"]; } // ===========讀取=========== ldap_close($conn); return $user_info; } catch (\Throwable $th) { ldap_close($conn); myLog('==========error========='); myLog(print_r($th, true)); myLog('==========error========='); } }
2、config/ldap.php(位置:xxx/module/user/ext/config)ldap配置
<?
$config->ldap->ldap_server = '111:111:111:111'; // ldap地址
$config->ldap->ldap_port = '389'; // ldap地址 port
$config->ldap->ldap_root_dn = 'cn=xx,dc=xxx,dc=xxx'; // admin路徑
$config->ldap->ldap_bind_passwd = 'password'; //密碼
$config->ldap->ldap_uid_field = 'uid'; // uid
$config->ldap->ldap_bind_dn = 'ou=xxx,dc=xxx,dc=xxx'; // 域
$config->ldap->ldap_version = 3; // 版本
$config->ldap->ldap_referrals = 0; // 開啟referrals
$config->ldap->ldap_log_filePath = '/home/pdf/wuhao.log'; // 日誌地址
3、model/ldap.php(位置:xxx/module/user/ext/model)具體覆蓋邏輯,登入需要覆蓋的是 identify 函式
注意:
(1)有些函式加了public修飾,則需要用$this->fn才能訪問到
(2)此處不能有<?,需要是乾淨的程式碼(PS:不信可以試試,哈哈)
function identify($account, $password)
{
if (!$account or !$password) return false;
//$shaPasswd = '{SHA}' . base64_encode(pack('H*', sha1($password)));
// 1.admin 不進行ldap驗證,直接驗證密碼。
if ($account == "admin") {
/* If the length of $password is 32 or 40, checking by the auth hash. */
$record = $this->dao->select('*')->from(TABLE_USER)
->where('account')->eq($account)
->beginIF(strlen($password) < 32)->andWhere('password')->eq(md5($password))->fi()
->andWhere('deleted')->eq(0)
->fetch();
return $record;
}
// 引入定義的ldap業務邏輯
$this->app->loadClass('ldap', true);
// 2.驗證員工賬號密碼是否匹配
$checkUser = my_ldap_login($account, $password);
$record = ""; // 禪道查詢的賬號
if ($checkUser) {
// 員工
$ldapUser = queryLdapUser($account);
$ldapDep = $ldapUser["departmentnumber"]["0"];
$ldapTitle = $ldapUser["title"]["0"];
// 分類id
$otherData = $this->getUserOtherData($ldapDep, $ldapTitle);
// 使用者資訊
$userInfo = [
"dept" => $otherData["dept"], // 0
"group" => $otherData["group"], // 許可權2
"role" => $otherData["role"], // ""
"commiter" => $otherData["commiter"], // ""
"realname" => $ldapUser["displayname"]["0"],
"gender" => $ldapUser["sex"]["0"] == "男" ? "m" : "f",
"email" => $ldapUser["mail"]["0"],
"join" => date("Y-m-d H:i:s", $ldapUser["entrytime"]["0"]),
"password" => md5($password),
"account" => $account,
];
// 3.查詢是否存在此人
$record = $this->dao->select('*')->from(TABLE_USER)
->where('account')->eq($account)
// ->beginIF(strlen($password) < 32)->andWhere('password')->eq(md5($password))->fi()
->andWhere('deleted')->eq(0)
->fetch();
if (!!$record) {
// 4.存在,返回資料, 更新下使用者資料
$this->myUpdateUser($userInfo);
} else {
// 5.不存在此人,新增
$this->myCreateUser($userInfo);
// 6.新增後將此人資訊回查
$record = $this->dao->select('*')->from(TABLE_USER)
->where('account')->eq($account)
// ->beginIF(strlen($password) < 32)->andWhere('password')->eq(md5($password))->fi()
->andWhere('deleted')->eq(0)
->fetch();
}
}
$user = false;
if ($record) {
$passwordLength = strlen($password);
if ($passwordLength < 32) {
$user = $record;
} elseif ($passwordLength == 32) {
$hash = $this->session->rand ? md5($record->password . $this->session->rand) : $record->password;
// $user = $password == $hash ? $record : '';
$user = $record;
} elseif ($passwordLength == 40) {
$hash = sha1($record->account . $record->password . $record->last);
$user = $password == $hash ? $record : '';
}
if (!$user and md5($password) == $record->password) $user = $record;
}
if ($user) {
$ip = $this->server->remote_addr;
$last = $this->server->request_time;
/* code for bug #2729. */
if (defined('IN_USE')) $this->dao->update(TABLE_USER)->set('visits = visits + 1')->set('ip')->eq($ip)->set('last')->eq($last)->where('account')->eq($account)->exec();
// 驗證密碼強度, 如果你需要可以放開
// $user->lastTime = $user->last;
// $user->last = date(DT_DATETIME1, $last);
// $user->admin = strpos($this->app->company->admins, ",{$user->account},") !== false;
// $user->modifyPassword = ($user->visits == 0 and !empty($this->config->safe->modifyPasswordFirstLogin));
// if ($user->modifyPassword) $user->modifyPasswordReason = 'modifyPasswordFirstLogin';
// if (!$user->modifyPassword and !empty($this->config->safe->changeWeak)) {
// $user->modifyPassword = $this->loadModel('admin')->checkWeak($user);
// if ($user->modifyPassword) $user->modifyPasswordReason = 'weak';
// }
/* Create cycle todo in login. */
// $todoList = $this->dao->select('*')->from(TABLE_TODO)->where('cycle')->eq(1)->andWhere('account')->eq($user->account)->fetchAll('id');
// $this->loadModel('todo')->createByCycle($todoList);
}
return $user;
}
/**
* 修改為不檢驗登入次數
*/
function failPlus($account) {
return 0;
}
/**
* 新增成員
*/
public function myCreateUser($userInfo) {
$dept = $userInfo["dept"]; //
$realname = $userInfo["realname"]; //
$role = $userInfo["role"]; //
$commiter = $userInfo["commiter"]; //
$gender = $userInfo["gender"]; //
$email = $userInfo["email"]; //
$join = $userInfo["join"]; //
$password = $userInfo["password"]; //
$group = $userInfo["group"]; // 分組,無分組無法
$account = $userInfo["account"]; // 使用者名稱
$user = fixer::input('post')
->remove('password')
->setDefault('join', $join)
->setDefault('password', $password)
->setDefault('dept', $dept)
->setDefault('realname', $realname)
->setDefault('role', $role)
->setDefault('commiter', $commiter)
->setDefault('gender', $gender)
->remove('group, password1, password2, verifyPassword, passwordStrength, newPassword, referer, verifyRand, keepLogin')
->get();
$this->dao->insert(TABLE_USER)->data($user)
->autoCheck()
->batchCheck($this->config->user->create->requiredFields, 'notempty')
->check('account', 'unique')
->check('account', 'account')
->checkIF($email != '', 'email', 'email')
->exec();
if (!dao::isError()) {
$userID = $this->dao->lastInsertID();
if ($userInfo["group"]) {
// 角色
$sql = "INSERT INTO zt_usergroup VALUES('$account', '".$userInfo["group"]."')";
$row = $this->dbh->query($sql);
}
$this->computeUserView($user->account);
$this->loadModel('action')->create('user', $userID, 'Created');
$this->loadModel('mail');
if ($this->config->mail->mta == 'sendcloud' and !empty($user->email)) $this->mail->syncSendCloud('sync', $user->email, $user->realname);
}
return $user;
}
/**
* 根據自己需求,改造一下
*/
public function getUserOtherData($ldapDep, $ldapTitle){
return [
"dept" => -1, // 部門,只能是數字
"role" => "", // 職位
"commiter" => "",
"group" => 2, // 許可權
];
}
/**
* 更新使用者資訊
*/
public function myUpdateUser($userInfo){
$account = $userInfo["account"]; // 使用者名稱
$needUpdate = [
$dept => $userInfo["dept"],
$realname => $userInfo["realname"],
$role => $userInfo["role"],
$email => $userInfo["email"],
$join => $userInfo["join"],
$password => $userInfo["password"],
]; // 需要更新的欄位
// 不為null則更新
$update = [];
foreach ($needUpdate as $key => $value) {
if(!is_null($value)){
$update[] = "`$key` = '$value'";
}
}
$update = implode(", ", $update);
// 更新某些資訊
$sql = "UPDATE zt_user SET $update WHERE account = '$account'";
$this->dbh->exec($sql);
}
三、打包(壓縮)
如圖所示
需要打包(壓縮)後仍然保留最外層資料夾,否則無法解析。
四、使用
如圖所示
1、使用admin進入管理後臺,選擇外掛,再根據提示, tocuh /opt/zbox/app/zentao/www/ok.txt,建立資料夾。
2、重新整理後點擊本地安裝,上傳打包後的檔案,跟著提示走完
3、點選授權,外掛已經安裝好了。
4、修改 /opt/zbox/app/zentao/config/my.php 檔案,新增$config->notMd5Pwd = true;
搞定!