From 4aa99d07761934020488ae037d51c057b1ccb2da Mon Sep 17 00:00:00 2001 From: HGHNICE <84365959+HGHNice@users.noreply.github.com> Date: Thu, 17 Jul 2025 11:51:07 +0800 Subject: [PATCH 1/5] =?UTF-8?q?=F0=9F=9A=A7=20Fix=20ldap=20Size=20Limit=20?= =?UTF-8?q?Exceeded?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/services/ldap.go | 100 +++++++++++++++++++++++++++++--------- 1 file changed, 76 insertions(+), 24 deletions(-) diff --git a/internal/services/ldap.go b/internal/services/ldap.go index f47fed5f..0438e546 100644 --- a/internal/services/ldap.go +++ b/internal/services/ldap.go @@ -2,14 +2,15 @@ package services import ( "fmt" - "github.com/robfig/cron/v3" - "github.com/zeromicro/go-zero/core/logc" - "gopkg.in/ldap.v2" "time" "watchAlert/internal/global" "watchAlert/internal/models" "watchAlert/pkg/ctx" "watchAlert/pkg/tools" + + "github.com/robfig/cron/v3" + "github.com/zeromicro/go-zero/core/logc" + "gopkg.in/ldap.v2" ) type ldapService struct { @@ -53,38 +54,89 @@ type ldapUser struct { func (l ldapService) ListUsers() ([]ldapUser, error) { lc := global.Config.Ldap - searchRequest := ldap.NewSearchRequest( - lc.BaseDN, - ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, - "(&(objectClass=*))", - []string{}, - nil, - ) auth, err := l.getAdminAuth() if err != nil { return nil, err } defer auth.Close() - sr, err := auth.Search(searchRequest) - if err != nil { - logc.Errorf(l.ctx.Ctx, fmt.Sprintf("LDAP 用户搜索失败, err: %s", err.Error())) - return nil, err - } var users []ldapUser - for _, entry := range sr.Entries { - uid := entry.GetAttributeValue("uid") - if uid == "" { - continue + pageSize := uint32(50) + var pagingControl *ldap.ControlPaging + + logc.Infof(l.ctx.Ctx, "开始分页查询LDAP用户,每页大小: %d", pageSize) + + pageNum := 1 + for { + logc.Infof(l.ctx.Ctx, "正在查询第 %d 页用户...", pageNum) + + searchRequest := ldap.NewSearchRequest( + lc.BaseDN, + ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, // size limit 设为 0,由分页控制 + "(&(objectClass=person)(objectClass=user))", + []string{"uid", "cn", "sAMAccountName", "mobile", "mail"}, + nil, + ) + + // 设置分页控制 + if pagingControl != nil { + searchRequest.Controls = []ldap.Control{pagingControl} + } else { + pagingControl = ldap.NewControlPaging(pageSize) + searchRequest.Controls = []ldap.Control{pagingControl} + } + + sr, err := auth.Search(searchRequest) + if err != nil { + logc.Errorf(l.ctx.Ctx, fmt.Sprintf("LDAP 第 %d 页用户搜索失败, err: %s", pageNum, err.Error())) + return nil, err + } + + // 处理当前页的搜索结果 + pageUserCount := 0 + for _, entry := range sr.Entries { + uid := entry.GetAttributeValue("sAMAccountName") + if uid == "" { + uid = entry.GetAttributeValue("uid") + } + if uid == "" { + uid = entry.GetAttributeValue("cn") + } + if uid == "" { + continue + } + users = append(users, ldapUser{ + Uid: uid, + Mobile: entry.GetAttributeValue("mobile"), + Mail: entry.GetAttributeValue("mail"), + }) + pageUserCount++ + } + + logc.Infof(l.ctx.Ctx, "第 %d 页查询完成,获取到 %d 个用户", pageNum, pageUserCount) + + updatedControl := ldap.FindControl(sr.Controls, ldap.ControlTypePaging) + if updatedControl == nil { + logc.Infof(l.ctx.Ctx, "没有更多页面,查询结束") + break + } + + pagingControl = updatedControl.(*ldap.ControlPaging) + if len(pagingControl.Cookie) == 0 { + logc.Infof(l.ctx.Ctx, "分页Cookie为空,查询结束") + break + } + + pageNum++ + + if pageNum > 100 { + logc.Errorf(l.ctx.Ctx, "查询页数超过100页,可能存在问题,停止查询") + break } - users = append(users, ldapUser{ - Uid: entry.GetAttributeValue("uid"), - Mobile: entry.GetAttributeValue("mobile"), - Mail: entry.GetAttributeValue("mail"), - }) } + logc.Infof(l.ctx.Ctx, "LDAP 用户同步完成,共获取 %d 个用户", len(users)) return users, nil } From 4f61142b767c7c777d2c814410f0574b20343e6e Mon Sep 17 00:00:00 2001 From: HGHNICE <84365959+HGHNice@users.noreply.github.com> Date: Thu, 17 Jul 2025 16:36:38 +0800 Subject: [PATCH 2/5] =?UTF-8?q?=F0=9F=9A=A7=20Fix=20ldap=20Size=20Limit=20?= =?UTF-8?q?Exceeded?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/services/ldap.go | 77 +++++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 39 deletions(-) diff --git a/internal/services/ldap.go b/internal/services/ldap.go index 0438e546..f6a6a07e 100644 --- a/internal/services/ldap.go +++ b/internal/services/ldap.go @@ -61,52 +61,45 @@ func (l ldapService) ListUsers() ([]ldapUser, error) { } defer auth.Close() - var users []ldapUser - pageSize := uint32(50) - var pagingControl *ldap.ControlPaging + logc.Infof(l.ctx.Ctx, "开始LDAP分页查询用户...") - logc.Infof(l.ctx.Ctx, "开始分页查询LDAP用户,每页大小: %d", pageSize) + var totalResults []ldapUser + pageSize := uint32(500) + pages := 0 + pagingControl := ldap.NewControlPaging(pageSize) - pageNum := 1 for { - logc.Infof(l.ctx.Ctx, "正在查询第 %d 页用户...", pageNum) + pages++ + logc.Infof(l.ctx.Ctx, "正在查询第 %d 页,页面大小: %d", pages, pageSize) + // 创建搜索请求 searchRequest := ldap.NewSearchRequest( lc.BaseDN, - ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, // size limit 设为 0,由分页控制 - "(&(objectClass=person)(objectClass=user))", - []string{"uid", "cn", "sAMAccountName", "mobile", "mail"}, - nil, + ldap.ScopeWholeSubtree, + ldap.NeverDerefAliases, + 0, 0, false, + "(objectClass=person)", + []string{"sAMAccountName", "cn", "mail", "mobile"}, + []ldap.Control{pagingControl}, ) - // 设置分页控制 - if pagingControl != nil { - searchRequest.Controls = []ldap.Control{pagingControl} - } else { - pagingControl = ldap.NewControlPaging(pageSize) - searchRequest.Controls = []ldap.Control{pagingControl} - } - - sr, err := auth.Search(searchRequest) + searchResult, err := auth.Search(searchRequest) if err != nil { - logc.Errorf(l.ctx.Ctx, fmt.Sprintf("LDAP 第 %d 页用户搜索失败, err: %s", pageNum, err.Error())) + logc.Errorf(l.ctx.Ctx, fmt.Sprintf("第 %d 页查询失败: %s", pages, err.Error())) return nil, err } - // 处理当前页的搜索结果 pageUserCount := 0 - for _, entry := range sr.Entries { + for _, entry := range searchResult.Entries { uid := entry.GetAttributeValue("sAMAccountName") - if uid == "" { - uid = entry.GetAttributeValue("uid") - } if uid == "" { uid = entry.GetAttributeValue("cn") } if uid == "" { continue } - users = append(users, ldapUser{ + + totalResults = append(totalResults, ldapUser{ Uid: uid, Mobile: entry.GetAttributeValue("mobile"), Mail: entry.GetAttributeValue("mail"), @@ -114,30 +107,36 @@ func (l ldapService) ListUsers() ([]ldapUser, error) { pageUserCount++ } - logc.Infof(l.ctx.Ctx, "第 %d 页查询完成,获取到 %d 个用户", pageNum, pageUserCount) + logc.Infof(l.ctx.Ctx, "第 %d 页完成,获取到 %d 个用户,总计: %d", pages, pageUserCount, len(totalResults)) - updatedControl := ldap.FindControl(sr.Controls, ldap.ControlTypePaging) - if updatedControl == nil { - logc.Infof(l.ctx.Ctx, "没有更多页面,查询结束") - break + var nextPageControl *ldap.ControlPaging + for _, control := range searchResult.Controls { + if control.GetControlType() == ldap.ControlTypePaging { + nextPageControl = control.(*ldap.ControlPaging) + break + } } - pagingControl = updatedControl.(*ldap.ControlPaging) - if len(pagingControl.Cookie) == 0 { - logc.Infof(l.ctx.Ctx, "分页Cookie为空,查询结束") + if nextPageControl == nil || len(nextPageControl.Cookie) == 0 { + logc.Infof(l.ctx.Ctx, "没有更多页面,查询完成") break } - pageNum++ + pagingControl = &ldap.ControlPaging{ + PagingSize: pageSize, + Cookie: nextPageControl.Cookie, + } + + logc.Infof(l.ctx.Ctx, "找到下一页Cookie,长度: %d", len(nextPageControl.Cookie)) - if pageNum > 100 { - logc.Errorf(l.ctx.Ctx, "查询页数超过100页,可能存在问题,停止查询") + if pages >= 50 { + logc.Errorf(l.ctx.Ctx, "查询页数超过50页,停止查询") break } } - logc.Infof(l.ctx.Ctx, "LDAP 用户同步完成,共获取 %d 个用户", len(users)) - return users, nil + logc.Infof(l.ctx.Ctx, "LDAP分页查询完成,共 %d 页,获取 %d 个用户", pages, len(totalResults)) + return totalResults, nil } func (l ldapService) SyncUserToW8t() { From 8212f4dde3c61f2fa9d82c4f418ffe211b48b735 Mon Sep 17 00:00:00 2001 From: HGHNICE <84365959+HGHNice@users.noreply.github.com> Date: Tue, 22 Jul 2025 17:10:53 +0800 Subject: [PATCH 3/5] =?UTF-8?q?=F0=9F=9A=A7Fix=20ldap=20login=20passwd?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复当以ldap登录时密码被加密导致无法登录问题 --- internal/services/user.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/services/user.go b/internal/services/user.go index 473c9883..37baadc9 100644 --- a/internal/services/user.go +++ b/internal/services/user.go @@ -62,6 +62,7 @@ func (us userService) Get(req interface{}) (interface{}, interface{}) { func (us userService) Login(req interface{}) (interface{}, interface{}) { r := req.(*models.Member) + originalPassword := r.Password r.Password = tools.GenerateHashPassword(r.Password) q := models.MemberQuery{ @@ -75,7 +76,7 @@ func (us userService) Login(req interface{}) (interface{}, interface{}) { switch data.CreateBy { case "LDAP": if global.Config.Ldap.Enabled { - err := LdapService.Login(r.UserName, r.Password) + err := LdapService.Login(r.UserName, originalPassword) if err != nil { logc.Error(us.ctx.Ctx, fmt.Sprintf("LDAP 用户登陆失败, err: %s", err.Error())) return nil, fmt.Errorf("LDAP 用户登陆失败, err: %s", err.Error()) From 48cd2b485a35b9e6d88887e66d1ea1fe09019ccd Mon Sep 17 00:00:00 2001 From: HGHNICE <84365959+HGHNice@users.noreply.github.com> Date: Fri, 29 Aug 2025 11:31:30 +0800 Subject: [PATCH 4/5] fix: ldap.go MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 使用真实的DN进行绑定认证,更换的支持用户登录 --- internal/services/ldap.go | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/internal/services/ldap.go b/internal/services/ldap.go index 5b49ac9f..a0bcea66 100644 --- a/internal/services/ldap.go +++ b/internal/services/ldap.go @@ -187,17 +187,44 @@ func (l ldapService) Login(username, password string) error { logc.Errorf(l.ctx.Ctx, err.Error()) return err } + defer auth.Close() - userDn := fmt.Sprintf("%s=%s,%s", l.ldapConfig.UserPrefix, username, l.ldapConfig.UserDN) - err = auth.Bind(userDn, password) + // 先搜索用户,获取真实的DN + searchRequest := ldap.NewSearchRequest( + l.ldapConfig.BaseDN, + ldap.ScopeWholeSubtree, + ldap.NeverDerefAliases, + 1, 0, false, + fmt.Sprintf("(sAMAccountName=%s)", ldap.EscapeFilter(username)), + []string{"dn"}, + nil, + ) + + searchResult, err := auth.Search(searchRequest) if err != nil { - logc.Errorf(l.ctx.Ctx, fmt.Sprintf("LDAP 用户登陆失败, err: %s", err.Error())) + logc.Errorf(l.ctx.Ctx, fmt.Sprintf("LDAP 搜索用户失败, username: %s, err: %s", username, err.Error())) return err } + if len(searchResult.Entries) == 0 { + logc.Errorf(l.ctx.Ctx, fmt.Sprintf("LDAP 用户不存在, username: %s", username)) + return fmt.Errorf("用户不存在") + } + + userDN := searchResult.Entries[0].DN + logc.Infof(l.ctx.Ctx, fmt.Sprintf("找到用户DN: %s", userDN)) + + err = auth.Bind(userDN, password) + if err != nil { + logc.Errorf(l.ctx.Ctx, fmt.Sprintf("LDAP 用户登陆失败, username: %s, DN: %s, err: %s", username, userDN, err.Error())) + return err + } + + logc.Infof(l.ctx.Ctx, fmt.Sprintf("LDAP 用户登陆成功, username: %s", username)) return nil } + func (l ldapService) SyncUsersCronjob(ctx context.Context) { c := cron.New() _, err := c.AddFunc(l.ldapConfig.Cronjob, func() { From 788b0029841e572fcaf53a05a52c910b93851c98 Mon Sep 17 00:00:00 2001 From: hgh <1481634250@qq.com> Date: Mon, 9 Mar 2026 22:13:40 +0800 Subject: [PATCH 5/5] =?UTF-8?q?=E2=9C=A8=20feat(ldap):=20add=20filter=20su?= =?UTF-8?q?pport=20to=20restrict=20login=20users?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在 LdapConfig 中添加 Filter 字段,以允许限制哪些 LDAP 用户可以登录并同步。当设置了 Filter 时,它会与现有的搜索过滤器使用 AND 逻辑组合。 Closes #174 --- internal/models/settings.go | 2 ++ internal/services/ldap.go | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/internal/models/settings.go b/internal/models/settings.go index 504bec0f..2c37f35c 100644 --- a/internal/models/settings.go +++ b/internal/models/settings.go @@ -53,6 +53,8 @@ type LdapConfig struct { UserPrefix string `json:"userPrefix"` DefaultUserRole string `json:"defaultUserRole"` Cronjob string `json:"cronjob"` + // Filter 用于限制允许登录的用户范围,例如: (&(objectClass=person)(memberOf=cn=jms,ou=groups,dc=test,dc=com)) + Filter string `json:"filter"` } type OidcConfig struct { diff --git a/internal/services/ldap.go b/internal/services/ldap.go index a0bcea66..d51846f6 100644 --- a/internal/services/ldap.go +++ b/internal/services/ldap.go @@ -73,6 +73,11 @@ func (l ldapService) ListUsers() ([]ldapUser, error) { pages := 0 pagingControl := ldap.NewControlPaging(pageSize) + listFilter := "(objectClass=person)" + if l.ldapConfig.Filter != "" { + listFilter = fmt.Sprintf("(&%s(objectClass=person))", l.ldapConfig.Filter) + } + for { pages++ @@ -82,7 +87,7 @@ func (l ldapService) ListUsers() ([]ldapUser, error) { ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, - "(objectClass=person)", + listFilter, []string{"sAMAccountName", "cn", "mail", "mobile"}, []ldap.Control{pagingControl}, ) @@ -190,12 +195,17 @@ func (l ldapService) Login(username, password string) error { defer auth.Close() // 先搜索用户,获取真实的DN + loginFilter := fmt.Sprintf("(sAMAccountName=%s)", ldap.EscapeFilter(username)) + if l.ldapConfig.Filter != "" { + loginFilter = fmt.Sprintf("(&%s(sAMAccountName=%s))", l.ldapConfig.Filter, ldap.EscapeFilter(username)) + } + searchRequest := ldap.NewSearchRequest( l.ldapConfig.BaseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 1, 0, false, - fmt.Sprintf("(sAMAccountName=%s)", ldap.EscapeFilter(username)), + loginFilter, []string{"dn"}, nil, )