diff --git a/config-raw.json b/config-raw.json index b90212ebf..b9ea8cd9b 100644 --- a/config-raw.json +++ b/config-raw.json @@ -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": "", diff --git a/pkg/config/config.go b/pkg/config/config.go index d54e8a256..c5c5bc040 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -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") diff --git a/pkg/modules/auth/ldap/ldap.go b/pkg/modules/auth/ldap/ldap.go index 79226320e..d5c302892 100644 --- a/pkg/modules/auth/ldap/ldap.go +++ b/pkg/modules/auth/ldap/ldap.go @@ -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", diff --git a/pkg/modules/auth/ldap/ldap_test.go b/pkg/modules/auth/ldap/ldap_test.go index 372ee2d02..633bca6a1 100644 --- a/pkg/modules/auth/ldap/ldap_test.go +++ b/pkg/modules/auth/ldap/ldap_test.go @@ -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) + }) } diff --git a/pkg/routes/api/v1/login.go b/pkg/routes/api/v1/login.go index 3b1b07ab7..b83857307 100644 --- a/pkg/routes/api/v1/login.go +++ b/pkg/routes/api/v1/login.go @@ -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)