mirror of https://github.com/portainer/portainer
feat(UAC): change default ownership to admininstrators (#2137)
* #960 feat(UAC): change ownership to admins for externally created ressources * feat(UAC): change ownership to admins for externally created resources Deprecated AdministratorsOnly js and go backend * #960 feat(UAC): remove AdministratorsOnly property and minor GUI fixes Update swagger definition changing AdministratorsOnly to Public * #960 feat(UAC): fix create resource with access control data * #960 feat(UAC): authorization of non-admin users for restricted operations On stacks, containers networks, services , tasks and volumes. * #960 feat(UAC): database migration to version 14 The administrator resources are deleted and Public resources are now managed by admins * #960 feat(UAC): small fixes from PR #2137 * #960 feat(UAC): improve the readability of the source code * feat(UAC) fix displayed ownership for Swarm related resources (#960)pull/2193/head
parent
31c2a6d9e7
commit
e1e263d8c8
|
@ -0,0 +1,19 @@
|
||||||
|
package migrator
|
||||||
|
|
||||||
|
func (m *Migrator) updateResourceControlsToDBVersion14() error {
|
||||||
|
resourceControls, err := m.resourceControlService.ResourceControls()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, resourceControl := range resourceControls {
|
||||||
|
if resourceControl.AdministratorsOnly == true {
|
||||||
|
err = m.resourceControlService.DeleteResourceControl(resourceControl.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -178,5 +178,13 @@ func (m *Migrator) Migrate() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 1.19.2-dev
|
||||||
|
if m.currentDBVersion < 14 {
|
||||||
|
err := m.updateResourceControlsToDBVersion14()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return m.versionService.StoreDBVersion(portainer.DBVersion)
|
return m.versionService.StoreDBVersion(portainer.DBVersion)
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ import (
|
||||||
type resourceControlCreatePayload struct {
|
type resourceControlCreatePayload struct {
|
||||||
ResourceID string
|
ResourceID string
|
||||||
Type string
|
Type string
|
||||||
AdministratorsOnly bool
|
Public bool
|
||||||
Users []int
|
Users []int
|
||||||
Teams []int
|
Teams []int
|
||||||
SubResourceIDs []string
|
SubResourceIDs []string
|
||||||
|
@ -29,8 +29,8 @@ func (payload *resourceControlCreatePayload) Validate(r *http.Request) error {
|
||||||
return portainer.Error("Invalid type")
|
return portainer.Error("Invalid type")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(payload.Users) == 0 && len(payload.Teams) == 0 && !payload.AdministratorsOnly {
|
if len(payload.Users) == 0 && len(payload.Teams) == 0 && !payload.Public {
|
||||||
return portainer.Error("Invalid resource control declaration. Must specify Users, Teams or AdministratorOnly")
|
return portainer.Error("Invalid resource control declaration. Must specify Users, Teams or Public")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -93,7 +93,7 @@ func (handler *Handler) resourceControlCreate(w http.ResponseWriter, r *http.Req
|
||||||
ResourceID: payload.ResourceID,
|
ResourceID: payload.ResourceID,
|
||||||
SubResourceIDs: payload.SubResourceIDs,
|
SubResourceIDs: payload.SubResourceIDs,
|
||||||
Type: resourceControlType,
|
Type: resourceControlType,
|
||||||
AdministratorsOnly: payload.AdministratorsOnly,
|
Public: payload.Public,
|
||||||
UserAccesses: userAccesses,
|
UserAccesses: userAccesses,
|
||||||
TeamAccesses: teamAccesses,
|
TeamAccesses: teamAccesses,
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,14 +11,14 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type resourceControlUpdatePayload struct {
|
type resourceControlUpdatePayload struct {
|
||||||
AdministratorsOnly bool
|
Public bool
|
||||||
Users []int
|
Users []int
|
||||||
Teams []int
|
Teams []int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (payload *resourceControlUpdatePayload) Validate(r *http.Request) error {
|
func (payload *resourceControlUpdatePayload) Validate(r *http.Request) error {
|
||||||
if len(payload.Users) == 0 && len(payload.Teams) == 0 && !payload.AdministratorsOnly {
|
if len(payload.Users) == 0 && len(payload.Teams) == 0 && !payload.Public {
|
||||||
return portainer.Error("Invalid resource control declaration. Must specify Users, Teams or AdministratorOnly")
|
return portainer.Error("Invalid resource control declaration. Must specify Users, Teams or Public")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,7 @@ func (handler *Handler) resourceControlUpdate(w http.ResponseWriter, r *http.Req
|
||||||
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to update the resource control", portainer.ErrResourceAccessDenied}
|
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to update the resource control", portainer.ErrResourceAccessDenied}
|
||||||
}
|
}
|
||||||
|
|
||||||
resourceControl.AdministratorsOnly = payload.AdministratorsOnly
|
resourceControl.Public = payload.Public
|
||||||
|
|
||||||
var userAccesses = make([]portainer.UserResourceAccess, 0)
|
var userAccesses = make([]portainer.UserResourceAccess, 0)
|
||||||
for _, v := range payload.Users {
|
for _, v := range payload.Users {
|
||||||
|
|
|
@ -48,8 +48,8 @@ func (handler *Handler) stackDelete(w http.ResponseWriter, r *http.Request) *htt
|
||||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve info from request context", err}
|
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve info from request context", err}
|
||||||
}
|
}
|
||||||
|
|
||||||
if resourceControl != nil {
|
if !securityContext.IsAdmin {
|
||||||
if !securityContext.IsAdmin && !proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) {
|
if !proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) {
|
||||||
return &httperror.HandlerError{http.StatusForbidden, "Access denied to resource", portainer.ErrResourceAccessDenied}
|
return &httperror.HandlerError{http.StatusForbidden, "Access denied to resource", portainer.ErrResourceAccessDenied}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,10 @@ func (handler *Handler) stackFile(w http.ResponseWriter, r *http.Request) *httpe
|
||||||
}
|
}
|
||||||
|
|
||||||
extendedStack := proxy.ExtendedStack{*stack, portainer.ResourceControl{}}
|
extendedStack := proxy.ExtendedStack{*stack, portainer.ResourceControl{}}
|
||||||
|
if !securityContext.IsAdmin && resourceControl == nil {
|
||||||
|
return &httperror.HandlerError{http.StatusForbidden, "Access denied to resource", portainer.ErrResourceAccessDenied}
|
||||||
|
}
|
||||||
|
|
||||||
if resourceControl != nil {
|
if resourceControl != nil {
|
||||||
if securityContext.IsAdmin || proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) {
|
if securityContext.IsAdmin || proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) {
|
||||||
extendedStack.ResourceControl = *resourceControl
|
extendedStack.ResourceControl = *resourceControl
|
||||||
|
|
|
@ -36,6 +36,10 @@ func (handler *Handler) stackInspect(w http.ResponseWriter, r *http.Request) *ht
|
||||||
}
|
}
|
||||||
|
|
||||||
extendedStack := proxy.ExtendedStack{*stack, portainer.ResourceControl{}}
|
extendedStack := proxy.ExtendedStack{*stack, portainer.ResourceControl{}}
|
||||||
|
if !securityContext.IsAdmin && resourceControl == nil {
|
||||||
|
return &httperror.HandlerError{http.StatusForbidden, "Access denied to resource", portainer.ErrResourceAccessDenied}
|
||||||
|
}
|
||||||
|
|
||||||
if resourceControl != nil {
|
if resourceControl != nil {
|
||||||
if securityContext.IsAdmin || proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) {
|
if securityContext.IsAdmin || proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) {
|
||||||
extendedStack.ResourceControl = *resourceControl
|
extendedStack.ResourceControl = *resourceControl
|
||||||
|
|
|
@ -53,8 +53,8 @@ func (handler *Handler) stackMigrate(w http.ResponseWriter, r *http.Request) *ht
|
||||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve info from request context", err}
|
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve info from request context", err}
|
||||||
}
|
}
|
||||||
|
|
||||||
if resourceControl != nil {
|
if !securityContext.IsAdmin {
|
||||||
if !securityContext.IsAdmin && !proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) {
|
if !proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) {
|
||||||
return &httperror.HandlerError{http.StatusForbidden, "Access denied to resource", portainer.ErrResourceAccessDenied}
|
return &httperror.HandlerError{http.StatusForbidden, "Access denied to resource", portainer.ErrResourceAccessDenied}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,8 +62,8 @@ func (handler *Handler) stackUpdate(w http.ResponseWriter, r *http.Request) *htt
|
||||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve info from request context", err}
|
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve info from request context", err}
|
||||||
}
|
}
|
||||||
|
|
||||||
if resourceControl != nil {
|
if !securityContext.IsAdmin {
|
||||||
if !securityContext.IsAdmin && !proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) {
|
if !proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) {
|
||||||
return &httperror.HandlerError{http.StatusForbidden, "Access denied to resource", portainer.ErrResourceAccessDenied}
|
return &httperror.HandlerError{http.StatusForbidden, "Access denied to resource", portainer.ErrResourceAccessDenied}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package proxy
|
package proxy
|
||||||
|
|
||||||
import "github.com/portainer/portainer"
|
import (
|
||||||
|
"github.com/portainer/portainer"
|
||||||
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// ExtendedStack represents a stack combined with its associated access control
|
// ExtendedStack represents a stack combined with its associated access control
|
||||||
|
@ -15,7 +17,7 @@ type (
|
||||||
// It will retrieve an identifier from the labels object. If an identifier exists, it will check for
|
// It will retrieve an identifier from the labels object. If an identifier exists, it will check for
|
||||||
// an existing resource control associated to it.
|
// an existing resource control associated to it.
|
||||||
// Returns a decorated object and authorized access (true) when a resource control is found and the user can access the resource.
|
// Returns a decorated object and authorized access (true) when a resource control is found and the user can access the resource.
|
||||||
// Returns the original object and authorized access (true) when no resource control is found.
|
// Returns the original object and denied access (false) when no resource control is found.
|
||||||
// Returns the original object and denied access (false) when a resource control is found and the user cannot access the resource.
|
// Returns the original object and denied access (false) when a resource control is found and the user cannot access the resource.
|
||||||
func applyResourceAccessControlFromLabel(labelsObject, resourceObject map[string]interface{}, labelIdentifier string,
|
func applyResourceAccessControlFromLabel(labelsObject, resourceObject map[string]interface{}, labelIdentifier string,
|
||||||
context *restrictedOperationContext) (map[string]interface{}, bool) {
|
context *restrictedOperationContext) (map[string]interface{}, bool) {
|
||||||
|
@ -24,32 +26,31 @@ func applyResourceAccessControlFromLabel(labelsObject, resourceObject map[string
|
||||||
resourceIdentifier := labelsObject[labelIdentifier].(string)
|
resourceIdentifier := labelsObject[labelIdentifier].(string)
|
||||||
return applyResourceAccessControl(resourceObject, resourceIdentifier, context)
|
return applyResourceAccessControl(resourceObject, resourceIdentifier, context)
|
||||||
}
|
}
|
||||||
return resourceObject, true
|
return resourceObject, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// applyResourceAccessControl returns an optionally decorated object as the first return value and the
|
// applyResourceAccessControl returns an optionally decorated object as the first return value and the
|
||||||
// access level for the user (granted or denied) as the second return value.
|
// access level for the user (granted or denied) as the second return value.
|
||||||
// Returns a decorated object and authorized access (true) when a resource control is found to the specified resource
|
// Returns a decorated object and authorized access (true) when a resource control is found to the specified resource
|
||||||
// identifier and the user can access the resource.
|
// identifier and the user can access the resource.
|
||||||
// Returns the original object and authorized access (true) when no resource control is found for the specified
|
// Returns the original object and authorized access (false) when no resource control is found for the specified
|
||||||
// resource identifier.
|
// resource identifier.
|
||||||
// Returns the original object and denied access (false) when a resource control is associated to the resource
|
// Returns the original object and denied access (false) when a resource control is associated to the resource
|
||||||
// and the user cannot access the resource.
|
// and the user cannot access the resource.
|
||||||
func applyResourceAccessControl(resourceObject map[string]interface{}, resourceIdentifier string,
|
func applyResourceAccessControl(resourceObject map[string]interface{}, resourceIdentifier string,
|
||||||
context *restrictedOperationContext) (map[string]interface{}, bool) {
|
context *restrictedOperationContext) (map[string]interface{}, bool) {
|
||||||
|
|
||||||
authorizedAccess := true
|
|
||||||
|
|
||||||
resourceControl := getResourceControlByResourceID(resourceIdentifier, context.resourceControls)
|
resourceControl := getResourceControlByResourceID(resourceIdentifier, context.resourceControls)
|
||||||
if resourceControl != nil {
|
if resourceControl == nil {
|
||||||
if context.isAdmin || canUserAccessResource(context.userID, context.userTeamIDs, resourceControl) {
|
return resourceObject, context.isAdmin
|
||||||
resourceObject = decorateObject(resourceObject, resourceControl)
|
|
||||||
} else {
|
|
||||||
authorizedAccess = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return resourceObject, authorizedAccess
|
if context.isAdmin || resourceControl.Public || canUserAccessResource(context.userID, context.userTeamIDs, resourceControl) {
|
||||||
|
resourceObject = decorateObject(resourceObject, resourceControl)
|
||||||
|
return resourceObject, true
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceObject, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// decorateResourceWithAccessControlFromLabel will retrieve an identifier from the labels object. If an identifier exists,
|
// decorateResourceWithAccessControlFromLabel will retrieve an identifier from the labels object. If an identifier exists,
|
||||||
|
@ -94,7 +95,7 @@ func canUserAccessResource(userID portainer.UserID, userTeamIDs []portainer.Team
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return resourceControl.Public
|
||||||
}
|
}
|
||||||
|
|
||||||
func decorateObject(object map[string]interface{}, resourceControl *portainer.ResourceControl) map[string]interface{} {
|
func decorateObject(object map[string]interface{}, resourceControl *portainer.ResourceControl) map[string]interface{} {
|
||||||
|
@ -123,6 +124,10 @@ func getResourceControlByResourceID(resourceID string, resourceControls []portai
|
||||||
|
|
||||||
// CanAccessStack checks if a user can access a stack
|
// CanAccessStack checks if a user can access a stack
|
||||||
func CanAccessStack(stack *portainer.Stack, resourceControl *portainer.ResourceControl, userID portainer.UserID, memberships []portainer.TeamMembership) bool {
|
func CanAccessStack(stack *portainer.Stack, resourceControl *portainer.ResourceControl, userID portainer.UserID, memberships []portainer.TeamMembership) bool {
|
||||||
|
if resourceControl == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
userTeamIDs := make([]portainer.TeamID, 0)
|
userTeamIDs := make([]portainer.TeamID, 0)
|
||||||
for _, membership := range memberships {
|
for _, membership := range memberships {
|
||||||
userTeamIDs = append(userTeamIDs, membership.TeamID)
|
userTeamIDs = append(userTeamIDs, membership.TeamID)
|
||||||
|
@ -132,7 +137,7 @@ func CanAccessStack(stack *portainer.Stack, resourceControl *portainer.ResourceC
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return resourceControl.Public
|
||||||
}
|
}
|
||||||
|
|
||||||
// FilterStacks filters stacks based on user role and resource controls.
|
// FilterStacks filters stacks based on user role and resource controls.
|
||||||
|
@ -149,9 +154,9 @@ func FilterStacks(stacks []portainer.Stack, resourceControls []portainer.Resourc
|
||||||
for _, stack := range stacks {
|
for _, stack := range stacks {
|
||||||
extendedStack := ExtendedStack{stack, portainer.ResourceControl{}}
|
extendedStack := ExtendedStack{stack, portainer.ResourceControl{}}
|
||||||
resourceControl := getResourceControlByResourceID(stack.Name, resourceControls)
|
resourceControl := getResourceControlByResourceID(stack.Name, resourceControls)
|
||||||
if resourceControl == nil {
|
if resourceControl == nil && isAdmin {
|
||||||
filteredStacks = append(filteredStacks, extendedStack)
|
filteredStacks = append(filteredStacks, extendedStack)
|
||||||
} else if resourceControl != nil && (isAdmin || canUserAccessResource(userID, userTeamIDs, resourceControl)) {
|
} else if resourceControl != nil && (isAdmin || resourceControl.Public || canUserAccessResource(userID, userTeamIDs, resourceControl)) {
|
||||||
extendedStack.ResourceControl = *resourceControl
|
extendedStack.ResourceControl = *resourceControl
|
||||||
filteredStacks = append(filteredStacks, extendedStack)
|
filteredStacks = append(filteredStacks, extendedStack)
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,27 +62,27 @@ func containerInspectOperation(response *http.Response, executor *operationExecu
|
||||||
|
|
||||||
containerID := responseObject[containerIdentifier].(string)
|
containerID := responseObject[containerIdentifier].(string)
|
||||||
responseObject, access := applyResourceAccessControl(responseObject, containerID, executor.operationContext)
|
responseObject, access := applyResourceAccessControl(responseObject, containerID, executor.operationContext)
|
||||||
if !access {
|
if access {
|
||||||
return rewriteAccessDeniedResponse(response)
|
return rewriteResponse(response, responseObject, http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
containerLabels := extractContainerLabelsFromContainerInspectObject(responseObject)
|
containerLabels := extractContainerLabelsFromContainerInspectObject(responseObject)
|
||||||
responseObject, access = applyResourceAccessControlFromLabel(containerLabels, responseObject, containerLabelForServiceIdentifier, executor.operationContext)
|
responseObject, access = applyResourceAccessControlFromLabel(containerLabels, responseObject, containerLabelForServiceIdentifier, executor.operationContext)
|
||||||
if !access {
|
if access {
|
||||||
return rewriteAccessDeniedResponse(response)
|
return rewriteResponse(response, responseObject, http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
responseObject, access = applyResourceAccessControlFromLabel(containerLabels, responseObject, containerLabelForSwarmStackIdentifier, executor.operationContext)
|
responseObject, access = applyResourceAccessControlFromLabel(containerLabels, responseObject, containerLabelForSwarmStackIdentifier, executor.operationContext)
|
||||||
if !access {
|
if access {
|
||||||
return rewriteAccessDeniedResponse(response)
|
return rewriteResponse(response, responseObject, http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
responseObject, access = applyResourceAccessControlFromLabel(containerLabels, responseObject, containerLabelForComposeStackIdentifier, executor.operationContext)
|
responseObject, access = applyResourceAccessControlFromLabel(containerLabels, responseObject, containerLabelForComposeStackIdentifier, executor.operationContext)
|
||||||
if !access {
|
if access {
|
||||||
return rewriteAccessDeniedResponse(response)
|
return rewriteResponse(response, responseObject, http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
return rewriteResponse(response, responseObject, http.StatusOK)
|
return rewriteAccessDeniedResponse(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
// extractContainerLabelsFromContainerInspectObject retrieve the Labels of the container if present.
|
// extractContainerLabelsFromContainerInspectObject retrieve the Labels of the container if present.
|
||||||
|
@ -148,20 +148,21 @@ func filterContainerList(containerData []interface{}, context *restrictedOperati
|
||||||
|
|
||||||
containerID := containerObject[containerIdentifier].(string)
|
containerID := containerObject[containerIdentifier].(string)
|
||||||
containerObject, access := applyResourceAccessControl(containerObject, containerID, context)
|
containerObject, access := applyResourceAccessControl(containerObject, containerID, context)
|
||||||
if access {
|
if !access {
|
||||||
containerLabels := extractContainerLabelsFromContainerListObject(containerObject)
|
containerLabels := extractContainerLabelsFromContainerListObject(containerObject)
|
||||||
containerObject, access = applyResourceAccessControlFromLabel(containerLabels, containerObject, containerLabelForComposeStackIdentifier, context)
|
containerObject, access = applyResourceAccessControlFromLabel(containerLabels, containerObject, containerLabelForComposeStackIdentifier, context)
|
||||||
if access {
|
if !access {
|
||||||
containerObject, access = applyResourceAccessControlFromLabel(containerLabels, containerObject, containerLabelForServiceIdentifier, context)
|
containerObject, access = applyResourceAccessControlFromLabel(containerLabels, containerObject, containerLabelForServiceIdentifier, context)
|
||||||
if access {
|
if !access {
|
||||||
containerObject, access = applyResourceAccessControlFromLabel(containerLabels, containerObject, containerLabelForSwarmStackIdentifier, context)
|
containerObject, access = applyResourceAccessControlFromLabel(containerLabels, containerObject, containerLabelForSwarmStackIdentifier, context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if access {
|
if access {
|
||||||
filteredContainerData = append(filteredContainerData, containerObject)
|
filteredContainerData = append(filteredContainerData, containerObject)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return filteredContainerData, nil
|
return filteredContainerData, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,17 +53,17 @@ func networkInspectOperation(response *http.Response, executor *operationExecuto
|
||||||
|
|
||||||
networkID := responseObject[networkIdentifier].(string)
|
networkID := responseObject[networkIdentifier].(string)
|
||||||
responseObject, access := applyResourceAccessControl(responseObject, networkID, executor.operationContext)
|
responseObject, access := applyResourceAccessControl(responseObject, networkID, executor.operationContext)
|
||||||
if !access {
|
if access {
|
||||||
return rewriteAccessDeniedResponse(response)
|
return rewriteResponse(response, responseObject, http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
networkLabels := extractNetworkLabelsFromNetworkInspectObject(responseObject)
|
networkLabels := extractNetworkLabelsFromNetworkInspectObject(responseObject)
|
||||||
responseObject, access = applyResourceAccessControlFromLabel(networkLabels, responseObject, networkLabelForStackIdentifier, executor.operationContext)
|
responseObject, access = applyResourceAccessControlFromLabel(networkLabels, responseObject, networkLabelForStackIdentifier, executor.operationContext)
|
||||||
if !access {
|
if access {
|
||||||
return rewriteAccessDeniedResponse(response)
|
return rewriteResponse(response, responseObject, http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
return rewriteResponse(response, responseObject, http.StatusOK)
|
return rewriteAccessDeniedResponse(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
// extractNetworkLabelsFromNetworkInspectObject retrieve the Labels of the network if present.
|
// extractNetworkLabelsFromNetworkInspectObject retrieve the Labels of the network if present.
|
||||||
|
@ -121,14 +121,15 @@ func filterNetworkList(networkData []interface{}, context *restrictedOperationCo
|
||||||
|
|
||||||
networkID := networkObject[networkIdentifier].(string)
|
networkID := networkObject[networkIdentifier].(string)
|
||||||
networkObject, access := applyResourceAccessControl(networkObject, networkID, context)
|
networkObject, access := applyResourceAccessControl(networkObject, networkID, context)
|
||||||
if access {
|
if !access {
|
||||||
networkLabels := extractNetworkLabelsFromNetworkListObject(networkObject)
|
networkLabels := extractNetworkLabelsFromNetworkListObject(networkObject)
|
||||||
networkObject, access = applyResourceAccessControlFromLabel(networkLabels, networkObject, networkLabelForStackIdentifier, context)
|
networkObject, access = applyResourceAccessControlFromLabel(networkLabels, networkObject, networkLabelForStackIdentifier, context)
|
||||||
|
}
|
||||||
|
|
||||||
if access {
|
if access {
|
||||||
filteredNetworkData = append(filteredNetworkData, networkObject)
|
filteredNetworkData = append(filteredNetworkData, networkObject)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return filteredNetworkData, nil
|
return filteredNetworkData, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,17 +53,17 @@ func serviceInspectOperation(response *http.Response, executor *operationExecuto
|
||||||
|
|
||||||
serviceID := responseObject[serviceIdentifier].(string)
|
serviceID := responseObject[serviceIdentifier].(string)
|
||||||
responseObject, access := applyResourceAccessControl(responseObject, serviceID, executor.operationContext)
|
responseObject, access := applyResourceAccessControl(responseObject, serviceID, executor.operationContext)
|
||||||
if !access {
|
if access {
|
||||||
return rewriteAccessDeniedResponse(response)
|
return rewriteResponse(response, responseObject, http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
serviceLabels := extractServiceLabelsFromServiceInspectObject(responseObject)
|
serviceLabels := extractServiceLabelsFromServiceInspectObject(responseObject)
|
||||||
responseObject, access = applyResourceAccessControlFromLabel(serviceLabels, responseObject, serviceLabelForStackIdentifier, executor.operationContext)
|
responseObject, access = applyResourceAccessControlFromLabel(serviceLabels, responseObject, serviceLabelForStackIdentifier, executor.operationContext)
|
||||||
if !access {
|
if access {
|
||||||
return rewriteAccessDeniedResponse(response)
|
return rewriteResponse(response, responseObject, http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
return rewriteResponse(response, responseObject, http.StatusOK)
|
return rewriteAccessDeniedResponse(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
// extractServiceLabelsFromServiceInspectObject retrieve the Labels of the service if present.
|
// extractServiceLabelsFromServiceInspectObject retrieve the Labels of the service if present.
|
||||||
|
@ -129,14 +129,15 @@ func filterServiceList(serviceData []interface{}, context *restrictedOperationCo
|
||||||
|
|
||||||
serviceID := serviceObject[serviceIdentifier].(string)
|
serviceID := serviceObject[serviceIdentifier].(string)
|
||||||
serviceObject, access := applyResourceAccessControl(serviceObject, serviceID, context)
|
serviceObject, access := applyResourceAccessControl(serviceObject, serviceID, context)
|
||||||
if access {
|
if !access {
|
||||||
serviceLabels := extractServiceLabelsFromServiceListObject(serviceObject)
|
serviceLabels := extractServiceLabelsFromServiceListObject(serviceObject)
|
||||||
serviceObject, access = applyResourceAccessControlFromLabel(serviceLabels, serviceObject, serviceLabelForStackIdentifier, context)
|
serviceObject, access = applyResourceAccessControlFromLabel(serviceLabels, serviceObject, serviceLabelForStackIdentifier, context)
|
||||||
|
}
|
||||||
|
|
||||||
if access {
|
if access {
|
||||||
filteredServiceData = append(filteredServiceData, serviceObject)
|
filteredServiceData = append(filteredServiceData, serviceObject)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return filteredServiceData, nil
|
return filteredServiceData, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,14 +65,15 @@ func filterTaskList(taskData []interface{}, context *restrictedOperationContext)
|
||||||
|
|
||||||
serviceID := taskObject[taskServiceIdentifier].(string)
|
serviceID := taskObject[taskServiceIdentifier].(string)
|
||||||
taskObject, access := applyResourceAccessControl(taskObject, serviceID, context)
|
taskObject, access := applyResourceAccessControl(taskObject, serviceID, context)
|
||||||
if access {
|
if !access {
|
||||||
taskLabels := extractTaskLabelsFromTaskListObject(taskObject)
|
taskLabels := extractTaskLabelsFromTaskListObject(taskObject)
|
||||||
taskObject, access = applyResourceAccessControlFromLabel(taskLabels, taskObject, taskLabelForStackIdentifier, context)
|
taskObject, access = applyResourceAccessControlFromLabel(taskLabels, taskObject, taskLabelForStackIdentifier, context)
|
||||||
|
}
|
||||||
|
|
||||||
if access {
|
if access {
|
||||||
filteredTaskData = append(filteredTaskData, taskObject)
|
filteredTaskData = append(filteredTaskData, taskObject)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return filteredTaskData, nil
|
return filteredTaskData, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,17 +62,17 @@ func volumeInspectOperation(response *http.Response, executor *operationExecutor
|
||||||
|
|
||||||
volumeID := responseObject[volumeIdentifier].(string)
|
volumeID := responseObject[volumeIdentifier].(string)
|
||||||
responseObject, access := applyResourceAccessControl(responseObject, volumeID, executor.operationContext)
|
responseObject, access := applyResourceAccessControl(responseObject, volumeID, executor.operationContext)
|
||||||
if !access {
|
if access {
|
||||||
return rewriteAccessDeniedResponse(response)
|
return rewriteResponse(response, responseObject, http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
volumeLabels := extractVolumeLabelsFromVolumeInspectObject(responseObject)
|
volumeLabels := extractVolumeLabelsFromVolumeInspectObject(responseObject)
|
||||||
responseObject, access = applyResourceAccessControlFromLabel(volumeLabels, responseObject, volumeLabelForStackIdentifier, executor.operationContext)
|
responseObject, access = applyResourceAccessControlFromLabel(volumeLabels, responseObject, volumeLabelForStackIdentifier, executor.operationContext)
|
||||||
if !access {
|
if access {
|
||||||
return rewriteAccessDeniedResponse(response)
|
return rewriteResponse(response, responseObject, http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
return rewriteResponse(response, responseObject, http.StatusOK)
|
return rewriteAccessDeniedResponse(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
// extractVolumeLabelsFromVolumeInspectObject retrieve the Labels of the volume if present.
|
// extractVolumeLabelsFromVolumeInspectObject retrieve the Labels of the volume if present.
|
||||||
|
@ -130,14 +130,15 @@ func filterVolumeList(volumeData []interface{}, context *restrictedOperationCont
|
||||||
|
|
||||||
volumeID := volumeObject[volumeIdentifier].(string)
|
volumeID := volumeObject[volumeIdentifier].(string)
|
||||||
volumeObject, access := applyResourceAccessControl(volumeObject, volumeID, context)
|
volumeObject, access := applyResourceAccessControl(volumeObject, volumeID, context)
|
||||||
if access {
|
if !access {
|
||||||
volumeLabels := extractVolumeLabelsFromVolumeListObject(volumeObject)
|
volumeLabels := extractVolumeLabelsFromVolumeListObject(volumeObject)
|
||||||
volumeObject, access = applyResourceAccessControlFromLabel(volumeLabels, volumeObject, volumeLabelForStackIdentifier, context)
|
volumeObject, access = applyResourceAccessControlFromLabel(volumeLabels, volumeObject, volumeLabelForStackIdentifier, context)
|
||||||
|
}
|
||||||
|
|
||||||
if access {
|
if access {
|
||||||
filteredVolumeData = append(filteredVolumeData, volumeObject)
|
filteredVolumeData = append(filteredVolumeData, volumeObject)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return filteredVolumeData, nil
|
return filteredVolumeData, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,19 @@
|
||||||
package security
|
package security
|
||||||
|
|
||||||
import "github.com/portainer/portainer"
|
import (
|
||||||
|
"github.com/portainer/portainer"
|
||||||
|
)
|
||||||
|
|
||||||
// AuthorizedResourceControlDeletion ensure that the user can delete a resource control object.
|
// AuthorizedResourceControlDeletion ensure that the user can delete a resource control object.
|
||||||
// A non-administrator user cannot delete a resource control where:
|
// A non-administrator user cannot delete a resource control where:
|
||||||
// * the AdministratorsOnly flag is set
|
// * the Public flag is false
|
||||||
// * he is not one of the users in the user accesses
|
// * he is not one of the users in the user accesses
|
||||||
// * he is not a member of any team within the team accesses
|
// * he is not a member of any team within the team accesses
|
||||||
func AuthorizedResourceControlDeletion(resourceControl *portainer.ResourceControl, context *RestrictedRequestContext) bool {
|
func AuthorizedResourceControlDeletion(resourceControl *portainer.ResourceControl, context *RestrictedRequestContext) bool {
|
||||||
if context.IsAdmin {
|
if context.IsAdmin || resourceControl.Public {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if resourceControl.AdministratorsOnly {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
userAccessesCount := len(resourceControl.UserAccesses)
|
userAccessesCount := len(resourceControl.UserAccesses)
|
||||||
teamAccessesCount := len(resourceControl.TeamAccesses)
|
teamAccessesCount := len(resourceControl.TeamAccesses)
|
||||||
|
|
||||||
|
@ -42,39 +40,25 @@ func AuthorizedResourceControlDeletion(resourceControl *portainer.ResourceContro
|
||||||
|
|
||||||
// AuthorizedResourceControlAccess checks whether the user can alter an existing resource control.
|
// AuthorizedResourceControlAccess checks whether the user can alter an existing resource control.
|
||||||
func AuthorizedResourceControlAccess(resourceControl *portainer.ResourceControl, context *RestrictedRequestContext) bool {
|
func AuthorizedResourceControlAccess(resourceControl *portainer.ResourceControl, context *RestrictedRequestContext) bool {
|
||||||
if context.IsAdmin {
|
if context.IsAdmin || resourceControl.Public {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if resourceControl.AdministratorsOnly {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
authorizedTeamAccess := false
|
|
||||||
for _, access := range resourceControl.TeamAccesses {
|
for _, access := range resourceControl.TeamAccesses {
|
||||||
for _, membership := range context.UserMemberships {
|
for _, membership := range context.UserMemberships {
|
||||||
if membership.TeamID == access.TeamID {
|
if membership.TeamID == access.TeamID {
|
||||||
authorizedTeamAccess = true
|
return true
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !authorizedTeamAccess {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
authorizedUserAccess := false
|
|
||||||
for _, access := range resourceControl.UserAccesses {
|
for _, access := range resourceControl.UserAccesses {
|
||||||
if context.UserID == access.UserID {
|
if context.UserID == access.UserID {
|
||||||
authorizedUserAccess = true
|
return true
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !authorizedUserAccess {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthorizedResourceControlUpdate ensure that the user can update a resource control object.
|
// AuthorizedResourceControlUpdate ensure that the user can update a resource control object.
|
||||||
|
@ -92,20 +76,16 @@ func AuthorizedResourceControlUpdate(resourceControl *portainer.ResourceControl,
|
||||||
|
|
||||||
// AuthorizedResourceControlCreation ensure that the user can create a resource control object.
|
// AuthorizedResourceControlCreation ensure that the user can create a resource control object.
|
||||||
// A non-administrator user cannot create a resource control where:
|
// A non-administrator user cannot create a resource control where:
|
||||||
// * the AdministratorsOnly flag is set
|
// * the Public flag is set false
|
||||||
// * he wants to create a resource control without any user/team accesses
|
// * he wants to create a resource control without any user/team accesses
|
||||||
// * he wants to add more than one user in the user accesses
|
// * he wants to add more than one user in the user accesses
|
||||||
// * he wants tp add a user in the user accesses that is not corresponding to its id
|
// * he wants tp add a user in the user accesses that is not corresponding to its id
|
||||||
// * he wants to add a team he is not a member of
|
// * he wants to add a team he is not a member of
|
||||||
func AuthorizedResourceControlCreation(resourceControl *portainer.ResourceControl, context *RestrictedRequestContext) bool {
|
func AuthorizedResourceControlCreation(resourceControl *portainer.ResourceControl, context *RestrictedRequestContext) bool {
|
||||||
if context.IsAdmin {
|
if context.IsAdmin || resourceControl.Public {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if resourceControl.AdministratorsOnly {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
userAccessesCount := len(resourceControl.UserAccesses)
|
userAccessesCount := len(resourceControl.UserAccesses)
|
||||||
teamAccessesCount := len(resourceControl.TeamAccesses)
|
teamAccessesCount := len(resourceControl.TeamAccesses)
|
||||||
|
|
||||||
|
@ -126,19 +106,15 @@ func AuthorizedResourceControlCreation(resourceControl *portainer.ResourceContro
|
||||||
|
|
||||||
if teamAccessesCount > 0 {
|
if teamAccessesCount > 0 {
|
||||||
for _, access := range resourceControl.TeamAccesses {
|
for _, access := range resourceControl.TeamAccesses {
|
||||||
isMember := false
|
|
||||||
for _, membership := range context.UserMemberships {
|
for _, membership := range context.UserMemberships {
|
||||||
if membership.TeamID == access.TeamID {
|
if membership.TeamID == access.TeamID {
|
||||||
isMember = true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !isMember {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthorizedTeamManagement ensure that access to the management of the specified team is granted.
|
// AuthorizedTeamManagement ensure that access to the management of the specified team is granted.
|
||||||
|
|
|
@ -278,14 +278,17 @@ type (
|
||||||
ResourceID string `json:"ResourceId"`
|
ResourceID string `json:"ResourceId"`
|
||||||
SubResourceIDs []string `json:"SubResourceIds"`
|
SubResourceIDs []string `json:"SubResourceIds"`
|
||||||
Type ResourceControlType `json:"Type"`
|
Type ResourceControlType `json:"Type"`
|
||||||
AdministratorsOnly bool `json:"AdministratorsOnly"`
|
|
||||||
UserAccesses []UserResourceAccess `json:"UserAccesses"`
|
UserAccesses []UserResourceAccess `json:"UserAccesses"`
|
||||||
TeamAccesses []TeamResourceAccess `json:"TeamAccesses"`
|
TeamAccesses []TeamResourceAccess `json:"TeamAccesses"`
|
||||||
|
Public bool `json:"Public"`
|
||||||
|
|
||||||
// Deprecated fields
|
// Deprecated fields
|
||||||
// Deprecated in DBVersion == 2
|
// Deprecated in DBVersion == 2
|
||||||
OwnerID UserID `json:"OwnerId,omitempty"`
|
OwnerID UserID `json:"OwnerId,omitempty"`
|
||||||
AccessLevel ResourceAccessLevel `json:"AccessLevel,omitempty"`
|
AccessLevel ResourceAccessLevel `json:"AccessLevel,omitempty"`
|
||||||
|
|
||||||
|
// Deprecated in DBVersion == 14
|
||||||
|
AdministratorsOnly bool `json:"AdministratorsOnly,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResourceControlType represents the type of resource associated to the resource control (volume, container, service...).
|
// ResourceControlType represents the type of resource associated to the resource control (volume, container, service...).
|
||||||
|
@ -613,7 +616,7 @@ const (
|
||||||
// APIVersion is the version number of the Portainer API.
|
// APIVersion is the version number of the Portainer API.
|
||||||
APIVersion = "1.19.2-dev"
|
APIVersion = "1.19.2-dev"
|
||||||
// DBVersion is the version number of the Portainer database.
|
// DBVersion is the version number of the Portainer database.
|
||||||
DBVersion = 13
|
DBVersion = 14
|
||||||
// PortainerAgentHeader represents the name of the header available in any agent response
|
// PortainerAgentHeader represents the name of the header available in any agent response
|
||||||
PortainerAgentHeader = "Portainer-Agent"
|
PortainerAgentHeader = "Portainer-Agent"
|
||||||
// PortainerAgentTargetHeader represent the name of the header containing the target node name.
|
// PortainerAgentTargetHeader represent the name of the header containing the target node name.
|
||||||
|
|
|
@ -3268,11 +3268,10 @@ definitions:
|
||||||
example: "container"
|
example: "container"
|
||||||
description: "Type of Docker resource. Valid values are: container, volume\
|
description: "Type of Docker resource. Valid values are: container, volume\
|
||||||
\ service, secret, config or stack"
|
\ service, secret, config or stack"
|
||||||
AdministratorsOnly:
|
Public:
|
||||||
type: "boolean"
|
type: "boolean"
|
||||||
example: true
|
example: true
|
||||||
description: "Restrict access to the associated resource to administrators\
|
description: "Permit access to the associated resource to any user"
|
||||||
\ only"
|
|
||||||
Users:
|
Users:
|
||||||
type: "array"
|
type: "array"
|
||||||
description: "List of user identifiers with access to the associated resource"
|
description: "List of user identifiers with access to the associated resource"
|
||||||
|
@ -3491,11 +3490,10 @@ definitions:
|
||||||
example: "container"
|
example: "container"
|
||||||
description: "Type of Docker resource. Valid values are: container, volume\
|
description: "Type of Docker resource. Valid values are: container, volume\
|
||||||
\ service, secret, config or stack"
|
\ service, secret, config or stack"
|
||||||
AdministratorsOnly:
|
Public:
|
||||||
type: "boolean"
|
type: "boolean"
|
||||||
example: true
|
example: true
|
||||||
description: "Restrict access to the associated resource to administrators\
|
description: "Permit access to the associated resource to any user"
|
||||||
\ only"
|
|
||||||
Users:
|
Users:
|
||||||
type: "array"
|
type: "array"
|
||||||
description: "List of user identifiers with access to the associated resource"
|
description: "List of user identifiers with access to the associated resource"
|
||||||
|
@ -3520,11 +3518,10 @@ definitions:
|
||||||
ResourceControlUpdateRequest:
|
ResourceControlUpdateRequest:
|
||||||
type: "object"
|
type: "object"
|
||||||
properties:
|
properties:
|
||||||
AdministratorsOnly:
|
Public:
|
||||||
type: "boolean"
|
type: "boolean"
|
||||||
example: false
|
example: false
|
||||||
description: "Restrict access to the associated resource to administrators\
|
description: "Permit access to the associated resource to any user"
|
||||||
\ only"
|
|
||||||
Users:
|
Users:
|
||||||
type: "array"
|
type: "array"
|
||||||
description: "List of user identifiers with access to the associated resource"
|
description: "List of user identifiers with access to the associated resource"
|
||||||
|
|
|
@ -63,7 +63,7 @@
|
||||||
<td ng-if="$ctrl.showOwnershipColumn">
|
<td ng-if="$ctrl.showOwnershipColumn">
|
||||||
<span>
|
<span>
|
||||||
<i ng-class="item.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
|
<i ng-class="item.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
|
||||||
{{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = 'public' }}
|
{{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = 'administrators' }}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -244,7 +244,7 @@
|
||||||
<td ng-if="$ctrl.showOwnershipColumn" ng-show="$ctrl.columnVisibility.columns.ownership.display">
|
<td ng-if="$ctrl.showOwnershipColumn" ng-show="$ctrl.columnVisibility.columns.ownership.display">
|
||||||
<span>
|
<span>
|
||||||
<i ng-class="item.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
|
<i ng-class="item.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
|
||||||
{{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = 'public' }}
|
{{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = 'administrators' }}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -111,7 +111,7 @@
|
||||||
<td ng-if="$ctrl.showOwnershipColumn">
|
<td ng-if="$ctrl.showOwnershipColumn">
|
||||||
<span>
|
<span>
|
||||||
<i ng-class="item.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
|
<i ng-class="item.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
|
||||||
{{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = 'public' }}
|
{{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = 'administrators' }}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -63,7 +63,7 @@
|
||||||
<td ng-if="$ctrl.showOwnershipColumn">
|
<td ng-if="$ctrl.showOwnershipColumn">
|
||||||
<span>
|
<span>
|
||||||
<i ng-class="item.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
|
<i ng-class="item.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
|
||||||
{{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = 'public' }}
|
{{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = 'administrators' }}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -118,7 +118,7 @@
|
||||||
<td ng-if="$ctrl.showOwnershipColumn">
|
<td ng-if="$ctrl.showOwnershipColumn">
|
||||||
<span>
|
<span>
|
||||||
<i ng-class="item.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
|
<i ng-class="item.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
|
||||||
{{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = 'public' }}
|
{{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = 'administrators' }}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -115,7 +115,7 @@
|
||||||
<td ng-if="$ctrl.showOwnershipColumn">
|
<td ng-if="$ctrl.showOwnershipColumn">
|
||||||
<span>
|
<span>
|
||||||
<i ng-class="item.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
|
<i ng-class="item.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
|
||||||
{{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = 'public' }}
|
{{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = 'administrators' }}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -285,7 +285,7 @@ function ($q, $scope, $state, $transition$, $filter, Commit, ContainerHelper, Co
|
||||||
var teams = resourceControl.TeamAccesses.map(function(t) {
|
var teams = resourceControl.TeamAccesses.map(function(t) {
|
||||||
return t.TeamId;
|
return t.TeamId;
|
||||||
});
|
});
|
||||||
return ResourceControlService.createResourceControl(resourceControl.AdministratorsOnly, users, teams, containerIdentifier, 'container', []);
|
return ResourceControlService.createResourceControl(resourceControl.Public, users, teams, containerIdentifier, 'container', []);
|
||||||
}
|
}
|
||||||
|
|
||||||
function notifyAndChangeView() {
|
function notifyAndChangeView() {
|
||||||
|
|
|
@ -11,12 +11,12 @@
|
||||||
<td>
|
<td>
|
||||||
<i ng-class="$ctrl.resourceControl.Ownership | ownershipicon" aria-hidden="true" style="margin-right: 2px;"></i>
|
<i ng-class="$ctrl.resourceControl.Ownership | ownershipicon" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||||
<span ng-if="!$ctrl.resourceControl">
|
<span ng-if="!$ctrl.resourceControl">
|
||||||
public
|
administrators
|
||||||
<portainer-tooltip message="This resource can be managed by any user with access to this endpoint." position="bottom" style="margin-left: -3px;"></portainer-tooltip>
|
<portainer-tooltip message="This resource can only be managed by administrators." position="bottom" style="margin-left: -3px;"></portainer-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<span ng-if="$ctrl.resourceControl">
|
<span ng-if="$ctrl.resourceControl">
|
||||||
{{ $ctrl.resourceControl.Ownership }}
|
{{ $ctrl.resourceControl.Ownership }}
|
||||||
<portainer-tooltip ng-if="$ctrl.resourceControl.Ownership === 'administrators'" message="This resource can only be managed by administrators." position="bottom" style="margin-left: -3px;"></portainer-tooltip>
|
<portainer-tooltip ng-if="$ctrl.resourceControl.Ownership === 'public'" message="This resource can be managed by any user with access to this endpoint." position="bottom" style="margin-left: -3px;"></portainer-tooltip>
|
||||||
<portainer-tooltip ng-if="$ctrl.resourceControl.Ownership === 'private'" message="Management of this resource is restricted to a single user." position="bottom" style="margin-left: -3px;"></portainer-tooltip>
|
<portainer-tooltip ng-if="$ctrl.resourceControl.Ownership === 'private'" message="Management of this resource is restricted to a single user." position="bottom" style="margin-left: -3px;"></portainer-tooltip>
|
||||||
<portainer-tooltip ng-if="$ctrl.resourceControl.Ownership === 'restricted'" message="This resource can be managed by a restricted set of users and/or teams." position="bottom" style="margin-left: -3px;"></portainer-tooltip>
|
<portainer-tooltip ng-if="$ctrl.resourceControl.Ownership === 'restricted'" message="This resource can be managed by a restricted set of users and/or teams." position="bottom" style="margin-left: -3px;"></portainer-tooltip>
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -12,7 +12,7 @@ function ($q, $state, UserService, TeamService, ResourceControlService, Notifica
|
||||||
};
|
};
|
||||||
|
|
||||||
ctrl.formValues = {
|
ctrl.formValues = {
|
||||||
Ownership: 'public',
|
Ownership: 'administrators',
|
||||||
Ownership_Users: [],
|
Ownership_Users: [],
|
||||||
Ownership_Teams: []
|
Ownership_Teams: []
|
||||||
};
|
};
|
||||||
|
@ -60,13 +60,14 @@ function ($q, $state, UserService, TeamService, ResourceControlService, Notifica
|
||||||
angular.forEach(ctrl.formValues.Ownership_Teams, function(team) {
|
angular.forEach(ctrl.formValues.Ownership_Teams, function(team) {
|
||||||
teamIds.push(team.Id);
|
teamIds.push(team.Id);
|
||||||
});
|
});
|
||||||
var administratorsOnly = ctrl.formValues.Ownership === 'administrators' ? true : false;
|
|
||||||
|
var publicOnly = ctrl.formValues.Ownership === 'public' ? true : false;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ownership: ctrl.formValues.Ownership,
|
ownership: ctrl.formValues.Ownership,
|
||||||
authorizedUserIds: administratorsOnly ? [] : userIds,
|
authorizedUserIds: publicOnly ? [] : userIds,
|
||||||
authorizedTeamIds: administratorsOnly ? [] : teamIds,
|
authorizedTeamIds: publicOnly ? [] : teamIds,
|
||||||
administratorsOnly: administratorsOnly
|
publicOnly: publicOnly
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,12 +97,13 @@ function ($q, $state, UserService, TeamService, ResourceControlService, Notifica
|
||||||
if (resourceControl) {
|
if (resourceControl) {
|
||||||
ctrl.formValues.Ownership = resourceControl.Ownership === 'private' ? 'restricted' : resourceControl.Ownership;
|
ctrl.formValues.Ownership = resourceControl.Ownership === 'private' ? 'restricted' : resourceControl.Ownership;
|
||||||
} else {
|
} else {
|
||||||
ctrl.formValues.Ownership = 'public';
|
ctrl.formValues.Ownership = 'administrators';
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ctrl.formValues.Ownership = 'public';
|
ctrl.formValues.Ownership = 'administrators';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ResourceControlService.retrieveOwnershipDetails(resourceControl)
|
ResourceControlService.retrieveOwnershipDetails(resourceControl)
|
||||||
.then(function success(data) {
|
.then(function success(data) {
|
||||||
ctrl.authorizedUsers = data.authorizedUsers;
|
ctrl.authorizedUsers = data.authorizedUsers;
|
||||||
|
|
|
@ -70,7 +70,7 @@
|
||||||
<td ng-if="$ctrl.showOwnershipColumn">
|
<td ng-if="$ctrl.showOwnershipColumn">
|
||||||
<span>
|
<span>
|
||||||
<i ng-class="item.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
|
<i ng-class="item.ResourceControl.Ownership | ownershipicon" aria-hidden="true"></i>
|
||||||
{{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = 'public' }}
|
{{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = 'administrators' }}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -4,16 +4,18 @@ function ResourceControlViewModel(data) {
|
||||||
this.ResourceId = data.ResourceId;
|
this.ResourceId = data.ResourceId;
|
||||||
this.UserAccesses = data.UserAccesses;
|
this.UserAccesses = data.UserAccesses;
|
||||||
this.TeamAccesses = data.TeamAccesses;
|
this.TeamAccesses = data.TeamAccesses;
|
||||||
this.AdministratorsOnly = data.AdministratorsOnly;
|
this.Public = data.Public;
|
||||||
this.Ownership = determineOwnership(this);
|
this.Ownership = determineOwnership(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
function determineOwnership(resourceControl) {
|
function determineOwnership(resourceControl) {
|
||||||
if (resourceControl.AdministratorsOnly) {
|
if (resourceControl.Public) {
|
||||||
return 'administrators';
|
return 'public';
|
||||||
} else if (resourceControl.UserAccesses.length === 1 && resourceControl.TeamAccesses.length === 0) {
|
} else if (resourceControl.UserAccesses.length === 1 && resourceControl.TeamAccesses.length === 0) {
|
||||||
return 'private';
|
return 'private';
|
||||||
} else if (resourceControl.UserAccesses.length > 1 || resourceControl.TeamAccesses.length > 0) {
|
} else if (resourceControl.UserAccesses.length > 1 || resourceControl.TeamAccesses.length > 0) {
|
||||||
return 'restricted';
|
return 'restricted';
|
||||||
|
} else {
|
||||||
|
return 'administrators';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,10 +3,10 @@ angular.module('portainer.app')
|
||||||
'use strict';
|
'use strict';
|
||||||
var service = {};
|
var service = {};
|
||||||
|
|
||||||
service.createResourceControl = function(administratorsOnly, userIDs, teamIDs, resourceID, type, subResourceIDs) {
|
service.createResourceControl = function(publicOnly, userIDs, teamIDs, resourceID, type, subResourceIDs) {
|
||||||
var payload = {
|
var payload = {
|
||||||
Type: type,
|
Type: type,
|
||||||
AdministratorsOnly: administratorsOnly,
|
Public: publicOnly,
|
||||||
ResourceID: resourceID,
|
ResourceID: resourceID,
|
||||||
Users: userIDs,
|
Users: userIDs,
|
||||||
Teams: teamIDs,
|
Teams: teamIDs,
|
||||||
|
@ -19,9 +19,9 @@ angular.module('portainer.app')
|
||||||
return ResourceControl.remove({id: rcID}).$promise;
|
return ResourceControl.remove({id: rcID}).$promise;
|
||||||
};
|
};
|
||||||
|
|
||||||
service.updateResourceControl = function(admin, userIDs, teamIDs, resourceControlId) {
|
service.updateResourceControl = function(publicOnly, userIDs, teamIDs, resourceControlId) {
|
||||||
var payload = {
|
var payload = {
|
||||||
AdministratorsOnly: admin,
|
Public: publicOnly,
|
||||||
Users: userIDs,
|
Users: userIDs,
|
||||||
Teams: teamIDs
|
Teams: teamIDs
|
||||||
};
|
};
|
||||||
|
@ -30,15 +30,15 @@ angular.module('portainer.app')
|
||||||
|
|
||||||
service.applyResourceControl = function(resourceControlType, resourceIdentifier, userId, accessControlData, subResources) {
|
service.applyResourceControl = function(resourceControlType, resourceIdentifier, userId, accessControlData, subResources) {
|
||||||
if (!accessControlData.AccessControlEnabled) {
|
if (!accessControlData.AccessControlEnabled) {
|
||||||
return;
|
accessControlData.Ownership = 'public';
|
||||||
}
|
}
|
||||||
|
|
||||||
var authorizedUserIds = [];
|
var authorizedUserIds = [];
|
||||||
var authorizedTeamIds = [];
|
var authorizedTeamIds = [];
|
||||||
var administratorsOnly = false;
|
var publicOnly = false;
|
||||||
switch (accessControlData.Ownership) {
|
switch (accessControlData.Ownership) {
|
||||||
case 'administrators':
|
case 'public':
|
||||||
administratorsOnly = true;
|
publicOnly = true;
|
||||||
break;
|
break;
|
||||||
case 'private':
|
case 'private':
|
||||||
authorizedUserIds.push(userId);
|
authorizedUserIds.push(userId);
|
||||||
|
@ -51,21 +51,23 @@ angular.module('portainer.app')
|
||||||
authorizedTeamIds.push(team.Id);
|
authorizedTeamIds.push(team.Id);
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
return service.createResourceControl(administratorsOnly, authorizedUserIds,
|
return service.createResourceControl(publicOnly, authorizedUserIds,
|
||||||
authorizedTeamIds, resourceIdentifier, resourceControlType, subResources);
|
authorizedTeamIds, resourceIdentifier, resourceControlType, subResources);
|
||||||
};
|
};
|
||||||
|
|
||||||
service.applyResourceControlChange = function(resourceControlType, resourceId, resourceControl, ownershipParameters) {
|
service.applyResourceControlChange = function(resourceControlType, resourceId, resourceControl, ownershipParameters) {
|
||||||
if (resourceControl) {
|
if (resourceControl) {
|
||||||
if (ownershipParameters.ownership === 'public') {
|
if (ownershipParameters.ownership === 'administrators') {
|
||||||
return service.deleteResourceControl(resourceControl.Id);
|
return service.deleteResourceControl(resourceControl.Id);
|
||||||
} else {
|
} else {
|
||||||
return service.updateResourceControl(ownershipParameters.administratorsOnly, ownershipParameters.authorizedUserIds,
|
return service.updateResourceControl(ownershipParameters.publicOnly, ownershipParameters.authorizedUserIds,
|
||||||
ownershipParameters.authorizedTeamIds, resourceControl.Id);
|
ownershipParameters.authorizedTeamIds, resourceControl.Id);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return service.createResourceControl(ownershipParameters.administratorsOnly, ownershipParameters.authorizedUserIds,
|
return service.createResourceControl(ownershipParameters.publicOnly, ownershipParameters.authorizedUserIds,
|
||||||
ownershipParameters.authorizedTeamIds, resourceId, resourceControlType);
|
ownershipParameters.authorizedTeamIds, resourceId, resourceControlType);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue