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 @@ - {{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = 'public' }} + {{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = 'administrators' }} diff --git a/app/docker/components/datatables/containers-datatable/containersDatatable.html b/app/docker/components/datatables/containers-datatable/containersDatatable.html index 2119d8f35..19cd2a0c0 100644 --- a/app/docker/components/datatables/containers-datatable/containersDatatable.html +++ b/app/docker/components/datatables/containers-datatable/containersDatatable.html @@ -244,7 +244,7 @@ - {{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = 'public' }} + {{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = 'administrators' }} diff --git a/app/docker/components/datatables/networks-datatable/networksDatatable.html b/app/docker/components/datatables/networks-datatable/networksDatatable.html index 98fb15791..7e88a2549 100644 --- a/app/docker/components/datatables/networks-datatable/networksDatatable.html +++ b/app/docker/components/datatables/networks-datatable/networksDatatable.html @@ -111,7 +111,7 @@ - {{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = 'public' }} + {{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = 'administrators' }} diff --git a/app/docker/components/datatables/secrets-datatable/secretsDatatable.html b/app/docker/components/datatables/secrets-datatable/secretsDatatable.html index 4e6a6c148..ed5249557 100644 --- a/app/docker/components/datatables/secrets-datatable/secretsDatatable.html +++ b/app/docker/components/datatables/secrets-datatable/secretsDatatable.html @@ -63,7 +63,7 @@ - {{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = 'public' }} + {{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = 'administrators' }} diff --git a/app/docker/components/datatables/services-datatable/servicesDatatable.html b/app/docker/components/datatables/services-datatable/servicesDatatable.html index 8d25b3bf1..8ed52ff2b 100644 --- a/app/docker/components/datatables/services-datatable/servicesDatatable.html +++ b/app/docker/components/datatables/services-datatable/servicesDatatable.html @@ -118,7 +118,7 @@ - {{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = 'public' }} + {{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = 'administrators' }} diff --git a/app/docker/components/datatables/volumes-datatable/volumesDatatable.html b/app/docker/components/datatables/volumes-datatable/volumesDatatable.html index c93f506f4..60632f296 100644 --- a/app/docker/components/datatables/volumes-datatable/volumesDatatable.html +++ b/app/docker/components/datatables/volumes-datatable/volumesDatatable.html @@ -115,7 +115,7 @@ - {{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = 'public' }} + {{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = 'administrators' }} diff --git a/app/docker/views/containers/edit/containerController.js b/app/docker/views/containers/edit/containerController.js index 22f56e7b7..e2c017632 100644 --- a/app/docker/views/containers/edit/containerController.js +++ b/app/docker/views/containers/edit/containerController.js @@ -285,7 +285,7 @@ function ($q, $scope, $state, $transition$, $filter, Commit, ContainerHelper, Co var teams = resourceControl.TeamAccesses.map(function(t) { return t.TeamId; }); - return ResourceControlService.createResourceControl(resourceControl.AdministratorsOnly, users, teams, containerIdentifier, 'container', []); + return ResourceControlService.createResourceControl(resourceControl.Public, users, teams, containerIdentifier, 'container', []); } function notifyAndChangeView() { diff --git a/app/portainer/components/accessControlPanel/porAccessControlPanel.html b/app/portainer/components/accessControlPanel/porAccessControlPanel.html index 1f5752f0d..0927dd23e 100644 --- a/app/portainer/components/accessControlPanel/porAccessControlPanel.html +++ b/app/portainer/components/accessControlPanel/porAccessControlPanel.html @@ -11,12 +11,12 @@ - public - + administrators + {{ $ctrl.resourceControl.Ownership }} - + diff --git a/app/portainer/components/accessControlPanel/porAccessControlPanelController.js b/app/portainer/components/accessControlPanel/porAccessControlPanelController.js index 940c33a74..142603304 100644 --- a/app/portainer/components/accessControlPanel/porAccessControlPanelController.js +++ b/app/portainer/components/accessControlPanel/porAccessControlPanelController.js @@ -12,7 +12,7 @@ function ($q, $state, UserService, TeamService, ResourceControlService, Notifica }; ctrl.formValues = { - Ownership: 'public', + Ownership: 'administrators', Ownership_Users: [], Ownership_Teams: [] }; @@ -51,7 +51,7 @@ function ($q, $state, UserService, TeamService, ResourceControlService, Notifica return true; } - function processOwnershipFormValues() { + function processOwnershipFormValues() { var userIds = []; angular.forEach(ctrl.formValues.Ownership_Users, function(user) { userIds.push(user.Id); @@ -60,13 +60,14 @@ function ($q, $state, UserService, TeamService, ResourceControlService, Notifica angular.forEach(ctrl.formValues.Ownership_Teams, function(team) { teamIds.push(team.Id); }); - var administratorsOnly = ctrl.formValues.Ownership === 'administrators' ? true : false; + + var publicOnly = ctrl.formValues.Ownership === 'public' ? true : false; return { ownership: ctrl.formValues.Ownership, - authorizedUserIds: administratorsOnly ? [] : userIds, - authorizedTeamIds: administratorsOnly ? [] : teamIds, - administratorsOnly: administratorsOnly + authorizedUserIds: publicOnly ? [] : userIds, + authorizedTeamIds: publicOnly ? [] : teamIds, + publicOnly: publicOnly }; } @@ -96,12 +97,13 @@ function ($q, $state, UserService, TeamService, ResourceControlService, Notifica if (resourceControl) { ctrl.formValues.Ownership = resourceControl.Ownership === 'private' ? 'restricted' : resourceControl.Ownership; } else { - ctrl.formValues.Ownership = 'public'; + ctrl.formValues.Ownership = 'administrators'; } } else { - ctrl.formValues.Ownership = 'public'; + ctrl.formValues.Ownership = 'administrators'; } + ResourceControlService.retrieveOwnershipDetails(resourceControl) .then(function success(data) { ctrl.authorizedUsers = data.authorizedUsers; diff --git a/app/portainer/components/datatables/stacks-datatable/stacksDatatable.html b/app/portainer/components/datatables/stacks-datatable/stacksDatatable.html index 694eb8014..6c8dbbb79 100644 --- a/app/portainer/components/datatables/stacks-datatable/stacksDatatable.html +++ b/app/portainer/components/datatables/stacks-datatable/stacksDatatable.html @@ -70,7 +70,7 @@ - {{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = 'public' }} + {{ item.ResourceControl.Ownership ? item.ResourceControl.Ownership : item.ResourceControl.Ownership = 'administrators' }} diff --git a/app/portainer/models/resourceControl.js b/app/portainer/models/resourceControl.js index f7e08c3f1..28c49602b 100644 --- a/app/portainer/models/resourceControl.js +++ b/app/portainer/models/resourceControl.js @@ -4,16 +4,18 @@ function ResourceControlViewModel(data) { this.ResourceId = data.ResourceId; this.UserAccesses = data.UserAccesses; this.TeamAccesses = data.TeamAccesses; - this.AdministratorsOnly = data.AdministratorsOnly; + this.Public = data.Public; this.Ownership = determineOwnership(this); } function determineOwnership(resourceControl) { - if (resourceControl.AdministratorsOnly) { - return 'administrators'; + if (resourceControl.Public) { + return 'public'; } else if (resourceControl.UserAccesses.length === 1 && resourceControl.TeamAccesses.length === 0) { return 'private'; } else if (resourceControl.UserAccesses.length > 1 || resourceControl.TeamAccesses.length > 0) { return 'restricted'; + } else { + return 'administrators'; } } diff --git a/app/portainer/services/api/resourceControlService.js b/app/portainer/services/api/resourceControlService.js index 1ee0b148c..ff4e60107 100644 --- a/app/portainer/services/api/resourceControlService.js +++ b/app/portainer/services/api/resourceControlService.js @@ -3,10 +3,10 @@ angular.module('portainer.app') 'use strict'; var service = {}; - service.createResourceControl = function(administratorsOnly, userIDs, teamIDs, resourceID, type, subResourceIDs) { + service.createResourceControl = function(publicOnly, userIDs, teamIDs, resourceID, type, subResourceIDs) { var payload = { Type: type, - AdministratorsOnly: administratorsOnly, + Public: publicOnly, ResourceID: resourceID, Users: userIDs, Teams: teamIDs, @@ -19,9 +19,9 @@ angular.module('portainer.app') return ResourceControl.remove({id: rcID}).$promise; }; - service.updateResourceControl = function(admin, userIDs, teamIDs, resourceControlId) { + service.updateResourceControl = function(publicOnly, userIDs, teamIDs, resourceControlId) { var payload = { - AdministratorsOnly: admin, + Public: publicOnly, Users: userIDs, Teams: teamIDs }; @@ -30,15 +30,15 @@ angular.module('portainer.app') service.applyResourceControl = function(resourceControlType, resourceIdentifier, userId, accessControlData, subResources) { if (!accessControlData.AccessControlEnabled) { - return; + accessControlData.Ownership = 'public'; } var authorizedUserIds = []; var authorizedTeamIds = []; - var administratorsOnly = false; + var publicOnly = false; switch (accessControlData.Ownership) { - case 'administrators': - administratorsOnly = true; + case 'public': + publicOnly = true; break; case 'private': authorizedUserIds.push(userId); @@ -51,21 +51,23 @@ angular.module('portainer.app') authorizedTeamIds.push(team.Id); }); break; - } - return service.createResourceControl(administratorsOnly, authorizedUserIds, + default: + return; + } + return service.createResourceControl(publicOnly, authorizedUserIds, authorizedTeamIds, resourceIdentifier, resourceControlType, subResources); }; - service.applyResourceControlChange = function(resourceControlType, resourceId, resourceControl, ownershipParameters) { + service.applyResourceControlChange = function(resourceControlType, resourceId, resourceControl, ownershipParameters) { if (resourceControl) { - if (ownershipParameters.ownership === 'public') { + if (ownershipParameters.ownership === 'administrators') { return service.deleteResourceControl(resourceControl.Id); } else { - return service.updateResourceControl(ownershipParameters.administratorsOnly, ownershipParameters.authorizedUserIds, + return service.updateResourceControl(ownershipParameters.publicOnly, ownershipParameters.authorizedUserIds, ownershipParameters.authorizedTeamIds, resourceControl.Id); } } else { - return service.createResourceControl(ownershipParameters.administratorsOnly, ownershipParameters.authorizedUserIds, + return service.createResourceControl(ownershipParameters.publicOnly, ownershipParameters.authorizedUserIds, ownershipParameters.authorizedTeamIds, resourceId, resourceControlType); } };