chore(internal): export endpoints and authorizations so they can be shared between CE and EE BE-12893 (#2464)

This commit is contained in:
andres-portainer
2026-05-11 10:44:09 -03:00
committed by GitHub
parent c0ac6c56ac
commit 62f4d47ee5
4 changed files with 215 additions and 194 deletions
+11 -147
View File
@@ -1,122 +1,32 @@
package authorization
import (
"slices"
"strconv"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/slicesx"
"github.com/portainer/portainer/api/stacks/stackutils"
"github.com/portainer/portainer/pkg/authorization"
)
// NewAdministratorsOnlyResourceControl will create a new administrators only resource control associated to the resource specified by the
// identifier and type parameters.
func NewAdministratorsOnlyResourceControl(resourceIdentifier string, resourceType portainer.ResourceControlType) *portainer.ResourceControl {
return &portainer.ResourceControl{
Type: resourceType,
ResourceID: resourceIdentifier,
SubResourceIDs: []string{},
UserAccesses: []portainer.UserResourceAccess{},
TeamAccesses: []portainer.TeamResourceAccess{},
AdministratorsOnly: true,
Public: false,
System: false,
}
}
// NewPrivateResourceControl will create a new private resource control associated to the resource specified by the
// identifier and type parameters. It automatically assigns it to the user specified by the userID parameter.
func NewPrivateResourceControl(resourceIdentifier string, resourceType portainer.ResourceControlType, userID portainer.UserID) *portainer.ResourceControl {
return &portainer.ResourceControl{
Type: resourceType,
ResourceID: resourceIdentifier,
SubResourceIDs: []string{},
UserAccesses: []portainer.UserResourceAccess{
{
UserID: userID,
AccessLevel: portainer.ReadWriteAccessLevel,
},
},
TeamAccesses: []portainer.TeamResourceAccess{},
AdministratorsOnly: false,
Public: false,
System: false,
}
}
// NewSystemResourceControl will create a new public resource control with the System flag set to true.
// These kind of resource control are not persisted and are created on the fly by the Portainer API.
func NewSystemResourceControl(resourceIdentifier string, resourceType portainer.ResourceControlType) *portainer.ResourceControl {
return &portainer.ResourceControl{
Type: resourceType,
ResourceID: resourceIdentifier,
SubResourceIDs: []string{},
UserAccesses: []portainer.UserResourceAccess{},
TeamAccesses: []portainer.TeamResourceAccess{},
AdministratorsOnly: false,
Public: true,
System: true,
}
}
// NewPublicResourceControl will create a new public resource control.
func NewPublicResourceControl(resourceIdentifier string, resourceType portainer.ResourceControlType) *portainer.ResourceControl {
return &portainer.ResourceControl{
Type: resourceType,
ResourceID: resourceIdentifier,
SubResourceIDs: []string{},
UserAccesses: []portainer.UserResourceAccess{},
TeamAccesses: []portainer.TeamResourceAccess{},
AdministratorsOnly: false,
Public: true,
System: false,
}
}
var (
NewAdministratorsOnlyResourceControl = authorization.NewAdministratorsOnlyResourceControl
NewPrivateResourceControl = authorization.NewPrivateResourceControl
NewSystemResourceControl = authorization.NewSystemResourceControl
NewPublicResourceControl = authorization.NewPublicResourceControl
NewRestrictedResourceControl = authorization.NewRestrictedResourceControl
UserCanAccessResource = authorization.UserCanAccessResource
GetResourceControlByResourceIDAndType = authorization.GetResourceControlByResourceIDAndType
TeamIDs = authorization.TeamIDs
)
func NewEmptyRestrictedResourceControl(resourceIdentifier string, resourceType portainer.ResourceControlType) *portainer.ResourceControl {
return NewRestrictedResourceControl(resourceIdentifier, resourceType, []portainer.UserID{}, []portainer.TeamID{})
}
// NewRestrictedResourceControl will create a new resource control with user and team accesses restrictions.
func NewRestrictedResourceControl(resourceIdentifier string, resourceType portainer.ResourceControlType, userIDs []portainer.UserID, teamIDs []portainer.TeamID) *portainer.ResourceControl {
userAccesses := make([]portainer.UserResourceAccess, 0)
teamAccesses := make([]portainer.TeamResourceAccess, 0)
for _, id := range userIDs {
access := portainer.UserResourceAccess{
UserID: id,
AccessLevel: portainer.ReadWriteAccessLevel,
}
userAccesses = append(userAccesses, access)
}
for _, id := range teamIDs {
access := portainer.TeamResourceAccess{
TeamID: id,
AccessLevel: portainer.ReadWriteAccessLevel,
}
teamAccesses = append(teamAccesses, access)
}
return &portainer.ResourceControl{
Type: resourceType,
ResourceID: resourceIdentifier,
SubResourceIDs: []string{},
UserAccesses: userAccesses,
TeamAccesses: teamAccesses,
AdministratorsOnly: false,
Public: false,
System: false,
}
}
// DecorateStacks will iterate through a list of stacks, check for an associated resource control for each
// stack and decorate the stack element if a resource control is found.
func DecorateStacks(stacks []portainer.Stack, resourceControls []portainer.ResourceControl) []portainer.Stack {
for idx, stack := range stacks {
resourceControl := GetResourceControlByResourceIDAndType(stackutils.ResourceControlID(stack.EndpointID, stack.Name), portainer.StackResourceControl, resourceControls)
if resourceControl != nil {
stacks[idx].ResourceControl = resourceControl
@@ -130,7 +40,6 @@ func DecorateStacks(stacks []portainer.Stack, resourceControls []portainer.Resou
// template and decorate the template element if a resource control is found.
func DecorateCustomTemplates(templates []portainer.CustomTemplate, resourceControls []portainer.ResourceControl) []portainer.CustomTemplate {
for idx, template := range templates {
resourceControl := GetResourceControlByResourceIDAndType(strconv.Itoa(int(template.ID)), portainer.CustomTemplateResourceControl, resourceControls)
if resourceControl != nil {
templates[idx].ResourceControl = resourceControl
@@ -165,48 +74,3 @@ func FilterAuthorizedCustomTemplates(customTemplates []portainer.CustomTemplate,
return authorizedTemplates
}
// UserCanAccessResource will valid that a user has permissions defined in the specified resource control
// based on its identifier and the team(s) he is part of.
func UserCanAccessResource(userID portainer.UserID, userTeamIDs []portainer.TeamID, resourceControl *portainer.ResourceControl) bool {
if resourceControl == nil {
return false
}
for _, authorizedUserAccess := range resourceControl.UserAccesses {
if userID == authorizedUserAccess.UserID {
return true
}
}
for _, authorizedTeamAccess := range resourceControl.TeamAccesses {
if slices.Contains(userTeamIDs, authorizedTeamAccess.TeamID) {
return true
}
}
return resourceControl.Public
}
// GetResourceControlByResourceIDAndType retrieves the first matching resource control in a set of resource controls
// based on the specified id and resource type parameters.
func GetResourceControlByResourceIDAndType(resourceID string, resourceType portainer.ResourceControlType, resourceControls []portainer.ResourceControl) *portainer.ResourceControl {
for i := range resourceControls {
if resourceID == resourceControls[i].ResourceID && resourceType == resourceControls[i].Type {
return &resourceControls[i]
}
if slices.Contains(resourceControls[i].SubResourceIDs, resourceID) {
return &resourceControls[i]
}
}
return nil
}
// TeamIDs extracts the TeamID from each membership.
func TeamIDs(memberships []portainer.TeamMembership) []portainer.TeamID {
return slicesx.Map(memberships, func(m portainer.TeamMembership) portainer.TeamID {
return m.TeamID
})
}
+10 -47
View File
@@ -2,50 +2,24 @@ package endpointutils
import (
"errors"
"strings"
"time"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
"github.com/portainer/portainer/api/kubernetes/cli"
"github.com/portainer/portainer/pkg/endpoints"
log "github.com/rs/zerolog/log"
)
// TODO: this file should be migrated to package/server-ce/pkg/endpoints
// IsLocalEndpoint returns true if this is a local environment(endpoint)
func IsLocalEndpoint(endpoint *portainer.Endpoint) bool {
return strings.HasPrefix(endpoint.URL, "unix://") ||
strings.HasPrefix(endpoint.URL, "npipe://") ||
endpoint.Type == portainer.KubernetesLocalEnvironment
}
// IsKubernetesEndpoint returns true if this is a kubernetes environment(endpoint)
func IsKubernetesEndpoint(endpoint *portainer.Endpoint) bool {
return endpoint.Type == portainer.KubernetesLocalEnvironment ||
endpoint.Type == portainer.AgentOnKubernetesEnvironment ||
endpoint.Type == portainer.EdgeAgentOnKubernetesEnvironment
}
// IsDockerEndpoint returns true if this is a docker environment(endpoint)
func IsDockerEndpoint(endpoint *portainer.Endpoint) bool {
return endpoint.Type == portainer.DockerEnvironment ||
endpoint.Type == portainer.AgentOnDockerEnvironment ||
endpoint.Type == portainer.EdgeAgentOnDockerEnvironment
}
// IsEdgeEndpoint returns true if this is an Edge endpoint
func IsEdgeEndpoint(endpoint *portainer.Endpoint) bool {
return endpoint.Type == portainer.EdgeAgentOnDockerEnvironment || endpoint.Type == portainer.EdgeAgentOnKubernetesEnvironment
}
// IsAgentEndpoint returns true if this is an Agent endpoint
func IsAgentEndpoint(endpoint *portainer.Endpoint) bool {
return endpoint.Type == portainer.AgentOnDockerEnvironment ||
endpoint.Type == portainer.EdgeAgentOnDockerEnvironment ||
endpoint.Type == portainer.AgentOnKubernetesEnvironment ||
endpoint.Type == portainer.EdgeAgentOnKubernetesEnvironment
}
var (
IsLocalEndpoint = endpoints.IsLocalEndpoint
IsKubernetesEndpoint = endpoints.IsKubernetesEndpoint
IsDockerEndpoint = endpoints.IsDockerEndpoint
IsEdgeEndpoint = endpoints.IsEdgeEndpoint
IsAgentEndpoint = endpoints.IsAgentEndpoint
EndpointSet = endpoints.EndpointSet
)
// EndpointPlatformType returns the type of the endpoint based on the environment and container engine
func EndpointPlatformType(endpoint *portainer.Endpoint) portainer.PlatformType {
@@ -85,17 +59,6 @@ func FilterByExcludeIDs(endpoints []portainer.Endpoint, excludeIds []portainer.E
return filteredEndpoints
}
// EndpointSet receives an environment(endpoint) array and returns a set
func EndpointSet(endpointIDs []portainer.EndpointID) map[portainer.EndpointID]bool {
set := map[portainer.EndpointID]bool{}
for _, endpointID := range endpointIDs {
set[endpointID] = true
}
return set
}
func InitialIngressClassDetection(tx dataservices.DataStoreTx, endpoint *portainer.Endpoint, factory *cli.ClientFactory) {
if endpoint.Kubernetes.Flags.IsServerIngressClassDetected {
return
+152
View File
@@ -0,0 +1,152 @@
package authorization
import (
"slices"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/slicesx"
)
// NewAdministratorsOnlyResourceControl will create a new administrators only resource control associated to the resource specified by the
// identifier and type parameters
func NewAdministratorsOnlyResourceControl(resourceIdentifier string, resourceType portainer.ResourceControlType) *portainer.ResourceControl {
return &portainer.ResourceControl{
Type: resourceType,
ResourceID: resourceIdentifier,
SubResourceIDs: []string{},
UserAccesses: []portainer.UserResourceAccess{},
TeamAccesses: []portainer.TeamResourceAccess{},
AdministratorsOnly: true,
Public: false,
System: false,
}
}
// NewPrivateResourceControl will create a new private resource control associated to the resource specified by the
// identifier and type parameters. It automatically assigns it to the user specified by the userID parameter.
func NewPrivateResourceControl(resourceIdentifier string, resourceType portainer.ResourceControlType, userID portainer.UserID) *portainer.ResourceControl {
return &portainer.ResourceControl{
Type: resourceType,
ResourceID: resourceIdentifier,
SubResourceIDs: []string{},
UserAccesses: []portainer.UserResourceAccess{
{
UserID: userID,
AccessLevel: portainer.ReadWriteAccessLevel,
},
},
TeamAccesses: []portainer.TeamResourceAccess{},
AdministratorsOnly: false,
Public: false,
System: false,
}
}
// NewSystemResourceControl creates a new public resource control with the System flag set to true.
// These resource controls are not persisted and are created on the fly by the Portainer API.
func NewSystemResourceControl(resourceIdentifier string, resourceType portainer.ResourceControlType) *portainer.ResourceControl {
return &portainer.ResourceControl{
Type: resourceType,
ResourceID: resourceIdentifier,
SubResourceIDs: []string{},
UserAccesses: []portainer.UserResourceAccess{},
TeamAccesses: []portainer.TeamResourceAccess{},
AdministratorsOnly: false,
Public: true,
System: true,
}
}
// NewPublicResourceControl creates a new public resource control.
func NewPublicResourceControl(resourceIdentifier string, resourceType portainer.ResourceControlType) *portainer.ResourceControl {
return &portainer.ResourceControl{
Type: resourceType,
ResourceID: resourceIdentifier,
SubResourceIDs: []string{},
UserAccesses: []portainer.UserResourceAccess{},
TeamAccesses: []portainer.TeamResourceAccess{},
AdministratorsOnly: false,
Public: true,
System: false,
}
}
// NewRestrictedResourceControl creates a new resource control with user and team access restrictions.
func NewRestrictedResourceControl(resourceIdentifier string, resourceType portainer.ResourceControlType, userIDs []portainer.UserID, teamIDs []portainer.TeamID) *portainer.ResourceControl {
userAccesses := make([]portainer.UserResourceAccess, 0)
teamAccesses := make([]portainer.TeamResourceAccess, 0)
for _, id := range userIDs {
access := portainer.UserResourceAccess{
UserID: id,
AccessLevel: portainer.ReadWriteAccessLevel,
}
userAccesses = append(userAccesses, access)
}
for _, id := range teamIDs {
access := portainer.TeamResourceAccess{
TeamID: id,
AccessLevel: portainer.ReadWriteAccessLevel,
}
teamAccesses = append(teamAccesses, access)
}
return &portainer.ResourceControl{
Type: resourceType,
ResourceID: resourceIdentifier,
SubResourceIDs: []string{},
UserAccesses: userAccesses,
TeamAccesses: teamAccesses,
AdministratorsOnly: false,
Public: false,
System: false,
}
}
// UserCanAccessResource validates that a user has permissions defined in the specified resource control
// based on their identifier and the team(s) they belong to.
func UserCanAccessResource(userID portainer.UserID, userTeamIDs []portainer.TeamID, resourceControl *portainer.ResourceControl) bool {
if resourceControl == nil {
return false
}
for _, authorizedUserAccess := range resourceControl.UserAccesses {
if userID == authorizedUserAccess.UserID {
return true
}
}
for _, authorizedTeamAccess := range resourceControl.TeamAccesses {
if slices.Contains(userTeamIDs, authorizedTeamAccess.TeamID) {
return true
}
}
return resourceControl.Public
}
// GetResourceControlByResourceIDAndType retrieves the first matching resource control in a set of resource controls
// based on the specified id and resource type parameters.
func GetResourceControlByResourceIDAndType(resourceID string, resourceType portainer.ResourceControlType, resourceControls []portainer.ResourceControl) *portainer.ResourceControl {
for i := range resourceControls {
if resourceID == resourceControls[i].ResourceID && resourceType == resourceControls[i].Type {
return &resourceControls[i]
}
if slices.Contains(resourceControls[i].SubResourceIDs, resourceID) {
return &resourceControls[i]
}
}
return nil
}
// TeamIDs extracts the TeamID from each membership.
func TeamIDs(memberships []portainer.TeamMembership) []portainer.TeamID {
return slicesx.Map(memberships, func(m portainer.TeamMembership) portainer.TeamID {
return m.TeamID
})
}
+42
View File
@@ -1,11 +1,53 @@
package endpoints
import (
"strings"
portainer "github.com/portainer/portainer/api"
"github.com/Masterminds/semver/v3"
)
// IsLocalEndpoint returns true if this is a local environment(endpoint)
func IsLocalEndpoint(endpoint *portainer.Endpoint) bool {
return strings.HasPrefix(endpoint.URL, "unix://") ||
strings.HasPrefix(endpoint.URL, "npipe://") ||
endpoint.Type == portainer.KubernetesLocalEnvironment
}
// IsKubernetesEndpoint returns true if this is a kubernetes environment(endpoint)
func IsKubernetesEndpoint(endpoint *portainer.Endpoint) bool {
return endpoint.Type == portainer.KubernetesLocalEnvironment ||
endpoint.Type == portainer.AgentOnKubernetesEnvironment ||
endpoint.Type == portainer.EdgeAgentOnKubernetesEnvironment
}
// IsDockerEndpoint returns true if this is a docker environment(endpoint)
func IsDockerEndpoint(endpoint *portainer.Endpoint) bool {
return endpoint.Type == portainer.DockerEnvironment ||
endpoint.Type == portainer.AgentOnDockerEnvironment ||
endpoint.Type == portainer.EdgeAgentOnDockerEnvironment
}
// IsAgentEndpoint returns true if this is an Agent endpoint
func IsAgentEndpoint(endpoint *portainer.Endpoint) bool {
return endpoint.Type == portainer.AgentOnDockerEnvironment ||
endpoint.Type == portainer.EdgeAgentOnDockerEnvironment ||
endpoint.Type == portainer.AgentOnKubernetesEnvironment ||
endpoint.Type == portainer.EdgeAgentOnKubernetesEnvironment
}
// EndpointSet receives an environment(endpoint) array and returns a set
func EndpointSet(endpointIDs []portainer.EndpointID) map[portainer.EndpointID]bool {
set := map[portainer.EndpointID]bool{}
for _, endpointID := range endpointIDs {
set[endpointID] = true
}
return set
}
// IsRegularAgentEndpoint returns true if this is a regular agent endpoint
func IsRegularAgentEndpoint(endpoint *portainer.Endpoint) bool {
return endpoint.Type == portainer.AgentOnDockerEnvironment ||