禅道开源版 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 登录验证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;

搞定!


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM