mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2025-06-06 01:18:50 +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/logging"
|
||||||
"go.woodpecker-ci.org/woodpecker/server/model"
|
"go.woodpecker-ci.org/woodpecker/server/model"
|
||||||
"go.woodpecker-ci.org/woodpecker/server/plugins/config"
|
"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/pubsub"
|
||||||
"go.woodpecker-ci.org/woodpecker/server/router"
|
"go.woodpecker-ci.org/woodpecker/server/router"
|
||||||
"go.woodpecker-ci.org/woodpecker/server/router/middleware"
|
"go.woodpecker-ci.org/woodpecker/server/router/middleware"
|
||||||
|
@ -177,7 +178,6 @@ func run(c *cli.Context) error {
|
||||||
webUIServe,
|
webUIServe,
|
||||||
middleware.Logger(time.RFC3339, true),
|
middleware.Logger(time.RFC3339, true),
|
||||||
middleware.Version,
|
middleware.Version,
|
||||||
middleware.Config(c),
|
|
||||||
middleware.Store(c, _store),
|
middleware.Store(c, _store),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -367,4 +367,10 @@ func setupEvilGlobals(c *cli.Context, v store.Store, f forge.Forge) {
|
||||||
|
|
||||||
// prometheus
|
// prometheus
|
||||||
server.Config.Prometheus.AuthToken = c.String("prometheus-auth-token")
|
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"
|
||||||
"go.woodpecker-ci.org/woodpecker/server/model"
|
"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"
|
||||||
"go.woodpecker-ci.org/woodpecker/server/store/types"
|
"go.woodpecker-ci.org/woodpecker/server/store/types"
|
||||||
"go.woodpecker-ci.org/woodpecker/shared/httputil"
|
"go.woodpecker-ci.org/woodpecker/shared/httputil"
|
||||||
|
@ -60,7 +59,6 @@ func HandleAuth(c *gin.Context) {
|
||||||
if tmpuser == nil {
|
if tmpuser == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
config := middleware.GetConfig(c)
|
|
||||||
|
|
||||||
// get the user from the database
|
// get the user from the database
|
||||||
u, err := _store.GetUserRemoteID(tmpuser.ForgeRemoteID, tmpuser.Login)
|
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 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)
|
log.Error().Msgf("cannot register %s. registration closed", tmpuser.Login)
|
||||||
c.Redirect(http.StatusSeeOther, server.Config.Server.RootPath+"/login?error=access_denied")
|
c.Redirect(http.StatusSeeOther, server.Config.Server.RootPath+"/login?error=access_denied")
|
||||||
return
|
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.
|
// check the user's organization membership.
|
||||||
if len(config.Orgs) != 0 {
|
if server.Config.Permissions.Orgs.IsConfigured {
|
||||||
teams, terr := _forge.Teams(c, tmpuser)
|
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)
|
log.Error().Err(terr).Msgf("cannot verify team membership for %s.", u.Login)
|
||||||
c.Redirect(303, server.Config.Server.RootPath+"/login?error=access_denied")
|
c.Redirect(303, server.Config.Server.RootPath+"/login?error=access_denied")
|
||||||
return
|
return
|
||||||
|
@ -125,13 +123,13 @@ func HandleAuth(c *gin.Context) {
|
||||||
u.Avatar = tmpuser.Avatar
|
u.Avatar = tmpuser.Avatar
|
||||||
u.ForgeRemoteID = tmpuser.ForgeRemoteID
|
u.ForgeRemoteID = tmpuser.ForgeRemoteID
|
||||||
u.Login = tmpuser.Login
|
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.
|
// check the user's organization membership.
|
||||||
if len(config.Orgs) != 0 {
|
if server.Config.Permissions.Orgs.IsConfigured {
|
||||||
teams, terr := _forge.Teams(c, u)
|
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)
|
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")
|
c.Redirect(http.StatusSeeOther, server.Config.Server.RootPath+"/login?error=access_denied")
|
||||||
return
|
return
|
||||||
|
|
|
@ -72,6 +72,11 @@ func PostRepo(c *gin.Context) {
|
||||||
}
|
}
|
||||||
if !from.Perm.Admin {
|
if !from.Perm.Admin {
|
||||||
c.String(http.StatusForbidden, "User has to be a admin of this repository")
|
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 {
|
if enabledOnce {
|
||||||
|
|
|
@ -111,17 +111,20 @@ func GetRepos(c *gin.Context) {
|
||||||
|
|
||||||
var repos []*model.Repo
|
var repos []*model.Repo
|
||||||
for _, r := range _repos {
|
for _, r := range _repos {
|
||||||
if r.Perm.Push {
|
if r.Perm.Push && server.Config.Permissions.OwnersAllowlist.IsAllowed(r) {
|
||||||
if active[r.ForgeRemoteID] != nil {
|
if active[r.ForgeRemoteID] != nil {
|
||||||
existingRepo := active[r.ForgeRemoteID]
|
existingRepo := active[r.ForgeRemoteID]
|
||||||
existingRepo.Update(r)
|
existingRepo.Update(r)
|
||||||
existingRepo.IsActive = active[r.ForgeRemoteID].IsActive
|
existingRepo.IsActive = active[r.ForgeRemoteID].IsActive
|
||||||
repos = append(repos, existingRepo)
|
repos = append(repos, existingRepo)
|
||||||
} else {
|
} else {
|
||||||
|
if r.Perm.Admin {
|
||||||
|
// you must be admin to enable the repo
|
||||||
repos = append(repos, r)
|
repos = append(repos, r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, repos)
|
c.JSON(http.StatusOK, repos)
|
||||||
return
|
return
|
||||||
|
|
|
@ -26,6 +26,7 @@ import (
|
||||||
"go.woodpecker-ci.org/woodpecker/server/logging"
|
"go.woodpecker-ci.org/woodpecker/server/logging"
|
||||||
"go.woodpecker-ci.org/woodpecker/server/model"
|
"go.woodpecker-ci.org/woodpecker/server/model"
|
||||||
"go.woodpecker-ci.org/woodpecker/server/plugins/config"
|
"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/pubsub"
|
||||||
"go.woodpecker-ci.org/woodpecker/server/queue"
|
"go.woodpecker-ci.org/woodpecker/server/queue"
|
||||||
)
|
)
|
||||||
|
@ -96,4 +97,10 @@ var Config = struct {
|
||||||
HTTPS string
|
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
|
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