mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2026-01-18 05:12:29 +00:00
feat: store ldap group results in cache
This commit is contained in:
@@ -19,6 +19,11 @@ import (
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
type LdapGroupsCache struct {
|
||||
Groups []string
|
||||
Expires time.Time
|
||||
}
|
||||
|
||||
type LoginAttempt struct {
|
||||
FailedAttempts int
|
||||
LastAttempt time.Time
|
||||
@@ -36,24 +41,28 @@ type AuthServiceConfig struct {
|
||||
LoginMaxRetries int
|
||||
SessionCookieName string
|
||||
IP config.IPConfig
|
||||
LDAPGroupsCacheTTL int
|
||||
}
|
||||
|
||||
type AuthService struct {
|
||||
config AuthServiceConfig
|
||||
docker *DockerService
|
||||
loginAttempts map[string]*LoginAttempt
|
||||
loginMutex sync.RWMutex
|
||||
ldap *LdapService
|
||||
queries *repository.Queries
|
||||
config AuthServiceConfig
|
||||
docker *DockerService
|
||||
loginAttempts map[string]*LoginAttempt
|
||||
ldapGroupsCache map[string]*LdapGroupsCache
|
||||
loginMutex sync.RWMutex
|
||||
ldapGroupsMutex sync.RWMutex
|
||||
ldap *LdapService
|
||||
queries *repository.Queries
|
||||
}
|
||||
|
||||
func NewAuthService(config AuthServiceConfig, docker *DockerService, ldap *LdapService, queries *repository.Queries) *AuthService {
|
||||
return &AuthService{
|
||||
config: config,
|
||||
docker: docker,
|
||||
loginAttempts: make(map[string]*LoginAttempt),
|
||||
ldap: ldap,
|
||||
queries: queries,
|
||||
config: config,
|
||||
docker: docker,
|
||||
loginAttempts: make(map[string]*LoginAttempt),
|
||||
ldapGroupsCache: make(map[string]*LdapGroupsCache),
|
||||
ldap: ldap,
|
||||
queries: queries,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,12 +141,30 @@ func (auth *AuthService) GetLocalUser(username string) config.User {
|
||||
}
|
||||
|
||||
func (auth *AuthService) GetLdapUser(userDN string) (config.LdapUser, error) {
|
||||
auth.ldapGroupsMutex.Lock()
|
||||
entry, exists := auth.ldapGroupsCache[userDN]
|
||||
auth.ldapGroupsMutex.Unlock()
|
||||
|
||||
if exists && time.Now().Before(entry.Expires) {
|
||||
return config.LdapUser{
|
||||
DN: userDN,
|
||||
Groups: entry.Groups,
|
||||
}, nil
|
||||
}
|
||||
|
||||
groups, err := auth.ldap.GetUserGroups(userDN)
|
||||
|
||||
if err != nil {
|
||||
return config.LdapUser{}, err
|
||||
}
|
||||
|
||||
auth.ldapGroupsMutex.Lock()
|
||||
auth.ldapGroupsCache[userDN] = &LdapGroupsCache{
|
||||
Groups: groups,
|
||||
Expires: time.Now().Add(time.Duration(auth.config.LDAPGroupsCacheTTL) * time.Second),
|
||||
}
|
||||
auth.ldapGroupsMutex.Unlock()
|
||||
|
||||
return config.LdapUser{
|
||||
DN: userDN,
|
||||
Groups: groups,
|
||||
|
||||
@@ -4,8 +4,6 @@ import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -148,11 +146,13 @@ func (ldap *LdapService) GetUserDN(username string) (string, error) {
|
||||
}
|
||||
|
||||
func (ldap *LdapService) GetUserGroups(userDN string) ([]string, error) {
|
||||
escapedUserDN := ldapgo.EscapeFilter(userDN)
|
||||
|
||||
searchRequest := ldapgo.NewSearchRequest(
|
||||
ldap.config.BaseDN,
|
||||
ldapgo.ScopeWholeSubtree, ldapgo.NeverDerefAliases, 0, 0, false,
|
||||
"(objectclass=groupOfUniqueNames)",
|
||||
[]string{"uniquemember"},
|
||||
fmt.Sprintf("(&(objectclass=groupOfUniqueNames)(uniquemember=%s))", escapedUserDN),
|
||||
[]string{"dn"},
|
||||
nil,
|
||||
)
|
||||
|
||||
@@ -167,22 +167,21 @@ func (ldap *LdapService) GetUserGroups(userDN string) ([]string, error) {
|
||||
groupDNs := []string{}
|
||||
|
||||
for _, entry := range searchResult.Entries {
|
||||
memberAttributes := entry.GetAttributeValues("uniquemember")
|
||||
// no need to escape username here, if it's malicious it won't match anything
|
||||
if slices.Contains(memberAttributes, userDN) {
|
||||
groupDNs = append(groupDNs, entry.DN)
|
||||
}
|
||||
groupDNs = append(groupDNs, entry.DN)
|
||||
}
|
||||
|
||||
// Should work for most ldap providers?
|
||||
groups := []string{}
|
||||
|
||||
for _, groupDN := range groupDNs {
|
||||
groupDN = strings.TrimPrefix(groupDN, "cn=")
|
||||
parts := strings.SplitN(groupDN, ",", 2)
|
||||
if len(parts) > 0 {
|
||||
groups = append(groups, parts[0])
|
||||
// I guess it should work for most ldap providers
|
||||
for _, dn := range groupDNs {
|
||||
rdnParts, err := ldapgo.ParseDN(dn)
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
if len(rdnParts.RDNs) == 0 || len(rdnParts.RDNs[0].Attributes) == 0 {
|
||||
return []string{}, fmt.Errorf("invalid DN format: %s", dn)
|
||||
}
|
||||
groups = append(groups, rdnParts.RDNs[0].Attributes[0].Value)
|
||||
}
|
||||
|
||||
return groups, nil
|
||||
|
||||
Reference in New Issue
Block a user