From 767845698eece1b3a7c826c194aaa18d2e4e6dbb Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Wed, 12 Oct 2022 18:48:59 +0000 Subject: [PATCH 1/6] Move code from module to service. --- modules/context/api.go | 24 ------------------- modules/context/context.go | 26 -------------------- routers/api/v1/api.go | 2 +- routers/web/web.go | 2 +- services/auth/middleware.go | 47 +++++++++++++++++++++++++++++++++++++ 5 files changed, 49 insertions(+), 52 deletions(-) create mode 100644 services/auth/middleware.go diff --git a/modules/context/api.go b/modules/context/api.go index b9d130e2a8ac0..065a3b62dd5a8 100644 --- a/modules/context/api.go +++ b/modules/context/api.go @@ -20,7 +20,6 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/web/middleware" - auth_service "code.gitea.io/gitea/services/auth" ) // APIContext is a specific context for API service @@ -216,29 +215,6 @@ func (ctx *APIContext) CheckForOTP() { } } -// APIAuth converts auth_service.Auth as a middleware -func APIAuth(authMethod auth_service.Method) func(*APIContext) { - return func(ctx *APIContext) { - // Get user from session if logged in. - ctx.Doer = authMethod.Verify(ctx.Req, ctx.Resp, ctx, ctx.Session) - if ctx.Doer != nil { - if ctx.Locale.Language() != ctx.Doer.Language { - ctx.Locale = middleware.Locale(ctx.Resp, ctx.Req) - } - ctx.IsBasicAuth = ctx.Data["AuthedMethod"].(string) == auth_service.BasicMethodName - ctx.IsSigned = true - ctx.Data["IsSigned"] = ctx.IsSigned - ctx.Data["SignedUser"] = ctx.Doer - ctx.Data["SignedUserID"] = ctx.Doer.ID - ctx.Data["SignedUserName"] = ctx.Doer.Name - ctx.Data["IsAdmin"] = ctx.Doer.IsAdmin - } else { - ctx.Data["SignedUserID"] = int64(0) - ctx.Data["SignedUserName"] = "" - } - } -} - // APIContexter returns apicontext as middleware func APIContexter() func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { diff --git a/modules/context/context.go b/modules/context/context.go index 4b6a21b217c3b..bf18d13d08925 100644 --- a/modules/context/context.go +++ b/modules/context/context.go @@ -36,7 +36,6 @@ import ( "code.gitea.io/gitea/modules/translation" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web/middleware" - "code.gitea.io/gitea/services/auth" "gitea.com/go-chi/cache" "gitea.com/go-chi/session" @@ -632,31 +631,6 @@ func getCsrfOpts() CsrfOptions { } } -// Auth converts auth.Auth as a middleware -func Auth(authMethod auth.Method) func(*Context) { - return func(ctx *Context) { - ctx.Doer = authMethod.Verify(ctx.Req, ctx.Resp, ctx, ctx.Session) - if ctx.Doer != nil { - if ctx.Locale.Language() != ctx.Doer.Language { - ctx.Locale = middleware.Locale(ctx.Resp, ctx.Req) - } - ctx.IsBasicAuth = ctx.Data["AuthedMethod"].(string) == auth.BasicMethodName - ctx.IsSigned = true - ctx.Data["IsSigned"] = ctx.IsSigned - ctx.Data["SignedUser"] = ctx.Doer - ctx.Data["SignedUserID"] = ctx.Doer.ID - ctx.Data["SignedUserName"] = ctx.Doer.Name - ctx.Data["IsAdmin"] = ctx.Doer.IsAdmin - } else { - ctx.Data["SignedUserID"] = int64(0) - ctx.Data["SignedUserName"] = "" - - // ensure the session uid is deleted - _ = ctx.Session.Delete("uid") - } - } -} - // Contexter initializes a classic context for a request. func Contexter(ctx context.Context) func(next http.Handler) http.Handler { _, rnd := templates.HTMLRenderer(ctx) diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 0d11674aa9971..7b1ca6bdbb1c1 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -629,7 +629,7 @@ func Routes(ctx gocontext.Context) *web.Route { } // Get user from session if logged in. - m.Use(context.APIAuth(group)) + m.Use(auth.APIAuth(group)) m.Use(context.ToggleAPI(&context.ToggleOptions{ SignInRequired: setting.Service.RequireSignInView, diff --git a/routers/web/web.go b/routers/web/web.go index c74343c8cf719..8b15241028b52 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -203,7 +203,7 @@ func Routes(ctx gocontext.Context) *web.Route { } // Get user from session if logged in. - common = append(common, context.Auth(group)) + common = append(common, auth_service.Auth(group)) // GetHead allows a HEAD request redirect to GET if HEAD method is not defined for that route common = append(common, middleware.GetHead) diff --git a/services/auth/middleware.go b/services/auth/middleware.go new file mode 100644 index 0000000000000..5396812acaf4a --- /dev/null +++ b/services/auth/middleware.go @@ -0,0 +1,47 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package auth + +import ( + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/web/middleware" +) + +// Auth is a middleware to authenticate a web user +func Auth(authMethod Method) func(*context.Context) { + return func(ctx *context.Context) { + authShared(ctx, authMethod) + if ctx.Doer == nil { + // ensure the session uid is deleted + _ = ctx.Session.Delete("uid") + } + } +} + +// APIAuth is a middleware to authenticate an api user +func APIAuth(authMethod Method) func(*context.APIContext) { + return func(ctx *context.APIContext) { + authShared(ctx.Context, authMethod) + } +} + +func authShared(ctx *context.Context, authMethod Method) { + ctx.Doer = authMethod.Verify(ctx.Req, ctx.Resp, ctx, ctx.Session) + if ctx.Doer != nil { + if ctx.Locale.Language() != ctx.Doer.Language { + ctx.Locale = middleware.Locale(ctx.Resp, ctx.Req) + } + ctx.IsBasicAuth = ctx.Data["AuthedMethod"].(string) == BasicMethodName + ctx.IsSigned = true + ctx.Data["IsSigned"] = ctx.IsSigned + ctx.Data["SignedUser"] = ctx.Doer + ctx.Data["SignedUserID"] = ctx.Doer.ID + ctx.Data["SignedUserName"] = ctx.Doer.Name + ctx.Data["IsAdmin"] = ctx.Doer.IsAdmin + } else { + ctx.Data["SignedUserID"] = int64(0) + ctx.Data["SignedUserName"] = "" + } +} \ No newline at end of file From df9e21cdab30b9af23a82d1e3683456dac5b4145 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Wed, 12 Oct 2022 19:32:14 +0000 Subject: [PATCH 2/6] Pass context as parameter. --- models/organization/org.go | 20 ++++++-------------- models/organization/org_test.go | 16 ++++++++-------- modules/context/org.go | 2 +- modules/repository/create_test.go | 4 ++-- modules/repository/repo.go | 2 +- routers/api/v1/api.go | 2 +- routers/api/v1/repo/fork.go | 2 +- routers/api/v1/repo/repo.go | 2 +- routers/web/repo/issue_label.go | 2 +- routers/web/repo/setting.go | 2 +- services/auth/middleware.go | 2 +- tests/integration/auth_ldap_test.go | 4 ++-- 12 files changed, 26 insertions(+), 34 deletions(-) diff --git a/models/organization/org.go b/models/organization/org.go index 044ea065637c5..05d4013f904e7 100644 --- a/models/organization/org.go +++ b/models/organization/org.go @@ -101,22 +101,14 @@ func (org *Organization) CanCreateOrgRepo(uid int64) (bool, error) { return CanCreateOrgRepo(org.ID, uid) } -func (org *Organization) getTeam(ctx context.Context, name string) (*Team, error) { - return GetTeam(ctx, org.ID, name) -} - // GetTeam returns named team of organization. -func (org *Organization) GetTeam(name string) (*Team, error) { - return org.getTeam(db.DefaultContext, name) -} - -func (org *Organization) getOwnerTeam(ctx context.Context) (*Team, error) { - return org.getTeam(ctx, OwnerTeamName) +func (org *Organization) GetTeam(ctx context.Context, name string) (*Team, error) { + return GetTeam(ctx, org.ID, name) } // GetOwnerTeam returns owner team of organization. -func (org *Organization) GetOwnerTeam() (*Team, error) { - return org.getOwnerTeam(db.DefaultContext) +func (org *Organization) GetOwnerTeam(ctx context.Context) (*Team, error) { + return org.GetTeam(ctx, OwnerTeamName) } // FindOrgTeams returns all teams of a given organization @@ -333,7 +325,7 @@ func CreateOrganization(org *Organization, owner *user_model.User) (err error) { } // GetOrgByName returns organization by given name. -func GetOrgByName(name string) (*Organization, error) { +func GetOrgByName(ctx context.Context, name string) (*Organization, error) { if len(name) == 0 { return nil, ErrOrgNotExist{0, name} } @@ -341,7 +333,7 @@ func GetOrgByName(name string) (*Organization, error) { LowerName: strings.ToLower(name), Type: user_model.UserTypeOrganization, } - has, err := db.GetEngine(db.DefaultContext).Get(u) + has, err := db.GetEngine(ctx).Get(u) if err != nil { return nil, err } else if !has { diff --git a/models/organization/org_test.go b/models/organization/org_test.go index 0fba6e25925cc..e5db44978ad26 100644 --- a/models/organization/org_test.go +++ b/models/organization/org_test.go @@ -62,28 +62,28 @@ func TestUser_IsOrgMember(t *testing.T) { func TestUser_GetTeam(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}) - team, err := org.GetTeam("team1") + team, err := org.GetTeam(db.DefaultContext, "team1") assert.NoError(t, err) assert.Equal(t, org.ID, team.OrgID) assert.Equal(t, "team1", team.LowerName) - _, err = org.GetTeam("does not exist") + _, err = org.GetTeam(db.DefaultContext, "does not exist") assert.True(t, organization.IsErrTeamNotExist(err)) nonOrg := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 2}) - _, err = nonOrg.GetTeam("team") + _, err = nonOrg.GetTeam(db.DefaultContext, "team") assert.True(t, organization.IsErrTeamNotExist(err)) } func TestUser_GetOwnerTeam(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}) - team, err := org.GetOwnerTeam() + team, err := org.GetOwnerTeam(db.DefaultContext) assert.NoError(t, err) assert.Equal(t, org.ID, team.OrgID) nonOrg := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 2}) - _, err = nonOrg.GetOwnerTeam() + _, err = nonOrg.GetOwnerTeam(db.DefaultContext) assert.True(t, organization.IsErrTeamNotExist(err)) } @@ -115,15 +115,15 @@ func TestUser_GetMembers(t *testing.T) { func TestGetOrgByName(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - org, err := organization.GetOrgByName("user3") + org, err := organization.GetOrgByName(db.DefaultContext, "user3") assert.NoError(t, err) assert.EqualValues(t, 3, org.ID) assert.Equal(t, "user3", org.Name) - _, err = organization.GetOrgByName("user2") // user2 is an individual + _, err = organization.GetOrgByName(db.DefaultContext, "user2") // user2 is an individual assert.True(t, organization.IsErrOrgNotExist(err)) - _, err = organization.GetOrgByName("") // corner case + _, err = organization.GetOrgByName(db.DefaultContext, "") // corner case assert.True(t, organization.IsErrOrgNotExist(err)) } diff --git a/modules/context/org.go b/modules/context/org.go index 89260b86544ec..638be28d19bed 100644 --- a/modules/context/org.go +++ b/modules/context/org.go @@ -53,7 +53,7 @@ func HandleOrgAssignment(ctx *Context, args ...bool) { orgName := ctx.Params(":org") var err error - ctx.Org.Organization, err = organization.GetOrgByName(orgName) + ctx.Org.Organization, err = organization.GetOrgByName(ctx, orgName) if err != nil { if organization.IsErrOrgNotExist(err) { redirectUserID, err := user_model.LookupUserRedirect(orgName) diff --git a/modules/repository/create_test.go b/modules/repository/create_test.go index 3040782845814..e64cae7424fa0 100644 --- a/modules/repository/create_test.go +++ b/modules/repository/create_test.go @@ -50,7 +50,7 @@ func TestIncludesAllRepositoriesTeams(t *testing.T) { assert.NoError(t, organization.CreateOrganization(org, user), "CreateOrganization") // Check Owner team. - ownerTeam, err := org.GetOwnerTeam() + ownerTeam, err := org.GetOwnerTeam(db.DefaultContext) assert.NoError(t, err, "GetOwnerTeam") assert.True(t, ownerTeam.IncludesAllRepositories, "Owner team includes all repositories") @@ -64,7 +64,7 @@ func TestIncludesAllRepositoriesTeams(t *testing.T) { } } // Get fresh copy of Owner team after creating repos. - ownerTeam, err = org.GetOwnerTeam() + ownerTeam, err = org.GetOwnerTeam(db.DefaultContext) assert.NoError(t, err, "GetOwnerTeam") // Create teams and check repositories. diff --git a/modules/repository/repo.go b/modules/repository/repo.go index b01be322d2f6f..9c6635209e292 100644 --- a/modules/repository/repo.go +++ b/modules/repository/repo.go @@ -57,7 +57,7 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User, repoPath := repo_model.RepoPath(u.Name, opts.RepoName) if u.IsOrganization() { - t, err := organization.OrgFromUser(u).GetOwnerTeam() + t, err := organization.OrgFromUser(u).GetOwnerTeam(ctx) if err != nil { return nil, err } diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 7b1ca6bdbb1c1..81b1135d1227d 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -449,7 +449,7 @@ func orgAssignment(args ...bool) func(ctx *context.APIContext) { var err error if assignOrg { - ctx.Org.Organization, err = organization.GetOrgByName(ctx.Params(":org")) + ctx.Org.Organization, err = organization.GetOrgByName(ctx, ctx.Params(":org")) if err != nil { if organization.IsErrOrgNotExist(err) { redirectUserID, err := user_model.LookupUserRedirect(ctx.Params(":org")) diff --git a/routers/api/v1/repo/fork.go b/routers/api/v1/repo/fork.go index 112a9562f0fdf..4e32947cacfc4 100644 --- a/routers/api/v1/repo/fork.go +++ b/routers/api/v1/repo/fork.go @@ -109,7 +109,7 @@ func CreateFork(ctx *context.APIContext) { if form.Organization == nil { forker = ctx.Doer } else { - org, err := organization.GetOrgByName(*form.Organization) + org, err := organization.GetOrgByName(ctx, *form.Organization) if err != nil { if organization.IsErrOrgNotExist(err) { ctx.Error(http.StatusUnprocessableEntity, "", err) diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go index de8a4d186489c..4df4b0f51328f 100644 --- a/routers/api/v1/repo/repo.go +++ b/routers/api/v1/repo/repo.go @@ -469,7 +469,7 @@ func CreateOrgRepo(ctx *context.APIContext) { // "403": // "$ref": "#/responses/forbidden" opt := web.GetForm(ctx).(*api.CreateRepoOption) - org, err := organization.GetOrgByName(ctx.Params(":org")) + org, err := organization.GetOrgByName(ctx, ctx.Params(":org")) if err != nil { if organization.IsErrOrgNotExist(err) { ctx.Error(http.StatusUnprocessableEntity, "", err) diff --git a/routers/web/repo/issue_label.go b/routers/web/repo/issue_label.go index 7af415a8faed1..3679a195304d8 100644 --- a/routers/web/repo/issue_label.go +++ b/routers/web/repo/issue_label.go @@ -79,7 +79,7 @@ func RetrieveLabels(ctx *context.Context) { } ctx.Data["OrgLabels"] = orgLabels - org, err := organization.GetOrgByName(ctx.Repo.Owner.LowerName) + org, err := organization.GetOrgByName(ctx, ctx.Repo.Owner.LowerName) if err != nil { ctx.ServerError("GetOrgByName", err) return diff --git a/routers/web/repo/setting.go b/routers/web/repo/setting.go index e7abec0d3e895..1ab466615fa86 100644 --- a/routers/web/repo/setting.go +++ b/routers/web/repo/setting.go @@ -980,7 +980,7 @@ func AddTeamPost(ctx *context.Context) { return } - team, err := organization.OrgFromUser(ctx.Repo.Owner).GetTeam(name) + team, err := organization.OrgFromUser(ctx.Repo.Owner).GetTeam(ctx, name) if err != nil { if organization.IsErrTeamNotExist(err) { ctx.Flash.Error(ctx.Tr("form.team_not_exist")) diff --git a/services/auth/middleware.go b/services/auth/middleware.go index 5396812acaf4a..907473dc11f60 100644 --- a/services/auth/middleware.go +++ b/services/auth/middleware.go @@ -44,4 +44,4 @@ func authShared(ctx *context.Context, authMethod Method) { ctx.Data["SignedUserID"] = int64(0) ctx.Data["SignedUserName"] = "" } -} \ No newline at end of file +} diff --git a/tests/integration/auth_ldap_test.go b/tests/integration/auth_ldap_test.go index f3c3e6d7b3e31..8ee8c37e64400 100644 --- a/tests/integration/auth_ldap_test.go +++ b/tests/integration/auth_ldap_test.go @@ -319,7 +319,7 @@ func TestLDAPGroupTeamSyncAddMember(t *testing.T) { } 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("org26") + org, err := organization.GetOrgByName(db.DefaultContext, "org26") assert.NoError(t, err) team, err := organization.GetTeam(db.DefaultContext, org.ID, "team11") assert.NoError(t, err) @@ -364,7 +364,7 @@ func TestLDAPGroupTeamSyncRemoveMember(t *testing.T) { } defer tests.PrepareTestEnv(t)() addAuthSourceLDAP(t, "", "on", `{"cn=dispatch,ou=people,dc=planetexpress,dc=com": {"org26": ["team11"]}}`) - org, err := organization.GetOrgByName("org26") + org, err := organization.GetOrgByName(db.DefaultContext, "org26") assert.NoError(t, err) team, err := organization.GetTeam(db.DefaultContext, org.ID, "team11") assert.NoError(t, err) From 81f8c4c723667c7d681ea0dfc802023c667de288 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Wed, 12 Oct 2022 19:50:48 +0000 Subject: [PATCH 3/6] Refactor group sync. --- .../auth/source/ldap/source_authenticate.go | 91 +++++++++--------- .../auth/source/ldap/source_group_sync.go | 95 ------------------ services/auth/source/ldap/source_search.go | 2 +- services/auth/source/ldap/source_sync.go | 5 +- services/auth/source/source_group_sync.go | 96 +++++++++++++++++++ 5 files changed, 147 insertions(+), 142 deletions(-) delete mode 100644 services/auth/source/ldap/source_group_sync.go create mode 100644 services/auth/source/source_group_sync.go diff --git a/services/auth/source/ldap/source_authenticate.go b/services/auth/source/ldap/source_authenticate.go index 785cb8ed31731..baffe1f4bfec3 100644 --- a/services/auth/source/ldap/source_authenticate.go +++ b/services/auth/source/ldap/source_authenticate.go @@ -11,9 +11,9 @@ import ( asymkey_model "code.gitea.io/gitea/models/asymkey" "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" - "code.gitea.io/gitea/models/organization" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/util" + source_service "code.gitea.io/gitea/services/auth/source" "code.gitea.io/gitea/services/mailer" user_service "code.gitea.io/gitea/services/user" ) @@ -65,61 +65,62 @@ func (source *Source) Authenticate(user *user_model.User, userName, password str } if user != nil { - if source.GroupsEnabled && (source.GroupTeamMap != "" || source.GroupTeamMapRemoval) { - orgCache := make(map[string]*organization.Organization) - teamCache := make(map[string]*organization.Team) - source.SyncLdapGroupsToTeams(user, sr.LdapTeamAdd, sr.LdapTeamRemove, orgCache, teamCache) - } if isAttributeSSHPublicKeySet && asymkey_model.SynchronizePublicKeys(user, source.authSource, sr.SSHPublicKey) { - return user, asymkey_model.RewriteAllPublicKeys() + if err := asymkey_model.RewriteAllPublicKeys(); err != nil { + return user, err + } + } + } else { + // Fallback. + if len(sr.Username) == 0 { + sr.Username = userName } - return user, nil - } - - // Fallback. - if len(sr.Username) == 0 { - sr.Username = userName - } - if len(sr.Mail) == 0 { - sr.Mail = fmt.Sprintf("%s@localhost", sr.Username) - } + if len(sr.Mail) == 0 { + sr.Mail = fmt.Sprintf("%s@localhost", sr.Username) + } - user = &user_model.User{ - LowerName: strings.ToLower(sr.Username), - Name: sr.Username, - FullName: composeFullName(sr.Name, sr.Surname, sr.Username), - Email: sr.Mail, - LoginType: source.authSource.Type, - LoginSource: source.authSource.ID, - LoginName: userName, - IsAdmin: sr.IsAdmin, - } - overwriteDefault := &user_model.CreateUserOverwriteOptions{ - IsRestricted: util.OptionalBoolOf(sr.IsRestricted), - IsActive: util.OptionalBoolTrue, - } + user = &user_model.User{ + LowerName: strings.ToLower(sr.Username), + Name: sr.Username, + FullName: composeFullName(sr.Name, sr.Surname, sr.Username), + Email: sr.Mail, + LoginType: source.authSource.Type, + LoginSource: source.authSource.ID, + LoginName: userName, + IsAdmin: sr.IsAdmin, + } + overwriteDefault := &user_model.CreateUserOverwriteOptions{ + IsRestricted: util.OptionalBoolOf(sr.IsRestricted), + IsActive: util.OptionalBoolTrue, + } - err := user_model.CreateUser(user, overwriteDefault) - if err != nil { - return user, err - } + err := user_model.CreateUser(user, overwriteDefault) + if err != nil { + return user, err + } - mailer.SendRegisterNotifyMail(user) + mailer.SendRegisterNotifyMail(user) - if isAttributeSSHPublicKeySet && asymkey_model.AddPublicKeysBySource(user, source.authSource, sr.SSHPublicKey) { - err = asymkey_model.RewriteAllPublicKeys() - } - if err == nil && len(source.AttributeAvatar) > 0 { - _ = user_service.UploadAvatar(user, sr.Avatar) + if isAttributeSSHPublicKeySet && asymkey_model.AddPublicKeysBySource(user, source.authSource, sr.SSHPublicKey) { + if err := asymkey_model.RewriteAllPublicKeys(); err != nil { + return user, err + } + } + if len(source.AttributeAvatar) > 0 { + if err := user_service.UploadAvatar(user, sr.Avatar); err != nil { + return user, err + } + } } + if source.GroupsEnabled && (source.GroupTeamMap != "" || source.GroupTeamMapRemoval) { - orgCache := make(map[string]*organization.Organization) - teamCache := make(map[string]*organization.Team) - source.SyncLdapGroupsToTeams(user, sr.LdapTeamAdd, sr.LdapTeamRemove, orgCache, teamCache) + if err := source_service.SyncGroupsToTeams(db.DefaultContext, user, sr.LdapTeamAdd, sr.LdapTeamRemove, source.GroupTeamMapRemoval); err != nil { + return user, err + } } - return user, err + return user, nil } // IsSkipLocalTwoFA returns if this source should skip local 2fa for password authentication diff --git a/services/auth/source/ldap/source_group_sync.go b/services/auth/source/ldap/source_group_sync.go deleted file mode 100644 index e797e015b23bd..0000000000000 --- a/services/auth/source/ldap/source_group_sync.go +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright 2021 The Gitea Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package ldap - -import ( - "code.gitea.io/gitea/models" - "code.gitea.io/gitea/models/db" - "code.gitea.io/gitea/models/organization" - user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/modules/log" -) - -// SyncLdapGroupsToTeams maps LDAP groups to organization and team memberships -func (source *Source) SyncLdapGroupsToTeams(user *user_model.User, ldapTeamAdd, ldapTeamRemove map[string][]string, orgCache map[string]*organization.Organization, teamCache map[string]*organization.Team) { - var err error - if source.GroupsEnabled && source.GroupTeamMapRemoval { - // when the user is not a member of configs LDAP group, remove mapped organizations/teams memberships - removeMappedMemberships(user, ldapTeamRemove, orgCache, teamCache) - } - for orgName, teamNames := range ldapTeamAdd { - org, ok := orgCache[orgName] - if !ok { - org, err = organization.GetOrgByName(orgName) - if err != nil { - // organization must be created before LDAP group sync - log.Warn("LDAP group sync: Could not find organisation %s: %v", orgName, err) - continue - } - orgCache[orgName] = org - } - - for _, teamName := range teamNames { - team, ok := teamCache[orgName+teamName] - if !ok { - team, err = org.GetTeam(teamName) - if err != nil { - // team must be created before LDAP group sync - log.Warn("LDAP group sync: Could not find team %s: %v", teamName, err) - continue - } - teamCache[orgName+teamName] = team - } - if isMember, err := organization.IsTeamMember(db.DefaultContext, org.ID, team.ID, user.ID); !isMember && err == nil { - log.Trace("LDAP group sync: adding user [%s] to team [%s]", user.Name, org.Name) - } else { - continue - } - err := models.AddTeamMember(team, user.ID) - if err != nil { - log.Error("LDAP group sync: Could not add user to team: %v", err) - } - } - } -} - -// remove membership to organizations/teams if user is not member of corresponding LDAP group -// e.g. lets assume user is member of LDAP group "x", but LDAP group team map contains LDAP groups "x" and "y" -// then users membership gets removed for all organizations/teams mapped by LDAP group "y" -func removeMappedMemberships(user *user_model.User, ldapTeamRemove map[string][]string, orgCache map[string]*organization.Organization, teamCache map[string]*organization.Team) { - var err error - for orgName, teamNames := range ldapTeamRemove { - org, ok := orgCache[orgName] - if !ok { - org, err = organization.GetOrgByName(orgName) - if err != nil { - // organization must be created before LDAP group sync - log.Warn("LDAP group sync: Could not find organisation %s: %v", orgName, err) - continue - } - orgCache[orgName] = org - } - for _, teamName := range teamNames { - team, ok := teamCache[orgName+teamName] - if !ok { - team, err = org.GetTeam(teamName) - if err != nil { - // team must must be created before LDAP group sync - log.Warn("LDAP group sync: Could not find team %s: %v", teamName, err) - continue - } - } - if isMember, err := organization.IsTeamMember(db.DefaultContext, org.ID, team.ID, user.ID); isMember && err == nil { - log.Trace("LDAP group sync: removing user [%s] from team [%s]", user.Name, org.Name) - } else { - continue - } - err = models.RemoveTeamMember(team, user.ID) - if err != nil { - log.Error("LDAP group sync: Could not remove user from team: %v", err) - } - } - } -} diff --git a/services/auth/source/ldap/source_search.go b/services/auth/source/ldap/source_search.go index a97a1179d9bcd..fa880d6cc59f9 100644 --- a/services/auth/source/ldap/source_search.go +++ b/services/auth/source/ldap/source_search.go @@ -252,7 +252,7 @@ func (source *Source) getMappedMemberships(l *ldap.Conn, uid string) (map[string for org, teams := range memberships { membershipsToAdd[org] = teams } - } else if !isUserInGroup { + } else { for org, teams := range memberships { membershipsToRemove[org] = teams } diff --git a/services/auth/source/ldap/source_sync.go b/services/auth/source/ldap/source_sync.go index eb5ee8463f71f..f543256e26c81 100644 --- a/services/auth/source/ldap/source_sync.go +++ b/services/auth/source/ldap/source_sync.go @@ -16,6 +16,7 @@ import ( user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/util" + source_service "code.gitea.io/gitea/services/auth/source" user_service "code.gitea.io/gitea/services/user" ) @@ -174,7 +175,9 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { } // Synchronize LDAP groups with organization and team memberships if source.GroupsEnabled && (source.GroupTeamMap != "" || source.GroupTeamMapRemoval) { - source.SyncLdapGroupsToTeams(usr, su.LdapTeamAdd, su.LdapTeamRemove, orgCache, teamCache) + if err := source_service.SyncGroupsToTeamsCached(ctx, usr, su.LdapTeamAdd, su.LdapTeamRemove, source.GroupTeamMapRemoval, orgCache, teamCache); err != nil { + log.Error("SyncGroupsToTeamsCached: %v", err) + } } } diff --git a/services/auth/source/source_group_sync.go b/services/auth/source/source_group_sync.go new file mode 100644 index 0000000000000..6512051271ec3 --- /dev/null +++ b/services/auth/source/source_group_sync.go @@ -0,0 +1,96 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package source + +import ( + "context" + "fmt" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/organization" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/log" +) + +type syncType int + +const ( + syncAdd syncType = iota + syncRemove +) + +// SyncGroupsToTeams maps authentication source groups to organization and team memberships +func SyncGroupsToTeams(ctx context.Context, user *user_model.User, teamAdd, teamRemove map[string][]string, performRemoval bool) error { + orgCache := make(map[string]*organization.Organization) + teamCache := make(map[string]*organization.Team) + return SyncGroupsToTeamsCached(ctx, user, teamAdd, teamRemove, performRemoval, orgCache, teamCache) +} + +// SyncGroupsToTeamsCached maps authentication source groups to organization and team memberships +func SyncGroupsToTeamsCached(ctx context.Context, user *user_model.User, teamAdd, teamRemove map[string][]string, performRemoval bool, orgCache map[string]*organization.Organization, teamCache map[string]*organization.Team) error { + if performRemoval { + if err := syncGroupsToTeamsCached(ctx, user, teamRemove, syncRemove, orgCache, teamCache); err != nil { + return fmt.Errorf("could not sync[remove] user groups: %w", err) + } + } + + if err := syncGroupsToTeamsCached(ctx, user, teamAdd, syncAdd, orgCache, teamCache); err != nil { + return fmt.Errorf("could not sync[add] user groups: %w", err) + } + + return nil +} + +func syncGroupsToTeamsCached(ctx context.Context, user *user_model.User, orgTeamMap map[string][]string, action syncType, orgCache map[string]*organization.Organization, teamCache map[string]*organization.Team) error { + for orgName, teamNames := range orgTeamMap { + var err error + org, ok := orgCache[orgName] + if !ok { + org, err = organization.GetOrgByName(ctx, orgName) + if err != nil { + if organization.IsErrOrgNotExist(err) { + // organization must be created before group sync + log.Warn("group sync: Could not find organisation %s: %v", orgName, err) + continue + } + return err + } + orgCache[orgName] = org + } + for _, teamName := range teamNames { + team, ok := teamCache[orgName+teamName] + if !ok { + team, err = org.GetTeam(ctx, teamName) + if err != nil { + if organization.IsErrTeamNotExist(err) { + // team must be created before group sync + log.Warn("group sync: Could not find team %s: %v", teamName, err) + continue + } + return err + } + teamCache[orgName+teamName] = team + } + + isMember, err := organization.IsTeamMember(ctx, org.ID, team.ID, user.ID) + if err != nil { + return err + } + + if action == syncAdd && !isMember { + if err := models.AddTeamMember(team, user.ID); err != nil { + log.Error("group sync: Could not add user to team: %v", err) + return err + } + } else if action == syncRemove && isMember { + if err := models.RemoveTeamMember(team, user.ID); err != nil { + log.Error("group sync: Could not remove user from team: %v", err) + return err + } + } + } + } + return nil +} From c41d51d410164d6ff6742e03acd21d9a3f91f8fd Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Thu, 13 Oct 2022 14:55:58 +0000 Subject: [PATCH 4/6] Add OAuth mapping. --- cmd/admin.go | 17 +++ docs/content/doc/usage/command-line.en-us.md | 2 + options/locale/locale_en-US.ini | 2 + routers/web/admin/auths.go | 2 + routers/web/auth/oauth.go | 91 +++++++++----- .../auth/source/ldap/source_authenticate.go | 6 +- services/auth/source/ldap/source_search.go | 116 ++++++------------ services/auth/source/ldap/source_sync.go | 7 +- services/auth/source/oauth2/source.go | 23 ++-- services/auth/source/source_group_sync.go | 45 ++++++- services/forms/auth_form.go | 2 + templates/admin/auth/edit.tmpl | 8 ++ templates/admin/auth/source/ldap.tmpl | 2 +- templates/admin/auth/source/oauth.tmpl | 8 ++ 14 files changed, 196 insertions(+), 135 deletions(-) diff --git a/cmd/admin.go b/cmd/admin.go index 2cf63d384abe8..32e950355e564 100644 --- a/cmd/admin.go +++ b/cmd/admin.go @@ -363,6 +363,15 @@ var ( Value: "", Usage: "Group Claim value for restricted users", }, + cli.StringFlag{ + Name: "group-team-map", + Value: "", + Usage: "JSON mapping between groups and org teams", + }, + cli.BoolFlag{ + Name: "group-team-map-removal", + Usage: "Activate automatic team membership removal depending on groups", + }, } microcmdAuthUpdateOauth = cli.Command{ @@ -832,6 +841,8 @@ func parseOAuth2Config(c *cli.Context) *oauth2.Source { GroupClaimName: c.String("group-claim-name"), AdminGroup: c.String("admin-group"), RestrictedGroup: c.String("restricted-group"), + GroupTeamMap: c.String("group-team-map"), + GroupTeamMapRemoval: c.Bool("group-team-map-removal"), } } @@ -914,6 +925,12 @@ func runUpdateOauth(c *cli.Context) error { if c.IsSet("restricted-group") { oAuth2Config.RestrictedGroup = c.String("restricted-group") } + if c.IsSet("group-team-map") { + oAuth2Config.GroupTeamMap = c.String("group-team-map") + } + if c.IsSet("group-team-map-removal") { + oAuth2Config.GroupTeamMapRemoval = c.Bool("group-team-map-removal") + } // update custom URL mapping customURLMapping := &oauth2.CustomURLMapping{} diff --git a/docs/content/doc/usage/command-line.en-us.md b/docs/content/doc/usage/command-line.en-us.md index 5f05bc4c3be3e..305998302b280 100644 --- a/docs/content/doc/usage/command-line.en-us.md +++ b/docs/content/doc/usage/command-line.en-us.md @@ -136,6 +136,8 @@ Admin operations: - `--group-claim-name`: Claim name providing group names for this source. (Optional) - `--admin-group`: Group Claim value for administrator users. (Optional) - `--restricted-group`: Group Claim value for restricted users. (Optional) + - `--group-team-map`: JSON mapping between groups and org teams. (Optional) + - `--group-team-map-removal`: Activate automatic team membership removal depending on groups. (Optional) - Examples: - `gitea admin auth add-oauth --name external-github --provider github --key OBTAIN_FROM_SOURCE --secret OBTAIN_FROM_SOURCE` - `update-oauth`: diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index fbf9b70643763..b22f1bde9b80f 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -2700,6 +2700,8 @@ auths.oauth2_required_claim_value_helper = Set this value to restrict login from auths.oauth2_group_claim_name = Claim name providing group names for this source. (Optional) auths.oauth2_admin_group = Group Claim value for administrator users. (Optional - requires claim name above) auths.oauth2_restricted_group = Group Claim value for restricted users. (Optional - requires claim name above) +auths.oauth2_map_group_to_team = Map claimed groups to Organization teams. (Optional - requires claim name above) +auths.oauth2_map_group_to_team_removal = Remove users from synchronized teams if user does not belong to corresponding group. auths.enable_auto_register = Enable Auto Registration auths.sspi_auto_create_users = Automatically create users auths.sspi_auto_create_users_helper = Allow SSPI auth method to automatically create new accounts for users that login for the first time diff --git a/routers/web/admin/auths.go b/routers/web/admin/auths.go index b79b317555966..22c1564d4c535 100644 --- a/routers/web/admin/auths.go +++ b/routers/web/admin/auths.go @@ -205,6 +205,8 @@ func parseOAuth2Config(form forms.AuthenticationForm) *oauth2.Source { GroupClaimName: form.Oauth2GroupClaimName, RestrictedGroup: form.Oauth2RestrictedGroup, AdminGroup: form.Oauth2AdminGroup, + GroupTeamMap: form.Oauth2GroupTeamMap, + GroupTeamMapRemoval: form.Oauth2GroupTeamMapRemoval, } } diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go index e6112b4276dcf..b20aee976dc24 100644 --- a/routers/web/auth/oauth.go +++ b/routers/web/auth/oauth.go @@ -19,6 +19,7 @@ import ( org_model "code.gitea.io/gitea/models/organization" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" @@ -29,6 +30,7 @@ import ( "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web/middleware" auth_service "code.gitea.io/gitea/services/auth" + source_service "code.gitea.io/gitea/services/auth/source" "code.gitea.io/gitea/services/auth/source/oauth2" "code.gitea.io/gitea/services/externalaccount" "code.gitea.io/gitea/services/forms" @@ -911,12 +913,29 @@ func SignInOAuthCallback(ctx *context.Context) { IsActive: util.OptionalBoolOf(!setting.OAuth2Client.RegisterEmailConfirm), } - setUserGroupClaims(authSource, u, &gothUser) + source := authSource.Cfg.(*oauth2.Source) + + setUserAdminAndRestrictedFromGroupClaims(source, u, &gothUser) if !createAndHandleCreatedUser(ctx, base.TplName(""), nil, u, overwriteDefault, &gothUser, setting.OAuth2Client.AccountLinking != setting.OAuth2AccountLinkingDisabled) { // error already handled return } + + if source.GroupTeamMap != "" || source.GroupTeamMapRemoval { + groupTeamMapping, err := source_service.UnmarshalGroupTeamMapping(source.GroupTeamMap) + if err != nil { + ctx.ServerError("UnmarshalGroupTeamMapping", err) + return + } + + groups := getClaimedGroups(source, &gothUser) + + if err := source_service.SyncGroupsToTeams(ctx, u, groups, groupTeamMapping, source.GroupTeamMapRemoval); err != nil { + ctx.ServerError("SyncGroupsToTeams", err) + return + } + } } else { // no existing user is found, request attach or new account showLinkingLogin(ctx, gothUser) @@ -927,7 +946,7 @@ func SignInOAuthCallback(ctx *context.Context) { handleOAuth2SignIn(ctx, authSource, u, gothUser) } -func claimValueToStringSlice(claimValue interface{}) []string { +func claimValueToStringSet(claimValue interface{}) container.Set[string] { var groups []string switch rawGroup := claimValue.(type) { @@ -941,37 +960,28 @@ func claimValueToStringSlice(claimValue interface{}) []string { str := fmt.Sprintf("%s", rawGroup) groups = strings.Split(str, ",") } - return groups + return container.SetOf(groups...) } -func setUserGroupClaims(loginSource *auth.Source, u *user_model.User, gothUser *goth.User) bool { - source := loginSource.Cfg.(*oauth2.Source) - if source.GroupClaimName == "" || (source.AdminGroup == "" && source.RestrictedGroup == "") { - return false - } - +func getClaimedGroups(source *oauth2.Source, gothUser *goth.User) container.Set[string] { groupClaims, has := gothUser.RawData[source.GroupClaimName] if !has { - return false + return nil } - groups := claimValueToStringSlice(groupClaims) + return claimValueToStringSet(groupClaims) +} + +func setUserAdminAndRestrictedFromGroupClaims(source *oauth2.Source, u *user_model.User, gothUser *goth.User) bool { + groups := getClaimedGroups(source, gothUser) wasAdmin, wasRestricted := u.IsAdmin, u.IsRestricted if source.AdminGroup != "" { - u.IsAdmin = false + u.IsAdmin = groups.Contains(source.AdminGroup) } if source.RestrictedGroup != "" { - u.IsRestricted = false - } - - for _, g := range groups { - if source.AdminGroup != "" && g == source.AdminGroup { - u.IsAdmin = true - } else if source.RestrictedGroup != "" && g == source.RestrictedGroup { - u.IsRestricted = true - } + u.IsRestricted = groups.Contains(source.RestrictedGroup) } return wasAdmin != u.IsAdmin || wasRestricted != u.IsRestricted @@ -1023,6 +1033,15 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model needs2FA = err == nil } + oauth2Source := source.Cfg.(*oauth2.Source) + groupTeamMapping, err := source_service.UnmarshalGroupTeamMapping(oauth2Source.GroupTeamMap) + if err != nil { + ctx.ServerError("UnmarshalGroupTeamMapping", err) + return + } + + groups := getClaimedGroups(oauth2Source, &gothUser) + // If this user is enrolled in 2FA and this source doesn't override it, // we can't sign the user in just yet. Instead, redirect them to the 2FA authentication page. if !needs2FA { @@ -1048,7 +1067,7 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model u.SetLastLogin() // Update GroupClaims - changed := setUserGroupClaims(source, u, &gothUser) + changed := setUserAdminAndRestrictedFromGroupClaims(oauth2Source, u, &gothUser) cols := []string{"last_login_unix"} if changed { cols = append(cols, "is_admin", "is_restricted") @@ -1059,6 +1078,13 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model return } + if oauth2Source.GroupTeamMap != "" || oauth2Source.GroupTeamMapRemoval { + if err := source_service.SyncGroupsToTeams(ctx, u, groups, groupTeamMapping, oauth2Source.GroupTeamMapRemoval); err != nil { + ctx.ServerError("SyncGroupsToTeams", err) + return + } + } + // update external user information if err := externalaccount.UpdateExternalUser(u, gothUser); err != nil { log.Error("UpdateExternalUser failed: %v", err) @@ -1079,7 +1105,7 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model return } - changed := setUserGroupClaims(source, u, &gothUser) + changed := setUserAdminAndRestrictedFromGroupClaims(oauth2Source, u, &gothUser) if changed { if err := user_model.UpdateUserCols(ctx, u, "is_admin", "is_restricted"); err != nil { ctx.ServerError("UpdateUserCols", err) @@ -1087,6 +1113,13 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model } } + if oauth2Source.GroupTeamMap != "" || oauth2Source.GroupTeamMapRemoval { + if err := source_service.SyncGroupsToTeams(ctx, u, groups, groupTeamMapping, oauth2Source.GroupTeamMapRemoval); err != nil { + ctx.ServerError("SyncGroupsToTeams", err) + return + } + } + if _, err := session.RegenerateSession(ctx.Resp, ctx.Req); err != nil { ctx.ServerError("RegenerateSession", err) return @@ -1153,15 +1186,9 @@ func oAuth2UserLoginCallback(authSource *auth.Source, request *http.Request, res } if oauth2Source.RequiredClaimValue != "" { - groups := claimValueToStringSlice(claimInterface) - found := false - for _, group := range groups { - if group == oauth2Source.RequiredClaimValue { - found = true - break - } - } - if !found { + groups := claimValueToStringSet(claimInterface) + + if !groups.Contains(oauth2Source.RequiredClaimValue) { return nil, goth.User{}, user_model.ErrUserProhibitLogin{Name: gothUser.UserID} } } diff --git a/services/auth/source/ldap/source_authenticate.go b/services/auth/source/ldap/source_authenticate.go index baffe1f4bfec3..18ecbb4d505a7 100644 --- a/services/auth/source/ldap/source_authenticate.go +++ b/services/auth/source/ldap/source_authenticate.go @@ -115,7 +115,11 @@ func (source *Source) Authenticate(user *user_model.User, userName, password str } if source.GroupsEnabled && (source.GroupTeamMap != "" || source.GroupTeamMapRemoval) { - if err := source_service.SyncGroupsToTeams(db.DefaultContext, user, sr.LdapTeamAdd, sr.LdapTeamRemove, source.GroupTeamMapRemoval); err != nil { + groupTeamMapping, err := source_service.UnmarshalGroupTeamMapping(source.GroupTeamMap) + if err != nil { + return user, err + } + if err := source_service.SyncGroupsToTeams(db.DefaultContext, user, sr.Groups, groupTeamMapping, source.GroupTeamMapRemoval); err != nil { return user, err } } diff --git a/services/auth/source/ldap/source_search.go b/services/auth/source/ldap/source_search.go index fa880d6cc59f9..a17bec989f4f1 100644 --- a/services/auth/source/ldap/source_search.go +++ b/services/auth/source/ldap/source_search.go @@ -12,26 +12,24 @@ import ( "strconv" "strings" - "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/util" "github.com/go-ldap/ldap/v3" ) // SearchResult : user data type SearchResult struct { - Username string // Username - Name string // Name - Surname string // Surname - Mail string // E-mail address - SSHPublicKey []string // SSH Public Key - IsAdmin bool // if user is administrator - IsRestricted bool // if user is restricted - LowerName string // LowerName - Avatar []byte - LdapTeamAdd map[string][]string // organizations teams to add - LdapTeamRemove map[string][]string // organizations teams to remove + Username string // Username + Name string // Name + Surname string // Surname + Mail string // E-mail address + SSHPublicKey []string // SSH Public Key + IsAdmin bool // if user is administrator + IsRestricted bool // if user is restricted + LowerName string // LowerName + Avatar []byte + Groups container.Set[string] } func (source *Source) sanitizedUserQuery(username string) (string, bool) { @@ -197,8 +195,8 @@ func checkRestricted(l *ldap.Conn, ls *Source, userDN string) bool { } // List all group memberships of a user -func (source *Source) listLdapGroupMemberships(l *ldap.Conn, uid string) []string { - var ldapGroups []string +func (source *Source) listLdapGroupMemberships(l *ldap.Conn, uid string) container.Set[string] { + ldapGroups := make(container.Set[string]) groupFilter := fmt.Sprintf("(%s=%s)", source.GroupMemberUID, ldap.EscapeFilter(uid)) result, err := l.Search(ldap.NewSearchRequest( source.GroupDN, @@ -221,46 +219,12 @@ func (source *Source) listLdapGroupMemberships(l *ldap.Conn, uid string) []strin log.Error("LDAP search was successful, but found no DN!") continue } - ldapGroups = append(ldapGroups, entry.DN) + ldapGroups.Add(entry.DN) } return ldapGroups } -// parse LDAP groups and return map of ldap groups to organizations teams -func (source *Source) mapLdapGroupsToTeams() map[string]map[string][]string { - ldapGroupsToTeams := make(map[string]map[string][]string) - err := json.Unmarshal([]byte(source.GroupTeamMap), &ldapGroupsToTeams) - if err != nil { - log.Error("Failed to unmarshall LDAP teams map: %v", err) - return ldapGroupsToTeams - } - return ldapGroupsToTeams -} - -// getMappedMemberships : returns the organizations and teams to modify the users membership -func (source *Source) getMappedMemberships(l *ldap.Conn, uid string) (map[string][]string, map[string][]string) { - // get all LDAP group memberships for user - usersLdapGroups := source.listLdapGroupMemberships(l, uid) - // unmarshall LDAP group team map from configs - ldapGroupsToTeams := source.mapLdapGroupsToTeams() - membershipsToAdd := map[string][]string{} - membershipsToRemove := map[string][]string{} - for group, memberships := range ldapGroupsToTeams { - isUserInGroup := util.IsStringInSlice(group, usersLdapGroups) - if isUserInGroup { - for org, teams := range memberships { - membershipsToAdd[org] = teams - } - } else { - for org, teams := range memberships { - membershipsToRemove[org] = teams - } - } - } - return membershipsToAdd, membershipsToRemove -} - // SearchEntry : search an LDAP source if an entry (name, passwd) is valid and in the specific filter func (source *Source) SearchEntry(name, passwd string, directBind bool) *SearchResult { // See https://tools.ietf.org/search/rfc4513#section-5.1.2 @@ -437,11 +401,7 @@ func (source *Source) SearchEntry(name, passwd string, directBind bool) *SearchR Avatar = sr.Entries[0].GetRawAttributeValue(source.AttributeAvatar) } - teamsToAdd := make(map[string][]string) - teamsToRemove := make(map[string][]string) - if source.GroupsEnabled && (source.GroupTeamMap != "" || source.GroupTeamMapRemoval) { - teamsToAdd, teamsToRemove = source.getMappedMemberships(l, uid) - } + groups := source.listLdapGroupMemberships(l, uid) if !directBind && source.AttributesInBind { // binds user (checking password) after looking-up attributes in BindDN context @@ -452,17 +412,16 @@ func (source *Source) SearchEntry(name, passwd string, directBind bool) *SearchR } return &SearchResult{ - LowerName: strings.ToLower(username), - Username: username, - Name: firstname, - Surname: surname, - Mail: mail, - SSHPublicKey: sshPublicKey, - IsAdmin: isAdmin, - IsRestricted: isRestricted, - Avatar: Avatar, - LdapTeamAdd: teamsToAdd, - LdapTeamRemove: teamsToRemove, + LowerName: strings.ToLower(username), + Username: username, + Name: firstname, + Surname: surname, + Mail: mail, + SSHPublicKey: sshPublicKey, + IsAdmin: isAdmin, + IsRestricted: isRestricted, + Avatar: Avatar, + Groups: groups, } } @@ -524,23 +483,18 @@ func (source *Source) SearchEntries() ([]*SearchResult, error) { result := make([]*SearchResult, len(sr.Entries)) for i, v := range sr.Entries { - teamsToAdd := make(map[string][]string) - teamsToRemove := make(map[string][]string) - if source.GroupsEnabled && (source.GroupTeamMap != "" || source.GroupTeamMapRemoval) { - userAttributeListedInGroup := v.GetAttributeValue(source.UserUID) - if source.UserUID == "dn" || source.UserUID == "DN" { - userAttributeListedInGroup = v.DN - } - teamsToAdd, teamsToRemove = source.getMappedMemberships(l, userAttributeListedInGroup) + userAttributeListedInGroup := v.GetAttributeValue(source.UserUID) + if source.UserUID == "dn" || source.UserUID == "DN" { + userAttributeListedInGroup = v.DN } + groups := source.listLdapGroupMemberships(l, userAttributeListedInGroup) result[i] = &SearchResult{ - Username: v.GetAttributeValue(source.AttributeUsername), - Name: v.GetAttributeValue(source.AttributeName), - Surname: v.GetAttributeValue(source.AttributeSurname), - Mail: v.GetAttributeValue(source.AttributeMail), - IsAdmin: checkAdmin(l, source, v.DN), - LdapTeamAdd: teamsToAdd, - LdapTeamRemove: teamsToRemove, + Username: v.GetAttributeValue(source.AttributeUsername), + Name: v.GetAttributeValue(source.AttributeName), + Surname: v.GetAttributeValue(source.AttributeSurname), + Mail: v.GetAttributeValue(source.AttributeMail), + IsAdmin: checkAdmin(l, source, v.DN), + Groups: groups, } if !result[i].IsAdmin { result[i].IsRestricted = checkRestricted(l, source, v.DN) diff --git a/services/auth/source/ldap/source_sync.go b/services/auth/source/ldap/source_sync.go index f543256e26c81..c5507fe1ca7a5 100644 --- a/services/auth/source/ldap/source_sync.go +++ b/services/auth/source/ldap/source_sync.go @@ -67,6 +67,11 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { orgCache := make(map[string]*organization.Organization) teamCache := make(map[string]*organization.Team) + groupTeamMapping, err := source_service.UnmarshalGroupTeamMapping(source.GroupTeamMap) + if err != nil { + return err + } + for _, su := range sr { select { case <-ctx.Done(): @@ -175,7 +180,7 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { } // Synchronize LDAP groups with organization and team memberships if source.GroupsEnabled && (source.GroupTeamMap != "" || source.GroupTeamMapRemoval) { - if err := source_service.SyncGroupsToTeamsCached(ctx, usr, su.LdapTeamAdd, su.LdapTeamRemove, source.GroupTeamMapRemoval, orgCache, teamCache); err != nil { + if err := source_service.SyncGroupsToTeamsCached(ctx, usr, su.Groups, groupTeamMapping, source.GroupTeamMapRemoval, orgCache, teamCache); err != nil { log.Error("SyncGroupsToTeamsCached: %v", err) } } diff --git a/services/auth/source/oauth2/source.go b/services/auth/source/oauth2/source.go index 457686ba1fd4d..d9fbde00218ec 100644 --- a/services/auth/source/oauth2/source.go +++ b/services/auth/source/oauth2/source.go @@ -9,13 +9,6 @@ import ( "code.gitea.io/gitea/modules/json" ) -// ________ _____ __ .__ ________ -// \_____ \ / _ \ __ ___/ |_| |__ \_____ \ -// / | \ / /_\ \| | \ __\ | \ / ____/ -// / | \/ | \ | /| | | Y \/ \ -// \_______ /\____|__ /____/ |__| |___| /\_______ \ -// \/ \/ \/ \/ - // Source holds configuration for the OAuth2 login source. type Source struct { Provider string @@ -25,13 +18,15 @@ type Source struct { CustomURLMapping *CustomURLMapping IconURL string - Scopes []string - RequiredClaimName string - RequiredClaimValue string - GroupClaimName string - AdminGroup string - RestrictedGroup string - SkipLocalTwoFA bool `json:",omitempty"` + Scopes []string + RequiredClaimName string + RequiredClaimValue string + GroupClaimName string + AdminGroup string + GroupTeamMap string + GroupTeamMapRemoval bool + RestrictedGroup string + SkipLocalTwoFA bool `json:",omitempty"` // reference to the authSource authSource *auth.Source diff --git a/services/auth/source/source_group_sync.go b/services/auth/source/source_group_sync.go index 6512051271ec3..050f7f72fe5de 100644 --- a/services/auth/source/source_group_sync.go +++ b/services/auth/source/source_group_sync.go @@ -11,6 +11,8 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/organization" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/container" + "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" ) @@ -21,28 +23,61 @@ const ( syncRemove ) +func UnmarshalGroupTeamMapping(raw string) (map[string]map[string][]string, error) { + groupTeamMapping := make(map[string]map[string][]string) + if raw == "" { + return groupTeamMapping, nil + } + err := json.Unmarshal([]byte(raw), &groupTeamMapping) + if err != nil { + log.Error("Failed to unmarshal group team mapping: %v", err) + return nil, err + } + return groupTeamMapping, nil +} + // SyncGroupsToTeams maps authentication source groups to organization and team memberships -func SyncGroupsToTeams(ctx context.Context, user *user_model.User, teamAdd, teamRemove map[string][]string, performRemoval bool) error { +func SyncGroupsToTeams(ctx context.Context, user *user_model.User, sourceUserGroups container.Set[string], sourceGroupTeamMapping map[string]map[string][]string, performRemoval bool) error { orgCache := make(map[string]*organization.Organization) teamCache := make(map[string]*organization.Team) - return SyncGroupsToTeamsCached(ctx, user, teamAdd, teamRemove, performRemoval, orgCache, teamCache) + return SyncGroupsToTeamsCached(ctx, user, sourceUserGroups, sourceGroupTeamMapping, performRemoval, orgCache, teamCache) } // SyncGroupsToTeamsCached maps authentication source groups to organization and team memberships -func SyncGroupsToTeamsCached(ctx context.Context, user *user_model.User, teamAdd, teamRemove map[string][]string, performRemoval bool, orgCache map[string]*organization.Organization, teamCache map[string]*organization.Team) error { +func SyncGroupsToTeamsCached(ctx context.Context, user *user_model.User, sourceUserGroups container.Set[string], sourceGroupTeamMapping map[string]map[string][]string, performRemoval bool, orgCache map[string]*organization.Organization, teamCache map[string]*organization.Team) error { + membershipsToAdd, membershipsToRemove := resolveMappedMemberships(sourceUserGroups, sourceGroupTeamMapping) + if performRemoval { - if err := syncGroupsToTeamsCached(ctx, user, teamRemove, syncRemove, orgCache, teamCache); err != nil { + if err := syncGroupsToTeamsCached(ctx, user, membershipsToRemove, syncRemove, orgCache, teamCache); err != nil { return fmt.Errorf("could not sync[remove] user groups: %w", err) } } - if err := syncGroupsToTeamsCached(ctx, user, teamAdd, syncAdd, orgCache, teamCache); err != nil { + if err := syncGroupsToTeamsCached(ctx, user, membershipsToAdd, syncAdd, orgCache, teamCache); err != nil { return fmt.Errorf("could not sync[add] user groups: %w", err) } return nil } +func resolveMappedMemberships(sourceUserGroups container.Set[string], sourceGroupTeamMapping map[string]map[string][]string) (map[string][]string, map[string][]string) { + membershipsToAdd := map[string][]string{} + membershipsToRemove := map[string][]string{} + for group, memberships := range sourceGroupTeamMapping { + isUserInGroup := sourceUserGroups.Contains(group) + if isUserInGroup { + for org, teams := range memberships { + membershipsToAdd[org] = teams + } + } else { + for org, teams := range memberships { + membershipsToRemove[org] = teams + } + } + } + return membershipsToAdd, membershipsToRemove +} + func syncGroupsToTeamsCached(ctx context.Context, user *user_model.User, orgTeamMap map[string][]string, action syncType, orgCache map[string]*organization.Organization, teamCache map[string]*organization.Team) error { for orgName, teamNames := range orgTeamMap { var err error diff --git a/services/forms/auth_form.go b/services/forms/auth_form.go index 9064be2cca38e..d707ce6f1bf97 100644 --- a/services/forms/auth_form.go +++ b/services/forms/auth_form.go @@ -73,6 +73,8 @@ type AuthenticationForm struct { Oauth2GroupClaimName string Oauth2AdminGroup string Oauth2RestrictedGroup string + Oauth2GroupTeamMap string + Oauth2GroupTeamMapRemoval bool SkipLocalTwoFA bool SSPIAutoCreateUsers bool SSPIAutoActivateUsers bool diff --git a/templates/admin/auth/edit.tmpl b/templates/admin/auth/edit.tmpl index bf9d53152c2e7..e7444b73a4caf 100644 --- a/templates/admin/auth/edit.tmpl +++ b/templates/admin/auth/edit.tmpl @@ -361,6 +361,14 @@ +
+ + +
+
+ + +
{{end}} diff --git a/templates/admin/auth/source/ldap.tmpl b/templates/admin/auth/source/ldap.tmpl index b44eb799b9ec7..8d199854ae418 100644 --- a/templates/admin/auth/source/ldap.tmpl +++ b/templates/admin/auth/source/ldap.tmpl @@ -52,7 +52,7 @@
- +

