mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-12-29 20:00:30 +00:00
Fix repo owner filter (#2808)
and move to server config instead of middleware cc @xoxys closes #2784
This commit is contained in:
parent
82877bc967
commit
fd77b2e9d7
11 changed files with 107 additions and 112 deletions
|
@ -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"))
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}{}
|
||||
|
|
|
@ -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
|
||||
}
|
18
server/plugins/permissions/admin.go
Normal file
18
server/plugins/permissions/admin.go
Normal 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]
|
||||
}
|
27
server/plugins/permissions/orgs.go
Normal file
27
server/plugins/permissions/orgs.go
Normal 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
|
||||
}
|
18
server/plugins/permissions/repo_owners.go
Normal file
18
server/plugins/permissions/repo_owners.go
Normal 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]
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue