Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix repo owner filter #2808

Merged
merged 5 commits into from
Nov 12, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion cmd/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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),
)

Expand Down Expand Up @@ -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"))
}
14 changes: 6 additions & 8 deletions server/api/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand All @@ -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
// 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
Expand Down Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions server/api/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
7 changes: 5 additions & 2 deletions server/api/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
}
Expand Down
7 changes: 7 additions & 0 deletions server/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -96,4 +97,10 @@ var Config = struct {
HTTPS string
}
}
Permissions struct {
Open bool
Admins *permissions.Admins
Orgs *permissions.Orgs
OwnersWhitelist *permissions.OwnersWhitelist
}
}{}
38 changes: 0 additions & 38 deletions server/model/settings.go

This file was deleted.

18 changes: 18 additions & 0 deletions server/plugins/permissions/admin.go
Original file line number Diff line number Diff line change
@@ -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 changes: 27 additions & 0 deletions server/plugins/permissions/orgs.go
Original file line number Diff line number Diff line change
@@ -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 changes: 18 additions & 0 deletions server/plugins/permissions/repo_owners.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package permissions

import (
"go.woodpecker-ci.org/woodpecker/server/model"
"go.woodpecker-ci.org/woodpecker/shared/utils"
)

func NewOwnersWhitelist(owners []string) *OwnersWhitelist {
return &OwnersWhitelist{owners: utils.SliceToBoolMap(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]
}
61 changes: 0 additions & 61 deletions server/router/middleware/config.go

This file was deleted.

12 changes: 12 additions & 0 deletions shared/utils/slices.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}