feat: 新增ldap帐号

This commit is contained in:
kuaifan 2023-02-08 20:20:53 +08:00
parent c9e7fc14a1
commit b60b572494
11 changed files with 471 additions and 22 deletions

View File

@ -4,7 +4,7 @@
/**
* A helper file for Laravel, to provide autocomplete information to your IDE
* Generated for Laravel 8.83.16.
* Generated for Laravel 8.83.27.
*
* This file should not be included in your code, only analyzed by your IDE!
*
@ -2167,6 +2167,17 @@
{
/** @var \Illuminate\Auth\SessionGuard $instance */
return $instance->setRequest($request);
}
/**
* Get the timebox instance used by the guard.
*
* @return \Illuminate\Support\Timebox
* @static
*/
public static function getTimebox()
{
/** @var \Illuminate\Auth\SessionGuard $instance */
return $instance->getTimebox();
}
/**
* Determine if the current user is authenticated. If not, throw an exception.
@ -16109,6 +16120,16 @@
{
/** @var \Facade\FlareClient\Flare $instance */
return $instance->filterExceptionsUsing($filterExceptionsCallable);
}
/**
*
*
* @static
*/
public static function filterReportsUsing($filterReportsCallable)
{
/** @var \Facade\FlareClient\Flare $instance */
return $instance->filterReportsUsing($filterReportsCallable);
}
/**
*

View File

@ -2,6 +2,7 @@
namespace App\Http\Controllers\Api;
use App\Ldap\LdapUser;
use App\Models\Setting;
use App\Models\User;
use App\Models\UserCheckinRecord;
@ -10,8 +11,13 @@ use App\Module\BillExport;
use App\Module\BillMultipleExport;
use Arr;
use Carbon\Carbon;
use Config;
use Guanguans\Notify\Factory;
use Guanguans\Notify\Messages\EmailMessage;
use LdapRecord\Auth\PasswordRequiredException;
use LdapRecord\Auth\UsernameRequiredException;
use LdapRecord\Container;
use LdapRecord\LdapRecordException;
use Madzipper;
use Request;
use Response;
@ -311,6 +317,74 @@ class SystemController extends AbstractController
return Base::retSuccess('success', $setting ?: json_decode('{}'));
}
/**
* @api {get} api/system/setting/thirdaccess 04. 第三方帐号(限管理员)
*
* @apiVersion 1.0.0
* @apiGroup system
* @apiName setting__thirdaccess
*
* @apiParam {String} type
* - get: 获取(默认)
* - save: 保存设置(参数:['ldap_open', 'ldap_host', 'ldap_port', 'ldap_password', 'ldap_cn', 'ldap_dn']
* - testldap: 测试ldap连接
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function setting__thirdaccess()
{
User::auth('admin');
//
$type = trim(Request::input('type'));
if ($type == 'testldap') {
$all = Base::newTrim(Request::input());
$connection = Container::getDefaultConnection();
try {
$connection->setConfiguration([
"hosts" => [$all['ldap_host']],
"port" => intval($all['ldap_port']),
"password" => $all['ldap_password'],
"username" => $all['ldap_cn'],
"base_dn" => $all['ldap_dn'],
]);
if ($connection->auth()->attempt($all['ldap_cn'], $all['ldap_password'])) {
return Base::retSuccess('验证通过');
} else {
return Base::retError('验证失败');
}
} catch (LdapRecordException $e) {
return Base::retError($e->getMessage() ?: "验证失败:未知错误", config("ldap.connections.default"));
}
} elseif ($type == 'save') {
if (env("SYSTEM_SETTING") == 'disabled') {
return Base::retError('当前环境禁止修改');
}
$all = Base::newTrim(Request::input());
foreach ($all as $key => $value) {
if (!in_array($key, [
'ldap_open',
'ldap_host',
'ldap_port',
'ldap_password',
'ldap_cn',
'ldap_dn'
])) {
unset($all[$key]);
}
}
$all['ldap_port'] = intval($all['ldap_port']) ?: 389;
$setting = Base::setting('thirdAccessSetting', Base::newTrim($all));
} else {
$setting = Base::setting('thirdAccessSetting');
}
//
$setting['ldap_open'] = $setting['ldap_open'] ?: 'close';
$setting['ldap_port'] = intval($setting['ldap_port']) ?: 389;
//
return Base::retSuccess('success', $setting ?: json_decode('{}'));
}
/**
* @api {get} api/system/demo 05. 获取演示帐号
*

View File

@ -2,6 +2,7 @@
namespace App\Http\Controllers\Api;
use App\Ldap\LdapUser;
use App\Models\AbstractModel;
use App\Models\Meeting;
use App\Models\Project;
@ -49,6 +50,8 @@ class UsersController extends AbstractController
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据(同"获取我的信息"接口)
*
* @throws \LdapRecord\Configuration\ConfigurationException
*/
public function login()
{
@ -89,11 +92,22 @@ class UsersController extends AbstractController
$needData = ['code' => $needCode ? 'need' : 'no'];
return Base::retError($msg, $needData);
};
//
$user = User::whereEmail($email)->first();
$checkPassword = true;
if (LdapUser::isOpen() && (empty($user) || in_array('ldap', $user->identity))) {
$user = LdapUser::userLogin($email, $password, $user);
if ($user) {
$identity = array_merge(array_diff($user->identity, ['ldap']), ['ldap']);
$user->identity = "," . implode(",", $identity) . ",";
$user->save();
}
$checkPassword = false;
}
if (empty($user)) {
return $retError('帐号或密码错误');
}
if ($user->password != Base::md52($password, $user->encrypt)) {
if ($checkPassword && $user->password != Base::md52($password, $user->encrypt)) {
return $retError('帐号或密码错误');
}
//

162
app/Ldap/LdapUser.php Normal file
View File

@ -0,0 +1,162 @@
<?php
namespace App\Ldap;
use App\Exceptions\ApiException;
use App\Models\User;
use App\Module\Base;
use LdapRecord\Container;
use LdapRecord\LdapRecordException;
use LdapRecord\Models\Model;
class LdapUser extends Model
{
protected static bool $init = false;
/**
* The object classes of the LDAP model.
*
* @var array
*/
public static $objectClasses = [
'inetOrgPerson',
'organizationalPerson',
'person',
'top',
'posixAccount',
];
/**
* @return LdapUser
*/
public static function static(): LdapUser
{
return new static;
}
/**
* 服务是否打开
* @return bool
*/
public static function isOpen(): bool
{
$setting = Base::setting('thirdAccessSetting');
return $setting['ldap_open'] === 'open';
}
/**
* 初始化配置
* @return void
* @throws \LdapRecord\Configuration\ConfigurationException
*/
public static function initConfig()
{
if (self::$init) {
return;
}
self::$init = true;
//
$setting = Base::setting('thirdAccessSetting');
$connection = Container::getDefaultConnection();
$connection->setConfiguration([
"hosts" => [$setting['ldap_host']],
"port" => intval($setting['ldap_port']),
"password" => $setting['ldap_password'],
"username" => $setting['ldap_cn'],
"base_dn" => $setting['ldap_dn'],
]);
}
/**
* 登录
* @param $username
* @param $password
* @param User|null $user
* @return User|mixed|null
* @throws \LdapRecord\Configuration\ConfigurationException
*/
public static function userLogin($username, $password, $user = null)
{
self::initConfig();
$row = self::static()
->where([
'cn' => $username,
'userPassword' => $password
])->first();
if (!$row) {
return null;
}
if ($user) {
return $user;
}
return User::reg($username, Base::generatePassword(32));
}
/**
* 添加
* @param $userid
* @param $username
* @param $password
* @param $description
* @return void
* @throws \LdapRecord\Configuration\ConfigurationException
*/
public static function userReg($userid, $username, $password, $description = '')
{
self::initConfig();
try {
self::static()->create([
'cn' => $username,
'gidNumber' => 0,
'homeDirectory' => '/home/ldap/dootask/' . env("APP_NAME"),
'sn' => $username,
'uid' => $username,
'uidNumber' => $userid,
'userPassword' => $password,
'description' => $description,
]);
} catch (LdapRecordException $e) {
throw new ApiException("reg ldap fail: " . $e->getMessage());
}
}
/**
* 更新
* @param $username
* @param $array
* @return void
* @throws \LdapRecord\Configuration\ConfigurationException
*/
public static function userUpdate($username, $array)
{
self::initConfig();
$row = self::static()
->where([
'cn' => $username,
])->first();
try {
$row?->update($array);
} catch (LdapRecordException $e) {
throw new ApiException("update ldap fail: " . $e->getMessage());
}
}
/**
* 删除
* @param $username
* @return void
* @throws \LdapRecord\Configuration\ConfigurationException
*/
public static function userDelete($username)
{
self::initConfig();
$row = self::static()
->where([
'cn' => $username,
])->first();
try {
$row?->delete();
} catch (LdapRecordException $e) {
throw new ApiException("delete ldap fail: " . $e->getMessage());
}
}
}

View File

@ -14,6 +14,7 @@
"ext-json": "*",
"ext-libxml": "*",
"ext-simplexml": "*",
"directorytree/ldaprecord-laravel": "^2.7",
"fideloper/proxy": "^4.4.1",
"fruitcake/laravel-cors": "^2.0.4",
"guanguans/notify": "^1.21.1",

73
config/ldap.php Normal file
View File

@ -0,0 +1,73 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default LDAP Connection Name
|--------------------------------------------------------------------------
|
| Here you may specify which of the LDAP connections below you wish
| to use as your default connection for all LDAP operations. Of
| course you may add as many connections you'd like below.
|
*/
'default' => env('LDAP_CONNECTION', 'default'),
/*
|--------------------------------------------------------------------------
| LDAP Connections
|--------------------------------------------------------------------------
|
| Below you may configure each LDAP connection your application requires
| access to. Be sure to include a valid base DN - otherwise you may
| not receive any results when performing LDAP search operations.
|
*/
'connections' => [
'default' => [
'hosts' => [env('LDAP_HOST', '127.0.0.1')],
'username' => env('LDAP_USERNAME', 'cn=user,dc=local,dc=com'),
'password' => env('LDAP_PASSWORD', 'secret'),
'port' => env('LDAP_PORT', 389),
'base_dn' => env('LDAP_BASE_DN', 'dc=local,dc=com'),
'timeout' => env('LDAP_TIMEOUT', 5),
'use_ssl' => env('LDAP_SSL', false),
'use_tls' => env('LDAP_TLS', false),
],
],
/*
|--------------------------------------------------------------------------
| LDAP Logging
|--------------------------------------------------------------------------
|
| When LDAP logging is enabled, all LDAP search and authentication
| operations are logged using the default application logging
| driver. This can assist in debugging issues and more.
|
*/
'logging' => env('LDAP_LOGGING', true),
/*
|--------------------------------------------------------------------------
| LDAP Cache
|--------------------------------------------------------------------------
|
| LDAP caching enables the ability of caching search results using the
| query builder. This is great for running expensive operations that
| may take many seconds to complete, such as a pagination request.
|
*/
'cache' => [
'enabled' => env('LDAP_CACHE', false),
'driver' => env('CACHE_DRIVER', 'file'),
],
];

