diff --git a/api/bolt/migrator/migrate_dbversion13.go b/api/bolt/migrator/migrate_dbversion13.go new file mode 100644 index 000000000..5434d00e2 --- /dev/null +++ b/api/bolt/migrator/migrate_dbversion13.go @@ -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 +} diff --git a/api/bolt/migrator/migrator.go b/api/bolt/migrator/migrator.go index 9c61fc2c7..b70172256 100644 --- a/api/bolt/migrator/migrator.go +++ b/api/bolt/migrator/migrator.go @@ -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) } diff --git a/api/http/handler/resourcecontrols/resourcecontrol_create.go b/api/http/handler/resourcecontrols/resourcecontrol_create.go index baaee3360..10bde9a4f 100644 --- a/api/http/handler/resourcecontrols/resourcecontrol_create.go +++ b/api/http/handler/resourcecontrols/resourcecontrol_create.go @@ -12,12 +12,12 @@ import ( ) type resourceControlCreatePayload struct { - ResourceID string - Type string - AdministratorsOnly bool - Users []int - Teams []int - SubResourceIDs []string + ResourceID string + Type string + Public bool + Users []int + Teams []int + SubResourceIDs []string } func (payload *resourceControlCreatePayload) Validate(r *http.Request) error { @@ -29,8 +29,8 @@ func (payload *resourceControlCreatePayload) Validate(r *http.Request) error { return portainer.Error("Invalid type") } - if len(payload.Users) == 0 && len(payload.Teams) == 0 && !payload.AdministratorsOnly { - return portainer.Error("Invalid resource control declaration. Must specify Users, Teams or AdministratorOnly") + if len(payload.Users) == 0 && len(payload.Teams) == 0 && !payload.Public { + return portainer.Error("Invalid resource control declaration. Must specify Users, Teams or Public") } return nil } @@ -90,12 +90,12 @@ func (handler *Handler) resourceControlCreate(w http.ResponseWriter, r *http.Req } resourceControl := portainer.ResourceControl{ - ResourceID: payload.ResourceID, - SubResourceIDs: payload.SubResourceIDs, - Type: resourceControlType, - AdministratorsOnly: payload.AdministratorsOnly, - UserAccesses: userAccesses, - TeamAccesses: teamAccesses, + ResourceID: payload.ResourceID, + SubResourceIDs: payload.SubResourceIDs, + Type: resourceControlType, + Public: payload.Public, + UserAccesses: userAccesses, + TeamAccesses: teamAccesses, } securityContext, err := security.RetrieveRestrictedRequestContext(r) diff --git a/api/http/handler/resourcecontrols/resourcecontrol_update.go b/api/http/handler/resourcecontrols/resourcecontrol_update.go index 3f3b25799..69299fcee 100644 --- a/api/http/handler/resourcecontrols/resourcecontrol_update.go +++ b/api/http/handler/resourcecontrols/resourcecontrol_update.go @@ -11,14 +11,14 @@ import ( ) type resourceControlUpdatePayload struct { - AdministratorsOnly bool - Users []int - Teams []int + Public bool + Users []int + Teams []int } func (payload *resourceControlUpdatePayload) Validate(r *http.Request) error { - if len(payload.Users) == 0 && len(payload.Teams) == 0 && !payload.AdministratorsOnly { - return portainer.Error("Invalid resource control declaration. Must specify Users, Teams or AdministratorOnly") + if len(payload.Users) == 0 && len(payload.Teams) == 0 && !payload.Public { + return portainer.Error("Invalid resource control declaration. Must specify Users, Teams or Public") } 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} } - resourceControl.AdministratorsOnly = payload.AdministratorsOnly + resourceControl.Public = payload.Public var userAccesses = make([]portainer.UserResourceAccess, 0) for _, v := range payload.Users { diff --git a/api/http/handler/stacks/stack_delete.go b/api/http/handler/stacks/stack_delete.go index 3844a4d7e..f481f808a 100644 --- a/api/http/handler/stacks/stack_delete.go +++ b/api/http/handler/stacks/stack_delete.go @@ -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} } - if resourceControl != nil { - if !securityContext.IsAdmin && !proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) { + if !securityContext.IsAdmin { + if !proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) { return &httperror.HandlerError{http.StatusForbidden, "Access denied to resource", portainer.ErrResourceAccessDenied} } } diff --git a/api/http/handler/stacks/stack_file.go b/api/http/handler/stacks/stack_file.go index f8d25af8d..b86888a37 100644 --- a/api/http/handler/stacks/stack_file.go +++ b/api/http/handler/stacks/stack_file.go @@ -41,6 +41,10 @@ func (handler *Handler) stackFile(w http.ResponseWriter, r *http.Request) *httpe } 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 securityContext.IsAdmin || proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) { extendedStack.ResourceControl = *resourceControl diff --git a/api/http/handler/stacks/stack_inspect.go b/api/http/handler/stacks/stack_inspect.go index 28d5030ac..380482149 100644 --- a/api/http/handler/stacks/stack_inspect.go +++ b/api/http/handler/stacks/stack_inspect.go @@ -36,6 +36,10 @@ func (handler *Handler) stackInspect(w http.ResponseWriter, r *http.Request) *ht } 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 securityContext.IsAdmin || proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) { extendedStack.ResourceControl = *resourceControl diff --git a/api/http/handler/stacks/stack_migrate.go b/api/http/handler/stacks/stack_migrate.go index beb579a34..b6fed5a96 100644 --- a/api/http/handler/stacks/stack_migrate.go +++ b/api/http/handler/stacks/stack_migrate.go @@ -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} } - if resourceControl != nil { - if !securityContext.IsAdmin && !proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) { + if !securityContext.IsAdmin { + if !proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) { return &httperror.HandlerError{http.StatusForbidden, "Access denied to resource", portainer.ErrResourceAccessDenied} } } diff --git a/api/http/handler/stacks/stack_update.go b/api/http/handler/stacks/stack_update.go index de1c560ae..456612249 100644 --- a/api/http/handler/stacks/stack_update.go +++ b/api/http/handler/stacks/stack_update.go @@ -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} } - if resourceControl != nil { - if !securityContext.IsAdmin && !proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) { + if !securityContext.IsAdmin { + if !proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) { return &httperror.HandlerError{http.StatusForbidden, "Access denied to resource", portainer.ErrResourceAccessDenied} } } diff --git a/api/http/proxy/access_control.go b/api/http/proxy/access_control.go index 331b9d0da..e63dc6346 100644 --- a/api/http/proxy/access_control.go +++ b/api/http/proxy/access_control.go @@ -1,6 +1,8 @@ package proxy -import "github.com/portainer/portainer" +import ( + "github.com/portainer/portainer" +) type ( // 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 // 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 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. func applyResourceAccessControlFromLabel(labelsObject, resourceObject map[string]interface{}, labelIdentifier string, context *restrictedOperationContext) (map[string]interface{}, bool) { @@ -24,32 +26,31 @@ func applyResourceAccessControlFromLabel(labelsObject, resourceObject map[string resourceIdentifier := labelsObject[labelIdentifier].(string) return applyResourceAccessControl(resourceObject, resourceIdentifier, context) } - return resourceObject, true + return resourceObject, false } // 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. // 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. -// 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. // Returns the original object and denied access (false) when a resource control is associated to the resource // and the user cannot access the resource. func applyResourceAccessControl(resourceObject map[string]interface{}, resourceIdentifier string, context *restrictedOperationContext) (map[string]interface{}, bool) { - authorizedAccess := true - resourceControl := getResourceControlByResourceID(resourceIdentifier, context.resourceControls) - if resourceControl != nil { - if context.isAdmin || canUserAccessResource(context.userID, context.userTeamIDs, resourceControl) { - resourceObject = decorateObject(resourceObject, resourceControl) - } else { - authorizedAccess = false - } + if resourceControl == nil { + return resourceObject, context.isAdmin } - 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, @@ -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{} { @@ -123,6 +124,10 @@ func getResourceControlByResourceID(resourceID string, resourceControls []portai // CanAccessStack checks if a user can access a stack 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) for _, membership := range memberships { userTeamIDs = append(userTeamIDs, membership.TeamID) @@ -132,7 +137,7 @@ func CanAccessStack(stack *portainer.Stack, resourceControl *portainer.ResourceC return true } - return false + return resourceControl.Public } // 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 { extendedStack := ExtendedStack{stack, portainer.ResourceControl{}} resourceControl := getResourceControlByResourceID(stack.Name, resourceControls) - if resourceControl == nil { + if resourceControl == nil && isAdmin { 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 filteredStacks = append(filteredStacks, extendedStack) } diff --git a/api/http/proxy/containers.go b/api/http/proxy/containers.go index 9da66e21f..4e6c835ff 100644 --- a/api/http/proxy/containers.go +++ b/api/http/proxy/containers.go @@ -62,27 +62,27 @@ func containerInspectOperation(response *http.Response, executor *operationExecu containerID := responseObject[containerIdentifier].(string) responseObject, access := applyResourceAccessControl(responseObject, containerID, executor.operationContext) - if !access { - return rewriteAccessDeniedResponse(response) + if access { + return rewriteResponse(response, responseObject, http.StatusOK) } containerLabels := extractContainerLabelsFromContainerInspectObject(responseObject) responseObject, access = applyResourceAccessControlFromLabel(containerLabels, responseObject, containerLabelForServiceIdentifier, executor.operationContext) - if !access { - return rewriteAccessDeniedResponse(response) + if access { + return rewriteResponse(response, responseObject, http.StatusOK) } responseObject, access = applyResourceAccessControlFromLabel(containerLabels, responseObject, containerLabelForSwarmStackIdentifier, executor.operationContext) - if !access { - return rewriteAccessDeniedResponse(response) + if access { + return rewriteResponse(response, responseObject, http.StatusOK) } responseObject, access = applyResourceAccessControlFromLabel(containerLabels, responseObject, containerLabelForComposeStackIdentifier, executor.operationContext) - if !access { - return rewriteAccessDeniedResponse(response) + if access { + return rewriteResponse(response, responseObject, http.StatusOK) } - return rewriteResponse(response, responseObject, http.StatusOK) + return rewriteAccessDeniedResponse(response) } // extractContainerLabelsFromContainerInspectObject retrieve the Labels of the container if present. @@ -148,19 +148,20 @@ func filterContainerList(containerData []interface{}, context *restrictedOperati containerID := containerObject[containerIdentifier].(string) containerObject, access := applyResourceAccessControl(containerObject, containerID, context) - if access { + if !access { containerLabels := extractContainerLabelsFromContainerListObject(containerObject) containerObject, access = applyResourceAccessControlFromLabel(containerLabels, containerObject, containerLabelForComposeStackIdentifier, context) - if access { + if !access { containerObject, access = applyResourceAccessControlFromLabel(containerLabels, containerObject, containerLabelForServiceIdentifier, context) - if access { + if !access { containerObject, access = applyResourceAccessControlFromLabel(containerLabels, containerObject, containerLabelForSwarmStackIdentifier, context) - if access { - filteredContainerData = append(filteredContainerData, containerObject) - } } } } + + if access { + filteredContainerData = append(filteredContainerData, containerObject) + } } return filteredContainerData, nil diff --git a/api/http/proxy/networks.go b/api/http/proxy/networks.go index 7e40d385a..a131ab2ab 100644 --- a/api/http/proxy/networks.go +++ b/api/http/proxy/networks.go @@ -53,17 +53,17 @@ func networkInspectOperation(response *http.Response, executor *operationExecuto networkID := responseObject[networkIdentifier].(string) responseObject, access := applyResourceAccessControl(responseObject, networkID, executor.operationContext) - if !access { - return rewriteAccessDeniedResponse(response) + if access { + return rewriteResponse(response, responseObject, http.StatusOK) } networkLabels := extractNetworkLabelsFromNetworkInspectObject(responseObject) responseObject, access = applyResourceAccessControlFromLabel(networkLabels, responseObject, networkLabelForStackIdentifier, executor.operationContext) - if !access { - return rewriteAccessDeniedResponse(response) + if access { + return rewriteResponse(response, responseObject, http.StatusOK) } - return rewriteResponse(response, responseObject, http.StatusOK) + return rewriteAccessDeniedResponse(response) } // extractNetworkLabelsFromNetworkInspectObject retrieve the Labels of the network if present. @@ -121,12 +121,13 @@ func filterNetworkList(networkData []interface{}, context *restrictedOperationCo networkID := networkObject[networkIdentifier].(string) networkObject, access := applyResourceAccessControl(networkObject, networkID, context) - if access { + if !access { networkLabels := extractNetworkLabelsFromNetworkListObject(networkObject) networkObject, access = applyResourceAccessControlFromLabel(networkLabels, networkObject, networkLabelForStackIdentifier, context) - if access { - filteredNetworkData = append(filteredNetworkData, networkObject) - } + } + + if access { + filteredNetworkData = append(filteredNetworkData, networkObject) } } diff --git a/api/http/proxy/services.go b/api/http/proxy/services.go index 9934cf381..b11208b58 100644 --- a/api/http/proxy/services.go +++ b/api/http/proxy/services.go @@ -53,17 +53,17 @@ func serviceInspectOperation(response *http.Response, executor *operationExecuto serviceID := responseObject[serviceIdentifier].(string) responseObject, access := applyResourceAccessControl(responseObject, serviceID, executor.operationContext) - if !access { - return rewriteAccessDeniedResponse(response) + if access { + return rewriteResponse(response, responseObject, http.StatusOK) } serviceLabels := extractServiceLabelsFromServiceInspectObject(responseObject) responseObject, access = applyResourceAccessControlFromLabel(serviceLabels, responseObject, serviceLabelForStackIdentifier, executor.operationContext) - if !access { - return rewriteAccessDeniedResponse(response) + if access { + return rewriteResponse(response, responseObject, http.StatusOK) } - return rewriteResponse(response, responseObject, http.StatusOK) + return rewriteAccessDeniedResponse(response) } // extractServiceLabelsFromServiceInspectObject retrieve the Labels of the service if present. @@ -129,12 +129,13 @@ func filterServiceList(serviceData []interface{}, context *restrictedOperationCo serviceID := serviceObject[serviceIdentifier].(string) serviceObject, access := applyResourceAccessControl(serviceObject, serviceID, context) - if access { + if !access { serviceLabels := extractServiceLabelsFromServiceListObject(serviceObject) serviceObject, access = applyResourceAccessControlFromLabel(serviceLabels, serviceObject, serviceLabelForStackIdentifier, context) - if access { - filteredServiceData = append(filteredServiceData, serviceObject) - } + } + + if access { + filteredServiceData = append(filteredServiceData, serviceObject) } } diff --git a/api/http/proxy/tasks.go b/api/http/proxy/tasks.go index daea6b926..d75ce54ec 100644 --- a/api/http/proxy/tasks.go +++ b/api/http/proxy/tasks.go @@ -65,12 +65,13 @@ func filterTaskList(taskData []interface{}, context *restrictedOperationContext) serviceID := taskObject[taskServiceIdentifier].(string) taskObject, access := applyResourceAccessControl(taskObject, serviceID, context) - if access { + if !access { taskLabels := extractTaskLabelsFromTaskListObject(taskObject) taskObject, access = applyResourceAccessControlFromLabel(taskLabels, taskObject, taskLabelForStackIdentifier, context) - if access { - filteredTaskData = append(filteredTaskData, taskObject) - } + } + + if access { + filteredTaskData = append(filteredTaskData, taskObject) } } diff --git a/api/http/proxy/volumes.go b/api/http/proxy/volumes.go index c0582a6bd..82f1b5cf6 100644 --- a/api/http/proxy/volumes.go +++ b/api/http/proxy/volumes.go @@ -62,17 +62,17 @@ func volumeInspectOperation(response *http.Response, executor *operationExecutor volumeID := responseObject[volumeIdentifier].(string) responseObject, access := applyResourceAccessControl(responseObject, volumeID, executor.operationContext) - if !access { - return rewriteAccessDeniedResponse(response) + if access { + return rewriteResponse(response, responseObject, http.StatusOK) } volumeLabels := extractVolumeLabelsFromVolumeInspectObject(responseObject) responseObject, access = applyResourceAccessControlFromLabel(volumeLabels, responseObject, volumeLabelForStackIdentifier, executor.operationContext) - if !access { - return rewriteAccessDeniedResponse(response) + if access { + return rewriteResponse(response, responseObject, http.StatusOK) } - return rewriteResponse(response, responseObject, http.StatusOK) + return rewriteAccessDeniedResponse(response) } // extractVolumeLabelsFromVolumeInspectObject retrieve the Labels of the volume if present. @@ -130,12 +130,13 @@ func filterVolumeList(volumeData []interface{}, context *restrictedOperationCont volumeID := volumeObject[volumeIdentifier].(string) volumeObject, access := applyResourceAccessControl(volumeObject, volumeID, context) - if access { + if !access { volumeLabels := extractVolumeLabelsFromVolumeListObject(volumeObject) volumeObject, access = applyResourceAccessControlFromLabel(volumeLabels, volumeObject, volumeLabelForStackIdentifier, context) - if access { - filteredVolumeData = append(filteredVolumeData, volumeObject) - } + } + + if access { + filteredVolumeData = append(filteredVolumeData, volumeObject) } } diff --git a/api/http/security/authorization.go b/api/http/security/authorization.go index 7fa7a6f31..6ffa3a0e4 100644 --- a/api/http/security/authorization.go +++ b/api/http/security/authorization.go @@ -1,21 +1,19 @@ package security -import "github.com/portainer/portainer" +import ( + "github.com/portainer/portainer" +) // AuthorizedResourceControlDeletion ensure that the user can delete a resource control object. // 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 a member of any team within the team accesses func AuthorizedResourceControlDeletion(resourceControl *portainer.ResourceControl, context *RestrictedRequestContext) bool { - if context.IsAdmin { + if context.IsAdmin || resourceControl.Public { return true } - if resourceControl.AdministratorsOnly { - return false - } - userAccessesCount := len(resourceControl.UserAccesses) teamAccessesCount := len(resourceControl.TeamAccesses) @@ -42,39 +40,25 @@ func AuthorizedResourceControlDeletion(resourceControl *portainer.ResourceContro // AuthorizedResourceControlAccess checks whether the user can alter an existing resource control. func AuthorizedResourceControlAccess(resourceControl *portainer.ResourceControl, context *RestrictedRequestContext) bool { - if context.IsAdmin { + if context.IsAdmin || resourceControl.Public { return true } - if resourceControl.AdministratorsOnly { - return false - } - - authorizedTeamAccess := false for _, access := range resourceControl.TeamAccesses { for _, membership := range context.UserMemberships { if membership.TeamID == access.TeamID { - authorizedTeamAccess = true - break + return true } } } - if !authorizedTeamAccess { - return false - } - authorizedUserAccess := false for _, access := range resourceControl.UserAccesses { if context.UserID == access.UserID { - authorizedUserAccess = true - break + return true } } - if !authorizedUserAccess { - return false - } - return true + return false } // 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. // 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 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 to add a team he is not a member of func AuthorizedResourceControlCreation(resourceControl *portainer.ResourceControl, context *RestrictedRequestContext) bool { - if context.IsAdmin { + if context.IsAdmin || resourceControl.Public { return true } - if resourceControl.AdministratorsOnly { - return false - } - userAccessesCount := len(resourceControl.UserAccesses) teamAccessesCount := len(resourceControl.TeamAccesses) @@ -126,19 +106,15 @@ func AuthorizedResourceControlCreation(resourceControl *portainer.ResourceContro if teamAccessesCount > 0 { for _, access := range resourceControl.TeamAccesses { - isMember := false for _, membership := range context.UserMemberships { 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. diff --git a/api/portainer.go b/api/portainer.go index 1f2d6b26a..4c65d1149 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -274,18 +274,21 @@ type ( // ResourceControl represent a reference to a Docker resource with specific access controls ResourceControl struct { - ID ResourceControlID `json:"Id"` - ResourceID string `json:"ResourceId"` - SubResourceIDs []string `json:"SubResourceIds"` - Type ResourceControlType `json:"Type"` - AdministratorsOnly bool `json:"AdministratorsOnly"` - UserAccesses []UserResourceAccess `json:"UserAccesses"` - TeamAccesses []TeamResourceAccess `json:"TeamAccesses"` + ID ResourceControlID `json:"Id"` + ResourceID string `json:"ResourceId"` + SubResourceIDs []string `json:"SubResourceIds"` + Type ResourceControlType `json:"Type"` + UserAccesses []UserResourceAccess `json:"UserAccesses"` + TeamAccesses []TeamResourceAccess `json:"TeamAccesses"` + Public bool `json:"Public"` // Deprecated fields // Deprecated in DBVersion == 2 OwnerID UserID `json:"OwnerId,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...). @@ -613,7 +616,7 @@ const ( // APIVersion is the version number of the Portainer API. APIVersion = "1.19.2-dev" // 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 = "Portainer-Agent" // PortainerAgentTargetHeader represent the name of the header containing the target node name. diff --git a/api/swagger.yaml b/api/swagger.yaml index 1e975c5bf..f589efd7f 100644 --- a/api/swagger.yaml +++ b/api/swagger.yaml @@ -3268,11 +3268,10 @@ definitions: example: "container" description: "Type of Docker resource. Valid values are: container, volume\ \ service, secret, config or stack" - AdministratorsOnly: + Public: type: "boolean" example: true - description: "Restrict access to the associated resource to administrators\ - \ only" + description: "Permit access to the associated resource to any user" Users: type: "array" description: "List of user identifiers with access to the associated resource" @@ -3491,11 +3490,10 @@ definitions: example: "container" description: "Type of Docker resource. Valid values are: container, volume\ \ service, secret, config or stack" - AdministratorsOnly: + Public: type: "boolean" example: true - description: "Restrict access to the associated resource to administrators\ - \ only" + description: "Permit access to the associated resource to any user" Users: type: "array" description: "List of user identifiers with access to the associated resource" @@ -3520,11 +3518,10 @@ definitions: ResourceControlUpdateRequest: type: "object" properties: - AdministratorsOnly: + Public: type: "boolean" example: false - description: "Restrict access to the associated resource to administrators\ - \ only" + description: "Permit access to the associated resource to any user" Users: type: "array" description: "List of user identifiers with access to the associated resource" diff --git a/app/docker/components/datatables/configs-datatable/configsDatatable.html b/app/docker/components/datatables/configs-datatable/configsDatatable.html index 7c9e5078f..b1e462101 100644 --- a/app/docker/components/datatables/configs-datatable/configsDatatable.html +++ b/app/docker/components/datatables/configs-datatable/configsDatatable.html @@ -63,7 +63,7 @@