1. 程式人生 > 其它 >禪道開源版 Ldap認證外掛開發

禪道開源版 Ldap認證外掛開發

禪道開源版-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;
搞定!