Fix repo owner filter (#2808)

and move to server config instead of middleware

cc @xoxys 

closes #2784
This commit is contained in:
qwerty287 2023-11-12 14:39:41 +01:00 committed by GitHub
parent 82877bc967
commit fd77b2e9d7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 107 additions and 112 deletions

View file

@ -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.OwnersAllowlist = permissions.NewOwnersAllowlist(c.StringSlice("repo-owners"))
}

View file

@ -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,17 +69,17 @@ 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
}
// 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 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
// if self-registration is enabled for allowed 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

View file

@ -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.OwnersAllowlist.IsAllowed(from) {
c.String(http.StatusForbidden, "Repo owner is not allowed")
return
}
if enabledOnce {

View file

@ -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.OwnersAllowlist.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)
}
}
}
}

View file

@ -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
OwnersAllowlist *permissions.OwnersAllowlist
}
}{}

View file

@ -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
}

View file

@ -0,0 +1,18 @@
package permissions
import (
"go.woodpecker-ci.org/woodpecker/server/model"
"go.woodpecker-ci.org/woodpecker/shared/utils"
)
func NewAdmins(admins []string) *Admins {
return &Admins{admins: utils.SliceToBoolMap(admins)}
}
type Admins struct {
admins map[string]bool
}
func (a *Admins) IsAdmin(user *model.User) bool {
return a.admins[user.Login]
}

View file

@ -0,0 +1,27 @@
package permissions
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: utils.SliceToBoolMap(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
}

View file

@ -0,0 +1,18 @@
package permissions
import (
"go.woodpecker-ci.org/woodpecker/server/model"
"go.woodpecker-ci.org/woodpecker/shared/utils"
)
func NewOwnersAllowlist(owners []string) *OwnersAllowlist {
return &OwnersAllowlist{owners: utils.SliceToBoolMap(owners)}
}
type OwnersAllowlist struct {
owners map[string]bool
}
func (o *OwnersAllowlist) IsAllowed(repo *model.Repo) bool {
return len(o.owners) > 0 && o.owners[repo.Owner]
}

View file

@ -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)
}

View file

@ -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
}