View File

@ -338,6 +338,13 @@ export default {
}
}))
}
if (identity.includes("ldap")) {
arr.push(h('Tag', {
props: {
color: 'orange'
}
}, this.$L('LDAP')))
}
if (identity.includes("admin")) {
arr.push(h('Tag', {
props: {

View File

@ -87,16 +87,6 @@ export default {
this.loadIng--;
});
},
hoursChange(e, key) {
let newNum = e * 10;
if (newNum % 5 !== 0) {
setTimeout(() => {
this.$set(this.formData, key, Math.round(e))
})
$A.messageError('任务提醒只能是0.5的倍数');
}
},
}
}
</script>

View File

@ -133,16 +133,6 @@ export default {
});
},
hoursChange(e, key) {
let newNum = e * 10;
if (newNum % 5 !== 0) {
setTimeout(() => {
this.$set(this.formData, key, Math.round(e))
})
$A.messageError('任务提醒只能是0.5的倍数');
}
},
checkEmailSend() {
$A.modalInput({
title: "测试邮件",

View File

@ -0,0 +1,112 @@
<template>
<div class="setting-component-item">
<Form ref="formData" :model="formData" :rules="ruleData" label-width="auto" @submit.native.prevent>
<div class="block-setting-box">
<h3>{{ $L('LDAP') }}</h3>
<FormItem :label="$L('启用 LDAP 认证')" prop="ldap_open">
<RadioGroup v-model="formData.ldap_open">
<Radio label="open">{{ $L('开启') }}</Radio>
<Radio label="close">{{ $L('关闭') }}</Radio>
</RadioGroup>
</FormItem>
<template v-if="formData.ldap_open === 'open'">
<FormItem :label="$L('LDAP 地址')" prop="ldap_host">
<Input v-model="formData.ldap_host"/>
<div class="form-tip">{{$L('如192.168.1.200')}}</div>
</FormItem>
<FormItem :label="$L('LDAP 端口')" prop="ldap_port">
<Input v-model="formData.ldap_port" type="number" :placeholder="`${$L('默认')}: 389`"/>
</FormItem>
<FormItem :label="$L('密码')" prop="ldap_password">
<Input v-model="formData.ldap_password"/>
</FormItem>
<FormItem :label="$L('绑定 CN')" prop="ldap_cn">
<Input v-model="formData.ldap_cn"/>
</FormItem>
<FormItem :label="$L('绑定 DN')" prop="ldap_dn">
<Input v-model="formData.ldap_dn"/>
</FormItem>
<FormItem>
<Button :loading="testLoad" @click="checkTest">{{ $L('测试链接') }}</Button>
</FormItem>
</template>
</div>
</Form>
<div class="setting-footer">
<Button :loading="loadIng > 0" type="primary" @click="submitForm">{{ $L('提交') }}</Button>
<Button :loading="loadIng > 0" @click="resetForm" style="margin-left: 8px">{{ $L('重置') }}</Button>
</div>
</div>
</template>
<script>
export default {
name: "SystemThirdAccess",
data() {
return {
loadIng: 0,
formData: {
},
ruleData: {},
testLoad: false,
}
},
mounted() {
this.systemSetting();
},
methods: {
submitForm() {
this.$refs.formData.validate((valid) => {
if (valid) {
this.systemSetting(true);
}
})
},
resetForm() {
this.formData = $A.cloneJSON(this.formDatum_bak);
},
systemSetting(save) {
this.loadIng++;
this.$store.dispatch("call", {
url: 'system/setting/thirdaccess?type=' + (save ? 'save' : 'all'),
data: this.formData,
}).then(({data}) => {
if (save) {
$A.messageSuccess('修改成功');
}
this.formData = data;
this.formDatum_bak = $A.cloneJSON(this.formData);
}).catch(({msg}) => {
if (save) {
$A.modalError(msg);
}
}).finally(_ => {
this.loadIng--;
});
},
checkTest() {
if (this.testLoad) {
return
}
this.testLoad = true
this.$store.dispatch("call", {
url: 'system/setting/thirdaccess?type=testldap',
data: this.formData,
}).then(({msg}) => {
$A.messageSuccess(msg);
}).catch(({msg}) => {
$A.modalError(msg);
}).finally(_ => {
this.testLoad = false;
});
}
}
}
</script>

View File

@ -19,6 +19,9 @@
<TabPane :label="$L('邮件设置')" name="emailSetting">
<SystemEmailSetting/>
</TabPane>
<TabPane :label="$L('认证设置')" name="thirdAccess">
<SystemThirdAccess/>
</TabPane>
<TabPane v-if="appPush" :label="$L('APP推送')" name="appPush">
<SystemAppPush/>
</TabPane>
@ -34,9 +37,11 @@ import SystemEmailSetting from "./components/SystemEmailSetting";
import SystemAppPush from "./components/SystemAppPush";
import SystemMeeting from "./components/SystemMeeting";
import SystemCheckin from "./components/SystemCheckin";
import SystemThirdAccess from "./components/SystemThirdAccess";
export default {
components: {
SystemThirdAccess,
SystemCheckin,
SystemMeeting,
SystemAppPush, SystemColumnTemplate, SystemTaskPriority, SystemSetting, SystemEmailSetting},