From dd136858f1ea40ad3c94191d647487fa4f31926c Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 18 Oct 2024 20:33:49 +0200 Subject: Adding upstream version 9.0.0. Signed-off-by: Daniel Baumann --- tests/integration/auth_ldap_test.go | 566 ++++++++++++++++++++++++++++++++++++ 1 file changed, 566 insertions(+) create mode 100644 tests/integration/auth_ldap_test.go (limited to 'tests/integration/auth_ldap_test.go') diff --git a/tests/integration/auth_ldap_test.go b/tests/integration/auth_ldap_test.go new file mode 100644 index 0000000..04c8a4b --- /dev/null +++ b/tests/integration/auth_ldap_test.go @@ -0,0 +1,566 @@ +// Copyright 2018 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package integration + +import ( + "context" + "net/http" + "os" + "strings" + "testing" + + "code.gitea.io/gitea/models" + auth_model "code.gitea.io/gitea/models/auth" + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/translation" + "code.gitea.io/gitea/services/auth" + "code.gitea.io/gitea/services/auth/source/ldap" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type ldapUser struct { + UserName string + Password string + FullName string + Email string + OtherEmails []string + IsAdmin bool + IsRestricted bool + SSHKeys []string +} + +var gitLDAPUsers = []ldapUser{ + { + UserName: "professor", + Password: "professor", + FullName: "Hubert Farnsworth", + Email: "professor@planetexpress.com", + OtherEmails: []string{"hubert@planetexpress.com"}, + IsAdmin: true, + }, + { + UserName: "hermes", + Password: "hermes", + FullName: "Conrad Hermes", + Email: "hermes@planetexpress.com", + SSHKeys: []string{ + "SHA256:qLY06smKfHoW/92yXySpnxFR10QFrLdRjf/GNPvwcW8", + "SHA256:QlVTuM5OssDatqidn2ffY+Lc4YA5Fs78U+0KOHI51jQ", + "SHA256:DXdeUKYOJCSSmClZuwrb60hUq7367j4fA+udNC3FdRI", + }, + IsAdmin: true, + }, + { + UserName: "fry", + Password: "fry", + FullName: "Philip Fry", + Email: "fry@planetexpress.com", + }, + { + UserName: "leela", + Password: "leela", + FullName: "Leela Turanga", + Email: "leela@planetexpress.com", + IsRestricted: true, + }, + { + UserName: "bender", + Password: "bender", + FullName: "Bender Rodríguez", + Email: "bender@planetexpress.com", + }, +} + +var otherLDAPUsers = []ldapUser{ + { + UserName: "zoidberg", + Password: "zoidberg", + FullName: "John Zoidberg", + Email: "zoidberg@planetexpress.com", + }, + { + UserName: "amy", + Password: "amy", + FullName: "Amy Kroker", + Email: "amy@planetexpress.com", + }, +} + +func skipLDAPTests() bool { + return os.Getenv("TEST_LDAP") != "1" +} + +func getLDAPServerHost() string { + host := os.Getenv("TEST_LDAP_HOST") + if len(host) == 0 { + host = "ldap" + } + return host +} + +func getLDAPServerPort() string { + port := os.Getenv("TEST_LDAP_PORT") + if len(port) == 0 { + port = "389" + } + return port +} + +func buildAuthSourceLDAPPayload(csrf, sshKeyAttribute, mailKeyAttribute, defaultDomainName, groupFilter, groupTeamMap, groupTeamMapRemoval string) map[string]string { + // Modify user filter to test group filter explicitly + userFilter := "(&(objectClass=inetOrgPerson)(memberOf=cn=git,ou=people,dc=planetexpress,dc=com)(uid=%s))" + if groupFilter != "" { + userFilter = "(&(objectClass=inetOrgPerson)(uid=%s))" + } + + if len(mailKeyAttribute) == 0 { + mailKeyAttribute = "mail" + } + + return map[string]string{ + "_csrf": csrf, + "type": "2", + "name": "ldap", + "host": getLDAPServerHost(), + "port": getLDAPServerPort(), + "bind_dn": "uid=gitea,ou=service,dc=planetexpress,dc=com", + "bind_password": "password", + "user_base": "ou=people,dc=planetexpress,dc=com", + "filter": userFilter, + "admin_filter": "(memberOf=cn=admin_staff,ou=people,dc=planetexpress,dc=com)", + "restricted_filter": "(uid=leela)", + "attribute_username": "uid", + "attribute_name": "givenName", + "attribute_surname": "sn", + "attribute_mail": mailKeyAttribute, + "attribute_ssh_public_key": sshKeyAttribute, + "default_domain_name": defaultDomainName, + "is_sync_enabled": "on", + "is_active": "on", + "groups_enabled": "on", + "group_dn": "ou=people,dc=planetexpress,dc=com", + "group_member_uid": "member", + "group_filter": groupFilter, + "group_team_map": groupTeamMap, + "group_team_map_removal": groupTeamMapRemoval, + "user_uid": "DN", + } +} + +func addAuthSourceLDAP(t *testing.T, sshKeyAttribute, mailKeyAttribute, defaultDomainName, groupFilter string, groupMapParams ...string) { + groupTeamMapRemoval := "off" + groupTeamMap := "" + if len(groupMapParams) == 2 { + groupTeamMapRemoval = groupMapParams[0] + groupTeamMap = groupMapParams[1] + } + session := loginUser(t, "user1") + csrf := GetCSRF(t, session, "/admin/auths/new") + req := NewRequestWithValues(t, "POST", "/admin/auths/new", buildAuthSourceLDAPPayload(csrf, sshKeyAttribute, mailKeyAttribute, defaultDomainName, groupFilter, groupTeamMap, groupTeamMapRemoval)) + session.MakeRequest(t, req, http.StatusSeeOther) +} + +func TestLDAPUserSignin(t *testing.T) { + if skipLDAPTests() { + t.Skip() + return + } + defer tests.PrepareTestEnv(t)() + addAuthSourceLDAP(t, "", "", "", "") + + u := gitLDAPUsers[0] + + session := loginUserWithPassword(t, u.UserName, u.Password) + req := NewRequest(t, "GET", "/user/settings") + resp := session.MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + + assert.Equal(t, u.UserName, htmlDoc.GetInputValueByName("name")) + assert.Equal(t, u.FullName, htmlDoc.GetInputValueByName("full_name")) + assert.Equal(t, u.Email, htmlDoc.Find("#signed-user-email").Text()) +} + +func TestLDAPAuthChange(t *testing.T) { + defer tests.PrepareTestEnv(t)() + addAuthSourceLDAP(t, "", "", "", "") + + session := loginUser(t, "user1") + req := NewRequest(t, "GET", "/admin/auths") + resp := session.MakeRequest(t, req, http.StatusOK) + doc := NewHTMLParser(t, resp.Body) + href, exists := doc.Find("table.table td a").Attr("href") + if !exists { + assert.True(t, exists, "No authentication source found") + return + } + + req = NewRequest(t, "GET", href) + resp = session.MakeRequest(t, req, http.StatusOK) + doc = NewHTMLParser(t, resp.Body) + csrf := doc.GetCSRF() + host, _ := doc.Find(`input[name="host"]`).Attr("value") + assert.Equal(t, host, getLDAPServerHost()) + binddn, _ := doc.Find(`input[name="bind_dn"]`).Attr("value") + assert.Equal(t, "uid=gitea,ou=service,dc=planetexpress,dc=com", binddn) + + req = NewRequestWithValues(t, "POST", href, buildAuthSourceLDAPPayload(csrf, "", "", "", "", "", "off")) + session.MakeRequest(t, req, http.StatusSeeOther) + + req = NewRequest(t, "GET", href) + resp = session.MakeRequest(t, req, http.StatusOK) + doc = NewHTMLParser(t, resp.Body) + host, _ = doc.Find(`input[name="host"]`).Attr("value") + assert.Equal(t, host, getLDAPServerHost()) + binddn, _ = doc.Find(`input[name="bind_dn"]`).Attr("value") + assert.Equal(t, "uid=gitea,ou=service,dc=planetexpress,dc=com", binddn) + domainname, _ := doc.Find(`input[name="default_domain_name"]`).Attr("value") + assert.Equal(t, "", domainname) + + req = NewRequestWithValues(t, "POST", href, buildAuthSourceLDAPPayload(csrf, "", "", "test.org", "", "", "off")) + session.MakeRequest(t, req, http.StatusSeeOther) + + req = NewRequest(t, "GET", href) + resp = session.MakeRequest(t, req, http.StatusOK) + doc = NewHTMLParser(t, resp.Body) + host, _ = doc.Find(`input[name="host"]`).Attr("value") + assert.Equal(t, host, getLDAPServerHost()) + binddn, _ = doc.Find(`input[name="bind_dn"]`).Attr("value") + assert.Equal(t, "uid=gitea,ou=service,dc=planetexpress,dc=com", binddn) + domainname, _ = doc.Find(`input[name="default_domain_name"]`).Attr("value") + assert.Equal(t, "test.org", domainname) +} + +func TestLDAPUserSync(t *testing.T) { + if skipLDAPTests() { + t.Skip() + return + } + defer tests.PrepareTestEnv(t)() + addAuthSourceLDAP(t, "", "", "", "") + err := auth.SyncExternalUsers(context.Background(), true) + require.NoError(t, err) + + // Check if users exists + for _, gitLDAPUser := range gitLDAPUsers { + dbUser, err := user_model.GetUserByName(db.DefaultContext, gitLDAPUser.UserName) + require.NoError(t, err) + assert.Equal(t, gitLDAPUser.UserName, dbUser.Name) + assert.Equal(t, gitLDAPUser.Email, dbUser.Email) + assert.Equal(t, gitLDAPUser.IsAdmin, dbUser.IsAdmin) + assert.Equal(t, gitLDAPUser.IsRestricted, dbUser.IsRestricted) + } + + // Check if no users exist + for _, otherLDAPUser := range otherLDAPUsers { + _, err := user_model.GetUserByName(db.DefaultContext, otherLDAPUser.UserName) + assert.True(t, user_model.IsErrUserNotExist(err)) + } +} + +func TestLDAPUserSyncWithEmptyUsernameAttribute(t *testing.T) { + if skipLDAPTests() { + t.Skip() + return + } + defer tests.PrepareTestEnv(t)() + + session := loginUser(t, "user1") + csrf := GetCSRF(t, session, "/admin/auths/new") + payload := buildAuthSourceLDAPPayload(csrf, "", "", "", "", "", "") + payload["attribute_username"] = "" + req := NewRequestWithValues(t, "POST", "/admin/auths/new", payload) + session.MakeRequest(t, req, http.StatusSeeOther) + + for _, u := range gitLDAPUsers { + req := NewRequest(t, "GET", "/admin/users?q="+u.UserName) + resp := session.MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + + tr := htmlDoc.doc.Find("table.table tbody tr") + assert.Equal(t, 0, tr.Length()) + } + + for _, u := range gitLDAPUsers { + req := NewRequestWithValues(t, "POST", "/user/login", map[string]string{ + "_csrf": csrf, + "user_name": u.UserName, + "password": u.Password, + }) + MakeRequest(t, req, http.StatusSeeOther) + } + + auth.SyncExternalUsers(context.Background(), true) + + authSource := unittest.AssertExistsAndLoadBean(t, &auth_model.Source{ + Name: payload["name"], + }) + unittest.AssertCount(t, &user_model.User{ + LoginType: auth_model.LDAP, + LoginSource: authSource.ID, + }, len(gitLDAPUsers)) + + for _, u := range gitLDAPUsers { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ + Name: u.UserName, + }) + assert.True(t, user.IsActive) + } +} + +func TestLDAPUserSyncWithGroupFilter(t *testing.T) { + if skipLDAPTests() { + t.Skip() + return + } + defer tests.PrepareTestEnv(t)() + addAuthSourceLDAP(t, "", "", "", "(cn=git)") + + // Assert a user not a member of the LDAP group "cn=git" cannot login + // This test may look like TestLDAPUserSigninFailed but it is not. + // The later test uses user filter containing group membership filter (memberOf) + // This test is for the case when LDAP user records may not be linked with + // all groups the user is a member of, the user filter is modified accordingly inside + // the addAuthSourceLDAP based on the value of the groupFilter + u := otherLDAPUsers[0] + testLoginFailed(t, u.UserName, u.Password, translation.NewLocale("en-US").TrString("form.username_password_incorrect")) + + auth.SyncExternalUsers(context.Background(), true) + + // Assert members of LDAP group "cn=git" are added + for _, gitLDAPUser := range gitLDAPUsers { + unittest.BeanExists(t, &user_model.User{ + Name: gitLDAPUser.UserName, + }) + } + + // Assert everyone else is not added + for _, gitLDAPUser := range otherLDAPUsers { + unittest.AssertNotExistsBean(t, &user_model.User{ + Name: gitLDAPUser.UserName, + }) + } + + ldapSource := unittest.AssertExistsAndLoadBean(t, &auth_model.Source{ + Name: "ldap", + }) + ldapConfig := ldapSource.Cfg.(*ldap.Source) + ldapConfig.GroupFilter = "(cn=ship_crew)" + auth_model.UpdateSource(db.DefaultContext, ldapSource) + + auth.SyncExternalUsers(context.Background(), true) + + for _, gitLDAPUser := range gitLDAPUsers { + if gitLDAPUser.UserName == "fry" || gitLDAPUser.UserName == "leela" || gitLDAPUser.UserName == "bender" { + // Assert members of the LDAP group "cn-ship_crew" are still active + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ + Name: gitLDAPUser.UserName, + }) + assert.True(t, user.IsActive, "User %s should be active", gitLDAPUser.UserName) + } else { + // Assert everyone else is inactive + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ + Name: gitLDAPUser.UserName, + }) + assert.False(t, user.IsActive, "User %s should be inactive", gitLDAPUser.UserName) + } + } +} + +func TestLDAPUserSigninFailed(t *testing.T) { + if skipLDAPTests() { + t.Skip() + return + } + defer tests.PrepareTestEnv(t)() + addAuthSourceLDAP(t, "", "", "", "") + + u := otherLDAPUsers[0] + testLoginFailed(t, u.UserName, u.Password, translation.NewLocale("en-US").TrString("form.username_password_incorrect")) +} + +func TestLDAPUserSSHKeySync(t *testing.T) { + if skipLDAPTests() { + t.Skip() + return + } + defer tests.PrepareTestEnv(t)() + addAuthSourceLDAP(t, "sshPublicKey", "", "", "") + + auth.SyncExternalUsers(context.Background(), true) + + // Check if users has SSH keys synced + for _, u := range gitLDAPUsers { + if len(u.SSHKeys) == 0 { + continue + } + session := loginUserWithPassword(t, u.UserName, u.Password) + + req := NewRequest(t, "GET", "/user/settings/keys") + resp := session.MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + + divs := htmlDoc.doc.Find("#keys-ssh .flex-item .flex-item-body:not(:last-child)") + + syncedKeys := make([]string, divs.Length()) + for i := 0; i < divs.Length(); i++ { + syncedKeys[i] = strings.TrimSpace(divs.Eq(i).Text()) + } + + assert.ElementsMatch(t, u.SSHKeys, syncedKeys, "Unequal number of keys synchronized for user: %s", u.UserName) + } +} + +func TestLDAPGroupTeamSyncAddMember(t *testing.T) { + if skipLDAPTests() { + t.Skip() + return + } + defer tests.PrepareTestEnv(t)() + addAuthSourceLDAP(t, "", "", "", "", "on", `{"cn=ship_crew,ou=people,dc=planetexpress,dc=com":{"org26": ["team11"]},"cn=admin_staff,ou=people,dc=planetexpress,dc=com": {"non-existent": ["non-existent"]}}`) + org, err := organization.GetOrgByName(db.DefaultContext, "org26") + require.NoError(t, err) + team, err := organization.GetTeam(db.DefaultContext, org.ID, "team11") + require.NoError(t, err) + auth.SyncExternalUsers(context.Background(), true) + for _, gitLDAPUser := range gitLDAPUsers { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ + Name: gitLDAPUser.UserName, + }) + usersOrgs, err := db.Find[organization.Organization](db.DefaultContext, organization.FindOrgOptions{ + UserID: user.ID, + IncludePrivate: true, + }) + require.NoError(t, err) + allOrgTeams, err := organization.GetUserOrgTeams(db.DefaultContext, org.ID, user.ID) + require.NoError(t, err) + if user.Name == "fry" || user.Name == "leela" || user.Name == "bender" { + // assert members of LDAP group "cn=ship_crew" are added to mapped teams + assert.Len(t, usersOrgs, 1, "User [%s] should be member of one organization", user.Name) + assert.Equal(t, "org26", usersOrgs[0].Name, "Membership should be added to the right organization") + isMember, err := organization.IsTeamMember(db.DefaultContext, usersOrgs[0].ID, team.ID, user.ID) + require.NoError(t, err) + assert.True(t, isMember, "Membership should be added to the right team") + err = models.RemoveTeamMember(db.DefaultContext, team, user.ID) + require.NoError(t, err) + err = models.RemoveOrgUser(db.DefaultContext, usersOrgs[0].ID, user.ID) + require.NoError(t, err) + } else { + // assert members of LDAP group "cn=admin_staff" keep initial team membership since mapped team does not exist + assert.Empty(t, usersOrgs, "User should be member of no organization") + isMember, err := organization.IsTeamMember(db.DefaultContext, org.ID, team.ID, user.ID) + require.NoError(t, err) + assert.False(t, isMember, "User should no be added to this team") + assert.Empty(t, allOrgTeams, "User should not be added to any team") + } + } +} + +func TestLDAPGroupTeamSyncRemoveMember(t *testing.T) { + if skipLDAPTests() { + t.Skip() + return + } + defer tests.PrepareTestEnv(t)() + addAuthSourceLDAP(t, "", "", "", "", "on", `{"cn=dispatch,ou=people,dc=planetexpress,dc=com": {"org26": ["team11"]}}`) + org, err := organization.GetOrgByName(db.DefaultContext, "org26") + require.NoError(t, err) + team, err := organization.GetTeam(db.DefaultContext, org.ID, "team11") + require.NoError(t, err) + loginUserWithPassword(t, gitLDAPUsers[0].UserName, gitLDAPUsers[0].Password) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ + Name: gitLDAPUsers[0].UserName, + }) + err = organization.AddOrgUser(db.DefaultContext, org.ID, user.ID) + require.NoError(t, err) + err = models.AddTeamMember(db.DefaultContext, team, user.ID) + require.NoError(t, err) + isMember, err := organization.IsOrganizationMember(db.DefaultContext, org.ID, user.ID) + require.NoError(t, err) + assert.True(t, isMember, "User should be member of this organization") + isMember, err = organization.IsTeamMember(db.DefaultContext, org.ID, team.ID, user.ID) + require.NoError(t, err) + assert.True(t, isMember, "User should be member of this team") + // assert team member "professor" gets removed from org26 team11 + loginUserWithPassword(t, gitLDAPUsers[0].UserName, gitLDAPUsers[0].Password) + isMember, err = organization.IsOrganizationMember(db.DefaultContext, org.ID, user.ID) + require.NoError(t, err) + assert.False(t, isMember, "User membership should have been removed from organization") + isMember, err = organization.IsTeamMember(db.DefaultContext, org.ID, team.ID, user.ID) + require.NoError(t, err) + assert.False(t, isMember, "User membership should have been removed from team") +} + +func TestLDAPPreventInvalidGroupTeamMap(t *testing.T) { + if skipLDAPTests() { + t.Skip() + return + } + defer tests.PrepareTestEnv(t)() + + session := loginUser(t, "user1") + csrf := GetCSRF(t, session, "/admin/auths/new") + req := NewRequestWithValues(t, "POST", "/admin/auths/new", buildAuthSourceLDAPPayload(csrf, "", "", "", "", `{"NOT_A_VALID_JSON"["MISSING_DOUBLE_POINT"]}`, "off")) + session.MakeRequest(t, req, http.StatusOK) // StatusOK = failed, StatusSeeOther = ok +} + +func TestLDAPUserSyncInvalidMail(t *testing.T) { + if skipLDAPTests() { + t.Skip() + return + } + defer tests.PrepareTestEnv(t)() + addAuthSourceLDAP(t, "", "nonexisting", "", "") + auth.SyncExternalUsers(context.Background(), true) + + // Check if users exists + for _, gitLDAPUser := range gitLDAPUsers { + dbUser, err := user_model.GetUserByName(db.DefaultContext, gitLDAPUser.UserName) + require.NoError(t, err) + assert.Equal(t, gitLDAPUser.UserName, dbUser.Name) + assert.Equal(t, gitLDAPUser.UserName+"@localhost.local", dbUser.Email) + assert.Equal(t, gitLDAPUser.IsAdmin, dbUser.IsAdmin) + assert.Equal(t, gitLDAPUser.IsRestricted, dbUser.IsRestricted) + } + + // Check if no users exist + for _, otherLDAPUser := range otherLDAPUsers { + _, err := user_model.GetUserByName(db.DefaultContext, otherLDAPUser.UserName) + assert.True(t, user_model.IsErrUserNotExist(err)) + } +} + +func TestLDAPUserSyncInvalidMailDefaultDomain(t *testing.T) { + if skipLDAPTests() { + t.Skip() + return + } + defer tests.PrepareTestEnv(t)() + addAuthSourceLDAP(t, "", "nonexisting", "test.org", "") + auth.SyncExternalUsers(context.Background(), true) + + // Check if users exists + for _, gitLDAPUser := range gitLDAPUsers { + dbUser, err := user_model.GetUserByName(db.DefaultContext, gitLDAPUser.UserName) + require.NoError(t, err) + assert.Equal(t, gitLDAPUser.UserName, dbUser.Name) + assert.Equal(t, gitLDAPUser.UserName+"@test.org", dbUser.Email) + assert.Equal(t, gitLDAPUser.IsAdmin, dbUser.IsAdmin) + assert.Equal(t, gitLDAPUser.IsRestricted, dbUser.IsRestricted) + } + + // Check if no users exist + for _, otherLDAPUser := range otherLDAPUsers { + _, err := user_model.GetUserByName(db.DefaultContext, otherLDAPUser.UserName) + assert.True(t, user_model.IsErrUserNotExist(err)) + } +} -- cgit v1.2.3