feat(ldap): make group sync configurable

This commit is contained in:
kolaente 2025-03-18 16:14:04 +01:00 committed by konrad
parent a3b19a7b3c
commit 216df5bedc
5 changed files with 52 additions and 6 deletions

View File

@ -765,6 +765,16 @@
"default_value": "",
"comment": "The password of the account used to search the LDAP directory."
},
{
"key": "groupsyncenabled",
"default_value": "false",
"comment": "If enabled, Vikunja will automagically add users to teams in Vikunja matching `groupsyncfilter`. The teams will be automatically created and kept in sync by Vikunja."
},
{
"key": "groupsyncfilter",
"default_value": "(&(objectclass=*)(|(objectclass=group)(objectclass=groupOfNames)))",
"comment": "The filter to search for group objects in the ldap directory. Only used when `groupsyncenabled` is set to `true`."
},
{
"key": "attribute",
"default_value": "",

View File

@ -88,6 +88,8 @@ const (
AuthLdapBindDN Key = `auth.ldap.binddn`
// #nosec G101
AuthLdapBindPassword Key = `auth.ldap.bindpassword`
AuthLdapGroupSyncEnabled Key = `auth.ldap.groupsyncenabled`
AuthLdapGroupSyncFilter Key = `auth.ldap.groupsyncfilter`
AuthLdapAttributeUsername Key = `auth.ldap.attribute.username`
AuthLdapAttributeEmail Key = `auth.ldap.attribute.email`
AuthLdapAttributeDisplayname Key = `auth.ldap.attribute.displayname`
@ -352,6 +354,8 @@ func InitDefaultConfig() {
AuthLdapPort.setDefault(389)
AuthLdapUseTLS.setDefault(true)
AuthLdapVerifyTLS.setDefault(true)
AuthLdapGroupSyncEnabled.setDefault(false)
AuthLdapGroupSyncFilter.setDefault("(&(objectclass=*)(|(objectclass=group)(objectclass=groupOfNames)))")
AuthLdapAttributeUsername.setDefault("uid")
AuthLdapAttributeEmail.setDefault("mail")
AuthLdapAttributeDisplayname.setDefault("displayName")

View File

@ -108,7 +108,7 @@ func sanitizedUserQuery(username string) (string, bool) {
return fmt.Sprintf(config.AuthLdapUserFilter.GetString(), username), true
}
func AuthenticateUserInLDAP(s *xorm.Session, username, password string) (u *user.User, err error) {
func AuthenticateUserInLDAP(s *xorm.Session, username, password string, syncGroups bool) (u *user.User, err error) {
if password == "" || username == "" {
return nil, user.ErrNoUsernamePassword{}
}
@ -169,6 +169,10 @@ func AuthenticateUserInLDAP(s *xorm.Session, username, password string) (u *user
return nil, err
}
if !syncGroups {
return
}
err = syncUserGroups(l, u, userdn)
return u, err
@ -211,7 +215,7 @@ func syncUserGroups(l *ldap.Conn, u *user.User, userdn string) (err error) {
searchRequest := ldap.NewSearchRequest(
config.AuthLdapBaseDN.GetString(),
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
"(&(objectclass=*)(|(objectclass=group)(objectclass=groupOfNames)))",
config.AuthLdapGroupSyncFilter.GetString(),
[]string{
"dn",
"cn",

View File

@ -40,7 +40,7 @@ func TestLdapLogin(t *testing.T) {
s := db.NewSession()
defer s.Close()
user, err := AuthenticateUserInLDAP(s, "professor", "professor")
user, err := AuthenticateUserInLDAP(s, "professor", "professor", false)
require.NoError(t, err)
assert.Equal(t, "professor", user.Username)
@ -48,6 +48,9 @@ func TestLdapLogin(t *testing.T) {
"username": "professor",
"issuer": "ldap",
}, false)
db.AssertMissing(t, "teams", map[string]interface{}{
"issuer": "ldap",
})
})
t.Run("should not create account for wrong password", func(t *testing.T) {
@ -55,7 +58,7 @@ func TestLdapLogin(t *testing.T) {
s := db.NewSession()
defer s.Close()
_, err := AuthenticateUserInLDAP(s, "professor", "wrongpassword")
_, err := AuthenticateUserInLDAP(s, "professor", "wrongpassword", false)
require.Error(t, err)
assert.True(t, user2.IsErrWrongUsernameOrPassword(err))
@ -66,9 +69,34 @@ func TestLdapLogin(t *testing.T) {
s := db.NewSession()
defer s.Close()
_, err := AuthenticateUserInLDAP(s, "gnome", "professor")
_, err := AuthenticateUserInLDAP(s, "gnome", "professor", false)
require.Error(t, err)
assert.True(t, user2.IsErrWrongUsernameOrPassword(err))
})
t.Run("should sync groups", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
s := db.NewSession()
defer s.Close()
user, err := AuthenticateUserInLDAP(s, "professor", "professor", true)
require.NoError(t, err)
assert.Equal(t, "professor", user.Username)
db.AssertExists(t, "users", map[string]interface{}{
"username": "professor",
"issuer": "ldap",
}, false)
db.AssertExists(t, "teams", map[string]interface{}{
"name": "admin_staff (LDAP)",
"issuer": "ldap",
"external_id": "cn=admin_staff,ou=people,dc=planetexpress,dc=com",
}, false)
db.AssertExists(t, "teams", map[string]interface{}{
"name": "git (LDAP)",
"issuer": "ldap",
"external_id": "cn=git,ou=people,dc=planetexpress,dc=com",
}, false)
})
}

View File

@ -55,7 +55,7 @@ func Login(c echo.Context) (err error) {
var user *user2.User
if config.AuthLdapEnabled.GetBool() {
user, err = ldap.AuthenticateUserInLDAP(s, u.Username, u.Password)
user, err = ldap.AuthenticateUserInLDAP(s, u.Username, u.Password, config.AuthLdapGroupSyncEnabled.GetBool())
if err != nil && !user2.IsErrWrongUsernameOrPassword(err) {
_ = s.Rollback()
return handler.HandleHTTPError(err)