禅道开源版 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;
搞定!
禅道开源版 Ldap认证插件开发的更多相关文章
- 远程访问禅道开源版数据库(基于docker)
navicat访问基于docker搭建的禅道的数据库,报错”2003 can't connect to MySQL server on '' (10061 'unknown error')“ 一.开启 ...
- 学习笔记——Ubuntu下使用Docker包部署禅道任务管理系统
写此文目的:利用搭建禅道环境联系Docker基本使用方法,加深对Docker容器的理解,Ubuntu下面才能原生运行Docker,因此选择了Ubuntu 1.下载禅道开源版 wget http://d ...
- 禅道在docker上部署与迁移
一.禅道部署 1.下载地址 禅道开源版: http://dl.cnezsoft.com/zentao/docker/docker_zentao.zip 数据库用户名: root,默认密码: 123 ...
- Linux部署禅道Steps&Q&A
1.查看Linux的位数: getconf LONG_BIT 结果:32/64 2. 禅道开源版安装包下载 Linux 64位 下载站点1: http://sourceforge.net/projec ...
- linux 安装禅道
1. 查看Linux服务器版本信息 # cat /etc/redhat-release CentOS Linux release 7.4.1708 (Core) 2. 禅道开源版安装包下载 # wge ...
- 将禅道部署到腾讯云linux 上
部署环境 :linux(腾讯云),用到了 xshell FileZilla 使用禅道集成环境lampp直接部署 1.首先下载 lampp j集成环境包.https://sourceforge.ne ...
- 禅道ZenTao在windows和Lniux下集成安装环境和一键安装方法整理
一共4种安装方法看官可以根据你自己的实际环境来选择一个都很简单 windows下用禅道官网的一键安装包方法(推荐): 为了简化大家在windows下面的安装,我们在xampp基础上做了禅道的windo ...
- 如何在Linux服务器上部署禅道
最近换了新的项目团队,由于新团队比较年轻化,没有实行正规的项目管理,于是我自告奋勇要为团队管理出一份力,帮助团队建立敏捷化的项目管理,经过多方考究和对比后,选择了目前较受欢迎的开源项目管理软件:禅道. ...
- 禅道——Linux服务器部署禅道
前言 2019年6月14日 22:01:24 看看时间我知道,我离猝死依然不远~ 禅道是什么 | 禅道是专业的研发项目管理软件 禅道的官网 | https://www.zentao.net/ 禅道开源 ...
随机推荐
- Servlet学习笔记(三)之HttpServletResponse
init() 方法中参数 ServletConfig 对象使用 通过ServletConfig 获得 ServletContext对象 使用 HttpServletRequest 与HttpServl ...
- (四)羽夏看C语言——循环与跳转
写在前面 由于此系列是本人一个字一个字码出来的,包括示例和实验截图.本人非计算机专业,可能对本教程涉及的事物没有了解的足够深入,如有错误,欢迎批评指正. 如有好的建议,欢迎反馈.码字不易,如果本篇 ...
- 微前端框架single-spa初探
前言 最近入职的一家公司采用single-spa这个微前端框架,所以自学了此框架. single-spa这个微前端框架虽然有中文文档,但是有些零散和晦涩. 所以我想在学习之余,写篇博客拉平一下这个学习 ...
- JDK1.8源码(八)——java.lang.ThreadLocal类
https://www.cnblogs.com/xdd666/p/14734047.html ThreadLocal https://www.cnblogs.com/yanfei1819/p/1473 ...
- Spring Cloud Zuul 学习+实践
首先有必要了解一下什么是Zuul,它和Spring Cloud有什么关系. Zuul在Spring Cloud中承担着网关的职责,可以理解为客户端和服务端交互中的唯一通道.所有的客户端请求都会首先发送 ...
- SpringBoot 如何生成接口文档,老鸟们都这么玩的!
大家好,我是飘渺. SpringBoot老鸟系列的文章已经写了两篇,每篇的阅读反响都还不错,果然大家还是对SpringBoot比较感兴趣.那今天我们就带来老鸟系列的第三篇:集成Swagger接口文档以 ...
- Devexpress主题/皮肤
如何在C#中使用DevExpress皮肤管理器.步骤1: 单击 新建项目,然后选择左侧的Visual C#,然后 视窗 ,然后选择 Windows窗体应用程序.将您的项目命名为" SkinD ...
- Redis-初见
目录 启动and连接 JRedis 宝塔 Redis.conf RDB AOF(Append Only File) 发布和订阅 主从复制 一主二从 复制原理 宕机后的手动配置主机 哨兵模式 Redis ...
- Vue开发多人聊天室 复盘总结
前言 在上个月初,接到一个需求,要开发一个 聊天通讯 模块 并且 集成到 项目中的多个 入口,实现业务数据的记录追踪. 接到需求后,还挺开心,这是我第一次 搞 通讯 类的需求,之前一直是 B 端 的业 ...
- IIS中配置WCF站点
http://msdn.microsoft.com/zh-cn/library/aa751852.aspx http://blog.csdn.net/hsg77/article/details/389 ...