diff --git a/api/authorizations.go b/api/authorizations.go index a1e782123..c5dc5c967 100644 --- a/api/authorizations.go +++ b/api/authorizations.go @@ -5,6 +5,7 @@ package portainer type AuthorizationService struct { endpointService EndpointService endpointGroupService EndpointGroupService + registryService RegistryService roleService RoleService teamMembershipService TeamMembershipService userService UserService @@ -15,6 +16,7 @@ type AuthorizationService struct { type AuthorizationServiceParameters struct { EndpointService EndpointService EndpointGroupService EndpointGroupService + RegistryService RegistryService RoleService RoleService TeamMembershipService TeamMembershipService UserService UserService @@ -25,6 +27,7 @@ func NewAuthorizationService(parameters *AuthorizationServiceParameters) *Author return &AuthorizationService{ endpointService: parameters.EndpointService, endpointGroupService: parameters.EndpointGroupService, + registryService: parameters.RegistryService, roleService: parameters.RoleService, teamMembershipService: parameters.TeamMembershipService, userService: parameters.UserService, @@ -53,43 +56,145 @@ func DefaultPortainerAuthorizations() Authorizations { } } -// UpdateUserAuthorizationsFromPolicies will update users authorizations based on the specified access policies. -func (service *AuthorizationService) UpdateUserAuthorizationsFromPolicies(userPolicies *UserAccessPolicies, teamPolicies *TeamAccessPolicies) error { - - for userID, policy := range *userPolicies { - if policy.RoleID == 0 { - continue - } - - err := service.UpdateUserAuthorizations(userID) - if err != nil { - return err - } - } - - for teamID, policy := range *teamPolicies { - if policy.RoleID == 0 { - continue - } - - err := service.updateUserAuthorizationsInTeam(teamID) - if err != nil { - return err - } - } - - return nil -} - -func (service *AuthorizationService) updateUserAuthorizationsInTeam(teamID TeamID) error { - - memberships, err := service.teamMembershipService.TeamMembershipsByTeamID(teamID) +// RemoveTeamAccessPolicies will remove all existing access policies associated to the specified team +func (service *AuthorizationService) RemoveTeamAccessPolicies(teamID TeamID) error { + endpoints, err := service.endpointService.Endpoints() if err != nil { return err } - for _, membership := range memberships { - err := service.UpdateUserAuthorizations(membership.UserID) + for _, endpoint := range endpoints { + for policyTeamID := range endpoint.TeamAccessPolicies { + if policyTeamID == teamID { + delete(endpoint.TeamAccessPolicies, policyTeamID) + + err := service.endpointService.UpdateEndpoint(endpoint.ID, &endpoint) + if err != nil { + return err + } + + break + } + } + } + + endpointGroups, err := service.endpointGroupService.EndpointGroups() + if err != nil { + return err + } + + for _, endpointGroup := range endpointGroups { + for policyTeamID := range endpointGroup.TeamAccessPolicies { + if policyTeamID == teamID { + delete(endpointGroup.TeamAccessPolicies, policyTeamID) + + err := service.endpointGroupService.UpdateEndpointGroup(endpointGroup.ID, &endpointGroup) + if err != nil { + return err + } + + break + } + } + } + + registries, err := service.registryService.Registries() + if err != nil { + return err + } + + for _, registry := range registries { + for policyTeamID := range registry.TeamAccessPolicies { + if policyTeamID == teamID { + delete(registry.TeamAccessPolicies, policyTeamID) + + err := service.registryService.UpdateRegistry(registry.ID, ®istry) + if err != nil { + return err + } + + break + } + } + } + + return nil +} + +// RemoveUserAccessPolicies will remove all existing access policies associated to the specified user +func (service *AuthorizationService) RemoveUserAccessPolicies(userID UserID) error { + endpoints, err := service.endpointService.Endpoints() + if err != nil { + return err + } + + for _, endpoint := range endpoints { + for policyUserID := range endpoint.UserAccessPolicies { + if policyUserID == userID { + delete(endpoint.UserAccessPolicies, policyUserID) + + err := service.endpointService.UpdateEndpoint(endpoint.ID, &endpoint) + if err != nil { + return err + } + + break + } + } + } + + endpointGroups, err := service.endpointGroupService.EndpointGroups() + if err != nil { + return err + } + + for _, endpointGroup := range endpointGroups { + for policyUserID := range endpointGroup.UserAccessPolicies { + if policyUserID == userID { + delete(endpointGroup.UserAccessPolicies, policyUserID) + + err := service.endpointGroupService.UpdateEndpointGroup(endpointGroup.ID, &endpointGroup) + if err != nil { + return err + } + + break + } + } + } + + registries, err := service.registryService.Registries() + if err != nil { + return err + } + + for _, registry := range registries { + for policyUserID := range registry.UserAccessPolicies { + if policyUserID == userID { + delete(registry.UserAccessPolicies, policyUserID) + + err := service.registryService.UpdateRegistry(registry.ID, ®istry) + if err != nil { + return err + } + + break + } + } + } + + return nil +} + +// UpdateUsersAuthorizations will trigger an update of the authorizations for all the users. +func (service *AuthorizationService) UpdateUsersAuthorizations() error { + users, err := service.userService.Users() + if err != nil { + return err + } + + for _, user := range users { + err := service.updateUserAuthorizations(user.ID) if err != nil { return err } @@ -98,8 +203,7 @@ func (service *AuthorizationService) updateUserAuthorizationsInTeam(teamID TeamI return nil } -// UpdateUserAuthorizations will trigger an update of the authorizations for the specified user. -func (service *AuthorizationService) UpdateUserAuthorizations(userID UserID) error { +func (service *AuthorizationService) updateUserAuthorizations(userID UserID) error { user, err := service.userService.User(userID) if err != nil { return err @@ -175,7 +279,10 @@ func getUserEndpointAuthorizations(user *User, endpoints []Endpoint, endpointGro continue } - endpointAuthorizations[endpoint.ID] = getAuthorizationsFromTeamEndpointGroupPolicies(userMemberships, &endpoint, roles, groupTeamAccessPolicies) + authorizations = getAuthorizationsFromTeamEndpointGroupPolicies(userMemberships, &endpoint, roles, groupTeamAccessPolicies) + if len(authorizations) > 0 { + endpointAuthorizations[endpoint.ID] = authorizations + } } return endpointAuthorizations diff --git a/api/bolt/migrator/migrate_dbversion19.go b/api/bolt/migrator/migrate_dbversion19.go index c6020e658..569bc43a9 100644 --- a/api/bolt/migrator/migrate_dbversion19.go +++ b/api/bolt/migrator/migrate_dbversion19.go @@ -3,27 +3,15 @@ package migrator import portainer "github.com/portainer/portainer/api" func (m *Migrator) updateUsersToDBVersion20() error { - legacyUsers, err := m.userService.Users() - if err != nil { - return err - } - authorizationServiceParameters := &portainer.AuthorizationServiceParameters{ EndpointService: m.endpointService, EndpointGroupService: m.endpointGroupService, + RegistryService: m.registryService, RoleService: m.roleService, TeamMembershipService: m.teamMembershipService, UserService: m.userService, } authorizationService := portainer.NewAuthorizationService(authorizationServiceParameters) - - for _, user := range legacyUsers { - err := authorizationService.UpdateUserAuthorizations(user.ID) - if err != nil { - return err - } - } - - return nil + return authorizationService.UpdateUsersAuthorizations() } diff --git a/api/http/handler/endpointgroups/endpointgroup_delete.go b/api/http/handler/endpointgroups/endpointgroup_delete.go index a7a3c0a79..dbb634eff 100644 --- a/api/http/handler/endpointgroups/endpointgroup_delete.go +++ b/api/http/handler/endpointgroups/endpointgroup_delete.go @@ -37,8 +37,10 @@ func (handler *Handler) endpointGroupDelete(w http.ResponseWriter, r *http.Reque return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve endpoints from the database", err} } + updateAuthorizations := false for _, endpoint := range endpoints { if endpoint.GroupID == portainer.EndpointGroupID(endpointGroupID) { + updateAuthorizations = true endpoint.GroupID = portainer.EndpointGroupID(1) err = handler.EndpointService.UpdateEndpoint(endpoint.ID, &endpoint) if err != nil { @@ -47,5 +49,12 @@ func (handler *Handler) endpointGroupDelete(w http.ResponseWriter, r *http.Reque } } + if updateAuthorizations { + err = handler.AuthorizationService.UpdateUsersAuthorizations() + if err != nil { + return &httperror.HandlerError{http.StatusInternalServerError, "Unable to update user authorizations", err} + } + } + return response.Empty(w) } diff --git a/api/http/handler/endpointgroups/endpointgroup_update.go b/api/http/handler/endpointgroups/endpointgroup_update.go index c21c3a452..58ea605fc 100644 --- a/api/http/handler/endpointgroups/endpointgroup_update.go +++ b/api/http/handler/endpointgroups/endpointgroup_update.go @@ -2,6 +2,7 @@ package endpointgroups import ( "net/http" + "reflect" httperror "github.com/portainer/libhttp/error" "github.com/portainer/libhttp/request" @@ -54,12 +55,12 @@ func (handler *Handler) endpointGroupUpdate(w http.ResponseWriter, r *http.Reque } updateAuthorizations := false - if payload.UserAccessPolicies != nil { + if payload.UserAccessPolicies != nil && !reflect.DeepEqual(payload.UserAccessPolicies, endpointGroup.UserAccessPolicies) { endpointGroup.UserAccessPolicies = payload.UserAccessPolicies updateAuthorizations = true } - if payload.TeamAccessPolicies != nil { + if payload.TeamAccessPolicies != nil && !reflect.DeepEqual(payload.TeamAccessPolicies, endpointGroup.TeamAccessPolicies) { endpointGroup.TeamAccessPolicies = payload.TeamAccessPolicies updateAuthorizations = true } @@ -70,7 +71,7 @@ func (handler *Handler) endpointGroupUpdate(w http.ResponseWriter, r *http.Reque } if updateAuthorizations { - err = handler.AuthorizationService.UpdateUserAuthorizationsFromPolicies(&payload.UserAccessPolicies, &payload.TeamAccessPolicies) + err = handler.AuthorizationService.UpdateUsersAuthorizations() if err != nil { return &httperror.HandlerError{http.StatusInternalServerError, "Unable to update user authorizations", err} } diff --git a/api/http/handler/endpoints/endpoint_create.go b/api/http/handler/endpoints/endpoint_create.go index 1ce454afa..865923149 100644 --- a/api/http/handler/endpoints/endpoint_create.go +++ b/api/http/handler/endpoints/endpoint_create.go @@ -192,9 +192,9 @@ func (handler *Handler) createAzureEndpoint(payload *endpointCreatePayload) (*po Snapshots: []portainer.Snapshot{}, } - err = handler.EndpointService.CreateEndpoint(endpoint) + err = handler.saveEndpointAndUpdateAuthorizations(endpoint) if err != nil { - return nil, &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist endpoint inside the database", err} + return nil, &httperror.HandlerError{http.StatusInternalServerError, "An error occured while trying to create the endpoint", err} } return endpoint, nil @@ -238,9 +238,9 @@ func (handler *Handler) createEdgeAgentEndpoint(payload *endpointCreatePayload) EdgeKey: edgeKey, } - err = handler.EndpointService.CreateEndpoint(endpoint) + err = handler.saveEndpointAndUpdateAuthorizations(endpoint) if err != nil { - return nil, &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist endpoint inside the database", err} + return nil, &httperror.HandlerError{http.StatusInternalServerError, "An error occured while trying to create the endpoint", err} } return endpoint, nil @@ -354,9 +354,27 @@ func (handler *Handler) snapshotAndPersistEndpoint(endpoint *portainer.Endpoint) endpoint.Snapshots = []portainer.Snapshot{*snapshot} } - err = handler.EndpointService.CreateEndpoint(endpoint) + err = handler.saveEndpointAndUpdateAuthorizations(endpoint) if err != nil { - return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist endpoint inside the database", err} + return &httperror.HandlerError{http.StatusInternalServerError, "An error occured while trying to create the endpoint", err} + } + + return nil +} + +func (handler *Handler) saveEndpointAndUpdateAuthorizations(endpoint *portainer.Endpoint) error { + err := handler.EndpointService.CreateEndpoint(endpoint) + if err != nil { + return err + } + + group, err := handler.EndpointGroupService.EndpointGroup(endpoint.GroupID) + if err != nil { + return err + } + + if len(group.UserAccessPolicies) > 0 || len(group.TeamAccessPolicies) > 0 { + return handler.AuthorizationService.UpdateUsersAuthorizations() } return nil diff --git a/api/http/handler/endpoints/endpoint_delete.go b/api/http/handler/endpoints/endpoint_delete.go index ca7af3aa9..4d82f9c60 100644 --- a/api/http/handler/endpoints/endpoint_delete.go +++ b/api/http/handler/endpoints/endpoint_delete.go @@ -43,5 +43,12 @@ func (handler *Handler) endpointDelete(w http.ResponseWriter, r *http.Request) * handler.ProxyManager.DeleteProxy(endpoint) + if len(endpoint.UserAccessPolicies) > 0 || len(endpoint.TeamAccessPolicies) > 0 { + err = handler.AuthorizationService.UpdateUsersAuthorizations() + if err != nil { + return &httperror.HandlerError{http.StatusInternalServerError, "Unable to update user authorizations", err} + } + } + return response.Empty(w) } diff --git a/api/http/handler/endpoints/endpoint_update.go b/api/http/handler/endpoints/endpoint_update.go index 9ff91113c..6816ec0d7 100644 --- a/api/http/handler/endpoints/endpoint_update.go +++ b/api/http/handler/endpoints/endpoint_update.go @@ -2,6 +2,7 @@ package endpoints import ( "net/http" + "reflect" "strconv" httperror "github.com/portainer/libhttp/error" @@ -77,12 +78,12 @@ func (handler *Handler) endpointUpdate(w http.ResponseWriter, r *http.Request) * } updateAuthorizations := false - if payload.UserAccessPolicies != nil { + if payload.UserAccessPolicies != nil && !reflect.DeepEqual(payload.UserAccessPolicies, endpoint.UserAccessPolicies) { endpoint.UserAccessPolicies = payload.UserAccessPolicies updateAuthorizations = true } - if payload.TeamAccessPolicies != nil { + if payload.TeamAccessPolicies != nil && !reflect.DeepEqual(payload.TeamAccessPolicies, endpoint.TeamAccessPolicies) { endpoint.TeamAccessPolicies = payload.TeamAccessPolicies updateAuthorizations = true } @@ -177,7 +178,7 @@ func (handler *Handler) endpointUpdate(w http.ResponseWriter, r *http.Request) * } if updateAuthorizations { - err = handler.AuthorizationService.UpdateUserAuthorizationsFromPolicies(&payload.UserAccessPolicies, &payload.TeamAccessPolicies) + err = handler.AuthorizationService.UpdateUsersAuthorizations() if err != nil { return &httperror.HandlerError{http.StatusInternalServerError, "Unable to update user authorizations", err} } diff --git a/api/http/handler/extensions/upgrade.go b/api/http/handler/extensions/upgrade.go index da1676df8..2d314638a 100644 --- a/api/http/handler/extensions/upgrade.go +++ b/api/http/handler/extensions/upgrade.go @@ -36,10 +36,10 @@ func (handler *Handler) upgradeRBACData() error { return err } - err = handler.AuthorizationService.UpdateUserAuthorizationsFromPolicies(&endpointGroup.UserAccessPolicies, &endpointGroup.TeamAccessPolicies) - if err != nil { - return err - } + //err = handler.AuthorizationService.UpdateUserAuthorizationsFromPolicies(&endpointGroup.UserAccessPolicies, &endpointGroup.TeamAccessPolicies) + //if err != nil { + // return err + //} } endpoints, err := handler.EndpointService.Endpoints() @@ -61,10 +61,13 @@ func (handler *Handler) upgradeRBACData() error { return err } - err = handler.AuthorizationService.UpdateUserAuthorizationsFromPolicies(&endpoint.UserAccessPolicies, &endpoint.TeamAccessPolicies) - if err != nil { - return err - } + //err = handler.AuthorizationService.UpdateUserAuthorizationsFromPolicies(&endpoint.UserAccessPolicies, &endpoint.TeamAccessPolicies) + //if err != nil { + // return err + //} } - return nil + + return handler.AuthorizationService.UpdateUsersAuthorizations() + + //return nil } diff --git a/api/http/handler/teammemberships/handler.go b/api/http/handler/teammemberships/handler.go index 3fd56bfc0..018f55007 100644 --- a/api/http/handler/teammemberships/handler.go +++ b/api/http/handler/teammemberships/handler.go @@ -13,8 +13,8 @@ import ( // Handler is the HTTP handler used to handle team membership operations. type Handler struct { *mux.Router - TeamMembershipService portainer.TeamMembershipService - ResourceControlService portainer.ResourceControlService + TeamMembershipService portainer.TeamMembershipService + AuthorizationService *portainer.AuthorizationService } // NewHandler creates a handler to manage team membership operations. diff --git a/api/http/handler/teammemberships/teammembership_create.go b/api/http/handler/teammemberships/teammembership_create.go index 0d8716de9..245b1fe67 100644 --- a/api/http/handler/teammemberships/teammembership_create.go +++ b/api/http/handler/teammemberships/teammembership_create.go @@ -70,5 +70,10 @@ func (handler *Handler) teamMembershipCreate(w http.ResponseWriter, r *http.Requ return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist team memberships inside the database", err} } + err = handler.AuthorizationService.UpdateUsersAuthorizations() + if err != nil { + return &httperror.HandlerError{http.StatusInternalServerError, "Unable to update user authorizations", err} + } + return response.JSON(w, membership) } diff --git a/api/http/handler/teammemberships/teammembership_delete.go b/api/http/handler/teammemberships/teammembership_delete.go index dd8fe6d0b..179bbe800 100644 --- a/api/http/handler/teammemberships/teammembership_delete.go +++ b/api/http/handler/teammemberships/teammembership_delete.go @@ -38,5 +38,10 @@ func (handler *Handler) teamMembershipDelete(w http.ResponseWriter, r *http.Requ return &httperror.HandlerError{http.StatusInternalServerError, "Unable to remove the team membership from the database", err} } + err = handler.AuthorizationService.UpdateUsersAuthorizations() + if err != nil { + return &httperror.HandlerError{http.StatusInternalServerError, "Unable to update user authorizations", err} + } + return response.Empty(w) } diff --git a/api/http/handler/teams/handler.go b/api/http/handler/teams/handler.go index 1aad28ef0..076901531 100644 --- a/api/http/handler/teams/handler.go +++ b/api/http/handler/teams/handler.go @@ -12,9 +12,9 @@ import ( // Handler is the HTTP handler used to handle team operations. type Handler struct { *mux.Router - TeamService portainer.TeamService - TeamMembershipService portainer.TeamMembershipService - ResourceControlService portainer.ResourceControlService + TeamService portainer.TeamService + TeamMembershipService portainer.TeamMembershipService + AuthorizationService *portainer.AuthorizationService } // NewHandler creates a handler to manage team operations. diff --git a/api/http/handler/teams/team_delete.go b/api/http/handler/teams/team_delete.go index f38010725..2b96e9351 100644 --- a/api/http/handler/teams/team_delete.go +++ b/api/http/handler/teams/team_delete.go @@ -33,5 +33,10 @@ func (handler *Handler) teamDelete(w http.ResponseWriter, r *http.Request) *http return &httperror.HandlerError{http.StatusInternalServerError, "Unable to delete associated team memberships from the database", err} } + err = handler.AuthorizationService.RemoveTeamAccessPolicies(portainer.TeamID(teamID)) + if err != nil { + return &httperror.HandlerError{http.StatusInternalServerError, "Unable to clean-up team access policies", err} + } + return response.Empty(w) } diff --git a/api/http/handler/users/handler.go b/api/http/handler/users/handler.go index 8e7f5035b..8cb5629d4 100644 --- a/api/http/handler/users/handler.go +++ b/api/http/handler/users/handler.go @@ -23,6 +23,7 @@ type Handler struct { ResourceControlService portainer.ResourceControlService CryptoService portainer.CryptoService SettingsService portainer.SettingsService + AuthorizationService *portainer.AuthorizationService } // NewHandler creates a handler to manage user operations. diff --git a/api/http/handler/users/user_delete.go b/api/http/handler/users/user_delete.go index c600e1943..203a3be47 100644 --- a/api/http/handler/users/user_delete.go +++ b/api/http/handler/users/user_delete.go @@ -65,15 +65,20 @@ func (handler *Handler) deleteAdminUser(w http.ResponseWriter, user *portainer.U } func (handler *Handler) deleteUser(w http.ResponseWriter, user *portainer.User) *httperror.HandlerError { - err := handler.UserService.DeleteUser(portainer.UserID(user.ID)) + err := handler.UserService.DeleteUser(user.ID) if err != nil { return &httperror.HandlerError{http.StatusInternalServerError, "Unable to remove user from the database", err} } - err = handler.TeamMembershipService.DeleteTeamMembershipByUserID(portainer.UserID(user.ID)) + err = handler.TeamMembershipService.DeleteTeamMembershipByUserID(user.ID) if err != nil { return &httperror.HandlerError{http.StatusInternalServerError, "Unable to remove user memberships from the database", err} } + err = handler.AuthorizationService.RemoveUserAccessPolicies(user.ID) + if err != nil { + return &httperror.HandlerError{http.StatusInternalServerError, "Unable to clean-up user access policies", err} + } + return response.Empty(w) } diff --git a/api/http/server.go b/api/http/server.go index 5810f4b2b..42d3751db 100644 --- a/api/http/server.go +++ b/api/http/server.go @@ -97,6 +97,7 @@ func (server *Server) Start() error { authorizationServiceParameters := &portainer.AuthorizationServiceParameters{ EndpointService: server.EndpointService, EndpointGroupService: server.EndpointGroupService, + RegistryService: server.RegistryService, RoleService: server.RoleService, TeamMembershipService: server.TeamMembershipService, UserService: server.UserService, @@ -213,9 +214,12 @@ func (server *Server) Start() error { var teamHandler = teams.NewHandler(requestBouncer) teamHandler.TeamService = server.TeamService teamHandler.TeamMembershipService = server.TeamMembershipService + teamHandler.AuthorizationService = authorizationService var teamMembershipHandler = teammemberships.NewHandler(requestBouncer) teamMembershipHandler.TeamMembershipService = server.TeamMembershipService + teamMembershipHandler.AuthorizationService = authorizationService + var statusHandler = status.NewHandler(requestBouncer, server.Status) var templatesHandler = templates.NewHandler(requestBouncer) @@ -232,6 +236,7 @@ func (server *Server) Start() error { userHandler.CryptoService = server.CryptoService userHandler.ResourceControlService = server.ResourceControlService userHandler.SettingsService = server.SettingsService + userHandler.AuthorizationService = authorizationService var websocketHandler = websocket.NewHandler(requestBouncer) websocketHandler.EndpointService = server.EndpointService