禅道开源版-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;

搞定!

禅道开源版 Ldap认证插件开发的更多相关文章

  1. 远程访问禅道开源版数据库(基于docker)

    navicat访问基于docker搭建的禅道的数据库,报错”2003 can't connect to MySQL server on '' (10061 'unknown error')“ 一.开启 ...

  2. 学习笔记——Ubuntu下使用Docker包部署禅道任务管理系统

    写此文目的:利用搭建禅道环境联系Docker基本使用方法,加深对Docker容器的理解,Ubuntu下面才能原生运行Docker,因此选择了Ubuntu 1.下载禅道开源版 wget http://d ...

  3. 禅道在docker上部署与迁移

    一.禅道部署 1.下载地址 禅道开源版:   http://dl.cnezsoft.com/zentao/docker/docker_zentao.zip 数据库用户名: root,默认密码: 123 ...

  4. Linux部署禅道Steps&Q&A

    1.查看Linux的位数: getconf LONG_BIT 结果:32/64 2. 禅道开源版安装包下载 Linux 64位 下载站点1: http://sourceforge.net/projec ...

  5. linux 安装禅道

    1. 查看Linux服务器版本信息 # cat /etc/redhat-release CentOS Linux release 7.4.1708 (Core) 2. 禅道开源版安装包下载 # wge ...

  6. 将禅道部署到腾讯云linux 上

    部署环境 :linux(腾讯云),用到了 xshell   FileZilla 使用禅道集成环境lampp直接部署 1.首先下载 lampp j集成环境包.https://sourceforge.ne ...

  7. 禅道ZenTao在windows和Lniux下集成安装环境和一键安装方法整理

    一共4种安装方法看官可以根据你自己的实际环境来选择一个都很简单 windows下用禅道官网的一键安装包方法(推荐): 为了简化大家在windows下面的安装,我们在xampp基础上做了禅道的windo ...

  8. 如何在Linux服务器上部署禅道

    最近换了新的项目团队,由于新团队比较年轻化,没有实行正规的项目管理,于是我自告奋勇要为团队管理出一份力,帮助团队建立敏捷化的项目管理,经过多方考究和对比后,选择了目前较受欢迎的开源项目管理软件:禅道. ...

  9. 禅道——Linux服务器部署禅道

    前言 2019年6月14日 22:01:24 看看时间我知道,我离猝死依然不远~ 禅道是什么 | 禅道是专业的研发项目管理软件 禅道的官网 | https://www.zentao.net/ 禅道开源 ...

随机推荐

  1. Servlet学习笔记(三)之HttpServletResponse

    init() 方法中参数 ServletConfig 对象使用 通过ServletConfig 获得 ServletContext对象 使用 HttpServletRequest 与HttpServl ...

  2. (四)羽夏看C语言——循环与跳转

    写在前面   由于此系列是本人一个字一个字码出来的,包括示例和实验截图.本人非计算机专业,可能对本教程涉及的事物没有了解的足够深入,如有错误,欢迎批评指正. 如有好的建议,欢迎反馈.码字不易,如果本篇 ...

  3. 微前端框架single-spa初探

    前言 最近入职的一家公司采用single-spa这个微前端框架,所以自学了此框架. single-spa这个微前端框架虽然有中文文档,但是有些零散和晦涩. 所以我想在学习之余,写篇博客拉平一下这个学习 ...

  4. JDK1.8源码(八)——java.lang.ThreadLocal类

    https://www.cnblogs.com/xdd666/p/14734047.html ThreadLocal https://www.cnblogs.com/yanfei1819/p/1473 ...

  5. Spring Cloud Zuul 学习+实践

    首先有必要了解一下什么是Zuul,它和Spring Cloud有什么关系. Zuul在Spring Cloud中承担着网关的职责,可以理解为客户端和服务端交互中的唯一通道.所有的客户端请求都会首先发送 ...

  6. SpringBoot 如何生成接口文档,老鸟们都这么玩的!

    大家好,我是飘渺. SpringBoot老鸟系列的文章已经写了两篇,每篇的阅读反响都还不错,果然大家还是对SpringBoot比较感兴趣.那今天我们就带来老鸟系列的第三篇:集成Swagger接口文档以 ...

  7. Devexpress主题/皮肤

    如何在C#中使用DevExpress皮肤管理器.步骤1: 单击 新建项目,然后选择左侧的Visual C#,然后 视窗 ,然后选择 Windows窗体应用程序.将您的项目命名为" SkinD ...

  8. Redis-初见

    目录 启动and连接 JRedis 宝塔 Redis.conf RDB AOF(Append Only File) 发布和订阅 主从复制 一主二从 复制原理 宕机后的手动配置主机 哨兵模式 Redis ...

  9. Vue开发多人聊天室 复盘总结

    前言 在上个月初,接到一个需求,要开发一个 聊天通讯 模块 并且 集成到 项目中的多个 入口,实现业务数据的记录追踪. 接到需求后,还挺开心,这是我第一次 搞 通讯 类的需求,之前一直是 B 端 的业 ...

  10. IIS中配置WCF站点

    http://msdn.microsoft.com/zh-cn/library/aa751852.aspx http://blog.csdn.net/hsg77/article/details/389 ...