mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-18 23:30:25 +08:00
feat: 实现 LDAP 登录
This commit is contained in:
5164
mayfly_go_web/package-lock.json
generated
5164
mayfly_go_web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -11,4 +11,6 @@ export default {
|
|||||||
logout: () => request.post('/auth/accounts/logout'),
|
logout: () => request.post('/auth/accounts/logout'),
|
||||||
getPermissions: () => request.get('/sys/accounts/permissions'),
|
getPermissions: () => request.get('/sys/accounts/permissions'),
|
||||||
oauth2Callback: (params: any) => request.get('/auth/oauth2/callback', params),
|
oauth2Callback: (params: any) => request.get('/auth/oauth2/callback', params),
|
||||||
|
getLdapEnabled: () => request.get("/auth/ldap/enabled"),
|
||||||
|
ldapLogin: (param: any) => request.post('/auth/ldap/login', param),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -67,3 +67,14 @@ function convertBool(value: string, defaultValue: boolean) {
|
|||||||
}
|
}
|
||||||
return value == '1' || value == 'true';
|
return value == '1' || value == 'true';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取LDAP登录配置
|
||||||
|
*
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export async function getLdapEnabled(): Promise<any> {
|
||||||
|
const value = await openApi.getLdapEnabled();
|
||||||
|
return convertBool(value, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,9 @@
|
|||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item v-if="ldapEnabled" prop="ldapLogin">
|
||||||
|
<el-checkbox v-model="loginForm.ldapLogin" label="LDAP 登录" size="small"/>
|
||||||
|
</el-form-item>
|
||||||
<span v-if="showLoginFailTips" style="color: #f56c6c; font-size: 12px">
|
<span v-if="showLoginFailTips" style="color: #f56c6c; font-size: 12px">
|
||||||
提示:登录失败超过{{ accountLoginSecurity.loginFailCount }}次后将被限制{{ accountLoginSecurity.loginFailMin }}分钟内不可再次登录
|
提示:登录失败超过{{ accountLoginSecurity.loginFailCount }}次后将被限制{{ accountLoginSecurity.loginFailMin }}分钟内不可再次登录
|
||||||
</span>
|
</span>
|
||||||
@@ -133,7 +136,7 @@ import { getSession, setSession, setUserInfo2Session, setUseWatermark2Session }
|
|||||||
import { formatAxis } from '@/common/utils/format';
|
import { formatAxis } from '@/common/utils/format';
|
||||||
import openApi from '@/common/openApi';
|
import openApi from '@/common/openApi';
|
||||||
import { RsaEncrypt } from '@/common/rsa';
|
import { RsaEncrypt } from '@/common/rsa';
|
||||||
import { getAccountLoginSecurity, useWartermark } from '@/common/sysconfig';
|
import {getAccountLoginSecurity, getLdapEnabled, useWartermark} from '@/common/sysconfig';
|
||||||
import { letterAvatar } from '@/common/utils/string';
|
import { letterAvatar } from '@/common/utils/string';
|
||||||
import { useUserInfo } from '@/store/userInfo';
|
import { useUserInfo } from '@/store/userInfo';
|
||||||
import QrcodeVue from 'qrcode.vue';
|
import QrcodeVue from 'qrcode.vue';
|
||||||
@@ -168,6 +171,7 @@ const state = reactive({
|
|||||||
password: '',
|
password: '',
|
||||||
captcha: '',
|
captcha: '',
|
||||||
cid: '',
|
cid: '',
|
||||||
|
ldapLogin: false,
|
||||||
},
|
},
|
||||||
loginRes: {} as any,
|
loginRes: {} as any,
|
||||||
changePwdDialog: {
|
changePwdDialog: {
|
||||||
@@ -223,9 +227,10 @@ const state = reactive({
|
|||||||
otpConfirm: false,
|
otpConfirm: false,
|
||||||
updateUserConfirm: false,
|
updateUserConfirm: false,
|
||||||
},
|
},
|
||||||
|
ldapEnabled: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { accountLoginSecurity, showLoginFailTips, captchaImage, loginForm, changePwdDialog, otpDialog, baseInfoDialog, loading } = toRefs(state);
|
const { accountLoginSecurity, showLoginFailTips, captchaImage, loginForm, changePwdDialog, otpDialog, baseInfoDialog, loading, ldapEnabled } = toRefs(state);
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
nextTick(async () => {
|
nextTick(async () => {
|
||||||
@@ -234,6 +239,10 @@ onMounted(async () => {
|
|||||||
state.accountLoginSecurity = res;
|
state.accountLoginSecurity = res;
|
||||||
}
|
}
|
||||||
getCaptcha();
|
getCaptcha();
|
||||||
|
|
||||||
|
const ldap = await getLdapEnabled();
|
||||||
|
state.ldapEnabled = ldap;
|
||||||
|
state.loginForm.ldapLogin = ldap
|
||||||
});
|
});
|
||||||
// 移除公钥, 方便后续重新获取
|
// 移除公钥, 方便后续重新获取
|
||||||
sessionStorage.removeItem('RsaPublicKey');
|
sessionStorage.removeItem('RsaPublicKey');
|
||||||
@@ -288,7 +297,11 @@ const onSignIn = async () => {
|
|||||||
try {
|
try {
|
||||||
const loginReq = { ...state.loginForm };
|
const loginReq = { ...state.loginForm };
|
||||||
loginReq.password = await RsaEncrypt(originPwd);
|
loginReq.password = await RsaEncrypt(originPwd);
|
||||||
|
if (state.loginForm.ldapLogin) {
|
||||||
|
loginRes = await openApi.ldapLogin(loginReq);
|
||||||
|
} else {
|
||||||
loginRes = await openApi.login(loginReq);
|
loginRes = await openApi.login(loginReq);
|
||||||
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
state.loading.signIn = false;
|
state.loading.signIn = false;
|
||||||
state.loginForm.captcha = '';
|
state.loginForm.captcha = '';
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ const oauth2Login = () => {
|
|||||||
box-shadow: 0 2px 12px 0 var(--color-primary-light-5);
|
box-shadow: 0 2px 12px 0 var(--color-primary-light-5);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
transition: height 0.2s linear;
|
transition: height 0.2s linear;
|
||||||
height: 480px;
|
height: 490px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
|
||||||
|
|||||||
@@ -38,3 +38,15 @@ log:
|
|||||||
# file:
|
# file:
|
||||||
# path: ./
|
# path: ./
|
||||||
# name: mayfly-go.log
|
# name: mayfly-go.log
|
||||||
|
ldap:
|
||||||
|
enabled: true
|
||||||
|
host: "ldap.example.com"
|
||||||
|
port: 389
|
||||||
|
baseDn: "ou=users,dc=example,dc=com"
|
||||||
|
bindDn: "cn=admin,dc=example,dc=com"
|
||||||
|
userFilter: "(&(objectClass=organizationalPerson)(uid=%s))"
|
||||||
|
bindPassword: "admin123."
|
||||||
|
fieldMapping:
|
||||||
|
identifier: "cn"
|
||||||
|
displayName: "displayName"
|
||||||
|
email: "mail"
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ type LoginForm struct {
|
|||||||
Password string `binding:"required"`
|
Password string `binding:"required"`
|
||||||
Captcha string `json:"captcha"`
|
Captcha string `json:"captcha"`
|
||||||
Cid string `json:"cid"`
|
Cid string `json:"cid"`
|
||||||
|
LdapLogin bool `json:"ldapLogin"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type OtpVerfiy struct {
|
type OtpVerfiy struct {
|
||||||
|
|||||||
109
server/internal/auth/api/ldap_login.go
Normal file
109
server/internal/auth/api/ldap_login.go
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"mayfly-go/internal/auth/api/form"
|
||||||
|
msgapp "mayfly-go/internal/msg/application"
|
||||||
|
sysapp "mayfly-go/internal/sys/application"
|
||||||
|
sysentity "mayfly-go/internal/sys/domain/entity"
|
||||||
|
"mayfly-go/pkg/biz"
|
||||||
|
"mayfly-go/pkg/cache"
|
||||||
|
"mayfly-go/pkg/captcha"
|
||||||
|
"mayfly-go/pkg/config"
|
||||||
|
"mayfly-go/pkg/ginx"
|
||||||
|
"mayfly-go/pkg/ldap"
|
||||||
|
"mayfly-go/pkg/req"
|
||||||
|
"mayfly-go/pkg/utils/cryptox"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LdapLogin struct {
|
||||||
|
AccountApp sysapp.Account
|
||||||
|
MsgApp msgapp.Msg
|
||||||
|
ConfigApp sysapp.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
// @router /auth/ldap/enabled [get]
|
||||||
|
func (a *LdapLogin) GetLdapEnabled(rc *req.Ctx) {
|
||||||
|
rc.ResData = config.Conf.Ldap.Enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
// @router /auth/ldap/login [post]
|
||||||
|
func (a *LdapLogin) Login(rc *req.Ctx) {
|
||||||
|
loginForm := ginx.BindJsonAndValid(rc.GinCtx, new(form.LoginForm))
|
||||||
|
|
||||||
|
// 确认是 LDAP 登录
|
||||||
|
biz.IsTrue(loginForm.LdapLogin, "LDAP 登录参数错误")
|
||||||
|
|
||||||
|
accountLoginSecurity := a.ConfigApp.GetConfig(sysentity.ConfigKeyAccountLoginSecurity).ToAccountLoginSecurity()
|
||||||
|
// 判断是否有开启登录验证码校验
|
||||||
|
if accountLoginSecurity.UseCaptcha {
|
||||||
|
// 校验验证码
|
||||||
|
biz.IsTrue(captcha.Verify(loginForm.Cid, loginForm.Captcha), "验证码错误")
|
||||||
|
}
|
||||||
|
|
||||||
|
username := loginForm.Username
|
||||||
|
|
||||||
|
clientIp := getIpAndRegion(rc)
|
||||||
|
rc.ReqParam = fmt.Sprintf("username: %s | ip: %s", username, clientIp)
|
||||||
|
|
||||||
|
originPwd, err := cryptox.DefaultRsaDecrypt(loginForm.Password, true)
|
||||||
|
biz.ErrIsNilAppendErr(err, "解密密码错误: %s")
|
||||||
|
// LDAP 用户本地密码为空,不允许本地登录
|
||||||
|
biz.NotEmpty(originPwd, "密码不能为空")
|
||||||
|
|
||||||
|
failCountKey := fmt.Sprintf("account:login:failcount:%s", username)
|
||||||
|
nowFailCount := cache.GetInt(failCountKey)
|
||||||
|
loginFailCount := accountLoginSecurity.LoginFailCount
|
||||||
|
loginFailMin := accountLoginSecurity.LoginFailMin
|
||||||
|
biz.IsTrue(nowFailCount < loginFailCount, "登录失败超过%d次, 请%d分钟后再试", loginFailCount, loginFailMin)
|
||||||
|
|
||||||
|
var account *sysentity.Account
|
||||||
|
cols := []string{"Id", "Name", "Username", "Password", "Status", "LastLoginTime", "LastLoginIp", "OtpSecret"}
|
||||||
|
account, err = a.getOrCreateUserWithLdap(username, originPwd, cols...)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
nowFailCount++
|
||||||
|
cache.SetStr(failCountKey, strconv.Itoa(nowFailCount), time.Minute*time.Duration(loginFailMin))
|
||||||
|
panic(biz.NewBizErr(fmt.Sprintf("用户名或密码错误【当前登录失败%d次】", nowFailCount)))
|
||||||
|
}
|
||||||
|
|
||||||
|
rc.ResData = LastLoginCheck(account, accountLoginSecurity, clientIp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *LdapLogin) getUser(userName string, cols ...string) (*sysentity.Account, error) {
|
||||||
|
account := &sysentity.Account{Username: userName}
|
||||||
|
if err := a.AccountApp.GetAccount(account, cols...); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return account, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *LdapLogin) createUser(userName, displayName string) {
|
||||||
|
account := &sysentity.Account{Username: userName}
|
||||||
|
account.SetBaseInfo(nil)
|
||||||
|
account.Name = displayName
|
||||||
|
a.AccountApp.Create(account)
|
||||||
|
// 将 LADP 用户本地密码设置为空,不允许本地登录
|
||||||
|
account.Password = cryptox.PwdHash("")
|
||||||
|
a.AccountApp.Update(account)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *LdapLogin) getOrCreateUserWithLdap(userName string, password string, cols ...string) (*sysentity.Account, error) {
|
||||||
|
userInfo, err := ldap.Authenticate(userName, password)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("用户名密码错误")
|
||||||
|
}
|
||||||
|
|
||||||
|
account, err := a.getUser(userName, cols...)
|
||||||
|
if err == gorm.ErrRecordNotFound {
|
||||||
|
a.createUser(userName, userInfo.DisplayName)
|
||||||
|
return a.getUser(userName, cols...)
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return account, nil
|
||||||
|
}
|
||||||
@@ -17,6 +17,12 @@ func Init(router *gin.RouterGroup) {
|
|||||||
MsgApp: msgapp.GetMsgApp(),
|
MsgApp: msgapp.GetMsgApp(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ldapLogin := &api.LdapLogin{
|
||||||
|
ConfigApp: sysapp.GetConfigApp(),
|
||||||
|
AccountApp: sysapp.GetAccountApp(),
|
||||||
|
MsgApp: msgapp.GetMsgApp(),
|
||||||
|
}
|
||||||
|
|
||||||
oauth2Login := &api.Oauth2Login{
|
oauth2Login := &api.Oauth2Login{
|
||||||
Oauth2App: application.GetAuthApp(),
|
Oauth2App: application.GetAuthApp(),
|
||||||
ConfigApp: sysapp.GetConfigApp(),
|
ConfigApp: sysapp.GetConfigApp(),
|
||||||
@@ -50,6 +56,10 @@ func Init(router *gin.RouterGroup) {
|
|||||||
req.NewGet("/oauth2/status", oauth2Login.Oauth2Status),
|
req.NewGet("/oauth2/status", oauth2Login.Oauth2Status),
|
||||||
|
|
||||||
req.NewGet("/oauth2/unbind", oauth2Login.Oauth2Unbind).Log(req.NewLogSave("oauth2解绑")),
|
req.NewGet("/oauth2/unbind", oauth2Login.Oauth2Unbind).Log(req.NewLogSave("oauth2解绑")),
|
||||||
|
|
||||||
|
// LDAP 登录
|
||||||
|
req.NewGet("/ldap/enabled", ldapLogin.GetLdapEnabled).DontNeedToken(),
|
||||||
|
req.NewPost("/ldap/login", ldapLogin.Login).Log(req.NewLogSave("LDAP 登录")).DontNeedToken(),
|
||||||
}
|
}
|
||||||
|
|
||||||
req.BatchSetGroup(rg, reqs[:])
|
req.BatchSetGroup(rg, reqs[:])
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ type Config struct {
|
|||||||
Mysql *Mysql `yaml:"mysql"`
|
Mysql *Mysql `yaml:"mysql"`
|
||||||
Redis *Redis `yaml:"redis"`
|
Redis *Redis `yaml:"redis"`
|
||||||
Log *Log `yaml:"log"`
|
Log *Log `yaml:"log"`
|
||||||
|
Ldap *Ldap `yaml:"ldap"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 配置文件内容校验
|
// 配置文件内容校验
|
||||||
|
|||||||
45
server/pkg/config/ldap.go
Normal file
45
server/pkg/config/ldap.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
// FieldMapping 表示用户属性和 LDAP 字段名之间的映射关系
|
||||||
|
type FieldMapping struct {
|
||||||
|
// Identifier 表示用户标识
|
||||||
|
Identifier string `yaml:"identifier,omitempty"`
|
||||||
|
// DisplayName 表示用户姓名
|
||||||
|
DisplayName string `yaml:"displayName,omitempty"`
|
||||||
|
// Email 表示 Email 地址
|
||||||
|
Email string `yaml:"email,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SecurityProtocol 表示连接 LDAP 服务器的安全协议
|
||||||
|
type SecurityProtocol string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// SecurityProtocolStartTLS 表示 StartTLS 安全协议
|
||||||
|
SecurityProtocolStartTLS SecurityProtocol = "starttls"
|
||||||
|
// SecurityProtocolLDAPS 表示 LDAPS 安全协议
|
||||||
|
SecurityProtocolLDAPS SecurityProtocol = "ldaps"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Ldap 是 LDAP 服务配置
|
||||||
|
type Ldap struct {
|
||||||
|
// Enabled 表示是否启用 LDAP 登录
|
||||||
|
Enabled bool `yaml:"enabled"`
|
||||||
|
// Host 是 LDAP 服务地址, 如: "ldap.example.com"
|
||||||
|
Host string `yaml:"host"`
|
||||||
|
// Port 是 LDAP 服务端口号, 如: 389
|
||||||
|
Port int `yaml:"port"`
|
||||||
|
// SkipTLSVerify 控制客户端是否跳过 TLS 证书验证
|
||||||
|
SkipTLSVerify bool `yaml:"skipTlsVerify"`
|
||||||
|
// BindDN 是 LDAP 服务的管理员账号,如: "cn=admin,dc=example,dc=com"
|
||||||
|
BindDN string `yaml:"bindDn"`
|
||||||
|
// BindPassword 是 LDAP 服务的管理员密码
|
||||||
|
BindPassword string `yaml:"bindPassword"`
|
||||||
|
// BaseDN 是用户所在的 base DN, 如: "ou=users,dc=example,dc=com".
|
||||||
|
BaseDN string `yaml:"baseDn"`
|
||||||
|
// UserFilter 是过滤用户的方式, 如: "(uid=%s)".
|
||||||
|
UserFilter string `yaml:"userFilter"`
|
||||||
|
// SecurityProtocol 是连接使用的 LDAP 安全协议(为空不使用安全协议),如: StartTLS, LDAPS
|
||||||
|
SecurityProtocol SecurityProtocol `yaml:"securityProtocol"`
|
||||||
|
// FieldMapping 表示用户属性和 LDAP 字段名之间的映射关系
|
||||||
|
FieldMapping FieldMapping `yaml:"fieldMapping"`
|
||||||
|
}
|
||||||
107
server/pkg/ldap/ldap.go
Normal file
107
server/pkg/ldap/ldap.go
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
package ldap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"github.com/go-ldap/ldap/v3"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"mayfly-go/pkg/config"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserInfo struct {
|
||||||
|
UserName string
|
||||||
|
DisplayName string
|
||||||
|
Email string
|
||||||
|
}
|
||||||
|
|
||||||
|
func dial() (*ldap.Conn, error) {
|
||||||
|
conf := config.Conf.Ldap
|
||||||
|
addr := fmt.Sprintf("%s:%d", conf.Host, conf.Port)
|
||||||
|
tlsConfig := &tls.Config{
|
||||||
|
ServerName: conf.Host,
|
||||||
|
InsecureSkipVerify: conf.SkipTLSVerify,
|
||||||
|
}
|
||||||
|
if conf.SecurityProtocol == config.SecurityProtocolLDAPS {
|
||||||
|
conn, err := ldap.DialTLS("tcp", addr, tlsConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Errorf("dial TLS: %v", err)
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := ldap.Dial("tcp", addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Errorf("dial: %v", err)
|
||||||
|
}
|
||||||
|
if conf.SecurityProtocol == config.SecurityProtocolStartTLS {
|
||||||
|
if err = conn.StartTLS(tlsConfig); err != nil {
|
||||||
|
_ = conn.Close()
|
||||||
|
return nil, errors.Errorf("start TLS: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect 创建 LDAP 连接
|
||||||
|
func Connect() (*ldap.Conn, error) {
|
||||||
|
conn, err := dial()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind with a system account
|
||||||
|
conf := config.Conf.Ldap
|
||||||
|
err = conn.Bind(conf.BindDN, conf.BindPassword)
|
||||||
|
if err != nil {
|
||||||
|
_ = conn.Close()
|
||||||
|
return nil, errors.Errorf("bind: %v", err)
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authenticate 通过 LDAP 验证用户名密码
|
||||||
|
func Authenticate(username, password string) (*UserInfo, error) {
|
||||||
|
conn, err := Connect()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Errorf("connect: %v", err)
|
||||||
|
}
|
||||||
|
defer func() { _ = conn.Close() }()
|
||||||
|
|
||||||
|
conf := config.Conf.Ldap
|
||||||
|
sr, err := conn.Search(
|
||||||
|
ldap.NewSearchRequest(
|
||||||
|
conf.BaseDN,
|
||||||
|
ldap.ScopeWholeSubtree,
|
||||||
|
ldap.NeverDerefAliases,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
strings.ReplaceAll(conf.UserFilter, "%s", username),
|
||||||
|
[]string{"dn", conf.FieldMapping.Identifier, conf.FieldMapping.DisplayName, conf.FieldMapping.Email},
|
||||||
|
nil,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Errorf("search user DN: %v", err)
|
||||||
|
} else if len(sr.Entries) != 1 {
|
||||||
|
return nil, errors.Errorf("expect 1 user DN but got %d", len(sr.Entries))
|
||||||
|
}
|
||||||
|
entry := sr.Entries[0]
|
||||||
|
|
||||||
|
// Bind as the user to verify their password
|
||||||
|
err = conn.Bind(entry.DN, password)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Errorf("bind user: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
userName := entry.GetAttributeValue(conf.FieldMapping.Identifier)
|
||||||
|
if userName == "" {
|
||||||
|
return nil, errors.Errorf("the attribute %q is not found or has empty value", conf.FieldMapping.Identifier)
|
||||||
|
}
|
||||||
|
return &UserInfo{
|
||||||
|
UserName: userName,
|
||||||
|
DisplayName: entry.GetAttributeValue(conf.FieldMapping.DisplayName),
|
||||||
|
Email: entry.GetAttributeValue(conf.FieldMapping.Email),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user