禪道開源版-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 登錄驗證s
*/
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=xxx,dc=xxx,dc=xxx');
$password = getLdapConfig('ldap_bind_passwd', '123456');
$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進入管理后台,選擇插件,再根據提示, touch /opt/zbox/app/zentao/www/ok.txt,創建文件夾。
2、刷新后點擊本地安裝,上傳打包后的文件,跟着提示走完
3、點擊授權,插件已經安裝好了。
4、修改 /opt/zbox/app/zentao/config/my.php 文件,添加$config->notMd5Pwd = true;
搞定!