Compare commits

..

6 Commits

Author SHA1 Message Date
lejianwen
dbf8b23b15 fix: Config watchConfig (#135)
---
Closes: #135
2025-02-10 10:13:34 +08:00
lejianwen
79a5dd53ae fix: User disabled can not work (#133)
---
Closes: #133
2025-02-10 10:13:15 +08:00
Tao Chen
8a5b20685c fix: When OIDC and LDAP work togethar (#132 #134)
* fix OIDC create user if LDAP enable

* `newUser.GroupId = 1` for ldap

* fix
2025-02-10 10:08:49 +08:00
lejianwen
5a9c972de0 docs: Readme 2025-02-09 21:13:01 +08:00
Tao Chen
fc0e67122d docs: add LDAP info (#130) 2025-02-09 19:36:31 +08:00
lejianwen
eb642f66ca docs: Readme 2025-02-07 18:14:12 +08:00
8 changed files with 192 additions and 86 deletions

View File

@@ -20,7 +20,10 @@
- 登录 - 登录
- 地址簿 - 地址簿
- 群组 - 群组
- 授权登录,支持`github`, `google``OIDC` 登录,支持`web后台`授权登录 - 授权登录
- 支持`github`, `google``OIDC` 登录,
- 支持`web后台`授权登录
- 支持`LDAP`(AD和OpenLDAP已测试), 如果API Server配置了LDAP
- i18n - i18n
- Web Admin - Web Admin
- 用户管理 - 用户管理
@@ -29,6 +32,7 @@
- 标签管理 - 标签管理
- 群组管理 - 群组管理
- Oauth 管理 - Oauth 管理
- 配置LDAP, 配置文件或者环境变量
- 登录日志 - 登录日志
- 链接日志 - 链接日志
- 文件传输日志 - 文件传输日志
@@ -107,6 +111,7 @@
![rustdesk_command_advance](./docs/rustdesk_command_advance.png) ![rustdesk_command_advance](./docs/rustdesk_command_advance.png)
11. **LDAP 支持**, 当在API Server上设置了LDAP(已测试AD和LDAP),可以通过LDAP中的用户信息进行登录 https://github.com/lejianwen/rustdesk-api/issues/114 ,如果LDAP验证失败返回本地用户
### Web Client: ### Web Client:
@@ -208,43 +213,43 @@ ldap:
环境变量和配置文件`conf/config.yaml`中的配置一一对应,变量名前缀是`RUSTDESK_API` 环境变量和配置文件`conf/config.yaml`中的配置一一对应,变量名前缀是`RUSTDESK_API`
下面表格并未全部列出,可以参考`conf/config.yaml`中的配置。 下面表格并未全部列出,可以参考`conf/config.yaml`中的配置。
| 变量名 | 说明 | 示例 | | 变量名 | 说明 | 示例 |
|---------------------------------------------------|--------------------------------------------------------------------------------|------------------------------| |--------------------------------------------------------|--------------------------------------------------------------------------------|------------------------------|
| TZ | 时区 | Asia/Shanghai | | TZ | 时区 | Asia/Shanghai |
| RUSTDESK_API_LANG | 语言 | `en`,`zh-CN` | | RUSTDESK_API_LANG | 语言 | `en`,`zh-CN` |
| RUSTDESK_API_APP_WEB_CLIENT | 是否启用web-client; 1:启用,0:不启用; 默认启用 | 1 | | RUSTDESK_API_APP_WEB_CLIENT | 是否启用web-client; 1:启用,0:不启用; 默认启用 | 1 |
| RUSTDESK_API_APP_REGISTER | 是否开启注册; `true`, `false` 默认`false` | `false` | | RUSTDESK_API_APP_REGISTER | 是否开启注册; `true`, `false` 默认`false` | `false` |
| RUSTDESK_API_APP_SHOW_SWAGGER | 是否可见swagger文档;`1`显示,`0`不显示,默认`0`不显示 | `1` | | RUSTDESK_API_APP_SHOW_SWAGGER | 是否可见swagger文档;`1`显示,`0`不显示,默认`0`不显示 | `1` |
| RUSTDESK_API_APP_TOKEN_EXPIRE | token有效时长 | `3600` | | RUSTDESK_API_APP_TOKEN_EXPIRE | token有效时长 | `3600` |
| -----ADMIN配置----- | ---------- | ---------- | | -----ADMIN配置----- | ---------- | ---------- |
| RUSTDESK_API_ADMIN_TITLE | 后台标题 | `RustDesk Api Admin` | | RUSTDESK_API_ADMIN_TITLE | 后台标题 | `RustDesk Api Admin` |
| RUSTDESK_API_ADMIN_HELLO | 后台欢迎语,可以使用`html` | | | RUSTDESK_API_ADMIN_HELLO | 后台欢迎语,可以使用`html` | |
| RUSTDESK_API_ADMIN_HELLO_FILE | 后台欢迎语文件,如果内容多,使用文件更方便。<br>会覆盖`RUSTDESK_API_ADMIN_HELLO` | `./conf/admin/hello.html` | | RUSTDESK_API_ADMIN_HELLO_FILE | 后台欢迎语文件,如果内容多,使用文件更方便。<br>会覆盖`RUSTDESK_API_ADMIN_HELLO` | `./conf/admin/hello.html` |
| -----GIN配置----- | ---------- | ---------- | | -----GIN配置----- | ---------- | ---------- |
| RUSTDESK_API_GIN_TRUST_PROXY | 信任的代理IP列表`,`分割,默认信任所有 | 192.168.1.2,192.168.1.3 | | RUSTDESK_API_GIN_TRUST_PROXY | 信任的代理IP列表`,`分割,默认信任所有 | 192.168.1.2,192.168.1.3 |
| -----GORM配置----- | ---------- | --------------------------- | | -----GORM配置----- | ---------- | --------------------------- |
| RUSTDESK_API_GORM_TYPE | 数据库类型sqlite或者mysql默认sqlite | sqlite | | RUSTDESK_API_GORM_TYPE | 数据库类型sqlite或者mysql默认sqlite | sqlite |
| RUSTDESK_API_GORM_MAX_IDLE_CONNS | 数据库最大空闲连接数 | 10 | | RUSTDESK_API_GORM_MAX_IDLE_CONNS | 数据库最大空闲连接数 | 10 |
| RUSTDESK_API_GORM_MAX_OPEN_CONNS | 数据库最大打开连接数 | 100 | | RUSTDESK_API_GORM_MAX_OPEN_CONNS | 数据库最大打开连接数 | 100 |
| RUSTDESK_API_RUSTDESK_PERSONAL | 是否启用个人版API 1:启用,0:不启用; 默认启用 | 1 | | RUSTDESK_API_RUSTDESK_PERSONAL | 是否启用个人版API 1:启用,0:不启用; 默认启用 | 1 |
| -----MYSQL配置----- | ---------- | ---------- | | -----MYSQL配置----- | ---------- | ---------- |
| RUSTDESK_API_MYSQL_USERNAME | mysql用户名 | root | | RUSTDESK_API_MYSQL_USERNAME | mysql用户名 | root |
| RUSTDESK_API_MYSQL_PASSWORD | mysql密码 | 111111 | | RUSTDESK_API_MYSQL_PASSWORD | mysql密码 | 111111 |
| RUSTDESK_API_MYSQL_ADDR | mysql地址 | 192.168.1.66:3306 | | RUSTDESK_API_MYSQL_ADDR | mysql地址 | 192.168.1.66:3306 |
| RUSTDESK_API_MYSQL_DBNAME | mysql数据库名 | rustdesk | | RUSTDESK_API_MYSQL_DBNAME | mysql数据库名 | rustdesk |
| -----RUSTDESK配置----- | ---------- | ---------- | | -----RUSTDESK配置----- | ---------- | ---------- |
| RUSTDESK_API_RUSTDESK_ID_SERVER | Rustdesk的id服务器地址 | 192.168.1.66:21116 | | RUSTDESK_API_RUSTDESK_ID_SERVER | Rustdesk的id服务器地址 | 192.168.1.66:21116 |
| RUSTDESK_API_RUSTDESK_RELAY_SERVER | Rustdesk的relay服务器地址 | 192.168.1.66:21117 | | RUSTDESK_API_RUSTDESK_RELAY_SERVER | Rustdesk的relay服务器地址 | 192.168.1.66:21117 |
| RUSTDESK_API_RUSTDESK_API_SERVER | Rustdesk的api服务器地址 | http://192.168.1.66:21114 | | RUSTDESK_API_RUSTDESK_API_SERVER | Rustdesk的api服务器地址 | http://192.168.1.66:21114 |
| RUSTDESK_API_RUSTDESK_KEY | Rustdesk的key | 123456789 | | RUSTDESK_API_RUSTDESK_KEY | Rustdesk的key | 123456789 |
| RUSTDESK_API_RUSTDESK_KEY_FILE | Rustdesk存放key的文件 | `./conf/data/id_ed25519.pub` | | RUSTDESK_API_RUSTDESK_KEY_FILE | Rustdesk存放key的文件 | `./conf/data/id_ed25519.pub` |
| RUSTDESK_API_RUSTDESK_WEBCLIENT_MAGIC_QUERYONLINE | Web client v2 中是否启用新的在线状态查询方法; `1`:启用,`0`:不启用,默认不启用 | `0` | | RUSTDESK_API_RUSTDESK_WEBCLIENT<br/>_MAGIC_QUERYONLINE | Web client v2 中是否启用新的在线状态查询方法; `1`:启用,`0`:不启用,默认不启用 | `0` |
| ----PROXY配置----- | ---------- | ---------- | | ----PROXY配置----- | ---------- | ---------- |
| RUSTDESK_API_PROXY_ENABLE | 是否启用代理:`false`, `true` | `false` | | RUSTDESK_API_PROXY_ENABLE | 是否启用代理:`false`, `true` | `false` |
| RUSTDESK_API_PROXY_HOST | 代理地址 | `http://127.0.0.1:1080` | | RUSTDESK_API_PROXY_HOST | 代理地址 | `http://127.0.0.1:1080` |
| ----JWT配置---- | -------- | -------- | | ----JWT配置---- | -------- | -------- |
| RUSTDESK_API_JWT_KEY | 自定义JWT KEY,为空则不启用JWT<br/>如果没使用`lejianwen/rustdesk-server`中的`MUST_LOGIN`,建议设置为空 | | | RUSTDESK_API_JWT_KEY | 自定义JWT KEY,为空则不启用JWT<br/>如果没使用`lejianwen/rustdesk-server`中的`MUST_LOGIN`,建议设置为空 | |
| RUSTDESK_API_JWT_EXPIRE_DURATION | JWT有效时间 | 360000 | | RUSTDESK_API_JWT_EXPIRE_DURATION | JWT有效时间 | 360000 |
### 运行 ### 运行
@@ -367,4 +372,4 @@ ldap:
<img src="https://contrib.rocks/image?repo=lejianwen/rustdesk-api" /> <img src="https://contrib.rocks/image?repo=lejianwen/rustdesk-api" />
</a> </a>
## 如果觉得这个项目对你有帮助,请给一个star,谢谢! ## 感谢你的支持!如果这个项目对你有帮助,请点个⭐️鼓励一下,谢谢!

View File

@@ -19,7 +19,10 @@ desktop software that provides self-hosted solutions.
- Login - Login
- Address Book - Address Book
- Groups - Groups
- Authorized login, supports `GitHub`, `Google` and `OIDC` login, supports `web admin` authorized login - Authorized login,
- supports `GitHub`, `Google` and `OIDC` login,
- supports `web admin` authorized login,
- supports LDAP(test AD and openladp) if API Server config
- i18n - i18n
- Web Admin - Web Admin
- User Management - User Management
@@ -28,6 +31,7 @@ desktop software that provides self-hosted solutions.
- Tag Management - Tag Management
- Group Management - Group Management
- OAuth Management - OAuth Management
- LDAP Config by config file or ENV
- Login Logs - Login Logs
- Connection Logs - Connection Logs
- File Transfer Logs - File Transfer Logs
@@ -107,7 +111,7 @@ displaying data.Frontend code is available at [rustdesk-api-web](https://github.
![rustdesk_command_advance](./docs/en_img/rustdesk_command_advance.png) ![rustdesk_command_advance](./docs/en_img/rustdesk_command_advance.png)
11. **LDAP Support**, When you setup the LDAP(test for OpenLDAP and AD), you can login with the LDAP's user. https://github.com/lejianwen/rustdesk-api/issues/114 , if LDAP fail fallback local user
### Web Client: ### Web Client:
@@ -238,7 +242,7 @@ The table below does not list all configurations. Please refer to the configurat
| RUSTDESK_API_RUSTDESK_API_SERVER | Rustdesk API server address | http://192.168.1.66:21114 | | RUSTDESK_API_RUSTDESK_API_SERVER | Rustdesk API server address | http://192.168.1.66:21114 |
| RUSTDESK_API_RUSTDESK_KEY | Rustdesk key | 123456789 | | RUSTDESK_API_RUSTDESK_KEY | Rustdesk key | 123456789 |
| RUSTDESK_API_RUSTDESK_KEY_FILE | Rustdesk key file | `./conf/data/id_ed25519.pub` | | RUSTDESK_API_RUSTDESK_KEY_FILE | Rustdesk key file | `./conf/data/id_ed25519.pub` |
| RUSTDESK_API_RUSTDESK_WEBCLIENT_MAGIC_QUERYONLINE | New online query method is enabled in the web client v2; '1': Enabled, '0': Disabled, not enabled by default | `0` | | RUSTDESK_API_RUSTDESK<br/>_WEBCLIENT_MAGIC_QUERYONLINE | New online query method is enabled in the web client v2; '1': Enabled, '0': Disabled, not enabled by default | `0` |
| ---- PROXY ----- | --------------- | ---------- | | ---- PROXY ----- | --------------- | ---------- |
| RUSTDESK_API_PROXY_ENABLE | proxy_enable :`false`, `true` | `false` | | RUSTDESK_API_PROXY_ENABLE | proxy_enable :`false`, `true` | `false` |
| RUSTDESK_API_PROXY_HOST | proxy_host | `http://127.0.0.1:1080` | | RUSTDESK_API_PROXY_HOST | proxy_host | `http://127.0.0.1:1080` |
@@ -367,4 +371,4 @@ Thanks to everyone who contributed!
<img src="https://contrib.rocks/image?repo=lejianwen/rustdesk-api" /> <img src="https://contrib.rocks/image?repo=lejianwen/rustdesk-api" />
</a> </a>
## If you find this project helpful, please give it a star, thank you! ## Thanks for your support! If you find this project useful, please give it a ⭐️. Thank you!

View File

@@ -56,19 +56,20 @@ func Init(rowVal *Config, path string) *viper.Viper {
if err != nil { if err != nil {
panic(fmt.Errorf("Fatal error config file: %s \n", err)) panic(fmt.Errorf("Fatal error config file: %s \n", err))
} }
v.WatchConfig()
/* /*
//监听配置修改没什么必要 v.WatchConfig()
v.OnConfigChange(func(e fsnotify.Event) {
//配置文件修改监听
fmt.Println("config file changed:", e.Name) //监听配置修改没什么必要
if err2 := v.Unmarshal(rowVal); err2 != nil { v.OnConfigChange(func(e fsnotify.Event) {
fmt.Println(err2) //配置文件修改监听
} fmt.Println("config file changed:", e.Name)
rowVal.Rustdesk.LoadKeyFile() if err2 := v.Unmarshal(rowVal); err2 != nil {
rowVal.Rustdesk.ParsePort() fmt.Println(err2)
}) }
rowVal.Rustdesk.LoadKeyFile()
rowVal.Rustdesk.ParsePort()
})
*/ */
if err := v.Unmarshal(rowVal); err != nil { if err := v.Unmarshal(rowVal); err != nil {
fmt.Println(err) fmt.Println(err)

View File

@@ -182,15 +182,20 @@ func (ct *Login) Login(c *gin.Context) {
global.Logger.Warn(fmt.Sprintf("Login Fail: %s %s %s", "UsernameOrPasswordError", c.RemoteIP(), clientIp)) global.Logger.Warn(fmt.Sprintf("Login Fail: %s %s %s", "UsernameOrPasswordError", c.RemoteIP(), clientIp))
loginLimiter.RecordFailure(clientIp) loginLimiter.RecordFailure(clientIp)
if loginLimiter.NeedsCaptcha(clientIp) { if loginLimiter.NeedsCaptcha(clientIp) {
// 移除原验证码,重新生成
loginLimiter.RemoveCaptcha(clientIp) loginLimiter.RemoveCaptcha(clientIp)
response.Fail(c, 110, response.TranslateMsg(c, "UsernameOrPasswordError"))
return
} }
response.Fail(c, 101, response.TranslateMsg(c, "UsernameOrPasswordError")) response.Fail(c, 101, response.TranslateMsg(c, "UsernameOrPasswordError"))
return return
} }
if !service.AllService.UserService.CheckUserEnable(u) {
if loginLimiter.NeedsCaptcha(clientIp) {
loginLimiter.RemoveCaptcha(clientIp)
}
response.Fail(c, 101, response.TranslateMsg(c, "UserDisabled"))
return
}
ut := service.AllService.UserService.Login(u, &model.LoginLog{ ut := service.AllService.UserService.Login(u, &model.LoginLog{
UserId: u.Id, UserId: u.Id,
Client: model.LoginLogClientWebAdmin, Client: model.LoginLogClientWebAdmin,

View File

@@ -51,6 +51,11 @@ func (l *Login) Login(c *gin.Context) {
return return
} }
if !service.AllService.UserService.CheckUserEnable(u) {
response.Error(c, response.TranslateMsg(c, "UserDisabled"))
return
}
//根据refer判断是webclient还是app //根据refer判断是webclient还是app
ref := c.GetHeader("referer") ref := c.GetHeader("referer")
if ref != "" { if ref != "" {

View File

@@ -24,6 +24,14 @@ func AdminAuth() gin.HandlerFunc {
return return
} }
if !service.AllService.UserService.CheckUserEnable(user) {
c.JSON(401, gin.H{
"error": "Unauthorized",
})
c.Abort()
return
}
c.Set("curUser", user) c.Set("curUser", user)
c.Set("token", token) c.Set("token", token)
//如果时间小于1天,token自动续期 //如果时间小于1天,token自动续期

View File

@@ -4,15 +4,30 @@ import (
"crypto/tls" "crypto/tls"
"errors" "errors"
"fmt" "fmt"
"github.com/go-ldap/ldap/v3"
"strconv" "strconv"
"strings" "strings"
"github.com/go-ldap/ldap/v3"
"Gwen/config" "Gwen/config"
"Gwen/global" "Gwen/global"
"Gwen/model" "Gwen/model"
) )
var (
ErrLdapNotEnabled = errors.New("LdapNotEnabled")
ErrLdapUserDisabled = errors.New("UserDisabledAtLdap")
ErrLdapUserNotFound = errors.New("UserNotFound")
ErrLdapMailNotMatch = errors.New("MailNotMatch")
ErrLdapConnectFailed = errors.New("LdapConnectFailed")
ErrLdapSearchFailed = errors.New("LdapSearchRequestFailed")
ErrLdapTlsFailed = errors.New("LdapStartTLSFailed")
ErrLdapBindService = errors.New("LdapBindServiceFailed")
ErrLdapBindFailed = errors.New("LdapBindFailed")
ErrLdapToLocalUserFailed = errors.New("LdapToLocalUserFailed")
ErrLdapCreateUserFailed = errors.New("LdapCreateUserFailed")
)
// LdapService is responsible for LDAP authentication and user synchronization. // LdapService is responsible for LDAP authentication and user synchronization.
type LdapService struct { type LdapService struct {
} }
@@ -43,6 +58,11 @@ func (lu *LdapUser) ToUser(u *model.User) *model.User {
u.Username = lu.Username u.Username = lu.Username
u.Email = lu.Email u.Email = lu.Email
u.Nickname = lu.Name() u.Nickname = lu.Name()
if lu.Enabled {
u.Status = model.COMMON_STATUS_ENABLE
} else {
u.Status = model.COMMON_STATUS_DISABLED
}
return u return u
} }
@@ -50,21 +70,21 @@ func (lu *LdapUser) ToUser(u *model.User) *model.User {
func (ls *LdapService) connectAndBind(cfg *config.Ldap, username, password string) (*ldap.Conn, error) { func (ls *LdapService) connectAndBind(cfg *config.Ldap, username, password string) (*ldap.Conn, error) {
conn, err := ldap.DialURL(cfg.Url) conn, err := ldap.DialURL(cfg.Url)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to dial LDAP: %w", err) return nil, errors.Join(ErrLdapConnectFailed, err)
} }
if cfg.TLS { if cfg.TLS {
// WARNING: InsecureSkipVerify: true is not recommended for production // WARNING: InsecureSkipVerify: true is not recommended for production
if err = conn.StartTLS(&tls.Config{InsecureSkipVerify: !cfg.TlsVerify}); err != nil { if err = conn.StartTLS(&tls.Config{InsecureSkipVerify: !cfg.TlsVerify}); err != nil {
conn.Close() conn.Close()
return nil, fmt.Errorf("failed to start TLS: %w", err) return nil, errors.Join(ErrLdapTlsFailed, err)
} }
} }
// Bind as the "service" user // Bind as the "service" user
if err = conn.Bind(username, password); err != nil { if err = conn.Bind(username, password); err != nil {
conn.Close() conn.Close()
return nil, fmt.Errorf("failed to bind with service account: %w", err) return nil, errors.Join(ErrLdapBindService, err)
} }
return conn, nil return conn, nil
} }
@@ -87,29 +107,17 @@ func (ls *LdapService) verifyCredentials(cfg *config.Ldap, username, password st
// Authenticate checks the provided username and password against LDAP. // Authenticate checks the provided username and password against LDAP.
// Returns the corresponding *model.User if successful, or an error if not. // Returns the corresponding *model.User if successful, or an error if not.
func (ls *LdapService) Authenticate(username, password string) (*model.User, error) { func (ls *LdapService) Authenticate(username, password string) (*model.User, error) {
cfg := &global.Config.Ldap ldapUser, err := ls.GetUserInfoByUsernameLdap(username)
// 1. Use a service bind to search for the user DN
sr, err := ls.usernameSearchResult(cfg, username)
if err != nil { if err != nil {
return nil, fmt.Errorf("LDAP search request failed: %w", err) return nil, err
} }
if len(sr.Entries) != 1 {
return nil, errors.New("user does not exist or too many entries returned")
}
entry := sr.Entries[0]
userDN := entry.DN
err = ls.verifyCredentials(cfg, userDN, password)
if err != nil {
return nil, fmt.Errorf("LDAP authentication failed: %w", err)
}
ldapUser := ls.userResultToLdapUser(cfg, entry)
if !ldapUser.Enabled { if !ldapUser.Enabled {
return nil, errors.New("UserDisabledAtLdap") return nil, ErrLdapUserDisabled
} }
cfg := &global.Config.Ldap
user, err := ls.mapToLocalUser(cfg, ldapUser) user, err := ls.mapToLocalUser(cfg, ldapUser)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to map LDAP user to local user: %w", err) return nil, errors.Join(ErrLdapToLocalUserFailed, err)
} }
return user, nil return user, nil
} }
@@ -126,8 +134,9 @@ func (ls *LdapService) mapToLocalUser(cfg *config.Ldap, lu *LdapUser) (*model.Us
// Typically, you dont store LDAP user passwords locally. // Typically, you dont store LDAP user passwords locally.
// If needed, you can set a random password here. // If needed, you can set a random password here.
newUser.IsAdmin = &isAdmin newUser.IsAdmin = &isAdmin
newUser.GroupId = 1
if err := global.DB.Create(newUser).Error; err != nil { if err := global.DB.Create(newUser).Error; err != nil {
return nil, fmt.Errorf("failed to create new user: %w", err) return nil, errors.Join(ErrLdapCreateUserFailed, err)
} }
return userService.InfoByUsername(lu.Username), nil return userService.InfoByUsername(lu.Username), nil
} }
@@ -137,6 +146,7 @@ func (ls *LdapService) mapToLocalUser(cfg *config.Ldap, lu *LdapUser) (*model.Us
originalEmail := localUser.Email originalEmail := localUser.Email
originalNickname := localUser.Nickname originalNickname := localUser.Nickname
originalIsAdmin := localUser.IsAdmin originalIsAdmin := localUser.IsAdmin
originalStatus := localUser.Status
lu.ToUser(localUser) // merges LDAP data into the existing user lu.ToUser(localUser) // merges LDAP data into the existing user
localUser.IsAdmin = &isAdmin localUser.IsAdmin = &isAdmin
if err := userService.Update(localUser); err != nil { if err := userService.Update(localUser); err != nil {
@@ -144,6 +154,7 @@ func (ls *LdapService) mapToLocalUser(cfg *config.Ldap, lu *LdapUser) (*model.Us
localUser.Email = originalEmail localUser.Email = originalEmail
localUser.Nickname = originalNickname localUser.Nickname = originalNickname
localUser.IsAdmin = originalIsAdmin localUser.IsAdmin = originalIsAdmin
localUser.Status = originalStatus
} }
} }
@@ -177,6 +188,56 @@ func (ls *LdapService) IsEmailExists(email string) bool {
return len(sr.Entries) > 0 return len(sr.Entries) > 0
} }
// GetUserInfoByUsernameLdap returns the user info from LDAP for the given username.
func (ls *LdapService) GetUserInfoByUsernameLdap(username string) (*LdapUser, error) {
cfg := &global.Config.Ldap
if !cfg.Enable {
return nil, ErrLdapNotEnabled
}
sr, err := ls.usernameSearchResult(cfg, username)
if err != nil {
return nil, errors.Join(ErrLdapSearchFailed, err)
}
if len(sr.Entries) != 1 {
return nil, ErrLdapUserNotFound
}
return ls.userResultToLdapUser(cfg, sr.Entries[0]), nil
}
// GetUserInfoByUsernameLocal returns the user info from LDAP for the given username. If the user exists, it will sync the user info to the local database.
func (ls *LdapService) GetUserInfoByUsernameLocal(username string) (*model.User, error) {
ldapUser, err := ls.GetUserInfoByUsernameLdap(username)
if err != nil {
return &model.User{}, err
}
return ls.mapToLocalUser(&global.Config.Ldap, ldapUser)
}
// GetUserInfoByEmailLdap returns the user info from LDAP for the given email.
func (ls *LdapService) GetUserInfoByEmailLdap(email string) (*LdapUser, error) {
cfg := &global.Config.Ldap
if !cfg.Enable {
return nil, ErrLdapNotEnabled
}
sr, err := ls.emailSearchResult(cfg, email)
if err != nil {
return nil, errors.Join(ErrLdapSearchFailed, err)
}
if len(sr.Entries) != 1 {
return nil, ErrLdapUserNotFound
}
return ls.userResultToLdapUser(cfg, sr.Entries[0]), nil
}
// GetUserInfoByEmailLocal returns the user info from LDAP for the given email. if the user exists, it will synchronize the user information to local database.
func (ls *LdapService) GetUserInfoByEmailLocal(email string) (*model.User, error) {
ldapUser, err := ls.GetUserInfoByEmailLdap(email)
if err != nil {
return &model.User{}, err
}
return ls.mapToLocalUser(&global.Config.Ldap, ldapUser)
}
// usernameSearchResult returns the search result for the given username. // usernameSearchResult returns the search result for the given username.
func (ls *LdapService) usernameSearchResult(cfg *config.Ldap, username string) (*ldap.SearchResult, error) { func (ls *LdapService) usernameSearchResult(cfg *config.Ldap, username string) (*ldap.SearchResult, error) {
// Build the combined filter for the username // Build the combined filter for the username

View File

@@ -5,12 +5,13 @@ import (
"Gwen/model" "Gwen/model"
"Gwen/utils" "Gwen/utils"
"errors" "errors"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"math/rand" "math/rand"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
) )
type UserService struct { type UserService struct {
@@ -322,7 +323,16 @@ func (us *UserService) RegisterByOauth(oauthUser *model.OauthUser, op string) (e
email = strings.ToLower(email) email = strings.ToLower(email)
// update email to oauthUser, in case it contain upper case // update email to oauthUser, in case it contain upper case
oauthUser.Email = email oauthUser.Email = email
user := us.InfoByEmail(email) // call this, if find user by email, it will update the email to local database
user, ldapErr := AllService.LdapService.GetUserInfoByEmailLocal(email)
// If we enable ldap, and the error is not ErrLdapUserNotFound, return the error because we could not sure if the user is not found in ldap
if !(errors.Is(ldapErr, ErrLdapNotEnabled) || errors.Is(ldapErr, ErrLdapUserNotFound) || ldapErr == nil) {
return ldapErr, user
}
if user.Id == 0 {
// this means the user is not found in ldap, maybe ldao is not enabled
user = us.InfoByEmail(email)
}
if user.Id != 0 { if user.Id != 0 {
ut.FromOauthUser(user.Id, oauthUser, oauthType, op) ut.FromOauthUser(user.Id, oauthUser, oauthType, op)
global.DB.Create(ut) global.DB.Create(ut)
@@ -491,8 +501,15 @@ func (us *UserService) VerifyJWT(token string) (uint, error) {
// IsUsernameExists 判断用户名是否存在, it will check the internal database and LDAP(if enabled) // IsUsernameExists 判断用户名是否存在, it will check the internal database and LDAP(if enabled)
func (us *UserService) IsUsernameExists(username string) bool { func (us *UserService) IsUsernameExists(username string) bool {
return us.IsUsernameExistsLocal(username) || AllService.LdapService.IsUsernameExists(username)
}
func (us *UserService) IsUsernameExistsLocal(username string) bool {
u := &model.User{} u := &model.User{}
global.DB.Where("username = ?", username).First(u) global.DB.Where("username = ?", username).First(u)
existsInLdap := AllService.LdapService.IsUsernameExists(username) return u.Id != 0
return u.Id != 0 || existsInLdap }
func (us *UserService) IsEmailExistsLdap(email string) bool {
return AllService.LdapService.IsEmailExists(email)
} }