{{.locale.Tr "admin.auths.restricted_filter_helper"}}

diff --git a/templates/admin/auth/source/oauth.tmpl b/templates/admin/auth/source/oauth.tmpl index 166373a324899..85c77343a5287 100644 --- a/templates/admin/auth/source/oauth.tmpl +++ b/templates/admin/auth/source/oauth.tmpl @@ -98,4 +98,12 @@
+
+ + +
+
+ + +
From 146fd711b3da6f65da3b262819e0a3d036a7e8e2 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Thu, 13 Oct 2022 20:36:13 +0000 Subject: [PATCH 5/6] Fix tests. --- modules/auth/common.go | 23 +++++++ modules/validation/binding.go | 23 ++++++- modules/web/middleware/binding.go | 2 + options/locale/locale_en-US.ini | 1 + routers/web/auth/oauth.go | 5 +- .../auth/source/ldap/source_authenticate.go | 3 +- services/auth/source/ldap/source_sync.go | 3 +- services/auth/source/source_group_sync.go | 14 ---- services/forms/auth_form.go | 4 +- tests/integration/auth_ldap_test.go | 66 ++++++------------- 10 files changed, 77 insertions(+), 67 deletions(-) create mode 100644 modules/auth/common.go diff --git a/modules/auth/common.go b/modules/auth/common.go new file mode 100644 index 0000000000000..ac8d8a20e8aea --- /dev/null +++ b/modules/auth/common.go @@ -0,0 +1,23 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package auth + +import ( + "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/modules/log" +) + +func UnmarshalGroupTeamMapping(raw string) (map[string]map[string][]string, error) { + groupTeamMapping := make(map[string]map[string][]string) + if raw == "" { + return groupTeamMapping, nil + } + err := json.Unmarshal([]byte(raw), &groupTeamMapping) + if err != nil { + log.Error("Failed to unmarshal group team mapping: %v", err) + return nil, err + } + return groupTeamMapping, nil +} diff --git a/modules/validation/binding.go b/modules/validation/binding.go index f08f632426649..a9029c7d3cd71 100644 --- a/modules/validation/binding.go +++ b/modules/validation/binding.go @@ -9,6 +9,7 @@ import ( "regexp" "strings" + "code.gitea.io/gitea/modules/auth" "code.gitea.io/gitea/modules/git" "gitea.com/go-chi/binding" @@ -18,12 +19,12 @@ import ( const ( // ErrGitRefName is git reference name error ErrGitRefName = "GitRefNameError" - // ErrGlobPattern is returned when glob pattern is invalid ErrGlobPattern = "GlobPattern" - // ErrRegexPattern is returned when a regex pattern is invalid ErrRegexPattern = "RegexPattern" + // ErrInvalidGroupTeamMap is returned when a group team mapping is invalid + ErrInvalidGroupTeamMap = "InvalidGroupTeamMap" ) // AddBindingRules adds additional binding rules @@ -34,6 +35,7 @@ func AddBindingRules() { addGlobPatternRule() addRegexPatternRule() addGlobOrRegexPatternRule() + addValidGroupTeamMapRule() } func addGitRefNameBindingRule() { @@ -148,6 +150,23 @@ func addGlobOrRegexPatternRule() { }) } +func addValidGroupTeamMapRule() { + binding.AddRule(&binding.Rule{ + IsMatch: func(rule string) bool { + return strings.HasPrefix(rule, "ValidGroupTeamMap") + }, + IsValid: func(errs binding.Errors, name string, val interface{}) (bool, binding.Errors) { + _, err := auth.UnmarshalGroupTeamMapping(fmt.Sprintf("%v", val)) + if err != nil { + errs.Add([]string{name}, ErrInvalidGroupTeamMap, err.Error()) + return false, errs + } + + return true, errs + }, + }) +} + func portOnly(hostport string) string { colon := strings.IndexByte(hostport, ':') if colon == -1 { diff --git a/modules/web/middleware/binding.go b/modules/web/middleware/binding.go index 636e655b9e956..b437b836243da 100644 --- a/modules/web/middleware/binding.go +++ b/modules/web/middleware/binding.go @@ -135,6 +135,8 @@ func Validate(errs binding.Errors, data map[string]interface{}, f Form, l transl data["ErrorMsg"] = trName + l.Tr("form.glob_pattern_error", errs[0].Message) case validation.ErrRegexPattern: data["ErrorMsg"] = trName + l.Tr("form.regex_pattern_error", errs[0].Message) + case validation.ErrInvalidGroupTeamMap: + data["ErrorMsg"] = trName + l.Tr("form.invalid_group_team_map_error", errs[0].Message) default: msg := errs[0].Classification if msg != "" && errs[0].Message != "" { diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index b22f1bde9b80f..5385d91d3f01f 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -452,6 +452,7 @@ url_error = `'%s' is not a valid URL.` include_error = ` must contain substring '%s'.` glob_pattern_error = ` glob pattern is invalid: %s.` regex_pattern_error = ` regex pattern is invalid: %s.` +invalid_group_team_map_error = ` mapping is invalid: %s` unknown_error = Unknown error: captcha_incorrect = The CAPTCHA code is incorrect. password_not_match = The passwords do not match. diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go index b20aee976dc24..bdd28424286ba 100644 --- a/routers/web/auth/oauth.go +++ b/routers/web/auth/oauth.go @@ -18,6 +18,7 @@ import ( "code.gitea.io/gitea/models/auth" org_model "code.gitea.io/gitea/models/organization" user_model "code.gitea.io/gitea/models/user" + auth_module "code.gitea.io/gitea/modules/auth" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/context" @@ -923,7 +924,7 @@ func SignInOAuthCallback(ctx *context.Context) { } if source.GroupTeamMap != "" || source.GroupTeamMapRemoval { - groupTeamMapping, err := source_service.UnmarshalGroupTeamMapping(source.GroupTeamMap) + groupTeamMapping, err := auth_module.UnmarshalGroupTeamMapping(source.GroupTeamMap) if err != nil { ctx.ServerError("UnmarshalGroupTeamMapping", err) return @@ -1034,7 +1035,7 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model } oauth2Source := source.Cfg.(*oauth2.Source) - groupTeamMapping, err := source_service.UnmarshalGroupTeamMapping(oauth2Source.GroupTeamMap) + groupTeamMapping, err := auth_module.UnmarshalGroupTeamMapping(oauth2Source.GroupTeamMap) if err != nil { ctx.ServerError("UnmarshalGroupTeamMapping", err) return diff --git a/services/auth/source/ldap/source_authenticate.go b/services/auth/source/ldap/source_authenticate.go index 18ecbb4d505a7..3413db274fecd 100644 --- a/services/auth/source/ldap/source_authenticate.go +++ b/services/auth/source/ldap/source_authenticate.go @@ -12,6 +12,7 @@ import ( "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" user_model "code.gitea.io/gitea/models/user" + auth_module "code.gitea.io/gitea/modules/auth" "code.gitea.io/gitea/modules/util" source_service "code.gitea.io/gitea/services/auth/source" "code.gitea.io/gitea/services/mailer" @@ -115,7 +116,7 @@ func (source *Source) Authenticate(user *user_model.User, userName, password str } if source.GroupsEnabled && (source.GroupTeamMap != "" || source.GroupTeamMapRemoval) { - groupTeamMapping, err := source_service.UnmarshalGroupTeamMapping(source.GroupTeamMap) + groupTeamMapping, err := auth_module.UnmarshalGroupTeamMapping(source.GroupTeamMap) if err != nil { return user, err } diff --git a/services/auth/source/ldap/source_sync.go b/services/auth/source/ldap/source_sync.go index c5507fe1ca7a5..f0b6749aab54f 100644 --- a/services/auth/source/ldap/source_sync.go +++ b/services/auth/source/ldap/source_sync.go @@ -14,6 +14,7 @@ import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" user_model "code.gitea.io/gitea/models/user" + auth_module "code.gitea.io/gitea/modules/auth" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/util" source_service "code.gitea.io/gitea/services/auth/source" @@ -67,7 +68,7 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { orgCache := make(map[string]*organization.Organization) teamCache := make(map[string]*organization.Team) - groupTeamMapping, err := source_service.UnmarshalGroupTeamMapping(source.GroupTeamMap) + groupTeamMapping, err := auth_module.UnmarshalGroupTeamMapping(source.GroupTeamMap) if err != nil { return err } diff --git a/services/auth/source/source_group_sync.go b/services/auth/source/source_group_sync.go index 050f7f72fe5de..b8169af36ee43 100644 --- a/services/auth/source/source_group_sync.go +++ b/services/auth/source/source_group_sync.go @@ -12,7 +12,6 @@ import ( "code.gitea.io/gitea/models/organization" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/container" - "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" ) @@ -23,19 +22,6 @@ const ( syncRemove ) -func UnmarshalGroupTeamMapping(raw string) (map[string]map[string][]string, error) { - groupTeamMapping := make(map[string]map[string][]string) - if raw == "" { - return groupTeamMapping, nil - } - err := json.Unmarshal([]byte(raw), &groupTeamMapping) - if err != nil { - log.Error("Failed to unmarshal group team mapping: %v", err) - return nil, err - } - return groupTeamMapping, nil -} - // SyncGroupsToTeams maps authentication source groups to organization and team memberships func SyncGroupsToTeams(ctx context.Context, user *user_model.User, sourceUserGroups container.Set[string], sourceGroupTeamMapping map[string]map[string][]string, performRemoval bool) error { orgCache := make(map[string]*organization.Organization) diff --git a/services/forms/auth_form.go b/services/forms/auth_form.go index d707ce6f1bf97..2feb4b28661f2 100644 --- a/services/forms/auth_form.go +++ b/services/forms/auth_form.go @@ -73,7 +73,7 @@ type AuthenticationForm struct { Oauth2GroupClaimName string Oauth2AdminGroup string Oauth2RestrictedGroup string - Oauth2GroupTeamMap string + Oauth2GroupTeamMap string `binding:"ValidGroupTeamMap"` Oauth2GroupTeamMapRemoval bool SkipLocalTwoFA bool SSPIAutoCreateUsers bool @@ -81,7 +81,7 @@ type AuthenticationForm struct { SSPIStripDomainNames bool SSPISeparatorReplacement string `binding:"AlphaDashDot;MaxSize(5)"` SSPIDefaultLanguage string - GroupTeamMap string + GroupTeamMap string `binding:"ValidGroupTeamMap"` GroupTeamMapRemoval bool } diff --git a/tests/integration/auth_ldap_test.go b/tests/integration/auth_ldap_test.go index 8ee8c37e64400..362c998cf2812 100644 --- a/tests/integration/auth_ldap_test.go +++ b/tests/integration/auth_ldap_test.go @@ -103,16 +103,8 @@ func getLDAPServerHost() string { return host } -func addAuthSourceLDAP(t *testing.T, sshKeyAttribute 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", map[string]string{ +func buildAuthSourceLDAPPayload(csrf, sshKeyAttribute, groupTeamMap, groupTeamMapRemoval string) map[string]string { + return map[string]string{ "_csrf": csrf, "type": "2", "name": "ldap", @@ -137,7 +129,19 @@ func addAuthSourceLDAP(t *testing.T, sshKeyAttribute string, groupMapParams ...s "group_team_map": groupTeamMap, "group_team_map_removal": groupTeamMapRemoval, "user_uid": "DN", - }) + } +} + +func addAuthSourceLDAP(t *testing.T, sshKeyAttribute 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, groupTeamMap, groupTeamMapRemoval)) session.MakeRequest(t, req, http.StatusSeeOther) } @@ -185,26 +189,7 @@ func TestLDAPAuthChange(t *testing.T) { binddn, _ := doc.Find(`input[name="bind_dn"]`).Attr("value") assert.Equal(t, binddn, "uid=gitea,ou=service,dc=planetexpress,dc=com") - req = NewRequestWithValues(t, "POST", href, map[string]string{ - "_csrf": csrf, - "type": "2", - "name": "ldap", - "host": getLDAPServerHost(), - "port": "389", - "bind_dn": "uid=gitea,ou=service,dc=planetexpress,dc=com", - "bind_password": "password", - "user_base": "ou=people,dc=planetexpress,dc=com", - "filter": "(&(objectClass=inetOrgPerson)(memberOf=cn=git,ou=people,dc=planetexpress,dc=com)(uid=%s))", - "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": "mail", - "attribute_ssh_public_key": "", - "is_sync_enabled": "on", - "is_active": "on", - }) + req = NewRequestWithValues(t, "POST", href, buildAuthSourceLDAPPayload(csrf, "", "", "off")) session.MakeRequest(t, req, http.StatusSeeOther) req = NewRequest(t, "GET", href) @@ -392,24 +377,15 @@ func TestLDAPGroupTeamSyncRemoveMember(t *testing.T) { assert.False(t, isMember, "User membership should have been removed from team") } -// Login should work even if Team Group Map contains a broken JSON -func TestBrokenLDAPMapUserSignin(t *testing.T) { +func TestLDAPPreventInvalidGroupTeamMap(t *testing.T) { if skipLDAPTests() { t.Skip() return } defer tests.PrepareTestEnv(t)() - addAuthSourceLDAP(t, "", "on", `{"NOT_A_VALID_JSON"["MISSING_DOUBLE_POINT"]}`) - 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(`label[for="email"]`).Siblings().First().Text()) + 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 } From 0584661906cc395de2bbaeb3bcc3a8ec0bc2ee90 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Thu, 8 Dec 2022 17:56:50 +0000 Subject: [PATCH 6/6] Use new file header. --- modules/auth/common.go | 3 +-- services/auth/middleware.go | 3 +-- services/auth/source/source_group_sync.go | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/modules/auth/common.go b/modules/auth/common.go index ac8d8a20e8aea..77361f6561417 100644 --- a/modules/auth/common.go +++ b/modules/auth/common.go @@ -1,6 +1,5 @@ // Copyright 2022 The Gitea Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. +// SPDX-License-Identifier: MIT package auth diff --git a/services/auth/middleware.go b/services/auth/middleware.go index 907473dc11f60..d4d7391dda6bf 100644 --- a/services/auth/middleware.go +++ b/services/auth/middleware.go @@ -1,6 +1,5 @@ // Copyright 2022 The Gitea Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. +// SPDX-License-Identifier: MIT package auth diff --git a/services/auth/source/source_group_sync.go b/services/auth/source/source_group_sync.go index b8169af36ee43..20b6095345377 100644 --- a/services/auth/source/source_group_sync.go +++ b/services/auth/source/source_group_sync.go @@ -1,6 +1,5 @@ // Copyright 2022 The Gitea Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. +// SPDX-License-Identifier: MIT package source