Files
portainer/api/dataservices/source/access_rules.go
T
2026-06-22 20:38:21 -03:00

134 lines
2.9 KiB
Go

package source
import (
"errors"
"slices"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/set"
"github.com/portainer/portainer/api/slicesx"
)
var (
ErrInvalidSource = errors.New("invalid source")
ErrInvalidUserContext = errors.New("invalid user context")
ErrNotEnoughPermission = errors.New("not enough permissions to perform this action")
ErrDuplicateSource = errors.New("a source with this URL and credentials already exists")
)
func validateUserContext(ctx *userContext) error {
if ctx == nil || ctx.User == nil {
return ErrInvalidUserContext
}
return nil
}
type actionType string
const (
actionRead actionType = "read"
actionWrite actionType = "write"
)
func enforceUserPermissions(ctx *userContext, source *portainer.Source, action actionType) error {
if action == actionRead && userCanReadSource(source, ctx) {
return nil
}
if action == actionWrite && userCanWriteSource(source, ctx) {
return nil
}
return ErrNotEnoughPermission
}
func userCanWriteSource(source *portainer.Source, context *userContext) bool {
if source == nil || context == nil || context.User == nil {
return false
}
user := context.User
if user.Role == portainer.AdministratorRole {
return true
}
if source.OwnerID != 0 && source.OwnerID == user.ID && userCanReadSource(source, context) {
return true
}
return false
}
func filterSources(sources []portainer.Source, context *userContext) []portainer.Source {
return slicesx.Filter(sources, func(s portainer.Source) bool {
return userCanReadSource(&s, context)
})
}
func userCanReadSource(source *portainer.Source, context *userContext) bool {
if source == nil || context == nil || context.User == nil {
return false
}
user := context.User
userTeams := context.UserMemberships
if user.Role == portainer.AdministratorRole || source.Public {
return true
}
if source.AdministratorsOnly {
return false
}
if slices.Contains(source.UserAccesses, user.ID) {
return true
}
if len(userTeams) == 0 || len(source.TeamAccesses) == 0 {
return false
}
sTeams := set.ToSet(source.TeamAccesses)
uTeams := set.ToSet(slicesx.Map(userTeams, func(u portainer.TeamMembership) portainer.TeamID { return u.TeamID }))
return set.Intersection(sTeams, uTeams).Len() != 0
}
// enforceUniqueGitSource validates there are no other git sources with the same URL and credentials
// It ignores itself
func enforceUniqueGitSource(tx ServiceTx, src *portainer.Source) error {
if src.Type != portainer.SourceTypeGit || src.Git == nil {
return nil
}
normalized, err := normalizeGitSource(src)
if err != nil {
return err
}
existing, err := tx.base.ReadAll(func(s portainer.Source) bool {
if src.ID == s.ID {
return false
}
n, err := normalizeGitSource(&s)
if err != nil {
return false
}
return normalized.Equal(n)
})
if err != nil {
return err
}
if len(existing) > 0 {
return ErrDuplicateSource
}
return nil
}