From c4f8421ecea4326b38af60bf79f2bc9575a5c4ff Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Sat, 11 Nov 2023 15:11:20 +0100 Subject: [PATCH 1/4] Fix repo owner filter --- cmd/server/server.go | 8 ++- server/api/login.go | 14 +++--- server/api/repo.go | 5 ++ server/api/user.go | 7 ++- server/config.go | 7 +++ server/model/settings.go | 38 -------------- server/plugins/permissions/admin.go | 15 ++++++ server/plugins/permissions/orgs.go | 24 +++++++++ server/plugins/permissions/repo_owners.go | 15 ++++++ server/plugins/permissions/util.go | 13 +++++ server/router/middleware/config.go | 61 ----------------------- 11 files changed, 97 insertions(+), 110 deletions(-) delete mode 100644 server/model/settings.go create mode 100644 server/plugins/permissions/admin.go create mode 100644 server/plugins/permissions/orgs.go create mode 100644 server/plugins/permissions/repo_owners.go create mode 100644 server/plugins/permissions/util.go delete mode 100644 server/router/middleware/config.go diff --git a/cmd/server/server.go b/cmd/server/server.go index 26887c3495..ca5181bed2 100644 --- a/cmd/server/server.go +++ b/cmd/server/server.go @@ -43,6 +43,7 @@ import ( "go.woodpecker-ci.org/woodpecker/server/logging" "go.woodpecker-ci.org/woodpecker/server/model" "go.woodpecker-ci.org/woodpecker/server/plugins/config" + "go.woodpecker-ci.org/woodpecker/server/plugins/permissions" "go.woodpecker-ci.org/woodpecker/server/pubsub" "go.woodpecker-ci.org/woodpecker/server/router" "go.woodpecker-ci.org/woodpecker/server/router/middleware" @@ -177,7 +178,6 @@ func run(c *cli.Context) error { webUIServe, middleware.Logger(time.RFC3339, true), middleware.Version, - middleware.Config(c), middleware.Store(c, _store), ) @@ -367,4 +367,10 @@ func setupEvilGlobals(c *cli.Context, v store.Store, f forge.Forge) { // prometheus server.Config.Prometheus.AuthToken = c.String("prometheus-auth-token") + + // permissions + server.Config.Permissions.Open = c.Bool("open") + server.Config.Permissions.Admins = permissions.NewAdmins(c.StringSlice("admin")) + server.Config.Permissions.Orgs = permissions.NewOrgs(c.StringSlice("orgs")) + server.Config.Permissions.OwnersWhitelist = permissions.NewOwnersWhitelist(c.StringSlice("repo-owners")) } diff --git a/server/api/login.go b/server/api/login.go index 77b3acbb72..1c43c41a06 100644 --- a/server/api/login.go +++ b/server/api/login.go @@ -26,7 +26,6 @@ import ( "go.woodpecker-ci.org/woodpecker/server" "go.woodpecker-ci.org/woodpecker/server/model" - "go.woodpecker-ci.org/woodpecker/server/router/middleware" "go.woodpecker-ci.org/woodpecker/server/store" "go.woodpecker-ci.org/woodpecker/server/store/types" "go.woodpecker-ci.org/woodpecker/shared/httputil" @@ -60,7 +59,6 @@ func HandleAuth(c *gin.Context) { if tmpuser == nil { return } - config := middleware.GetConfig(c) // get the user from the database u, err := _store.GetUserRemoteID(tmpuser.ForgeRemoteID, tmpuser.Login) @@ -71,7 +69,7 @@ func HandleAuth(c *gin.Context) { } // if self-registration is disabled we should return a not authorized error - if !config.Open && !config.IsAdmin(tmpuser) { + if !server.Config.Permissions.Open && !server.Config.Permissions.Admins.IsAdmin(tmpuser) { log.Error().Msgf("cannot register %s. registration closed", tmpuser.Login) c.Redirect(http.StatusSeeOther, server.Config.Server.RootPath+"/login?error=access_denied") return @@ -79,9 +77,9 @@ func HandleAuth(c *gin.Context) { // if self-registration is enabled for whitelisted organizations we need to // check the user's organization membership. - if len(config.Orgs) != 0 { + if server.Config.Permissions.Orgs.IsConfigured { teams, terr := _forge.Teams(c, tmpuser) - if terr != nil || !config.IsMember(teams) { + if terr != nil || !server.Config.Permissions.Orgs.IsMember(teams) { log.Error().Err(terr).Msgf("cannot verify team membership for %s.", u.Login) c.Redirect(303, server.Config.Server.RootPath+"/login?error=access_denied") return @@ -125,13 +123,13 @@ func HandleAuth(c *gin.Context) { u.Avatar = tmpuser.Avatar u.ForgeRemoteID = tmpuser.ForgeRemoteID u.Login = tmpuser.Login - u.Admin = u.Admin || config.IsAdmin(tmpuser) + u.Admin = u.Admin || server.Config.Permissions.Admins.IsAdmin(tmpuser) // if self-registration is enabled for whitelisted organizations we need to // check the user's organization membership. - if len(config.Orgs) != 0 { + if server.Config.Permissions.Orgs.IsConfigured { teams, terr := _forge.Teams(c, u) - if terr != nil || !config.IsMember(teams) { + if terr != nil || !server.Config.Permissions.Orgs.IsMember(teams) { log.Error().Err(terr).Msgf("cannot verify team membership for %s.", u.Login) c.Redirect(http.StatusSeeOther, server.Config.Server.RootPath+"/login?error=access_denied") return diff --git a/server/api/repo.go b/server/api/repo.go index 4530b54e0f..667acad378 100644 --- a/server/api/repo.go +++ b/server/api/repo.go @@ -72,6 +72,11 @@ func PostRepo(c *gin.Context) { } if !from.Perm.Admin { c.String(http.StatusForbidden, "User has to be a admin of this repository") + return + } + if !server.Config.Permissions.OwnersWhitelist.IsAllowed(from) { + c.String(http.StatusForbidden, "Repo owner is not allowed") + return } if enabledOnce { diff --git a/server/api/user.go b/server/api/user.go index c39014d7c7..5904ef7dba 100644 --- a/server/api/user.go +++ b/server/api/user.go @@ -111,14 +111,17 @@ func GetRepos(c *gin.Context) { var repos []*model.Repo for _, r := range _repos { - if r.Perm.Push { + if r.Perm.Push && server.Config.Permissions.OwnersWhitelist.IsAllowed(r) { if active[r.ForgeRemoteID] != nil { existingRepo := active[r.ForgeRemoteID] existingRepo.Update(r) existingRepo.IsActive = active[r.ForgeRemoteID].IsActive repos = append(repos, existingRepo) } else { - repos = append(repos, r) + if r.Perm.Admin { + // you must be admin to enable the repo + repos = append(repos, r) + } } } } diff --git a/server/config.go b/server/config.go index 9dc7a69639..5fe5cb2e16 100644 --- a/server/config.go +++ b/server/config.go @@ -26,6 +26,7 @@ import ( "go.woodpecker-ci.org/woodpecker/server/logging" "go.woodpecker-ci.org/woodpecker/server/model" "go.woodpecker-ci.org/woodpecker/server/plugins/config" + "go.woodpecker-ci.org/woodpecker/server/plugins/permissions" "go.woodpecker-ci.org/woodpecker/server/pubsub" "go.woodpecker-ci.org/woodpecker/server/queue" ) @@ -96,4 +97,10 @@ var Config = struct { HTTPS string } } + Permissions struct { + Open bool + Admins *permissions.Admins + Orgs *permissions.Orgs + OwnersWhitelist *permissions.OwnersWhitelist + } }{} diff --git a/server/model/settings.go b/server/model/settings.go deleted file mode 100644 index d82cbf7ae0..0000000000 --- a/server/model/settings.go +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2018 Drone.IO Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package model - -// Settings defines system configuration parameters. -type Settings struct { - Open bool // Enables open registration - Admins map[string]bool // Administrative users - Orgs map[string]bool // Organization whitelist - OwnersWhitelist map[string]bool // Owners whitelist -} - -// IsAdmin returns true if the user is a member of the administrator list. -func (c *Settings) IsAdmin(user *User) bool { - return c.Admins[user.Login] -} - -// IsMember returns true if the user is a member of the whitelisted teams. -func (c *Settings) IsMember(teams []*Team) bool { - for _, team := range teams { - if c.Orgs[team.Login] { - return true - } - } - return false -} diff --git a/server/plugins/permissions/admin.go b/server/plugins/permissions/admin.go new file mode 100644 index 0000000000..7c40050599 --- /dev/null +++ b/server/plugins/permissions/admin.go @@ -0,0 +1,15 @@ +package permissions + +import "go.woodpecker-ci.org/woodpecker/server/model" + +func NewAdmins(admins []string) *Admins { + return &Admins{admins: sliceToMap(admins)} +} + +type Admins struct { + admins map[string]bool +} + +func (a *Admins) IsAdmin(user *model.User) bool { + return a.admins[user.Login] +} diff --git a/server/plugins/permissions/orgs.go b/server/plugins/permissions/orgs.go new file mode 100644 index 0000000000..54ab2b0665 --- /dev/null +++ b/server/plugins/permissions/orgs.go @@ -0,0 +1,24 @@ +package permissions + +import "go.woodpecker-ci.org/woodpecker/server/model" + +func NewOrgs(orgs []string) *Orgs { + return &Orgs{ + IsConfigured: len(orgs) > 0, + orgs: sliceToMap(orgs), + } +} + +type Orgs struct { + IsConfigured bool + orgs map[string]bool +} + +func (o *Orgs) IsMember(teams []*model.Team) bool { + for _, team := range teams { + if o.orgs[team.Login] { + return true + } + } + return false +} diff --git a/server/plugins/permissions/repo_owners.go b/server/plugins/permissions/repo_owners.go new file mode 100644 index 0000000000..73aeb2f106 --- /dev/null +++ b/server/plugins/permissions/repo_owners.go @@ -0,0 +1,15 @@ +package permissions + +import "go.woodpecker-ci.org/woodpecker/server/model" + +func NewOwnersWhitelist(owners []string) *OwnersWhitelist { + return &OwnersWhitelist{owners: sliceToMap(owners)} +} + +type OwnersWhitelist struct { + owners map[string]bool +} + +func (o *OwnersWhitelist) IsAllowed(repo *model.Repo) bool { + return len(o.owners) > 0 && o.owners[repo.Owner] +} diff --git a/server/plugins/permissions/util.go b/server/plugins/permissions/util.go new file mode 100644 index 0000000000..60030c3baa --- /dev/null +++ b/server/plugins/permissions/util.go @@ -0,0 +1,13 @@ +package permissions + +// sliceToMap is a helper function to convert a string slice to a map. +func sliceToMap(s []string) map[string]bool { + v := map[string]bool{} + for _, ss := range s { + if ss == "" { + continue + } + v[ss] = true + } + return v +} diff --git a/server/router/middleware/config.go b/server/router/middleware/config.go deleted file mode 100644 index 07b7b2419f..0000000000 --- a/server/router/middleware/config.go +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2018 Drone.IO Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package middleware - -import ( - "github.com/gin-gonic/gin" - "github.com/urfave/cli/v2" - - "go.woodpecker-ci.org/woodpecker/server/model" -) - -const configKey = "config" - -// Config is a middleware function that initializes the Configuration and -// attaches to the context of every http.Request. -func Config(cli *cli.Context) gin.HandlerFunc { - v := setupConfig(cli) - return func(c *gin.Context) { - c.Set(configKey, v) - } -} - -// helper function to create the configuration from the CLI context. -func setupConfig(c *cli.Context) *model.Settings { - return &model.Settings{ - Open: c.Bool("open"), - Admins: sliceToMap2(c.StringSlice("admin")), - Orgs: sliceToMap2(c.StringSlice("orgs")), - OwnersWhitelist: sliceToMap2(c.StringSlice("repo-owners")), - } -} - -// helper function to convert a string slice to a map. -func sliceToMap2(s []string) map[string]bool { - v := map[string]bool{} - for _, ss := range s { - if ss == "" { - continue - } - v[ss] = true - } - return v -} - -// GetConfig returns the config from the Context -func GetConfig(c *gin.Context) *model.Settings { - v := c.MustGet(configKey) - return v.(*model.Settings) -} From bd91968ea972c197796045d073722afc26b7845c Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Sun, 12 Nov 2023 09:39:20 +0100 Subject: [PATCH 2/4] Move to shared --- server/plugins/permissions/admin.go | 7 +++++-- server/plugins/permissions/orgs.go | 7 +++++-- server/plugins/permissions/repo_owners.go | 7 +++++-- server/plugins/permissions/util.go | 13 ------------- shared/utils/slices.go | 12 ++++++++++++ 5 files changed, 27 insertions(+), 19 deletions(-) delete mode 100644 server/plugins/permissions/util.go diff --git a/server/plugins/permissions/admin.go b/server/plugins/permissions/admin.go index 7c40050599..1156ddef02 100644 --- a/server/plugins/permissions/admin.go +++ b/server/plugins/permissions/admin.go @@ -1,9 +1,12 @@ package permissions -import "go.woodpecker-ci.org/woodpecker/server/model" +import ( + "go.woodpecker-ci.org/woodpecker/server/model" + "go.woodpecker-ci.org/woodpecker/shared/utils" +) func NewAdmins(admins []string) *Admins { - return &Admins{admins: sliceToMap(admins)} + return &Admins{admins: utils.SliceToBoolMap(admins)} } type Admins struct { diff --git a/server/plugins/permissions/orgs.go b/server/plugins/permissions/orgs.go index 54ab2b0665..b950db9d7c 100644 --- a/server/plugins/permissions/orgs.go +++ b/server/plugins/permissions/orgs.go @@ -1,11 +1,14 @@ package permissions -import "go.woodpecker-ci.org/woodpecker/server/model" +import ( + "go.woodpecker-ci.org/woodpecker/server/model" + "go.woodpecker-ci.org/woodpecker/shared/utils" +) func NewOrgs(orgs []string) *Orgs { return &Orgs{ IsConfigured: len(orgs) > 0, - orgs: sliceToMap(orgs), + orgs: utils.SliceToBoolMap(orgs), } } diff --git a/server/plugins/permissions/repo_owners.go b/server/plugins/permissions/repo_owners.go index 73aeb2f106..0b092f1ff8 100644 --- a/server/plugins/permissions/repo_owners.go +++ b/server/plugins/permissions/repo_owners.go @@ -1,9 +1,12 @@ package permissions -import "go.woodpecker-ci.org/woodpecker/server/model" +import ( + "go.woodpecker-ci.org/woodpecker/server/model" + "go.woodpecker-ci.org/woodpecker/shared/utils" +) func NewOwnersWhitelist(owners []string) *OwnersWhitelist { - return &OwnersWhitelist{owners: sliceToMap(owners)} + return &OwnersWhitelist{owners: utils.SliceToBoolMap(owners)} } type OwnersWhitelist struct { diff --git a/server/plugins/permissions/util.go b/server/plugins/permissions/util.go deleted file mode 100644 index 60030c3baa..0000000000 --- a/server/plugins/permissions/util.go +++ /dev/null @@ -1,13 +0,0 @@ -package permissions - -// sliceToMap is a helper function to convert a string slice to a map. -func sliceToMap(s []string) map[string]bool { - v := map[string]bool{} - for _, ss := range s { - if ss == "" { - continue - } - v[ss] = true - } - return v -} diff --git a/shared/utils/slices.go b/shared/utils/slices.go index 5aa6a832da..b2f64eb8b8 100644 --- a/shared/utils/slices.go +++ b/shared/utils/slices.go @@ -57,3 +57,15 @@ func sliceToCountMap[E comparable](list []E) map[E]int { } return m } + +// sliceToMap is a helper function to convert a string slice to a map. +func SliceToBoolMap(s []string) map[string]bool { + v := map[string]bool{} + for _, ss := range s { + if ss == "" { + continue + } + v[ss] = true + } + return v +} From e8923e8c56d57637e7a2b911280e78e4bcd51973 Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Sun, 12 Nov 2023 09:44:42 +0100 Subject: [PATCH 3/4] rename to allowlist --- cmd/server/server.go | 2 +- server/api/repo.go | 2 +- server/api/user.go | 2 +- server/config.go | 2 +- server/plugins/permissions/repo_owners.go | 8 ++++---- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cmd/server/server.go b/cmd/server/server.go index ca5181bed2..fae4f9b217 100644 --- a/cmd/server/server.go +++ b/cmd/server/server.go @@ -372,5 +372,5 @@ func setupEvilGlobals(c *cli.Context, v store.Store, f forge.Forge) { server.Config.Permissions.Open = c.Bool("open") server.Config.Permissions.Admins = permissions.NewAdmins(c.StringSlice("admin")) server.Config.Permissions.Orgs = permissions.NewOrgs(c.StringSlice("orgs")) - server.Config.Permissions.OwnersWhitelist = permissions.NewOwnersWhitelist(c.StringSlice("repo-owners")) + server.Config.Permissions.OwnersAllowlist = permissions.NewOwnersAllowlist(c.StringSlice("repo-owners")) } diff --git a/server/api/repo.go b/server/api/repo.go index 667acad378..9d7d4f3eb9 100644 --- a/server/api/repo.go +++ b/server/api/repo.go @@ -74,7 +74,7 @@ func PostRepo(c *gin.Context) { c.String(http.StatusForbidden, "User has to be a admin of this repository") return } - if !server.Config.Permissions.OwnersWhitelist.IsAllowed(from) { + if !server.Config.Permissions.OwnersAllowlist.IsAllowed(from) { c.String(http.StatusForbidden, "Repo owner is not allowed") return } diff --git a/server/api/user.go b/server/api/user.go index 5904ef7dba..8c85df89d1 100644 --- a/server/api/user.go +++ b/server/api/user.go @@ -111,7 +111,7 @@ func GetRepos(c *gin.Context) { var repos []*model.Repo for _, r := range _repos { - if r.Perm.Push && server.Config.Permissions.OwnersWhitelist.IsAllowed(r) { + if r.Perm.Push && server.Config.Permissions.OwnersAllowlist.IsAllowed(r) { if active[r.ForgeRemoteID] != nil { existingRepo := active[r.ForgeRemoteID] existingRepo.Update(r) diff --git a/server/config.go b/server/config.go index 5fe5cb2e16..af381b7a99 100644 --- a/server/config.go +++ b/server/config.go @@ -101,6 +101,6 @@ var Config = struct { Open bool Admins *permissions.Admins Orgs *permissions.Orgs - OwnersWhitelist *permissions.OwnersWhitelist + OwnersAllowlist *permissions.OwnersAllowlist } }{} diff --git a/server/plugins/permissions/repo_owners.go b/server/plugins/permissions/repo_owners.go index 0b092f1ff8..a26f543420 100644 --- a/server/plugins/permissions/repo_owners.go +++ b/server/plugins/permissions/repo_owners.go @@ -5,14 +5,14 @@ import ( "go.woodpecker-ci.org/woodpecker/shared/utils" ) -func NewOwnersWhitelist(owners []string) *OwnersWhitelist { - return &OwnersWhitelist{owners: utils.SliceToBoolMap(owners)} +func NewOwnersAllowlist(owners []string) *OwnersAllowlist { + return &OwnersAllowlist{owners: utils.SliceToBoolMap(owners)} } -type OwnersWhitelist struct { +type OwnersAllowlist struct { owners map[string]bool } -func (o *OwnersWhitelist) IsAllowed(repo *model.Repo) bool { +func (o *OwnersAllowlist) IsAllowed(repo *model.Repo) bool { return len(o.owners) > 0 && o.owners[repo.Owner] } From 80195958a6ba57e12833c7d6cbbd2523768f4c30 Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Sun, 12 Nov 2023 09:45:25 +0100 Subject: [PATCH 4/4] fix comments --- server/api/login.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/api/login.go b/server/api/login.go index 1c43c41a06..ae95e67c4c 100644 --- a/server/api/login.go +++ b/server/api/login.go @@ -75,7 +75,7 @@ func HandleAuth(c *gin.Context) { return } - // if self-registration is enabled for whitelisted organizations we need to + // if self-registration is enabled for allowed organizations we need to // check the user's organization membership. if server.Config.Permissions.Orgs.IsConfigured { teams, terr := _forge.Teams(c, tmpuser) @@ -125,7 +125,7 @@ func HandleAuth(c *gin.Context) { u.Login = tmpuser.Login u.Admin = u.Admin || server.Config.Permissions.Admins.IsAdmin(tmpuser) - // if self-registration is enabled for whitelisted organizations we need to + // if self-registration is enabled for allowed organizations we need to // check the user's organization membership. if server.Config.Permissions.Orgs.IsConfigured { teams, terr := _forge.Teams(c, u)