From 6e3b16d4256004f3c26221d07d614035abd21076 Mon Sep 17 00:00:00 2001 From: nomoneynolife <53741948+nomoneynolife@users.noreply.github.com> Date: Fri, 5 Sep 2025 12:53:45 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9Aldap=20allow-group=20(#388)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- conf/config.yaml | 2 +- config/ldap.go | 1 + service/ldap.go | 51 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/conf/config.yaml b/conf/config.yaml index 91162b7..542b802 100644 --- a/conf/config.yaml +++ b/conf/config.yaml @@ -81,4 +81,4 @@ ldap: last-name: "sn" sync: false # If true, the user will be synchronized to the database when the user logs in. If false, the user will be synchronized to the database when the user be created. admin-group: "cn=admin,dc=example,dc=com" # The group name of the admin group, if the user is in this group, the user will be an admin. - + allow-group: "cn=users,dc=example,dc=com" # The group name of the users group, if the user is in this group, the user will be an login. diff --git a/config/ldap.go b/config/ldap.go index f1ead74..b32ae63 100644 --- a/config/ldap.go +++ b/config/ldap.go @@ -11,6 +11,7 @@ type LdapUser struct { LastName string `mapstructure:"last-name"` Sync bool `mapstructure:"sync"` // Will sync the user's information to the internal database AdminGroup string `mapstructure:"admin-group"` // Which group is the admin group + AllowGroup string `mapstructure:"allow-group"` // Which group is allowed to login } // type LdapGroup struct { diff --git a/service/ldap.go b/service/ldap.go index f9c54f6..98f50a7 100644 --- a/service/ldap.go +++ b/service/ldap.go @@ -137,6 +137,17 @@ func (ls *LdapService) Authenticate(username, password string) (*model.User, err return nil, ErrLdapUserDisabled } cfg := &Config.Ldap + + // Skip allow-group check for admins + isAdmin := ls.isUserAdmin(cfg, ldapUser) + + // non-admins only check if allow-group is configured + if !isAdmin && cfg.User.AllowGroup != "" { + if !ls.isUserInGroup(cfg, ldapUser, cfg.User.AllowGroup) { + return nil, errors.New("user not in allowed group") + } + } + err = ls.verifyCredentials(cfg, ldapUser.Dn, password) if err != nil { return nil, err @@ -148,6 +159,46 @@ func (ls *LdapService) Authenticate(username, password string) (*model.User, err return user, nil } +// isUserInGroup checks if the user is a member of the specified group. by_sw +func (ls *LdapService) isUserInGroup(cfg *config.Ldap, ldapUser *LdapUser, groupDN string) bool { + // Check "memberOf" directly + if len(ldapUser.MemberOf) > 0 { + for _, group := range ldapUser.MemberOf { + if strings.EqualFold(group, groupDN) { + return true + } + } + } + + // For "member" attribute, perform a reverse search on the group + member := "member" + userDN := ldap.EscapeFilter(ldapUser.Dn) + groupDN = ldap.EscapeFilter(groupDN) + groupFilter := fmt.Sprintf("(%s=%s)", member, userDN) + + // Create the LDAP search request + groupSearchRequest := ldap.NewSearchRequest( + groupDN, + ldap.ScopeWholeSubtree, + ldap.NeverDerefAliases, + 0, // Unlimited search results + 0, // No time limit + false, // Return both attributes and DN + groupFilter, + []string{"dn"}, + nil, + ) + + // Perform the group search + groupResult, err := ls.searchResult(cfg, groupSearchRequest) + if err != nil { + return false + } + + // If any results are returned, the user is part of the group + return len(groupResult.Entries) > 0 +} + // mapToLocalUser checks whether the user exists locally; if not, creates one. // If the user exists and Ldap.Sync is enabled, it updates local info. func (ls *LdapService) mapToLocalUser(cfg *config.Ldap, lu *LdapUser) (*model.User, error) {