mirror of
https://github.com/kuaifan/dootask.git
synced 2026-04-23 10:18:41 +00:00
fix(ldap): 使用 LDAP Bind 认证替代 userPassword 查询,兼容 Active Directory
- 认证方式从 userPassword 属性过滤改为标准 LDAP Bind,兼容所有 LDAP 服务器 - 新增可配置的登录属性(cn/uid/mail/sAMAccountName),AD 用户选 sAMAccountName 即可 - 移除 posixAccount objectClass,兼容 AD 目录结构 - 同步创建用户时移除 POSIX 专属属性,添加 mail 属性 - 用户查找改用 findByEmail 按 mail/cn/uid/userPrincipalName 依次匹配 - initConfig 从静态变量缓存改为 RequestContext 请求级缓存,修复 Swoole 下配置变更不生效的问题 - 默认登录属性为 cn,与旧版本行为一致,确保向后兼容 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f27cef2d66
commit
09edb14d56
@ -612,6 +612,7 @@ class SystemController extends AbstractController
|
|||||||
'ldap_password',
|
'ldap_password',
|
||||||
'ldap_user_dn',
|
'ldap_user_dn',
|
||||||
'ldap_base_dn',
|
'ldap_base_dn',
|
||||||
|
'ldap_login_attr',
|
||||||
'ldap_sync_local'
|
'ldap_sync_local'
|
||||||
])) {
|
])) {
|
||||||
unset($all[$key]);
|
unset($all[$key]);
|
||||||
@ -625,6 +626,7 @@ class SystemController extends AbstractController
|
|||||||
//
|
//
|
||||||
$setting['ldap_open'] = $setting['ldap_open'] ?: 'close';
|
$setting['ldap_open'] = $setting['ldap_open'] ?: 'close';
|
||||||
$setting['ldap_port'] = intval($setting['ldap_port']) ?: 389;
|
$setting['ldap_port'] = intval($setting['ldap_port']) ?: 389;
|
||||||
|
$setting['ldap_login_attr'] = $setting['ldap_login_attr'] ?: 'cn';
|
||||||
$setting['ldap_sync_local'] = $setting['ldap_sync_local'] ?: 'close';
|
$setting['ldap_sync_local'] = $setting['ldap_sync_local'] ?: 'close';
|
||||||
//
|
//
|
||||||
return Base::retSuccess($type == 'save' ? '保存成功' : 'success', $setting ?: json_decode('{}'));
|
return Base::retSuccess($type == 'save' ? '保存成功' : 'success', $setting ?: json_decode('{}'));
|
||||||
|
|||||||
@ -4,6 +4,7 @@ namespace App\Ldap;
|
|||||||
|
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Module\Base;
|
use App\Module\Base;
|
||||||
|
use App\Services\RequestContext;
|
||||||
use LdapRecord\Configuration\ConfigurationException;
|
use LdapRecord\Configuration\ConfigurationException;
|
||||||
use LdapRecord\Container;
|
use LdapRecord\Container;
|
||||||
use LdapRecord\LdapRecordException;
|
use LdapRecord\LdapRecordException;
|
||||||
@ -11,7 +12,6 @@ use LdapRecord\Models\Model;
|
|||||||
|
|
||||||
class LdapUser extends Model
|
class LdapUser extends Model
|
||||||
{
|
{
|
||||||
protected static $init = null;
|
|
||||||
/**
|
/**
|
||||||
* The object classes of the LDAP model.
|
* The object classes of the LDAP model.
|
||||||
*
|
*
|
||||||
@ -22,9 +22,10 @@ class LdapUser extends Model
|
|||||||
'organizationalPerson',
|
'organizationalPerson',
|
||||||
'person',
|
'person',
|
||||||
'top',
|
'top',
|
||||||
'posixAccount',
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
private static $emailAttrs = ['mail', 'cn', 'uid', 'userPrincipalName'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return mixed|null
|
* @return mixed|null
|
||||||
*/
|
*/
|
||||||
@ -68,19 +69,29 @@ class LdapUser extends Model
|
|||||||
return Base::settingFind('thirdAccessSetting', 'ldap_sync_local') === 'open';
|
return Base::settingFind('thirdAccessSetting', 'ldap_sync_local') === 'open';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取登录属性名
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function getLoginAttr(): string
|
||||||
|
{
|
||||||
|
$attr = Base::settingFind('thirdAccessSetting', 'ldap_login_attr');
|
||||||
|
return in_array($attr, ['cn', 'uid', 'mail', 'sAMAccountName']) ? $attr : 'cn';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化配置
|
* 初始化配置
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public static function initConfig()
|
public static function initConfig()
|
||||||
{
|
{
|
||||||
if (is_bool(self::$init)) {
|
if (RequestContext::has('ldap_init')) {
|
||||||
return self::$init;
|
return RequestContext::get('ldap_init');
|
||||||
}
|
}
|
||||||
//
|
//
|
||||||
$setting = Base::setting('thirdAccessSetting');
|
$setting = Base::setting('thirdAccessSetting');
|
||||||
if ($setting['ldap_open'] !== 'open') {
|
if ($setting['ldap_open'] !== 'open') {
|
||||||
return self::$init = false;
|
return RequestContext::save('ldap_init', false);
|
||||||
}
|
}
|
||||||
//
|
//
|
||||||
$connection = Container::getDefaultConnection();
|
$connection = Container::getDefaultConnection();
|
||||||
@ -92,15 +103,15 @@ class LdapUser extends Model
|
|||||||
"username" => $setting['ldap_user_dn'],
|
"username" => $setting['ldap_user_dn'],
|
||||||
"password" => $setting['ldap_password'],
|
"password" => $setting['ldap_password'],
|
||||||
]);
|
]);
|
||||||
return self::$init = true;
|
return RequestContext::save('ldap_init', true);
|
||||||
} catch (ConfigurationException $e) {
|
} catch (ConfigurationException $e) {
|
||||||
info($e->getMessage());
|
info($e->getMessage());
|
||||||
return self::$init = false;
|
return RequestContext::save('ldap_init', false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取
|
* 通过管理员绑定搜索用户,然后用用户 DN 做 Bind 认证
|
||||||
* @param $username
|
* @param $username
|
||||||
* @param $password
|
* @param $password
|
||||||
* @return Model|null
|
* @return Model|null
|
||||||
@ -111,16 +122,68 @@ class LdapUser extends Model
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return self::static()
|
$loginAttr = self::getLoginAttr();
|
||||||
->where([
|
$row = self::static()
|
||||||
'cn' => $username,
|
->whereRaw($loginAttr, '=', $username)
|
||||||
'userPassword' => $password
|
->first();
|
||||||
])->first();
|
if (!$row) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$connection = Container::getDefaultConnection();
|
||||||
|
if (!$connection->auth()->attempt($row->getDn(), $password)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// Swoole 下连接共享,必须恢复管理员绑定
|
||||||
|
$connection->auth()->attempt(
|
||||||
|
$connection->getConfiguration()->get('username'),
|
||||||
|
$connection->getConfiguration()->get('password')
|
||||||
|
);
|
||||||
|
return $row;
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
info("[LDAP] auth fail: " . $e->getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过邮箱查找 LDAP 用户
|
||||||
|
* @param $email
|
||||||
|
* @return Model|null
|
||||||
|
*/
|
||||||
|
public static function findByEmail($email): ?Model
|
||||||
|
{
|
||||||
|
if (!self::initConfig()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
foreach (self::$emailAttrs as $attr) {
|
||||||
|
$row = self::static()->whereRaw($attr, '=', $email)->first();
|
||||||
|
if ($row) {
|
||||||
|
return $row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
} catch (\Exception) {
|
} catch (\Exception) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户的邮箱(从 LDAP 记录中提取)
|
||||||
|
* @param Model $row
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
public static function getUserEmail(Model $row): ?string
|
||||||
|
{
|
||||||
|
foreach (self::$emailAttrs as $attr) {
|
||||||
|
$val = $row->getFirstAttribute($attr);
|
||||||
|
if ($val && Base::isEmail($val)) {
|
||||||
|
return $val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 登录
|
* 登录
|
||||||
* @param $username
|
* @param $username
|
||||||
@ -138,7 +201,11 @@ class LdapUser extends Model
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (empty($user)) {
|
if (empty($user)) {
|
||||||
$user = User::reg($username, $password);
|
$email = self::getUserEmail($row) ?: $username;
|
||||||
|
$user = User::whereEmail($email)->first();
|
||||||
|
if (empty($user)) {
|
||||||
|
$user = User::reg($email, $password);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if ($user) {
|
if ($user) {
|
||||||
$userimg = $row->getPhoto();
|
$userimg = $row->getPhoto();
|
||||||
@ -173,7 +240,7 @@ class LdapUser extends Model
|
|||||||
}
|
}
|
||||||
//
|
//
|
||||||
if (self::isSyncLocal()) {
|
if (self::isSyncLocal()) {
|
||||||
$row = self::userFirst($user->email, $password);
|
$row = self::findByEmail($user->email);
|
||||||
if ($row) {
|
if ($row) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -184,17 +251,18 @@ class LdapUser extends Model
|
|||||||
} else {
|
} else {
|
||||||
$userimg = '';
|
$userimg = '';
|
||||||
}
|
}
|
||||||
self::static()->create([
|
$attrs = [
|
||||||
'cn' => $user->email,
|
'cn' => $user->email,
|
||||||
'gidNumber' => 0,
|
|
||||||
'homeDirectory' => '/home/ldap/dootask/' . env("APP_NAME"),
|
|
||||||
'sn' => $user->email,
|
'sn' => $user->email,
|
||||||
'uid' => $user->email,
|
'uid' => $user->email,
|
||||||
'uidNumber' => $user->userid,
|
|
||||||
'userPassword' => $password,
|
'userPassword' => $password,
|
||||||
'displayName' => $user->nickname,
|
'displayName' => $user->nickname,
|
||||||
'jpegPhoto' => $userimg,
|
'mail' => $user->email,
|
||||||
]);
|
];
|
||||||
|
if ($userimg) {
|
||||||
|
$attrs['jpegPhoto'] = $userimg;
|
||||||
|
}
|
||||||
|
self::static()->create($attrs);
|
||||||
$user->identity = Base::arrayImplode(array_merge(array_diff($user->identity, ['ldap']), ['ldap']));
|
$user->identity = Base::arrayImplode(array_merge(array_diff($user->identity, ['ldap']), ['ldap']));
|
||||||
$user->save();
|
$user->save();
|
||||||
} catch (LdapRecordException $e) {
|
} catch (LdapRecordException $e) {
|
||||||
@ -205,11 +273,11 @@ class LdapUser extends Model
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新
|
* 更新
|
||||||
* @param $username
|
* @param $email
|
||||||
* @param $array
|
* @param $array
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public static function userUpdate($username, $array)
|
public static function userUpdate($email, $array)
|
||||||
{
|
{
|
||||||
if (empty($array)) {
|
if (empty($array)) {
|
||||||
return;
|
return;
|
||||||
@ -218,10 +286,7 @@ class LdapUser extends Model
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
$row = self::static()
|
$row = self::findByEmail($email);
|
||||||
->where([
|
|
||||||
'cn' => $username,
|
|
||||||
])->first();
|
|
||||||
$row?->update($array);
|
$row?->update($array);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
info("[LDAP] update fail: " . $e->getMessage());
|
info("[LDAP] update fail: " . $e->getMessage());
|
||||||
@ -230,19 +295,16 @@ class LdapUser extends Model
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除
|
* 删除
|
||||||
* @param $username
|
* @param $email
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public static function userDelete($username)
|
public static function userDelete($email)
|
||||||
{
|
{
|
||||||
if (!self::initConfig()) {
|
if (!self::initConfig()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
$row = self::static()
|
$row = self::findByEmail($email);
|
||||||
->where([
|
|
||||||
'cn' => $username,
|
|
||||||
])->first();
|
|
||||||
$row?->delete();
|
$row?->delete();
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
info("[LDAP] delete fail: " . $e->getMessage());
|
info("[LDAP] delete fail: " . $e->getMessage());
|
||||||
|
|||||||
@ -2357,4 +2357,6 @@ AI任务分析
|
|||||||
(*)和(*)等人的聊天记录
|
(*)和(*)等人的聊天记录
|
||||||
|
|
||||||
生日
|
生日
|
||||||
请选择生日
|
请选择生日
|
||||||
|
登录属性
|
||||||
|
用于匹配登录用户名的 LDAP 属性,Active Directory 请选择 sAMAccountName
|
||||||
|
|||||||
@ -33,6 +33,15 @@
|
|||||||
<FormItem :label="$L('密码')" prop="ldap_password">
|
<FormItem :label="$L('密码')" prop="ldap_password">
|
||||||
<Input v-model="formData.ldap_password" type="password"/>
|
<Input v-model="formData.ldap_password" type="password"/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
<FormItem :label="$L('登录属性')" prop="ldap_login_attr">
|
||||||
|
<RadioGroup v-model="formData.ldap_login_attr">
|
||||||
|
<Radio label="uid">uid</Radio>
|
||||||
|
<Radio label="cn">cn</Radio>
|
||||||
|
<Radio label="mail">mail</Radio>
|
||||||
|
<Radio label="sAMAccountName">sAMAccountName</Radio>
|
||||||
|
</RadioGroup>
|
||||||
|
<div class="form-tip">{{$L('用于匹配登录用户名的 LDAP 属性,Active Directory 请选择 sAMAccountName')}}</div>
|
||||||
|
</FormItem>
|
||||||
<FormItem :label="$L('同步本地帐号')" prop="ldap_sync_local">
|
<FormItem :label="$L('同步本地帐号')" prop="ldap_sync_local">
|
||||||
<RadioGroup v-model="formData.ldap_sync_local">
|
<RadioGroup v-model="formData.ldap_sync_local">
|
||||||
<Radio label="open">{{ $L('开启') }}</Radio>
|
<Radio label="open">{{ $L('开启') }}</Radio>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user