diff --git a/api/bolt/datastore.go b/api/bolt/datastore.go index 068670b0e..9df787d71 100644 --- a/api/bolt/datastore.go +++ b/api/bolt/datastore.go @@ -340,11 +340,6 @@ func (store *Store) EndpointRelation() portainer.EndpointRelationService { return store.EndpointRelationService } -// Extension gives access to the Extension data management layer -func (store *Store) Extension() portainer.ExtensionService { - return store.ExtensionService -} - // Registry gives access to the Registry data management layer func (store *Store) Registry() portainer.RegistryService { return store.RegistryService diff --git a/api/bolt/init.go b/api/bolt/init.go index 29793a823..6cb80c83a 100644 --- a/api/bolt/init.go +++ b/api/bolt/init.go @@ -3,7 +3,6 @@ package bolt import ( portainer "github.com/portainer/portainer/api" "github.com/portainer/portainer/api/bolt/errors" - "github.com/portainer/portainer/api/internal/authorization" ) // Init creates the default data set. @@ -83,60 +82,5 @@ func (store *Store) Init() error { } } - roles, err := store.RoleService.Roles() - if err != nil { - return err - } - - if len(roles) == 0 { - environmentAdministratorRole := &portainer.Role{ - Name: "Endpoint administrator", - Description: "Full control of all resources in an endpoint", - Priority: 1, - Authorizations: authorization.DefaultEndpointAuthorizationsForEndpointAdministratorRole(), - } - - err = store.RoleService.CreateRole(environmentAdministratorRole) - if err != nil { - return err - } - - environmentReadOnlyUserRole := &portainer.Role{ - Name: "Helpdesk", - Description: "Read-only access of all resources in an endpoint", - Priority: 2, - Authorizations: authorization.DefaultEndpointAuthorizationsForHelpDeskRole(false), - } - - err = store.RoleService.CreateRole(environmentReadOnlyUserRole) - if err != nil { - return err - } - - standardUserRole := &portainer.Role{ - Name: "Standard user", - Description: "Full control of assigned resources in an endpoint", - Priority: 3, - Authorizations: authorization.DefaultEndpointAuthorizationsForStandardUserRole(false), - } - - err = store.RoleService.CreateRole(standardUserRole) - if err != nil { - return err - } - - readOnlyUserRole := &portainer.Role{ - Name: "Read-only user", - Description: "Read-only access of assigned resources in an endpoint", - Priority: 4, - Authorizations: authorization.DefaultEndpointAuthorizationsForReadOnlyUserRole(false), - } - - err = store.RoleService.CreateRole(readOnlyUserRole) - if err != nil { - return err - } - } - return nil } diff --git a/api/cmd/portainer/main.go b/api/cmd/portainer/main.go index 92fcdb1ed..56abc972c 100644 --- a/api/cmd/portainer/main.go +++ b/api/cmd/portainer/main.go @@ -17,7 +17,6 @@ import ( "github.com/portainer/portainer/api/git" "github.com/portainer/portainer/api/http" "github.com/portainer/portainer/api/http/client" - "github.com/portainer/portainer/api/internal/authorization" "github.com/portainer/portainer/api/internal/snapshot" "github.com/portainer/portainer/api/jwt" "github.com/portainer/portainer/api/kubernetes" @@ -318,17 +317,6 @@ func initEndpoint(flags *portainer.CLIFlags, dataStore portainer.DataStore, snap return createUnsecuredEndpoint(*flags.EndpointURL, dataStore, snapshotService) } -func initExtensionManager(fileService portainer.FileService, dataStore portainer.DataStore) (portainer.ExtensionManager, error) { - extensionManager := exec.NewExtensionManager(fileService, dataStore) - - err := extensionManager.StartExtensions() - if err != nil { - return nil, err - } - - return extensionManager, nil -} - func terminateIfNoAdminCreated(dataStore portainer.DataStore) { timer1 := time.NewTimer(5 * time.Minute) <-timer1.C @@ -372,11 +360,6 @@ func main() { log.Fatal(err) } - extensionManager, err := initExtensionManager(fileService, dataStore) - if err != nil { - log.Fatal(err) - } - reverseTunnelService := chisel.NewService(dataStore) dockerClientFactory := initDockerClientFactory(digitalSignatureService, reverseTunnelService) @@ -439,10 +422,9 @@ func main() { if len(users) == 0 { log.Println("Created admin user with the given password.") user := &portainer.User{ - Username: "admin", - Role: portainer.AdministratorRole, - Password: adminPasswordHash, - PortainerAuthorizations: authorization.DefaultPortainerAuthorizations(), + Username: "admin", + Role: portainer.AdministratorRole, + Password: adminPasswordHash, } err := dataStore.User().CreateUser(user) if err != nil { @@ -469,7 +451,6 @@ func main() { SwarmStackManager: swarmStackManager, ComposeStackManager: composeStackManager, KubernetesDeployer: kubernetesDeployer, - ExtensionManager: extensionManager, CryptoService: cryptoService, JWTService: jwtService, FileService: fileService, diff --git a/api/exec/extension.go b/api/exec/extension.go deleted file mode 100644 index 049bf9851..000000000 --- a/api/exec/extension.go +++ /dev/null @@ -1,307 +0,0 @@ -package exec - -import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "log" - "os" - "os/exec" - "path" - "regexp" - "runtime" - "strconv" - "strings" - "time" - - "github.com/coreos/go-semver/semver" - - "github.com/orcaman/concurrent-map" - "github.com/portainer/portainer/api" - "github.com/portainer/portainer/api/http/client" -) - -var extensionDownloadBaseURL = portainer.AssetsServerURL + "/extensions/" -var extensionVersionRegexp = regexp.MustCompile(`\d+(\.\d+)+`) - -var extensionBinaryMap = map[portainer.ExtensionID]string{ - portainer.RBACExtension: "extension-rbac", -} - -// ExtensionManager represents a service used to -// manage extension processes. -type ExtensionManager struct { - processes cmap.ConcurrentMap - fileService portainer.FileService - dataStore portainer.DataStore -} - -// NewExtensionManager returns a pointer to an ExtensionManager -func NewExtensionManager(fileService portainer.FileService, dataStore portainer.DataStore) *ExtensionManager { - return &ExtensionManager{ - processes: cmap.New(), - fileService: fileService, - dataStore: dataStore, - } -} - -func processKey(ID portainer.ExtensionID) string { - return strconv.Itoa(int(ID)) -} - -func buildExtensionURL(extension *portainer.Extension) string { - return fmt.Sprintf("%s%s-%s-%s-%s.zip", extensionDownloadBaseURL, extensionBinaryMap[extension.ID], runtime.GOOS, runtime.GOARCH, extension.Version) -} - -func buildExtensionPath(binaryPath string, extension *portainer.Extension) string { - extensionFilename := fmt.Sprintf("%s-%s-%s-%s", extensionBinaryMap[extension.ID], runtime.GOOS, runtime.GOARCH, extension.Version) - if runtime.GOOS == "windows" { - extensionFilename += ".exe" - } - - extensionPath := path.Join( - binaryPath, - extensionFilename) - - return extensionPath -} - -// FetchExtensionDefinitions will fetch the list of available -// extension definitions from the official Portainer assets server. -// If it cannot retrieve the data from the Internet it will fallback to the locally cached -// manifest file. -func (manager *ExtensionManager) FetchExtensionDefinitions() ([]portainer.Extension, error) { - var extensionData []byte - - extensionData, err := client.Get(portainer.ExtensionDefinitionsURL, 5) - if err != nil { - log.Printf("[WARN] [exec,extensions] [message: unable to retrieve extensions manifest via Internet. Extensions will be retrieved from local cache and might not be up to date] [err: %s]", err) - - extensionData, err = manager.fileService.GetFileContent(portainer.LocalExtensionManifestFile) - if err != nil { - return nil, err - } - } - - var extensions []portainer.Extension - err = json.Unmarshal(extensionData, &extensions) - if err != nil { - return nil, err - } - - return extensions, nil -} - -// InstallExtension will install the extension from an archive. It will extract the extension version number from -// the archive file name first and return an error if the file name is not valid (cannot find extension version). -// It will then extract the archive and execute the EnableExtension function to enable the extension. -// Since we're missing information about this extension (stored on Portainer.io server) we need to assume -// default information based on the extension ID. -func (manager *ExtensionManager) InstallExtension(extension *portainer.Extension, licenseKey string, archiveFileName string, extensionArchive []byte) error { - extensionVersion := extensionVersionRegexp.FindString(archiveFileName) - if extensionVersion == "" { - return errors.New("invalid extension archive filename: unable to retrieve extension version") - } - - err := manager.fileService.ExtractExtensionArchive(extensionArchive) - if err != nil { - return err - } - - switch extension.ID { - case portainer.RBACExtension: - extension.Name = "Role-Based Access Control" - } - extension.ShortDescription = "Extension enabled offline" - extension.Version = extensionVersion - extension.Available = true - - return manager.EnableExtension(extension, licenseKey) -} - -// EnableExtension will check for the existence of the extension binary on the filesystem -// first. If it does not exist, it will download it from the official Portainer assets server. -// After installing the binary on the filesystem, it will execute the binary in license check -// mode to validate the extension license. If the license is valid, it will then start -// the extension process and register it in the processes map. -func (manager *ExtensionManager) EnableExtension(extension *portainer.Extension, licenseKey string) error { - extensionBinaryPath := buildExtensionPath(manager.fileService.GetBinaryFolder(), extension) - extensionBinaryExists, err := manager.fileService.FileExists(extensionBinaryPath) - if err != nil { - return err - } - - if !extensionBinaryExists { - err := manager.downloadExtension(extension) - if err != nil { - return err - } - } - - licenseDetails, err := validateLicense(extensionBinaryPath, licenseKey) - if err != nil { - return err - } - - extension.License = portainer.LicenseInformation{ - LicenseKey: licenseKey, - Company: licenseDetails[0], - Expiration: licenseDetails[1], - Valid: true, - } - extension.Version = licenseDetails[2] - - return manager.startExtensionProcess(extension, extensionBinaryPath) -} - -// DisableExtension will retrieve the process associated to the extension -// from the processes map and kill the process. It will then remove the process -// from the processes map and remove the binary associated to the extension -// from the filesystem -func (manager *ExtensionManager) DisableExtension(extension *portainer.Extension) error { - process, ok := manager.processes.Get(processKey(extension.ID)) - if !ok { - return nil - } - - err := process.(*exec.Cmd).Process.Kill() - if err != nil { - return err - } - - manager.processes.Remove(processKey(extension.ID)) - - extensionBinaryPath := buildExtensionPath(manager.fileService.GetBinaryFolder(), extension) - return manager.fileService.RemoveDirectory(extensionBinaryPath) -} - -// StartExtensions will retrieve the extensions definitions from the Internet and check if a new version of each -// extension is available. If so, it will automatically install the new version of the extension. If no update is -// available it will simply start the extension. -// The purpose of this function is to be ran at startup, as such most of the error handling won't block the program execution -// and will log warning messages instead. -func (manager *ExtensionManager) StartExtensions() error { - extensions, err := manager.dataStore.Extension().Extensions() - if err != nil { - return err - } - - definitions, err := manager.FetchExtensionDefinitions() - if err != nil { - log.Printf("[WARN] [exec,extensions] [message: unable to retrieve extension information from Internet. Skipping extensions update check.] [err: %s]", err) - return nil - } - - return manager.updateAndStartExtensions(extensions, definitions) -} - -func (manager *ExtensionManager) updateAndStartExtensions(extensions []portainer.Extension, definitions []portainer.Extension) error { - for _, definition := range definitions { - for _, extension := range extensions { - if extension.ID == definition.ID { - definitionVersion := semver.New(definition.Version) - extensionVersion := semver.New(extension.Version) - - if extensionVersion.LessThan(*definitionVersion) { - log.Printf("[INFO] [exec,extensions] [message: new version detected, updating extension] [extension: %s] [current_version: %s] [available_version: %s]", extension.Name, extension.Version, definition.Version) - err := manager.UpdateExtension(&extension, definition.Version) - if err != nil { - log.Printf("[WARN] [exec,extensions] [message: unable to update extension automatically] [extension: %s] [current_version: %s] [available_version: %s] [err: %s]", extension.Name, extension.Version, definition.Version, err) - } - } else { - err := manager.EnableExtension(&extension, extension.License.LicenseKey) - if err != nil { - log.Printf("[WARN] [exec,extensions] [message: unable to start extension] [extension: %s] [err: %s]", extension.Name, err) - extension.Enabled = false - extension.License.Valid = false - } - } - - err := manager.dataStore.Extension().Persist(&extension) - if err != nil { - return err - } - - break - } - } - } - - return nil -} - -// UpdateExtension will download the new extension binary from the official Portainer assets -// server, disable the previous extension via DisableExtension, trigger a license check -// and then start the extension process and add it to the processes map -func (manager *ExtensionManager) UpdateExtension(extension *portainer.Extension, version string) error { - oldVersion := extension.Version - - extension.Version = version - err := manager.downloadExtension(extension) - if err != nil { - return err - } - - extension.Version = oldVersion - err = manager.DisableExtension(extension) - if err != nil { - return err - } - - extension.Version = version - extensionBinaryPath := buildExtensionPath(manager.fileService.GetBinaryFolder(), extension) - - licenseDetails, err := validateLicense(extensionBinaryPath, extension.License.LicenseKey) - if err != nil { - return err - } - - extension.Version = licenseDetails[2] - - return manager.startExtensionProcess(extension, extensionBinaryPath) -} - -func (manager *ExtensionManager) downloadExtension(extension *portainer.Extension) error { - extensionURL := buildExtensionURL(extension) - - data, err := client.Get(extensionURL, 30) - if err != nil { - return err - } - - return manager.fileService.ExtractExtensionArchive(data) -} - -func validateLicense(binaryPath, licenseKey string) ([]string, error) { - licenseCheckProcess := exec.Command(binaryPath, "-license", licenseKey, "-check") - cmdOutput := &bytes.Buffer{} - licenseCheckProcess.Stdout = cmdOutput - - err := licenseCheckProcess.Run() - if err != nil { - log.Printf("[DEBUG] [exec,extension] [message: unable to run extension process] [err: %s]", err) - return nil, errors.New("invalid extension license key") - } - - output := string(cmdOutput.Bytes()) - - return strings.Split(output, "|"), nil -} - -func (manager *ExtensionManager) startExtensionProcess(extension *portainer.Extension, binaryPath string) error { - extensionProcess := exec.Command(binaryPath, "-license", extension.License.LicenseKey) - extensionProcess.Stdout = os.Stdout - extensionProcess.Stderr = os.Stderr - - err := extensionProcess.Start() - if err != nil { - log.Printf("[DEBUG] [exec,extension] [message: unable to start extension process] [err: %s]", err) - return err - } - - time.Sleep(3 * time.Second) - - manager.processes.Set(processKey(extension.ID), extensionProcess) - return nil -} diff --git a/api/filesystem/filesystem.go b/api/filesystem/filesystem.go index ff99bd32c..5a766f7cc 100644 --- a/api/filesystem/filesystem.go +++ b/api/filesystem/filesystem.go @@ -10,7 +10,6 @@ import ( "github.com/gofrs/uuid" "github.com/portainer/portainer/api" - "github.com/portainer/portainer/api/archive" "io" "os" @@ -96,12 +95,6 @@ func (service *Service) GetBinaryFolder() string { return path.Join(service.fileStorePath, BinaryStorePath) } -// ExtractExtensionArchive extracts the content of an extension archive -// specified as raw data into the binary store on the filesystem -func (service *Service) ExtractExtensionArchive(data []byte) error { - return archive.UnzipArchive(data, path.Join(service.fileStorePath, BinaryStorePath)) -} - // RemoveDirectory removes a directory on the filesystem. func (service *Service) RemoveDirectory(directoryPath string) error { return os.RemoveAll(directoryPath) diff --git a/api/http/handler/auth/authenticate.go b/api/http/handler/auth/authenticate.go index 52c1982b9..ad39e93df 100644 --- a/api/http/handler/auth/authenticate.go +++ b/api/http/handler/auth/authenticate.go @@ -13,7 +13,6 @@ import ( "github.com/portainer/portainer/api" bolterrors "github.com/portainer/portainer/api/bolt/errors" httperrors "github.com/portainer/portainer/api/http/errors" - "github.com/portainer/portainer/api/internal/authorization" ) type authenticatePayload struct { @@ -79,11 +78,6 @@ func (handler *Handler) authenticateLDAP(w http.ResponseWriter, user *portainer. log.Printf("Warning: unable to automatically add user into teams: %s\n", err.Error()) } - err = handler.AuthorizationService.UpdateUsersAuthorizations() - if err != nil { - return &httperror.HandlerError{http.StatusInternalServerError, "Unable to update user authorizations", err} - } - return handler.writeToken(w, user) } @@ -103,9 +97,8 @@ func (handler *Handler) authenticateLDAPAndCreateUser(w http.ResponseWriter, use } user := &portainer.User{ - Username: username, - Role: portainer.StandardUserRole, - PortainerAuthorizations: authorization.DefaultPortainerAuthorizations(), + Username: username, + Role: portainer.StandardUserRole, } err = handler.DataStore.User().CreateUser(user) @@ -118,11 +111,6 @@ func (handler *Handler) authenticateLDAPAndCreateUser(w http.ResponseWriter, use log.Printf("Warning: unable to automatically add user into teams: %s\n", err.Error()) } - err = handler.AuthorizationService.UpdateUsersAuthorizations() - if err != nil { - return &httperror.HandlerError{http.StatusInternalServerError, "Unable to update user authorizations", err} - } - return handler.writeToken(w, user) } diff --git a/api/http/handler/auth/authenticate_oauth.go b/api/http/handler/auth/authenticate_oauth.go index c0e3e8bda..0e198f35f 100644 --- a/api/http/handler/auth/authenticate_oauth.go +++ b/api/http/handler/auth/authenticate_oauth.go @@ -11,7 +11,6 @@ import ( "github.com/portainer/portainer/api" bolterrors "github.com/portainer/portainer/api/bolt/errors" httperrors "github.com/portainer/portainer/api/http/errors" - "github.com/portainer/portainer/api/internal/authorization" ) type oauthPayload struct { @@ -76,9 +75,8 @@ func (handler *Handler) validateOAuth(w http.ResponseWriter, r *http.Request) *h if user == nil { user = &portainer.User{ - Username: username, - Role: portainer.StandardUserRole, - PortainerAuthorizations: authorization.DefaultPortainerAuthorizations(), + Username: username, + Role: portainer.StandardUserRole, } err = handler.DataStore.User().CreateUser(user) @@ -99,10 +97,6 @@ func (handler *Handler) validateOAuth(w http.ResponseWriter, r *http.Request) *h } } - err = handler.AuthorizationService.UpdateUsersAuthorizations() - if err != nil { - return &httperror.HandlerError{http.StatusInternalServerError, "Unable to update user authorizations", err} - } } return handler.writeToken(w, user) diff --git a/api/http/handler/auth/handler.go b/api/http/handler/auth/handler.go index 120e5980e..5ad73712a 100644 --- a/api/http/handler/auth/handler.go +++ b/api/http/handler/auth/handler.go @@ -9,7 +9,6 @@ import ( "github.com/portainer/portainer/api/http/proxy" "github.com/portainer/portainer/api/http/proxy/factory/kubernetes" "github.com/portainer/portainer/api/http/security" - "github.com/portainer/portainer/api/internal/authorization" ) // Handler is the HTTP handler used to handle authentication operations. @@ -21,7 +20,6 @@ type Handler struct { LDAPService portainer.LDAPService OAuthService portainer.OAuthService ProxyManager *proxy.Manager - AuthorizationService *authorization.Service KubernetesTokenCacheManager *kubernetes.TokenCacheManager } diff --git a/api/http/handler/endpointgroups/endpointgroup_delete.go b/api/http/handler/endpointgroups/endpointgroup_delete.go index 39ab4ba1e..2b845070c 100644 --- a/api/http/handler/endpointgroups/endpointgroup_delete.go +++ b/api/http/handler/endpointgroups/endpointgroup_delete.go @@ -39,10 +39,8 @@ 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.DataStore.Endpoint().UpdateEndpoint(endpoint.ID, &endpoint) if err != nil { @@ -56,13 +54,6 @@ 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} - } - } - for _, tagID := range endpointGroup.TagIDs { tag, err := handler.DataStore.Tag().Tag(tagID) if err != nil { diff --git a/api/http/handler/endpointgroups/endpointgroup_update.go b/api/http/handler/endpointgroups/endpointgroup_update.go index 72cf3b3f6..047bbb6b4 100644 --- a/api/http/handler/endpointgroups/endpointgroup_update.go +++ b/api/http/handler/endpointgroups/endpointgroup_update.go @@ -92,15 +92,12 @@ func (handler *Handler) endpointGroupUpdate(w http.ResponseWriter, r *http.Reque } } - updateAuthorizations := false if payload.UserAccessPolicies != nil && !reflect.DeepEqual(payload.UserAccessPolicies, endpointGroup.UserAccessPolicies) { endpointGroup.UserAccessPolicies = payload.UserAccessPolicies - updateAuthorizations = true } if payload.TeamAccessPolicies != nil && !reflect.DeepEqual(payload.TeamAccessPolicies, endpointGroup.TeamAccessPolicies) { endpointGroup.TeamAccessPolicies = payload.TeamAccessPolicies - updateAuthorizations = true } err = handler.DataStore.EndpointGroup().UpdateEndpointGroup(endpointGroup.ID, endpointGroup) @@ -108,13 +105,6 @@ func (handler *Handler) endpointGroupUpdate(w http.ResponseWriter, r *http.Reque return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist endpoint group changes inside the database", err} } - if updateAuthorizations { - err = handler.AuthorizationService.UpdateUsersAuthorizations() - if err != nil { - return &httperror.HandlerError{http.StatusInternalServerError, "Unable to update user authorizations", err} - } - } - if tagsChanged { endpoints, err := handler.DataStore.Endpoint().Endpoints() if err != nil { diff --git a/api/http/handler/endpointgroups/handler.go b/api/http/handler/endpointgroups/handler.go index 8a6586cb9..e73828814 100644 --- a/api/http/handler/endpointgroups/handler.go +++ b/api/http/handler/endpointgroups/handler.go @@ -7,14 +7,12 @@ import ( httperror "github.com/portainer/libhttp/error" "github.com/portainer/portainer/api" "github.com/portainer/portainer/api/http/security" - "github.com/portainer/portainer/api/internal/authorization" ) // Handler is the HTTP handler used to handle endpoint group operations. type Handler struct { *mux.Router - DataStore portainer.DataStore - AuthorizationService *authorization.Service + DataStore portainer.DataStore } // NewHandler creates a handler to manage endpoint group operations. diff --git a/api/http/handler/endpointproxy/proxy_azure.go b/api/http/handler/endpointproxy/proxy_azure.go index 9984b763f..8176edf8a 100644 --- a/api/http/handler/endpointproxy/proxy_azure.go +++ b/api/http/handler/endpointproxy/proxy_azure.go @@ -24,7 +24,7 @@ func (handler *Handler) proxyRequestsToAzureAPI(w http.ResponseWriter, r *http.R return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an endpoint with the specified identifier inside the database", err} } - err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint, false) + err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint) if err != nil { return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err} } diff --git a/api/http/handler/endpointproxy/proxy_docker.go b/api/http/handler/endpointproxy/proxy_docker.go index 5b082e419..c4671506d 100644 --- a/api/http/handler/endpointproxy/proxy_docker.go +++ b/api/http/handler/endpointproxy/proxy_docker.go @@ -26,7 +26,7 @@ func (handler *Handler) proxyRequestsToDockerAPI(w http.ResponseWriter, r *http. return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an endpoint with the specified identifier inside the database", err} } - err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint, true) + err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint) if err != nil { return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err} } diff --git a/api/http/handler/endpointproxy/proxy_kubernetes.go b/api/http/handler/endpointproxy/proxy_kubernetes.go index be6400265..7a4e9dc01 100644 --- a/api/http/handler/endpointproxy/proxy_kubernetes.go +++ b/api/http/handler/endpointproxy/proxy_kubernetes.go @@ -26,7 +26,7 @@ func (handler *Handler) proxyRequestsToKubernetesAPI(w http.ResponseWriter, r *h return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an endpoint with the specified identifier inside the database", err} } - err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint, true) + err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint) if err != nil { return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err} } diff --git a/api/http/handler/endpointproxy/proxy_storidge.go b/api/http/handler/endpointproxy/proxy_storidge.go index e44a74abc..6cc59ff17 100644 --- a/api/http/handler/endpointproxy/proxy_storidge.go +++ b/api/http/handler/endpointproxy/proxy_storidge.go @@ -27,7 +27,7 @@ func (handler *Handler) proxyRequestsToStoridgeAPI(w http.ResponseWriter, r *htt return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an endpoint with the specified identifier inside the database", err} } - err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint, false) + err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint) if err != nil { return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err} } diff --git a/api/http/handler/endpoints/endpoint_create.go b/api/http/handler/endpoints/endpoint_create.go index f16e177c1..728711d87 100644 --- a/api/http/handler/endpoints/endpoint_create.go +++ b/api/http/handler/endpoints/endpoint_create.go @@ -445,15 +445,6 @@ func (handler *Handler) saveEndpointAndUpdateAuthorizations(endpoint *portainer. return err } - group, err := handler.DataStore.EndpointGroup().EndpointGroup(endpoint.GroupID) - if err != nil { - return err - } - - if len(group.UserAccessPolicies) > 0 || len(group.TeamAccessPolicies) > 0 { - return handler.AuthorizationService.UpdateUsersAuthorizations() - } - for _, tagID := range endpoint.TagIDs { tag, err := handler.DataStore.Tag().Tag(tagID) if err != nil { diff --git a/api/http/handler/endpoints/endpoint_delete.go b/api/http/handler/endpoints/endpoint_delete.go index b35fc8cf2..875d4153e 100644 --- a/api/http/handler/endpoints/endpoint_delete.go +++ b/api/http/handler/endpoints/endpoint_delete.go @@ -40,13 +40,6 @@ func (handler *Handler) endpointDelete(w http.ResponseWriter, r *http.Request) * handler.ProxyManager.DeleteEndpointProxy(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} - } - } - err = handler.DataStore.EndpointRelation().DeleteEndpointRelation(endpoint.ID) if err != nil { return &httperror.HandlerError{http.StatusInternalServerError, "Unable to remove endpoint relation from the database", err} diff --git a/api/http/handler/endpoints/endpoint_inspect.go b/api/http/handler/endpoints/endpoint_inspect.go index 1ce758eb9..1411e93cb 100644 --- a/api/http/handler/endpoints/endpoint_inspect.go +++ b/api/http/handler/endpoints/endpoint_inspect.go @@ -24,7 +24,7 @@ func (handler *Handler) endpointInspect(w http.ResponseWriter, r *http.Request) return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an endpoint with the specified identifier inside the database", err} } - err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint, false) + err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint) if err != nil { return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err} } diff --git a/api/http/handler/endpoints/endpoint_update.go b/api/http/handler/endpoints/endpoint_update.go index 305a57e54..172c8a45c 100644 --- a/api/http/handler/endpoints/endpoint_update.go +++ b/api/http/handler/endpoints/endpoint_update.go @@ -126,15 +126,12 @@ func (handler *Handler) endpointUpdate(w http.ResponseWriter, r *http.Request) * endpoint.Kubernetes = *payload.Kubernetes } - updateAuthorizations := false if payload.UserAccessPolicies != nil && !reflect.DeepEqual(payload.UserAccessPolicies, endpoint.UserAccessPolicies) { endpoint.UserAccessPolicies = payload.UserAccessPolicies - updateAuthorizations = true } if payload.TeamAccessPolicies != nil && !reflect.DeepEqual(payload.TeamAccessPolicies, endpoint.TeamAccessPolicies) { endpoint.TeamAccessPolicies = payload.TeamAccessPolicies - updateAuthorizations = true } if payload.Status != nil { @@ -226,13 +223,6 @@ func (handler *Handler) endpointUpdate(w http.ResponseWriter, r *http.Request) * return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist endpoint changes inside the database", err} } - if updateAuthorizations { - err = handler.AuthorizationService.UpdateUsersAuthorizations() - if err != nil { - return &httperror.HandlerError{http.StatusInternalServerError, "Unable to update user authorizations", err} - } - } - if (endpoint.Type == portainer.EdgeAgentOnDockerEnvironment || endpoint.Type == portainer.EdgeAgentOnKubernetesEnvironment) && (groupIDChanged || tagsChanged) { relation, err := handler.DataStore.EndpointRelation().EndpointRelation(endpoint.ID) if err != nil { diff --git a/api/http/handler/endpoints/handler.go b/api/http/handler/endpoints/handler.go index 3722c6e24..c004b5751 100644 --- a/api/http/handler/endpoints/handler.go +++ b/api/http/handler/endpoints/handler.go @@ -5,7 +5,6 @@ import ( portainer "github.com/portainer/portainer/api" "github.com/portainer/portainer/api/http/proxy" "github.com/portainer/portainer/api/http/security" - "github.com/portainer/portainer/api/internal/authorization" "net/http" @@ -24,7 +23,6 @@ type Handler struct { *mux.Router requestBouncer *security.RequestBouncer DataStore portainer.DataStore - AuthorizationService *authorization.Service FileService portainer.FileService ProxyManager *proxy.Manager ReverseTunnelService portainer.ReverseTunnelService diff --git a/api/http/handler/extensions/data.go b/api/http/handler/extensions/data.go deleted file mode 100644 index 37dcd62fc..000000000 --- a/api/http/handler/extensions/data.go +++ /dev/null @@ -1,117 +0,0 @@ -package extensions - -import ( - portainer "github.com/portainer/portainer/api" -) - -func updateUserAccessPolicyToReadOnlyRole(policies portainer.UserAccessPolicies, key portainer.UserID) { - tmp := policies[key] - tmp.RoleID = 4 - policies[key] = tmp -} - -func updateTeamAccessPolicyToReadOnlyRole(policies portainer.TeamAccessPolicies, key portainer.TeamID) { - tmp := policies[key] - tmp.RoleID = 4 - policies[key] = tmp -} - -func (handler *Handler) upgradeRBACData() error { - endpointGroups, err := handler.DataStore.EndpointGroup().EndpointGroups() - if err != nil { - return err - } - - for _, endpointGroup := range endpointGroups { - for key := range endpointGroup.UserAccessPolicies { - updateUserAccessPolicyToReadOnlyRole(endpointGroup.UserAccessPolicies, key) - } - - for key := range endpointGroup.TeamAccessPolicies { - updateTeamAccessPolicyToReadOnlyRole(endpointGroup.TeamAccessPolicies, key) - } - - err := handler.DataStore.EndpointGroup().UpdateEndpointGroup(endpointGroup.ID, &endpointGroup) - if err != nil { - return err - } - } - - endpoints, err := handler.DataStore.Endpoint().Endpoints() - if err != nil { - return err - } - - for _, endpoint := range endpoints { - for key := range endpoint.UserAccessPolicies { - updateUserAccessPolicyToReadOnlyRole(endpoint.UserAccessPolicies, key) - } - - for key := range endpoint.TeamAccessPolicies { - updateTeamAccessPolicyToReadOnlyRole(endpoint.TeamAccessPolicies, key) - } - - err := handler.DataStore.Endpoint().UpdateEndpoint(endpoint.ID, &endpoint) - if err != nil { - return err - } - } - - return handler.AuthorizationService.UpdateUsersAuthorizations() -} - -func updateUserAccessPolicyToNoRole(policies portainer.UserAccessPolicies, key portainer.UserID) { - tmp := policies[key] - tmp.RoleID = 0 - policies[key] = tmp -} - -func updateTeamAccessPolicyToNoRole(policies portainer.TeamAccessPolicies, key portainer.TeamID) { - tmp := policies[key] - tmp.RoleID = 0 - policies[key] = tmp -} - -func (handler *Handler) downgradeRBACData() error { - endpointGroups, err := handler.DataStore.EndpointGroup().EndpointGroups() - if err != nil { - return err - } - - for _, endpointGroup := range endpointGroups { - for key := range endpointGroup.UserAccessPolicies { - updateUserAccessPolicyToNoRole(endpointGroup.UserAccessPolicies, key) - } - - for key := range endpointGroup.TeamAccessPolicies { - updateTeamAccessPolicyToNoRole(endpointGroup.TeamAccessPolicies, key) - } - - err := handler.DataStore.EndpointGroup().UpdateEndpointGroup(endpointGroup.ID, &endpointGroup) - if err != nil { - return err - } - } - - endpoints, err := handler.DataStore.Endpoint().Endpoints() - if err != nil { - return err - } - - for _, endpoint := range endpoints { - for key := range endpoint.UserAccessPolicies { - updateUserAccessPolicyToNoRole(endpoint.UserAccessPolicies, key) - } - - for key := range endpoint.TeamAccessPolicies { - updateTeamAccessPolicyToNoRole(endpoint.TeamAccessPolicies, key) - } - - err := handler.DataStore.Endpoint().UpdateEndpoint(endpoint.ID, &endpoint) - if err != nil { - return err - } - } - - return handler.AuthorizationService.UpdateUsersAuthorizations() -} diff --git a/api/http/handler/extensions/extension_create.go b/api/http/handler/extensions/extension_create.go deleted file mode 100644 index 8d7add8c4..000000000 --- a/api/http/handler/extensions/extension_create.go +++ /dev/null @@ -1,87 +0,0 @@ -package extensions - -import ( - "errors" - "net/http" - "strconv" - - "github.com/asaskevich/govalidator" - httperror "github.com/portainer/libhttp/error" - "github.com/portainer/libhttp/request" - "github.com/portainer/libhttp/response" - "github.com/portainer/portainer/api" -) - -type extensionCreatePayload struct { - License string -} - -func (payload *extensionCreatePayload) Validate(r *http.Request) error { - if govalidator.IsNull(payload.License) { - return errors.New("Invalid license") - } - - return nil -} - -func (handler *Handler) extensionCreate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { - var payload extensionCreatePayload - err := request.DecodeAndValidateJSONPayload(r, &payload) - if err != nil { - return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err} - } - - extensionIdentifier, err := strconv.Atoi(string(payload.License[0])) - if err != nil { - return &httperror.HandlerError{http.StatusBadRequest, "Invalid license format", err} - } - extensionID := portainer.ExtensionID(extensionIdentifier) - - extensions, err := handler.DataStore.Extension().Extensions() - if err != nil { - return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve extensions status from the database", err} - } - - for _, existingExtension := range extensions { - if existingExtension.ID == extensionID && existingExtension.Enabled { - return &httperror.HandlerError{http.StatusConflict, "Unable to enable extension", errors.New("This extension is already enabled")} - } - } - - extension := &portainer.Extension{ - ID: extensionID, - } - - extensionDefinitions, err := handler.ExtensionManager.FetchExtensionDefinitions() - if err != nil { - return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve extension definitions", err} - } - - for _, def := range extensionDefinitions { - if def.ID == extension.ID { - extension.Version = def.Version - break - } - } - - err = handler.ExtensionManager.EnableExtension(extension, payload.License) - if err != nil { - return &httperror.HandlerError{http.StatusInternalServerError, "Unable to enable extension", err} - } - - extension.Enabled = true - - if extension.ID == portainer.RBACExtension { - err = handler.upgradeRBACData() - if err != nil { - return &httperror.HandlerError{http.StatusInternalServerError, "An error occured during database update", err} - } - } - - err = handler.DataStore.Extension().Persist(extension) - if err != nil { - return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist extension status inside the database", err} - } - - return response.Empty(w) -} diff --git a/api/http/handler/extensions/extension_delete.go b/api/http/handler/extensions/extension_delete.go deleted file mode 100644 index 351de2406..000000000 --- a/api/http/handler/extensions/extension_delete.go +++ /dev/null @@ -1,46 +0,0 @@ -package extensions - -import ( - "net/http" - - httperror "github.com/portainer/libhttp/error" - "github.com/portainer/libhttp/request" - "github.com/portainer/libhttp/response" - "github.com/portainer/portainer/api" - "github.com/portainer/portainer/api/bolt/errors" -) - -// DELETE request on /api/extensions/:id -func (handler *Handler) extensionDelete(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { - extensionIdentifier, err := request.RetrieveNumericRouteVariableValue(r, "id") - if err != nil { - return &httperror.HandlerError{http.StatusBadRequest, "Invalid extension identifier route variable", err} - } - extensionID := portainer.ExtensionID(extensionIdentifier) - - extension, err := handler.DataStore.Extension().Extension(extensionID) - if err == errors.ErrObjectNotFound { - return &httperror.HandlerError{http.StatusNotFound, "Unable to find a extension with the specified identifier inside the database", err} - } else if err != nil { - return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find a extension with the specified identifier inside the database", err} - } - - err = handler.ExtensionManager.DisableExtension(extension) - if err != nil { - return &httperror.HandlerError{http.StatusInternalServerError, "Unable to delete extension", err} - } - - if extensionID == portainer.RBACExtension { - err = handler.downgradeRBACData() - if err != nil { - return &httperror.HandlerError{http.StatusInternalServerError, "An error occured during database update", err} - } - } - - err = handler.DataStore.Extension().DeleteExtension(extensionID) - if err != nil { - return &httperror.HandlerError{http.StatusInternalServerError, "Unable to delete the extension from the database", err} - } - - return response.Empty(w) -} diff --git a/api/http/handler/extensions/extension_inspect.go b/api/http/handler/extensions/extension_inspect.go deleted file mode 100644 index 4081a3cb7..000000000 --- a/api/http/handler/extensions/extension_inspect.go +++ /dev/null @@ -1,55 +0,0 @@ -package extensions - -import ( - "net/http" - - httperror "github.com/portainer/libhttp/error" - "github.com/portainer/libhttp/request" - "github.com/portainer/libhttp/response" - "github.com/portainer/portainer/api" - "github.com/portainer/portainer/api/bolt/errors" - "github.com/portainer/portainer/api/http/client" -) - -// GET request on /api/extensions/:id -func (handler *Handler) extensionInspect(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { - extensionIdentifier, err := request.RetrieveNumericRouteVariableValue(r, "id") - if err != nil { - return &httperror.HandlerError{http.StatusBadRequest, "Invalid extension identifier route variable", err} - } - - extensionID := portainer.ExtensionID(extensionIdentifier) - - definitions, err := handler.ExtensionManager.FetchExtensionDefinitions() - if err != nil { - return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve extensions informations", err} - } - - localExtension, err := handler.DataStore.Extension().Extension(extensionID) - if err != nil && err != errors.ErrObjectNotFound { - return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve extension information from the database", err} - } - - var extension portainer.Extension - var extensionDefinition portainer.Extension - - for _, definition := range definitions { - if definition.ID == extensionID { - extensionDefinition = definition - break - } - } - - if localExtension == nil { - extension = extensionDefinition - } else { - extension = *localExtension - } - - mergeExtensionAndDefinition(&extension, &extensionDefinition) - - description, _ := client.Get(extension.DescriptionURL, 5) - extension.Description = string(description) - - return response.JSON(w, extension) -} diff --git a/api/http/handler/extensions/extension_list.go b/api/http/handler/extensions/extension_list.go deleted file mode 100644 index 661f7b7df..000000000 --- a/api/http/handler/extensions/extension_list.go +++ /dev/null @@ -1,30 +0,0 @@ -package extensions - -import ( - "net/http" - - httperror "github.com/portainer/libhttp/error" - "github.com/portainer/libhttp/request" - "github.com/portainer/libhttp/response" -) - -// GET request on /api/extensions?store= -func (handler *Handler) extensionList(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { - fetchManifestInformation, _ := request.RetrieveBooleanQueryParameter(r, "store", true) - - extensions, err := handler.DataStore.Extension().Extensions() - if err != nil { - return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve extensions from the database", err} - } - - if fetchManifestInformation { - definitions, err := handler.ExtensionManager.FetchExtensionDefinitions() - if err != nil { - return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve extensions informations", err} - } - - extensions = mergeExtensionsAndDefinitions(extensions, definitions) - } - - return response.JSON(w, extensions) -} diff --git a/api/http/handler/extensions/extension_update.go b/api/http/handler/extensions/extension_update.go deleted file mode 100644 index 6ff469e48..000000000 --- a/api/http/handler/extensions/extension_update.go +++ /dev/null @@ -1,58 +0,0 @@ -package extensions - -import ( - "errors" - "net/http" - - "github.com/asaskevich/govalidator" - httperror "github.com/portainer/libhttp/error" - "github.com/portainer/libhttp/request" - "github.com/portainer/libhttp/response" - "github.com/portainer/portainer/api" - bolterrors "github.com/portainer/portainer/api/bolt/errors" -) - -type extensionUpdatePayload struct { - Version string -} - -func (payload *extensionUpdatePayload) Validate(r *http.Request) error { - if govalidator.IsNull(payload.Version) { - return errors.New("Invalid extension version") - } - - return nil -} - -func (handler *Handler) extensionUpdate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { - extensionIdentifier, err := request.RetrieveNumericRouteVariableValue(r, "id") - if err != nil { - return &httperror.HandlerError{http.StatusBadRequest, "Invalid extension identifier route variable", err} - } - extensionID := portainer.ExtensionID(extensionIdentifier) - - var payload extensionUpdatePayload - err = request.DecodeAndValidateJSONPayload(r, &payload) - if err != nil { - return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err} - } - - extension, err := handler.DataStore.Extension().Extension(extensionID) - if err == bolterrors.ErrObjectNotFound { - return &httperror.HandlerError{http.StatusNotFound, "Unable to find a extension with the specified identifier inside the database", err} - } else if err != nil { - return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find a extension with the specified identifier inside the database", err} - } - - err = handler.ExtensionManager.UpdateExtension(extension, payload.Version) - if err != nil { - return &httperror.HandlerError{http.StatusInternalServerError, "Unable to update extension", err} - } - - err = handler.DataStore.Extension().Persist(extension) - if err != nil { - return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist extension status inside the database", err} - } - - return response.Empty(w) -} diff --git a/api/http/handler/extensions/extension_upload.go b/api/http/handler/extensions/extension_upload.go deleted file mode 100644 index 236d445ee..000000000 --- a/api/http/handler/extensions/extension_upload.go +++ /dev/null @@ -1,76 +0,0 @@ -package extensions - -import ( - "errors" - "net/http" - "strconv" - - httperror "github.com/portainer/libhttp/error" - "github.com/portainer/libhttp/request" - "github.com/portainer/libhttp/response" - "github.com/portainer/portainer/api" -) - -type extensionUploadPayload struct { - License string - ExtensionArchive []byte - ArchiveFileName string -} - -func (payload *extensionUploadPayload) Validate(r *http.Request) error { - license, err := request.RetrieveMultiPartFormValue(r, "License", false) - if err != nil { - return errors.New("Invalid license") - } - payload.License = license - - fileData, fileName, err := request.RetrieveMultiPartFormFile(r, "file") - if err != nil { - return errors.New("Invalid extension archive file. Ensure that the file is uploaded correctly") - } - payload.ExtensionArchive = fileData - payload.ArchiveFileName = fileName - - return nil -} - -func (handler *Handler) extensionUpload(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { - payload := &extensionUploadPayload{} - err := payload.Validate(r) - if err != nil { - return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err} - } - - extensionIdentifier, err := strconv.Atoi(string(payload.License[0])) - if err != nil { - return &httperror.HandlerError{http.StatusBadRequest, "Invalid license format", err} - } - extensionID := portainer.ExtensionID(extensionIdentifier) - - extension := &portainer.Extension{ - ID: extensionID, - } - - _ = handler.ExtensionManager.DisableExtension(extension) - - err = handler.ExtensionManager.InstallExtension(extension, payload.License, payload.ArchiveFileName, payload.ExtensionArchive) - if err != nil { - return &httperror.HandlerError{http.StatusInternalServerError, "Unable to install extension", err} - } - - extension.Enabled = true - - if extension.ID == portainer.RBACExtension { - err = handler.upgradeRBACData() - if err != nil { - return &httperror.HandlerError{http.StatusInternalServerError, "An error occured during database update", err} - } - } - - err = handler.DataStore.Extension().Persist(extension) - if err != nil { - return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist extension status inside the database", err} - } - - return response.Empty(w) -} diff --git a/api/http/handler/extensions/handler.go b/api/http/handler/extensions/handler.go deleted file mode 100644 index b33c23881..000000000 --- a/api/http/handler/extensions/handler.go +++ /dev/null @@ -1,84 +0,0 @@ -package extensions - -import ( - "net/http" - - "github.com/coreos/go-semver/semver" - - "github.com/gorilla/mux" - httperror "github.com/portainer/libhttp/error" - "github.com/portainer/portainer/api" - "github.com/portainer/portainer/api/http/security" - "github.com/portainer/portainer/api/internal/authorization" -) - -// Handler is the HTTP handler used to handle extension operations. -type Handler struct { - *mux.Router - DataStore portainer.DataStore - ExtensionManager portainer.ExtensionManager - AuthorizationService *authorization.Service -} - -// NewHandler creates a handler to manage extension operations. -func NewHandler(bouncer *security.RequestBouncer) *Handler { - h := &Handler{ - Router: mux.NewRouter(), - } - - h.Handle("/extensions", - bouncer.RestrictedAccess(httperror.LoggerHandler(h.extensionList))).Methods(http.MethodGet) - h.Handle("/extensions", - bouncer.AdminAccess(httperror.LoggerHandler(h.extensionCreate))).Methods(http.MethodPost) - h.Handle("/extensions/upload", - bouncer.AdminAccess(httperror.LoggerHandler(h.extensionUpload))).Methods(http.MethodPost) - h.Handle("/extensions/{id}", - bouncer.AdminAccess(httperror.LoggerHandler(h.extensionInspect))).Methods(http.MethodGet) - h.Handle("/extensions/{id}", - bouncer.AdminAccess(httperror.LoggerHandler(h.extensionDelete))).Methods(http.MethodDelete) - h.Handle("/extensions/{id}/update", - bouncer.AdminAccess(httperror.LoggerHandler(h.extensionUpdate))).Methods(http.MethodPost) - - return h -} - -func mergeExtensionsAndDefinitions(extensions, definitions []portainer.Extension) []portainer.Extension { - for _, definition := range definitions { - foundInDB := false - - for idx, extension := range extensions { - if extension.ID == definition.ID { - foundInDB = true - mergeExtensionAndDefinition(&extensions[idx], &definition) - break - } - } - - if !foundInDB { - extensions = append(extensions, definition) - } - } - - return extensions -} - -func mergeExtensionAndDefinition(extension, definition *portainer.Extension) { - extension.Name = definition.Name - extension.ShortDescription = definition.ShortDescription - extension.Deal = definition.Deal - extension.Available = definition.Available - extension.DescriptionURL = definition.DescriptionURL - extension.Images = definition.Images - extension.Logo = definition.Logo - extension.Price = definition.Price - extension.PriceDescription = definition.PriceDescription - extension.ShopURL = definition.ShopURL - - definitionVersion := semver.New(definition.Version) - extensionVersion := semver.New(extension.Version) - if extensionVersion.LessThan(*definitionVersion) { - extension.UpdateAvailable = true - } - - extension.Version = definition.Version -} diff --git a/api/http/handler/handler.go b/api/http/handler/handler.go index 4d7dbf760..3ebe57a5b 100644 --- a/api/http/handler/handler.go +++ b/api/http/handler/handler.go @@ -15,7 +15,6 @@ import ( "github.com/portainer/portainer/api/http/handler/endpointgroups" "github.com/portainer/portainer/api/http/handler/endpointproxy" "github.com/portainer/portainer/api/http/handler/endpoints" - "github.com/portainer/portainer/api/http/handler/extensions" "github.com/portainer/portainer/api/http/handler/file" "github.com/portainer/portainer/api/http/handler/motd" "github.com/portainer/portainer/api/http/handler/registries" @@ -50,7 +49,6 @@ type Handler struct { EndpointProxyHandler *endpointproxy.Handler FileHandler *file.Handler MOTDHandler *motd.Handler - ExtensionHandler *extensions.Handler RegistryHandler *registries.Handler ResourceControlHandler *resourcecontrols.Handler RoleHandler *roles.Handler @@ -104,8 +102,6 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { default: http.StripPrefix("/api", h.EndpointHandler).ServeHTTP(w, r) } - case strings.HasPrefix(r.URL.Path, "/api/extensions"): - http.StripPrefix("/api", h.ExtensionHandler).ServeHTTP(w, r) case strings.HasPrefix(r.URL.Path, "/api/motd"): http.StripPrefix("/api", h.MOTDHandler).ServeHTTP(w, r) case strings.HasPrefix(r.URL.Path, "/api/registries"): diff --git a/api/http/handler/settings/handler.go b/api/http/handler/settings/handler.go index 143f99962..9fa17b842 100644 --- a/api/http/handler/settings/handler.go +++ b/api/http/handler/settings/handler.go @@ -3,8 +3,6 @@ package settings import ( "net/http" - "github.com/portainer/portainer/api/internal/authorization" - "github.com/gorilla/mux" httperror "github.com/portainer/libhttp/error" "github.com/portainer/portainer/api" @@ -19,12 +17,11 @@ func hideFields(settings *portainer.Settings) { // Handler is the HTTP handler used to handle settings operations. type Handler struct { *mux.Router - AuthorizationService *authorization.Service - DataStore portainer.DataStore - FileService portainer.FileService - JWTService portainer.JWTService - LDAPService portainer.LDAPService - SnapshotService portainer.SnapshotService + DataStore portainer.DataStore + FileService portainer.FileService + JWTService portainer.JWTService + LDAPService portainer.LDAPService + SnapshotService portainer.SnapshotService } // NewHandler creates a handler to manage settings operations. diff --git a/api/http/handler/settings/settings_update.go b/api/http/handler/settings/settings_update.go index 5d1aded0e..fbdf47bcf 100644 --- a/api/http/handler/settings/settings_update.go +++ b/api/http/handler/settings/settings_update.go @@ -10,7 +10,6 @@ import ( "github.com/portainer/libhttp/request" "github.com/portainer/libhttp/response" portainer "github.com/portainer/portainer/api" - bolterrors "github.com/portainer/portainer/api/bolt/errors" "github.com/portainer/portainer/api/filesystem" ) @@ -116,10 +115,8 @@ func (handler *Handler) settingsUpdate(w http.ResponseWriter, r *http.Request) * settings.AllowPrivilegedModeForRegularUsers = *payload.AllowPrivilegedModeForRegularUsers } - updateAuthorizations := false if payload.AllowVolumeBrowserForRegularUsers != nil { settings.AllowVolumeBrowserForRegularUsers = *payload.AllowVolumeBrowserForRegularUsers - updateAuthorizations = true } if payload.EnableHostManagementFeatures != nil { @@ -179,37 +176,9 @@ func (handler *Handler) settingsUpdate(w http.ResponseWriter, r *http.Request) * return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist settings changes inside the database", err} } - if updateAuthorizations { - err := handler.updateVolumeBrowserSetting(settings) - if err != nil { - return &httperror.HandlerError{http.StatusInternalServerError, "Unable to update RBAC authorizations", err} - } - } - return response.JSON(w, settings) } -func (handler *Handler) updateVolumeBrowserSetting(settings *portainer.Settings) error { - err := handler.AuthorizationService.UpdateVolumeBrowsingAuthorizations(settings.AllowVolumeBrowserForRegularUsers) - if err != nil { - return err - } - - extension, err := handler.DataStore.Extension().Extension(portainer.RBACExtension) - if err != nil && err != bolterrors.ErrObjectNotFound { - return err - } - - if extension != nil { - err = handler.AuthorizationService.UpdateUsersAuthorizations() - if err != nil { - return err - } - } - - return nil -} - func (handler *Handler) updateSnapshotInterval(settings *portainer.Settings, snapshotInterval string) error { settings.SnapshotInterval = snapshotInterval diff --git a/api/http/handler/stacks/handler.go b/api/http/handler/stacks/handler.go index b6210358f..c706afbfd 100644 --- a/api/http/handler/stacks/handler.go +++ b/api/http/handler/stacks/handler.go @@ -8,7 +8,6 @@ import ( "github.com/gorilla/mux" httperror "github.com/portainer/libhttp/error" "github.com/portainer/portainer/api" - bolterrors "github.com/portainer/portainer/api/bolt/errors" "github.com/portainer/portainer/api/http/security" "github.com/portainer/portainer/api/internal/authorization" ) @@ -81,22 +80,8 @@ func (handler *Handler) userCanAccessStack(securityContext *security.RestrictedR func (handler *Handler) userIsAdminOrEndpointAdmin(user *portainer.User, endpointID portainer.EndpointID) (bool, error) { isAdmin := user.Role == portainer.AdministratorRole - if isAdmin { - return true, nil - } - rbacExtension, err := handler.DataStore.Extension().Extension(portainer.RBACExtension) - if err != nil && err != bolterrors.ErrObjectNotFound { - return false, errors.New("Unable to verify if RBAC extension is loaded") - } - - if rbacExtension == nil { - return false, nil - } - - _, endpointResourceAccess := user.EndpointAuthorizations[portainer.EndpointID(endpointID)][portainer.EndpointResourcesAccess] - - return endpointResourceAccess, nil + return isAdmin, nil } func (handler *Handler) userCanCreateStack(securityContext *security.RestrictedRequestContext, endpointID portainer.EndpointID) (bool, error) { diff --git a/api/http/handler/stacks/stack_create.go b/api/http/handler/stacks/stack_create.go index daec00366..c9f115ad1 100644 --- a/api/http/handler/stacks/stack_create.go +++ b/api/http/handler/stacks/stack_create.go @@ -76,7 +76,7 @@ func (handler *Handler) stackCreate(w http.ResponseWriter, r *http.Request) *htt return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an endpoint with the specified identifier inside the database", err} } - err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint, true) + err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint) if err != nil { return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err} } diff --git a/api/http/handler/stacks/stack_delete.go b/api/http/handler/stacks/stack_delete.go index 89c7ab14b..6866c6809 100644 --- a/api/http/handler/stacks/stack_delete.go +++ b/api/http/handler/stacks/stack_delete.go @@ -65,7 +65,7 @@ func (handler *Handler) stackDelete(w http.ResponseWriter, r *http.Request) *htt return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find the endpoint associated to the stack inside the database", err} } - err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint, true) + err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint) if err != nil { return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err} } @@ -114,30 +114,8 @@ func (handler *Handler) deleteExternalStack(r *http.Request, w http.ResponseWrit return &httperror.HandlerError{http.StatusBadRequest, "Invalid query parameter: endpointId", err} } - user, err := handler.DataStore.User().User(securityContext.UserID) - if err != nil { - return &httperror.HandlerError{http.StatusInternalServerError, "Unable to load user information from the database", err} - } - - rbacExtension, err := handler.DataStore.Extension().Extension(portainer.RBACExtension) - if err != nil && err != bolterrors.ErrObjectNotFound { - return &httperror.HandlerError{http.StatusInternalServerError, "Unable to verify if RBAC extension is loaded", err} - } - - endpointResourceAccess := false - _, ok := user.EndpointAuthorizations[portainer.EndpointID(endpointID)][portainer.EndpointResourcesAccess] - if ok { - endpointResourceAccess = true - } - - if rbacExtension != nil { - if !securityContext.IsAdmin && !endpointResourceAccess { - return &httperror.HandlerError{http.StatusUnauthorized, "Permission denied to delete the stack", httperrors.ErrUnauthorized} - } - } else { - if !securityContext.IsAdmin { - return &httperror.HandlerError{http.StatusUnauthorized, "Permission denied to delete the stack", httperrors.ErrUnauthorized} - } + if !securityContext.IsAdmin { + return &httperror.HandlerError{http.StatusUnauthorized, "Permission denied to delete the stack", httperrors.ErrUnauthorized} } stack, err := handler.DataStore.Stack().StackByName(stackName) @@ -155,7 +133,7 @@ func (handler *Handler) deleteExternalStack(r *http.Request, w http.ResponseWrit return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find the endpoint associated to the stack inside the database", err} } - err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint, true) + err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint) if err != nil { return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err} } diff --git a/api/http/handler/stacks/stack_file.go b/api/http/handler/stacks/stack_file.go index e3a111668..8024a72f2 100644 --- a/api/http/handler/stacks/stack_file.go +++ b/api/http/handler/stacks/stack_file.go @@ -38,7 +38,7 @@ func (handler *Handler) stackFile(w http.ResponseWriter, r *http.Request) *httpe return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an endpoint with the specified identifier inside the database", err} } - err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint, true) + err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint) if err != nil { return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err} } diff --git a/api/http/handler/stacks/stack_inspect.go b/api/http/handler/stacks/stack_inspect.go index 3e78f66ec..df9bc279a 100644 --- a/api/http/handler/stacks/stack_inspect.go +++ b/api/http/handler/stacks/stack_inspect.go @@ -33,7 +33,7 @@ func (handler *Handler) stackInspect(w http.ResponseWriter, r *http.Request) *ht return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an endpoint with the specified identifier inside the database", err} } - err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint, true) + err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint) if err != nil { return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err} } diff --git a/api/http/handler/stacks/stack_list.go b/api/http/handler/stacks/stack_list.go index aff22b9a0..f8be720da 100644 --- a/api/http/handler/stacks/stack_list.go +++ b/api/http/handler/stacks/stack_list.go @@ -7,7 +7,6 @@ import ( "github.com/portainer/libhttp/request" "github.com/portainer/libhttp/response" "github.com/portainer/portainer/api" - "github.com/portainer/portainer/api/bolt/errors" "github.com/portainer/portainer/api/http/security" "github.com/portainer/portainer/api/internal/authorization" ) @@ -44,14 +43,6 @@ func (handler *Handler) stackList(w http.ResponseWriter, r *http.Request) *httpe stacks = authorization.DecorateStacks(stacks, resourceControls) if !securityContext.IsAdmin { - rbacExtensionEnabled := true - _, err := handler.DataStore.Extension().Extension(portainer.RBACExtension) - if err == errors.ErrObjectNotFound { - rbacExtensionEnabled = false - } else if err != nil && err != errors.ErrObjectNotFound { - return &httperror.HandlerError{http.StatusInternalServerError, "Unable to check if RBAC extension is enabled", err} - } - user, err := handler.DataStore.User().User(securityContext.UserID) if err != nil { return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve user information from the database", err} @@ -62,7 +53,7 @@ func (handler *Handler) stackList(w http.ResponseWriter, r *http.Request) *httpe userTeamIDs = append(userTeamIDs, membership.TeamID) } - stacks = authorization.FilterAuthorizedStacks(stacks, user, userTeamIDs, rbacExtensionEnabled) + stacks = authorization.FilterAuthorizedStacks(stacks, user, userTeamIDs) } return response.JSON(w, stacks) diff --git a/api/http/handler/stacks/stack_migrate.go b/api/http/handler/stacks/stack_migrate.go index 4e52c0e77..e84f7e5f7 100644 --- a/api/http/handler/stacks/stack_migrate.go +++ b/api/http/handler/stacks/stack_migrate.go @@ -53,7 +53,7 @@ func (handler *Handler) stackMigrate(w http.ResponseWriter, r *http.Request) *ht return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an endpoint with the specified identifier inside the database", err} } - err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint, true) + err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint) if err != nil { return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err} } diff --git a/api/http/handler/stacks/stack_update.go b/api/http/handler/stacks/stack_update.go index 86a1e65e5..df4178f8f 100644 --- a/api/http/handler/stacks/stack_update.go +++ b/api/http/handler/stacks/stack_update.go @@ -72,7 +72,7 @@ func (handler *Handler) stackUpdate(w http.ResponseWriter, r *http.Request) *htt return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find the endpoint associated to the stack inside the database", err} } - err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint, true) + err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint) if err != nil { return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err} } diff --git a/api/http/handler/teammemberships/handler.go b/api/http/handler/teammemberships/handler.go index 8628234e8..4ca0a0fdc 100644 --- a/api/http/handler/teammemberships/handler.go +++ b/api/http/handler/teammemberships/handler.go @@ -4,7 +4,6 @@ import ( httperror "github.com/portainer/libhttp/error" "github.com/portainer/portainer/api" "github.com/portainer/portainer/api/http/security" - "github.com/portainer/portainer/api/internal/authorization" "net/http" @@ -14,8 +13,7 @@ import ( // Handler is the HTTP handler used to handle team membership operations. type Handler struct { *mux.Router - DataStore portainer.DataStore - AuthorizationService *authorization.Service + DataStore portainer.DataStore } // 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 506608522..361534d07 100644 --- a/api/http/handler/teammemberships/teammembership_create.go +++ b/api/http/handler/teammemberships/teammembership_create.go @@ -72,10 +72,5 @@ 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 c12892325..f4e71d3c5 100644 --- a/api/http/handler/teammemberships/teammembership_delete.go +++ b/api/http/handler/teammemberships/teammembership_delete.go @@ -40,10 +40,5 @@ 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 d0a49e537..789dd8e7e 100644 --- a/api/http/handler/teams/handler.go +++ b/api/http/handler/teams/handler.go @@ -1,7 +1,6 @@ package teams import ( - "github.com/portainer/portainer/api/internal/authorization" "net/http" "github.com/gorilla/mux" @@ -13,8 +12,7 @@ import ( // Handler is the HTTP handler used to handle team operations. type Handler struct { *mux.Router - DataStore portainer.DataStore - AuthorizationService *authorization.Service + DataStore portainer.DataStore } // 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 6d3ac534c..b56551a7b 100644 --- a/api/http/handler/teams/team_delete.go +++ b/api/http/handler/teams/team_delete.go @@ -34,10 +34,5 @@ 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/admin_init.go b/api/http/handler/users/admin_init.go index d15ed7d7a..ef6c5425c 100644 --- a/api/http/handler/users/admin_init.go +++ b/api/http/handler/users/admin_init.go @@ -9,7 +9,6 @@ import ( "github.com/portainer/libhttp/request" "github.com/portainer/libhttp/response" "github.com/portainer/portainer/api" - "github.com/portainer/portainer/api/internal/authorization" ) type adminInitPayload struct { @@ -45,9 +44,8 @@ func (handler *Handler) adminInit(w http.ResponseWriter, r *http.Request) *httpe } user := &portainer.User{ - Username: payload.Username, - Role: portainer.AdministratorRole, - PortainerAuthorizations: authorization.DefaultPortainerAuthorizations(), + Username: payload.Username, + Role: portainer.AdministratorRole, } user.Password, err = handler.CryptoService.Hash(payload.Password) diff --git a/api/http/handler/users/handler.go b/api/http/handler/users/handler.go index 8c4da1c4a..5ada9b87a 100644 --- a/api/http/handler/users/handler.go +++ b/api/http/handler/users/handler.go @@ -6,7 +6,6 @@ import ( httperror "github.com/portainer/libhttp/error" "github.com/portainer/portainer/api" "github.com/portainer/portainer/api/http/security" - "github.com/portainer/portainer/api/internal/authorization" "net/http" @@ -28,9 +27,8 @@ func hideFields(user *portainer.User) { // Handler is the HTTP handler used to handle user operations. type Handler struct { *mux.Router - DataStore portainer.DataStore - CryptoService portainer.CryptoService - AuthorizationService *authorization.Service + DataStore portainer.DataStore + CryptoService portainer.CryptoService } // NewHandler creates a handler to manage user operations. diff --git a/api/http/handler/users/user_create.go b/api/http/handler/users/user_create.go index 36f9d9039..e9e1fa64a 100644 --- a/api/http/handler/users/user_create.go +++ b/api/http/handler/users/user_create.go @@ -12,7 +12,6 @@ import ( bolterrors "github.com/portainer/portainer/api/bolt/errors" httperrors "github.com/portainer/portainer/api/http/errors" "github.com/portainer/portainer/api/http/security" - "github.com/portainer/portainer/api/internal/authorization" ) type userCreatePayload struct { @@ -62,9 +61,8 @@ func (handler *Handler) userCreate(w http.ResponseWriter, r *http.Request) *http } user = &portainer.User{ - Username: payload.Username, - Role: portainer.UserRole(payload.Role), - PortainerAuthorizations: authorization.DefaultPortainerAuthorizations(), + Username: payload.Username, + Role: portainer.UserRole(payload.Role), } settings, err := handler.DataStore.Settings().Settings() diff --git a/api/http/handler/users/user_delete.go b/api/http/handler/users/user_delete.go index 12a1be2c0..289b12303 100644 --- a/api/http/handler/users/user_delete.go +++ b/api/http/handler/users/user_delete.go @@ -81,10 +81,5 @@ func (handler *Handler) deleteUser(w http.ResponseWriter, user *portainer.User) 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/handler/websocket/attach.go b/api/http/handler/websocket/attach.go index 6da16659a..ea43cd9cd 100644 --- a/api/http/handler/websocket/attach.go +++ b/api/http/handler/websocket/attach.go @@ -40,7 +40,7 @@ func (handler *Handler) websocketAttach(w http.ResponseWriter, r *http.Request) return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find the endpoint associated to the stack inside the database", err} } - err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint, true) + err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint) if err != nil { return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err} } diff --git a/api/http/handler/websocket/exec.go b/api/http/handler/websocket/exec.go index 0abbcb263..836cf252c 100644 --- a/api/http/handler/websocket/exec.go +++ b/api/http/handler/websocket/exec.go @@ -47,7 +47,7 @@ func (handler *Handler) websocketExec(w http.ResponseWriter, r *http.Request) *h return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find the endpoint associated to the stack inside the database", err} } - err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint, true) + err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint) if err != nil { return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err} } diff --git a/api/http/handler/websocket/pod.go b/api/http/handler/websocket/pod.go index 103b2b60f..b90f096b3 100644 --- a/api/http/handler/websocket/pod.go +++ b/api/http/handler/websocket/pod.go @@ -56,7 +56,7 @@ func (handler *Handler) websocketPodExec(w http.ResponseWriter, r *http.Request) return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find the endpoint associated to the stack inside the database", err} } - err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint, false) + err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint) if err != nil { return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err} } diff --git a/api/http/proxy/factory/docker/transport.go b/api/http/proxy/factory/docker/transport.go index 604d05d65..1cfbfd921 100644 --- a/api/http/proxy/factory/docker/transport.go +++ b/api/http/proxy/factory/docker/transport.go @@ -12,7 +12,6 @@ import ( "github.com/docker/docker/client" "github.com/portainer/portainer/api" - bolterrors "github.com/portainer/portainer/api/bolt/errors" "github.com/portainer/portainer/api/docker" "github.com/portainer/portainer/api/http/proxy/factory/responseutils" "github.com/portainer/portainer/api/http/security" @@ -402,16 +401,6 @@ func (transport *Transport) restrictedResourceOperation(request *http.Request, r } if tokenData.Role != portainer.AdministratorRole { - rbacExtension, err := transport.dataStore.Extension().Extension(portainer.RBACExtension) - if err != nil && err != bolterrors.ErrObjectNotFound { - return nil, err - } - - user, err := transport.dataStore.User().User(tokenData.ID) - if err != nil { - return nil, err - } - if volumeBrowseRestrictionCheck { settings, err := transport.dataStore.Settings().Settings() if err != nil { @@ -419,28 +408,10 @@ func (transport *Transport) restrictedResourceOperation(request *http.Request, r } if !settings.AllowVolumeBrowserForRegularUsers { - if rbacExtension == nil { - return responseutils.WriteAccessDeniedResponse() - } - - // Return access denied for all roles except endpoint-administrator - _, userCanBrowse := user.EndpointAuthorizations[transport.endpoint.ID][portainer.OperationDockerAgentBrowseList] - if !userCanBrowse { - return responseutils.WriteAccessDeniedResponse() - } + return responseutils.WriteAccessDeniedResponse() } } - endpointResourceAccess := false - _, ok := user.EndpointAuthorizations[transport.endpoint.ID][portainer.EndpointResourcesAccess] - if ok { - endpointResourceAccess = true - } - - if rbacExtension != nil && endpointResourceAccess { - return transport.executeDockerRequest(request) - } - teamMemberships, err := transport.dataStore.TeamMembership().TeamMembershipsByUserID(tokenData.ID) if err != nil { return nil, err @@ -713,25 +684,5 @@ func (transport *Transport) isAdminOrEndpointAdmin(request *http.Request) (bool, return false, err } - if tokenData.Role == portainer.AdministratorRole { - return true, nil - } - - user, err := transport.dataStore.User().User(tokenData.ID) - if err != nil { - return false, err - } - - rbacExtension, err := transport.dataStore.Extension().Extension(portainer.RBACExtension) - if err != nil && err != bolterrors.ErrObjectNotFound { - return false, err - } - - if rbacExtension == nil { - return false, nil - } - - _, endpointResourceAccess := user.EndpointAuthorizations[portainer.EndpointID(transport.endpoint.ID)][portainer.EndpointResourcesAccess] - - return endpointResourceAccess, nil + return tokenData.Role == portainer.AdministratorRole, nil } diff --git a/api/http/proxy/factory/factory.go b/api/http/proxy/factory/factory.go index 5beaba1f6..dd4e62440 100644 --- a/api/http/proxy/factory/factory.go +++ b/api/http/proxy/factory/factory.go @@ -1,7 +1,6 @@ package factory import ( - "fmt" "net/http" "net/http/httputil" "net/url" @@ -16,12 +15,8 @@ import ( const azureAPIBaseURL = "https://management.azure.com" -var extensionPorts = map[portainer.ExtensionID]string{ - portainer.RBACExtension: "7003", -} - type ( - // ProxyFactory is a factory to create reverse proxies to Docker endpoints and extensions + // ProxyFactory is a factory to create reverse proxies ProxyFactory struct { dataStore portainer.DataStore signatureService portainer.DigitalSignatureService @@ -44,25 +39,6 @@ func NewProxyFactory(dataStore portainer.DataStore, signatureService portainer.D } } -// BuildExtensionURL returns the URL to an extension server -func BuildExtensionURL(extensionID portainer.ExtensionID) string { - return fmt.Sprintf("http://%s:%s", portainer.ExtensionServer, extensionPorts[extensionID]) -} - -// NewExtensionProxy returns a new HTTP proxy to an extension server -func (factory *ProxyFactory) NewExtensionProxy(extensionID portainer.ExtensionID) (http.Handler, error) { - address := "http://" + portainer.ExtensionServer + ":" + extensionPorts[extensionID] - - extensionURL, err := url.Parse(address) - if err != nil { - return nil, err - } - - extensionURL.Scheme = "http" - proxy := httputil.NewSingleHostReverseProxy(extensionURL) - return proxy, nil -} - // NewLegacyExtensionProxy returns a new HTTP proxy to a legacy extension server (Storidge) func (factory *ProxyFactory) NewLegacyExtensionProxy(extensionAPIURL string) (http.Handler, error) { extensionURL, err := url.Parse(extensionAPIURL) diff --git a/api/http/proxy/manager.go b/api/http/proxy/manager.go index 10c2f4cf6..e539d89c2 100644 --- a/api/http/proxy/manager.go +++ b/api/http/proxy/manager.go @@ -2,7 +2,6 @@ package proxy import ( "net/http" - "strconv" "github.com/portainer/portainer/api/http/proxy/factory/kubernetes" @@ -21,7 +20,6 @@ type ( Manager struct { proxyFactory *factory.ProxyFactory endpointProxies cmap.ConcurrentMap - extensionProxies cmap.ConcurrentMap legacyExtensionProxies cmap.ConcurrentMap } ) @@ -30,7 +28,6 @@ type ( func NewManager(dataStore portainer.DataStore, signatureService portainer.DigitalSignatureService, tunnelService portainer.ReverseTunnelService, clientFactory *docker.ClientFactory, kubernetesClientFactory *cli.ClientFactory, kubernetesTokenCacheManager *kubernetes.TokenCacheManager) *Manager { return &Manager{ endpointProxies: cmap.New(), - extensionProxies: cmap.New(), legacyExtensionProxies: cmap.New(), proxyFactory: factory.NewProxyFactory(dataStore, signatureService, tunnelService, clientFactory, kubernetesClientFactory, kubernetesTokenCacheManager), } @@ -63,38 +60,6 @@ func (manager *Manager) DeleteEndpointProxy(endpoint *portainer.Endpoint) { manager.endpointProxies.Remove(string(endpoint.ID)) } -// CreateExtensionProxy creates a new HTTP reverse proxy for an extension and -// registers it in the extension map associated to the specified extension identifier -func (manager *Manager) CreateExtensionProxy(extensionID portainer.ExtensionID) (http.Handler, error) { - proxy, err := manager.proxyFactory.NewExtensionProxy(extensionID) - if err != nil { - return nil, err - } - - manager.extensionProxies.Set(strconv.Itoa(int(extensionID)), proxy) - return proxy, nil -} - -// GetExtensionProxy returns an extension proxy associated to an extension identifier -func (manager *Manager) GetExtensionProxy(extensionID portainer.ExtensionID) http.Handler { - proxy, ok := manager.extensionProxies.Get(strconv.Itoa(int(extensionID))) - if !ok { - return nil - } - - return proxy.(http.Handler) -} - -// GetExtensionURL retrieves the URL of an extension running locally based on the extension port table -func (manager *Manager) GetExtensionURL(extensionID portainer.ExtensionID) string { - return factory.BuildExtensionURL(extensionID) -} - -// DeleteExtensionProxy deletes the extension proxy associated to an extension identifier -func (manager *Manager) DeleteExtensionProxy(extensionID portainer.ExtensionID) { - manager.extensionProxies.Remove(strconv.Itoa(int(extensionID))) -} - // CreateLegacyExtensionProxy creates a new HTTP reverse proxy for a legacy extension and adds it to the registered proxies func (manager *Manager) CreateLegacyExtensionProxy(key, extensionAPIURL string) (http.Handler, error) { proxy, err := manager.proxyFactory.NewLegacyExtensionProxy(extensionAPIURL) diff --git a/api/http/security/bouncer.go b/api/http/security/bouncer.go index ca8c165cb..ac55555d3 100644 --- a/api/http/security/bouncer.go +++ b/api/http/security/bouncer.go @@ -14,9 +14,8 @@ import ( type ( // RequestBouncer represents an entity that manages API request accesses RequestBouncer struct { - dataStore portainer.DataStore - jwtService portainer.JWTService - rbacExtensionClient *rbacExtensionClient + dataStore portainer.DataStore + jwtService portainer.JWTService } // RestrictedRequestContext is a data structure containing information @@ -30,11 +29,10 @@ type ( ) // NewRequestBouncer initializes a new RequestBouncer -func NewRequestBouncer(dataStore portainer.DataStore, jwtService portainer.JWTService, rbacExtensionURL string) *RequestBouncer { +func NewRequestBouncer(dataStore portainer.DataStore, jwtService portainer.JWTService) *RequestBouncer { return &RequestBouncer{ - dataStore: dataStore, - jwtService: jwtService, - rbacExtensionClient: newRBACExtensionClient(rbacExtensionURL), + dataStore: dataStore, + jwtService: jwtService, } } @@ -47,8 +45,7 @@ func (bouncer *RequestBouncer) PublicAccess(h http.Handler) http.Handler { // AdminAccess defines a security check for API endpoints that require an authorization check. // Authentication is required to access these endpoints. -// If the RBAC extension is enabled, authorizations are required to use these endpoints. -// If the RBAC extension is not enabled, the administrator role is required to use these endpoints. +// The administrator role is required to use these endpoints. // The request context will be enhanced with a RestrictedRequestContext object // that might be used later to inside the API operation for extra authorization validation // and resource filtering. @@ -61,8 +58,6 @@ func (bouncer *RequestBouncer) AdminAccess(h http.Handler) http.Handler { // RestrictedAccess defines a security check for restricted API endpoints. // Authentication is required to access these endpoints. -// If the RBAC extension is enabled, authorizations are required to use these endpoints. -// If the RBAC extension is not enabled, access is granted to any authenticated user. // The request context will be enhanced with a RestrictedRequestContext object // that might be used later to inside the API operation for extra authorization validation // and resource filtering. @@ -86,11 +81,9 @@ func (bouncer *RequestBouncer) AuthenticatedAccess(h http.Handler) http.Handler // AuthorizedEndpointOperation retrieves the JWT token from the request context and verifies // that the user can access the specified endpoint. -// If the RBAC extension is enabled and the authorizationCheck flag is set, -// it will also validate that the user can execute the specified operation. // An error is returned when access to the endpoint is denied or if the user do not have the required // authorization to execute the operation. -func (bouncer *RequestBouncer) AuthorizedEndpointOperation(r *http.Request, endpoint *portainer.Endpoint, authorizationCheck bool) error { +func (bouncer *RequestBouncer) AuthorizedEndpointOperation(r *http.Request, endpoint *portainer.Endpoint) error { tokenData, err := RetrieveTokenData(r) if err != nil { return err @@ -114,13 +107,6 @@ func (bouncer *RequestBouncer) AuthorizedEndpointOperation(r *http.Request, endp return httperrors.ErrEndpointAccessDenied } - if authorizationCheck { - err = bouncer.checkEndpointOperationAuthorization(r, endpoint) - if err != nil { - return ErrAuthorizationRequired - } - } - return nil } @@ -142,38 +128,6 @@ func (bouncer *RequestBouncer) AuthorizedEdgeEndpointOperation(r *http.Request, return nil } -func (bouncer *RequestBouncer) checkEndpointOperationAuthorization(r *http.Request, endpoint *portainer.Endpoint) error { - tokenData, err := RetrieveTokenData(r) - if err != nil { - return err - } - - if tokenData.Role == portainer.AdministratorRole { - return nil - } - - extension, err := bouncer.dataStore.Extension().Extension(portainer.RBACExtension) - if err == bolterrors.ErrObjectNotFound { - return nil - } else if err != nil { - return err - } - - user, err := bouncer.dataStore.User().User(tokenData.ID) - if err != nil { - return err - } - - apiOperation := &portainer.APIOperationAuthorizationRequest{ - Path: r.URL.String(), - Method: r.Method, - Authorizations: user.EndpointAuthorizations[endpoint.ID], - } - - bouncer.rbacExtensionClient.setLicenseKey(extension.License.LicenseKey) - return bouncer.rbacExtensionClient.checkAuthorization(apiOperation) -} - // RegistryAccess retrieves the JWT token from the request context and verifies // that the user can access the specified registry. // An error is returned when access is denied. @@ -206,9 +160,8 @@ func (bouncer *RequestBouncer) mwAuthenticatedUser(h http.Handler) http.Handler } // mwCheckPortainerAuthorizations will verify that the user has the required authorization to access -// a specific API endpoint. It will leverage the RBAC extension authorization validation if the extension -// is enabled. -// If the administratorOnly flag is specified and the RBAC extension is not enabled, this will prevent non-admin +// a specific API endpoint. +// If the administratorOnly flag is specified, this will prevent non-admin // users from accessing the endpoint. func (bouncer *RequestBouncer) mwCheckPortainerAuthorizations(next http.Handler, administratorOnly bool) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -223,21 +176,12 @@ func (bouncer *RequestBouncer) mwCheckPortainerAuthorizations(next http.Handler, return } - extension, err := bouncer.dataStore.Extension().Extension(portainer.RBACExtension) - if err == bolterrors.ErrObjectNotFound { - if administratorOnly { - httperror.WriteError(w, http.StatusForbidden, "Access denied", httperrors.ErrUnauthorized) - return - } - - next.ServeHTTP(w, r) - return - } else if err != nil { - httperror.WriteError(w, http.StatusInternalServerError, "Unable to find a extension with the specified identifier inside the database", err) + if administratorOnly { + httperror.WriteError(w, http.StatusForbidden, "Access denied", httperrors.ErrUnauthorized) return } - user, err := bouncer.dataStore.User().User(tokenData.ID) + _, err = bouncer.dataStore.User().User(tokenData.ID) if err != nil && err == bolterrors.ErrObjectNotFound { httperror.WriteError(w, http.StatusUnauthorized, "Unauthorized", httperrors.ErrUnauthorized) return @@ -246,19 +190,6 @@ func (bouncer *RequestBouncer) mwCheckPortainerAuthorizations(next http.Handler, return } - apiOperation := &portainer.APIOperationAuthorizationRequest{ - Path: r.URL.String(), - Method: r.Method, - Authorizations: user.PortainerAuthorizations, - } - - bouncer.rbacExtensionClient.setLicenseKey(extension.License.LicenseKey) - err = bouncer.rbacExtensionClient.checkAuthorization(apiOperation) - if err != nil { - httperror.WriteError(w, http.StatusForbidden, "Access denied", ErrAuthorizationRequired) - return - } - next.ServeHTTP(w, r) }) } diff --git a/api/http/security/rbac.go b/api/http/security/rbac.go deleted file mode 100644 index f4281331b..000000000 --- a/api/http/security/rbac.go +++ /dev/null @@ -1,59 +0,0 @@ -package security - -import ( - "encoding/json" - "net/http" - "time" - - portainer "github.com/portainer/portainer/api" -) - -const ( - defaultHTTPTimeout = 5 -) - -type rbacExtensionClient struct { - httpClient *http.Client - extensionURL string - licenseKey string -} - -func newRBACExtensionClient(extensionURL string) *rbacExtensionClient { - return &rbacExtensionClient{ - extensionURL: extensionURL, - httpClient: &http.Client{ - Timeout: time.Second * time.Duration(defaultHTTPTimeout), - }, - } -} - -func (client *rbacExtensionClient) setLicenseKey(licenseKey string) { - client.licenseKey = licenseKey -} - -func (client *rbacExtensionClient) checkAuthorization(authRequest *portainer.APIOperationAuthorizationRequest) error { - encodedAuthRequest, err := json.Marshal(authRequest) - if err != nil { - return err - } - - req, err := http.NewRequest("GET", client.extensionURL+"/authorized_operation", nil) - if err != nil { - return err - } - - req.Header.Set("X-RBAC-AuthorizationRequest", string(encodedAuthRequest)) - req.Header.Set("X-PortainerExtension-License", client.licenseKey) - - resp, err := client.httpClient.Do(req) - if err != nil { - return err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusNoContent { - return ErrAuthorizationRequired - } - - return nil -} diff --git a/api/http/server.go b/api/http/server.go index f75aaf86e..4d5fd263a 100644 --- a/api/http/server.go +++ b/api/http/server.go @@ -20,7 +20,6 @@ import ( "github.com/portainer/portainer/api/http/handler/endpointgroups" "github.com/portainer/portainer/api/http/handler/endpointproxy" "github.com/portainer/portainer/api/http/handler/endpoints" - "github.com/portainer/portainer/api/http/handler/extensions" "github.com/portainer/portainer/api/http/handler/file" "github.com/portainer/portainer/api/http/handler/motd" "github.com/portainer/portainer/api/http/handler/registries" @@ -41,7 +40,6 @@ import ( "github.com/portainer/portainer/api/http/proxy" "github.com/portainer/portainer/api/http/proxy/factory/kubernetes" "github.com/portainer/portainer/api/http/security" - "github.com/portainer/portainer/api/internal/authorization" "github.com/portainer/portainer/api/kubernetes/cli" ) @@ -51,7 +49,6 @@ type Server struct { AssetsPath string Status *portainer.Status ReverseTunnelService portainer.ReverseTunnelService - ExtensionManager portainer.ExtensionManager ComposeStackManager portainer.ComposeStackManager CryptoService portainer.CryptoService SignatureService portainer.DigitalSignatureService @@ -74,12 +71,10 @@ type Server struct { // Start starts the HTTP server func (server *Server) Start() error { - authorizationService := authorization.NewService(server.DataStore) kubernetesTokenCacheManager := kubernetes.NewTokenCacheManager() proxyManager := proxy.NewManager(server.DataStore, server.SignatureService, server.ReverseTunnelService, server.DockerClientFactory, server.KubernetesClientFactory, kubernetesTokenCacheManager) - rbacExtensionURL := proxyManager.GetExtensionURL(portainer.RBACExtension) - requestBouncer := security.NewRequestBouncer(server.DataStore, server.JWTService, rbacExtensionURL) + requestBouncer := security.NewRequestBouncer(server.DataStore, server.JWTService) rateLimiter := security.NewRateLimiter(10, 1*time.Second, 1*time.Hour) @@ -89,7 +84,6 @@ func (server *Server) Start() error { authHandler.JWTService = server.JWTService authHandler.LDAPService = server.LDAPService authHandler.ProxyManager = proxyManager - authHandler.AuthorizationService = authorizationService authHandler.KubernetesTokenCacheManager = kubernetesTokenCacheManager authHandler.OAuthService = server.OAuthService @@ -122,7 +116,6 @@ func (server *Server) Start() error { var endpointHandler = endpoints.NewHandler(requestBouncer) endpointHandler.DataStore = server.DataStore - endpointHandler.AuthorizationService = authorizationService endpointHandler.FileService = server.FileService endpointHandler.ProxyManager = proxyManager endpointHandler.SnapshotService = server.SnapshotService @@ -136,7 +129,6 @@ func (server *Server) Start() error { var endpointGroupHandler = endpointgroups.NewHandler(requestBouncer) endpointGroupHandler.DataStore = server.DataStore - endpointGroupHandler.AuthorizationService = authorizationService var endpointProxyHandler = endpointproxy.NewHandler(requestBouncer) endpointProxyHandler.DataStore = server.DataStore @@ -147,11 +139,6 @@ func (server *Server) Start() error { var motdHandler = motd.NewHandler(requestBouncer) - var extensionHandler = extensions.NewHandler(requestBouncer) - extensionHandler.DataStore = server.DataStore - extensionHandler.ExtensionManager = server.ExtensionManager - extensionHandler.AuthorizationService = authorizationService - var registryHandler = registries.NewHandler(requestBouncer) registryHandler.DataStore = server.DataStore registryHandler.FileService = server.FileService @@ -161,7 +148,6 @@ func (server *Server) Start() error { resourceControlHandler.DataStore = server.DataStore var settingsHandler = settings.NewHandler(requestBouncer) - settingsHandler.AuthorizationService = authorizationService settingsHandler.DataStore = server.DataStore settingsHandler.FileService = server.FileService settingsHandler.JWTService = server.JWTService @@ -181,11 +167,9 @@ func (server *Server) Start() error { var teamHandler = teams.NewHandler(requestBouncer) teamHandler.DataStore = server.DataStore - teamHandler.AuthorizationService = authorizationService var teamMembershipHandler = teammemberships.NewHandler(requestBouncer) teamMembershipHandler.DataStore = server.DataStore - teamMembershipHandler.AuthorizationService = authorizationService var statusHandler = status.NewHandler(requestBouncer, server.Status) @@ -202,7 +186,6 @@ func (server *Server) Start() error { var userHandler = users.NewHandler(requestBouncer, rateLimiter) userHandler.DataStore = server.DataStore userHandler.CryptoService = server.CryptoService - userHandler.AuthorizationService = authorizationService var websocketHandler = websocket.NewHandler(requestBouncer) websocketHandler.DataStore = server.DataStore @@ -229,7 +212,6 @@ func (server *Server) Start() error { EndpointProxyHandler: endpointProxyHandler, FileHandler: fileHandler, MOTDHandler: motdHandler, - ExtensionHandler: extensionHandler, RegistryHandler: registryHandler, ResourceControlHandler: resourceControlHandler, SettingsHandler: settingsHandler, diff --git a/api/internal/authorization/access_control.go b/api/internal/authorization/access_control.go index cb263b76a..0e2d91ab7 100644 --- a/api/internal/authorization/access_control.go +++ b/api/internal/authorization/access_control.go @@ -119,16 +119,10 @@ func DecorateCustomTemplates(templates []portainer.CustomTemplate, resourceContr } // FilterAuthorizedStacks returns a list of decorated stacks filtered through resource control access checks. -func FilterAuthorizedStacks(stacks []portainer.Stack, user *portainer.User, userTeamIDs []portainer.TeamID, rbacEnabled bool) []portainer.Stack { +func FilterAuthorizedStacks(stacks []portainer.Stack, user *portainer.User, userTeamIDs []portainer.TeamID) []portainer.Stack { authorizedStacks := make([]portainer.Stack, 0) for _, stack := range stacks { - _, ok := user.EndpointAuthorizations[stack.EndpointID][portainer.EndpointResourcesAccess] - if rbacEnabled && ok { - authorizedStacks = append(authorizedStacks, stack) - continue - } - if stack.ResourceControl != nil && UserCanAccessResource(user.ID, userTeamIDs, stack.ResourceControl) { authorizedStacks = append(authorizedStacks, stack) } diff --git a/api/internal/authorization/authorizations.go b/api/internal/authorization/authorizations.go index 682f50a56..31916cd31 100644 --- a/api/internal/authorization/authorizations.go +++ b/api/internal/authorization/authorizations.go @@ -412,7 +412,6 @@ func DefaultPortainerAuthorizations() portainer.Authorizations { portainer.OperationPortainerEndpointInspect: true, portainer.OperationPortainerEndpointExtensionAdd: true, portainer.OperationPortainerEndpointExtensionRemove: true, - portainer.OperationPortainerExtensionList: true, portainer.OperationPortainerMOTD: true, portainer.OperationPortainerRegistryList: true, portainer.OperationPortainerRegistryInspect: true, @@ -425,182 +424,6 @@ func DefaultPortainerAuthorizations() portainer.Authorizations { } } -// UpdateVolumeBrowsingAuthorizations will update all the volume browsing authorizations for each role (except endpoint administrator) -// based on the specified removeAuthorizations parameter. If removeAuthorizations is set to true, all -// the authorizations will be dropped for the each role. If removeAuthorizations is set to false, the authorizations -// will be reset based for each role. -func (service Service) UpdateVolumeBrowsingAuthorizations(remove bool) error { - roles, err := service.dataStore.Role().Roles() - if err != nil { - return err - } - - for _, role := range roles { - // all roles except endpoint administrator - if role.ID != portainer.RoleID(1) { - updateRoleVolumeBrowsingAuthorizations(&role, remove) - - err := service.dataStore.Role().UpdateRole(role.ID, &role) - if err != nil { - return err - } - } - } - - return nil -} - -func updateRoleVolumeBrowsingAuthorizations(role *portainer.Role, removeAuthorizations bool) { - if !removeAuthorizations { - delete(role.Authorizations, portainer.OperationDockerAgentBrowseDelete) - delete(role.Authorizations, portainer.OperationDockerAgentBrowseGet) - delete(role.Authorizations, portainer.OperationDockerAgentBrowseList) - delete(role.Authorizations, portainer.OperationDockerAgentBrowsePut) - delete(role.Authorizations, portainer.OperationDockerAgentBrowseRename) - return - } - - role.Authorizations[portainer.OperationDockerAgentBrowseGet] = true - role.Authorizations[portainer.OperationDockerAgentBrowseList] = true - - // Standard-user - if role.ID == portainer.RoleID(3) { - role.Authorizations[portainer.OperationDockerAgentBrowseDelete] = true - role.Authorizations[portainer.OperationDockerAgentBrowsePut] = true - role.Authorizations[portainer.OperationDockerAgentBrowseRename] = true - } -} - -// RemoveTeamAccessPolicies will remove all existing access policies associated to the specified team -func (service *Service) RemoveTeamAccessPolicies(teamID portainer.TeamID) error { - endpoints, err := service.dataStore.Endpoint().Endpoints() - if err != nil { - return err - } - - for _, endpoint := range endpoints { - for policyTeamID := range endpoint.TeamAccessPolicies { - if policyTeamID == teamID { - delete(endpoint.TeamAccessPolicies, policyTeamID) - - err := service.dataStore.Endpoint().UpdateEndpoint(endpoint.ID, &endpoint) - if err != nil { - return err - } - - break - } - } - } - - endpointGroups, err := service.dataStore.EndpointGroup().EndpointGroups() - if err != nil { - return err - } - - for _, endpointGroup := range endpointGroups { - for policyTeamID := range endpointGroup.TeamAccessPolicies { - if policyTeamID == teamID { - delete(endpointGroup.TeamAccessPolicies, policyTeamID) - - err := service.dataStore.EndpointGroup().UpdateEndpointGroup(endpointGroup.ID, &endpointGroup) - if err != nil { - return err - } - - break - } - } - } - - registries, err := service.dataStore.Registry().Registries() - if err != nil { - return err - } - - for _, registry := range registries { - for policyTeamID := range registry.TeamAccessPolicies { - if policyTeamID == teamID { - delete(registry.TeamAccessPolicies, policyTeamID) - - err := service.dataStore.Registry().UpdateRegistry(registry.ID, ®istry) - if err != nil { - return err - } - - break - } - } - } - - return service.UpdateUsersAuthorizations() -} - -// RemoveUserAccessPolicies will remove all existing access policies associated to the specified user -func (service *Service) RemoveUserAccessPolicies(userID portainer.UserID) error { - endpoints, err := service.dataStore.Endpoint().Endpoints() - if err != nil { - return err - } - - for _, endpoint := range endpoints { - for policyUserID := range endpoint.UserAccessPolicies { - if policyUserID == userID { - delete(endpoint.UserAccessPolicies, policyUserID) - - err := service.dataStore.Endpoint().UpdateEndpoint(endpoint.ID, &endpoint) - if err != nil { - return err - } - - break - } - } - } - - endpointGroups, err := service.dataStore.EndpointGroup().EndpointGroups() - if err != nil { - return err - } - - for _, endpointGroup := range endpointGroups { - for policyUserID := range endpointGroup.UserAccessPolicies { - if policyUserID == userID { - delete(endpointGroup.UserAccessPolicies, policyUserID) - - err := service.dataStore.EndpointGroup().UpdateEndpointGroup(endpointGroup.ID, &endpointGroup) - if err != nil { - return err - } - - break - } - } - } - - registries, err := service.dataStore.Registry().Registries() - if err != nil { - return err - } - - for _, registry := range registries { - for policyUserID := range registry.UserAccessPolicies { - if policyUserID == userID { - delete(registry.UserAccessPolicies, policyUserID) - - err := service.dataStore.Registry().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 *Service) UpdateUsersAuthorizations() error { users, err := service.dataStore.User().Users() diff --git a/api/portainer.go b/api/portainer.go index f6f960380..58ee3d662 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -14,13 +14,6 @@ type ( // AgentPlatform represents a platform type for an Agent AgentPlatform int - // APIOperationAuthorizationRequest represent an request for the authorization to execute an API operation - APIOperationAuthorizationRequest struct { - Path string - Method string - Authorizations Authorizations - } - // AuthenticationMethod represents the authentication method used to authenticate a user AuthenticationMethod int @@ -287,7 +280,7 @@ type ( EdgeStacks map[EdgeStackID]bool } - // Extension represents a Portainer extension + // Extension represents a deprecated Portainer extension Extension struct { ID ExtensionID `json:"Id"` Enabled bool `json:"Enabled"` @@ -724,10 +717,13 @@ type ( // User represents a user account User struct { - ID UserID `json:"Id"` - Username string `json:"Username"` - Password string `json:"Password,omitempty"` - Role UserRole `json:"Role"` + ID UserID `json:"Id"` + Username string `json:"Username"` + Password string `json:"Password,omitempty"` + Role UserRole `json:"Role"` + + // Deprecated fields + // Deprecated in DBVersion == 25 PortainerAuthorizations Authorizations `json:"PortainerAuthorizations"` EndpointAuthorizations EndpointAuthorizations `json:"EndpointAuthorizations"` } @@ -807,7 +803,6 @@ type ( Endpoint() EndpointService EndpointGroup() EndpointGroupService EndpointRelation() EndpointRelationService - Extension() ExtensionService Registry() RegistryService ResourceControl() ResourceControlService Role() RoleService @@ -899,24 +894,6 @@ type ( DeleteEndpointRelation(EndpointID EndpointID) error } - // ExtensionManager represents a service used to manage extensions - ExtensionManager interface { - FetchExtensionDefinitions() ([]Extension, error) - InstallExtension(extension *Extension, licenseKey string, archiveFileName string, extensionArchive []byte) error - EnableExtension(extension *Extension, licenseKey string) error - DisableExtension(extension *Extension) error - UpdateExtension(extension *Extension, version string) error - StartExtensions() error - } - - // ExtensionService represents a service for managing extension data - ExtensionService interface { - Extension(ID ExtensionID) (*Extension, error) - Extensions() ([]Extension, error) - Persist(extension *Extension) error - DeleteExtension(ID ExtensionID) error - } - // FileService represents a service for managing files FileService interface { GetFileContent(filePath string) ([]byte, error) @@ -941,7 +918,6 @@ type ( ClearEdgeJobTaskLogs(edgeJobID, taskID string) error GetEdgeJobTaskLogFileContent(edgeJobID, taskID string) (string, error) StoreEdgeJobTaskLogFileFromBytes(edgeJobID, taskID string, data []byte) error - ExtractExtensionArchive(data []byte) error GetBinaryFolder() string StoreCustomTemplateFileFromBytes(identifier, fileName string, data []byte) (string, error) GetCustomTemplateProjectPath(identifier string) string @@ -1143,8 +1119,6 @@ const ( MessageOfTheDayURL = AssetsServerURL + "/motd.json" // VersionCheckURL represents the URL used to retrieve the latest version of Portainer VersionCheckURL = "https://api.github.com/repos/portainer/portainer/releases/latest" - // ExtensionDefinitionsURL represents the URL where Portainer extension definitions can be retrieved - ExtensionDefinitionsURL = AssetsServerURL + "/extensions-" + APIVersion + ".json" // SupportProductsURL represents the URL where Portainer support products can be retrieved SupportProductsURL = AssetsServerURL + "/support.json" // PortainerAgentHeader represents the name of the header available in any agent response @@ -1164,12 +1138,8 @@ const ( // PortainerAgentSignatureMessage represents the message used to create a digital signature // to be used when communicating with an agent PortainerAgentSignatureMessage = "Portainer-App" - // ExtensionServer represents the server used by Portainer to communicate with extensions - ExtensionServer = "127.0.0.1" // DefaultEdgeAgentCheckinIntervalInSeconds represents the default interval (in seconds) used by Edge agents to checkin with the Portainer instance DefaultEdgeAgentCheckinIntervalInSeconds = 5 - // LocalExtensionManifestFile represents the name of the local manifest file for extensions - LocalExtensionManifestFile = "/app/extensions.json" // DefaultTemplatesURL represents the URL to the official templates supported by Portainer DefaultTemplatesURL = "https://raw.githubusercontent.com/portainer/templates/master/templates-2.0.json" // DefaultUserSessionTimeout represents the default timeout after which the user session is cleared @@ -1254,16 +1224,6 @@ const ( EdgeAgentOnKubernetesEnvironment ) -const ( - _ ExtensionID = iota - // RegistryManagementExtension represents the registry management extension (removed) - RegistryManagementExtension - // OAuthAuthenticationExtension represents the OAuth authentication extension (Deprecated) - OAuthAuthenticationExtension - // RBACExtension represents the RBAC extension - RBACExtension -) - const ( _ JobType = iota // SnapshotJobType is a system job used to create endpoint snapshots diff --git a/api/swagger.yaml b/api/swagger.yaml index 1e29c9fae..6a8117eb6 100644 --- a/api/swagger.yaml +++ b/api/swagger.yaml @@ -834,196 +834,6 @@ paths: description: 'Server error' schema: $ref: '#/definitions/GenericError' - /extensions: - get: - tags: - - 'extensions' - summary: 'List extensions' - description: | - List all extensions registered inside Portainer. If the store parameter is set to true, - will retrieve extensions details from the online repository. - **Access policy**: administrator - operationId: 'ExtensionList' - produces: - - 'application/json' - security: - - jwt: [] - parameters: - - name: 'store' - in: 'query' - description: 'Retrieve online information about extensions. Possible values: true or false.' - required: false - type: 'boolean' - responses: - 200: - description: 'Success' - schema: - $ref: '#/definitions/ExtensionListResponse' - 500: - description: 'Server error' - schema: - $ref: '#/definitions/GenericError' - post: - tags: - - 'extensions' - summary: 'Enable an extension' - description: | - Enable an extension. - **Access policy**: administrator - operationId: 'ExtensionCreate' - consumes: - - 'application/json' - produces: - - 'application/json' - security: - - jwt: [] - parameters: - - in: 'body' - name: 'body' - description: 'Extension details' - required: true - schema: - $ref: '#/definitions/ExtensionCreateRequest' - responses: - 204: - description: 'Success' - 400: - description: 'Invalid request' - schema: - $ref: '#/definitions/GenericError' - examples: - application/json: - err: 'Invalid request data format' - 500: - description: 'Server error' - schema: - $ref: '#/definitions/GenericError' - /extensions/{id}: - get: - tags: - - 'extensions' - summary: 'Inspect an extension' - description: | - Retrieve details abount an extension. - **Access policy**: administrator - operationId: 'ExtensionInspect' - produces: - - 'application/json' - security: - - jwt: [] - parameters: - - name: 'id' - in: 'path' - description: 'extension identifier' - required: true - type: 'integer' - responses: - 200: - description: 'Success' - schema: - $ref: '#/definitions/Extension' - 400: - description: 'Invalid request' - schema: - $ref: '#/definitions/GenericError' - examples: - application/json: - err: 'Invalid request' - 404: - description: 'Extension not found' - schema: - $ref: '#/definitions/GenericError' - examples: - application/json: - err: 'Extension not found' - 500: - description: 'Server error' - schema: - $ref: '#/definitions/GenericError' - put: - tags: - - 'extensions' - summary: 'Update an extension' - description: | - Update an extension to a specific version of the extension. - **Access policy**: administrator - operationId: 'ExtensionUpdate' - consumes: - - 'application/json' - produces: - - 'application/json' - security: - - jwt: [] - parameters: - - name: 'id' - in: 'path' - description: 'Extension identifier' - required: true - type: 'integer' - - in: 'body' - name: 'body' - description: 'Extension details' - required: true - schema: - $ref: '#/definitions/ExtensionUpdateRequest' - responses: - 204: - description: 'Success' - 400: - description: 'Invalid request' - schema: - $ref: '#/definitions/GenericError' - examples: - application/json: - err: 'Invalid request data format' - 404: - description: 'Extension not found' - schema: - $ref: '#/definitions/GenericError' - examples: - application/json: - err: 'Extension not found' - 500: - description: 'Server error' - schema: - $ref: '#/definitions/GenericError' - delete: - tags: - - 'extensions' - summary: 'Disable an extension' - description: | - Disable an extension. - **Access policy**: administrator - operationId: 'ExtensionDelete' - security: - - jwt: [] - parameters: - - name: 'id' - in: 'path' - description: 'Extension identifier' - required: true - type: 'integer' - responses: - 204: - description: 'Success' - 400: - description: 'Invalid request' - schema: - $ref: '#/definitions/GenericError' - examples: - application/json: - err: 'Invalid request' - 404: - description: 'Extension not found' - schema: - $ref: '#/definitions/GenericError' - examples: - application/json: - err: 'Extension not found' - 500: - description: 'Server error' - schema: - $ref: '#/definitions/GenericError' /registries: get: tags: diff --git a/app/__module.js b/app/__module.js index 87b5ef593..bc835f9d7 100644 --- a/app/__module.js +++ b/app/__module.js @@ -35,7 +35,6 @@ angular.module('portainer', [ 'portainer.docker', 'portainer.kubernetes', 'portainer.edge', - 'portainer.extensions', 'portainer.integrations', 'rzModule', 'moment-picker', diff --git a/app/constants.js b/app/constants.js index f27f26557..3dba966dc 100644 --- a/app/constants.js +++ b/app/constants.js @@ -10,7 +10,6 @@ angular .constant('API_ENDPOINT_ENDPOINTS', 'api/endpoints') .constant('API_ENDPOINT_ENDPOINT_GROUPS', 'api/endpoint_groups') .constant('API_ENDPOINT_MOTD', 'api/motd') - .constant('API_ENDPOINT_EXTENSIONS', 'api/extensions') .constant('API_ENDPOINT_REGISTRIES', 'api/registries') .constant('API_ENDPOINT_RESOURCE_CONTROLS', 'api/resource_controls') .constant('API_ENDPOINT_SETTINGS', 'api/settings') diff --git a/app/docker/views/containers/create/createContainerController.js b/app/docker/views/containers/create/createContainerController.js index 501a7de24..6d9f85ec6 100644 --- a/app/docker/views/containers/create/createContainerController.js +++ b/app/docker/views/containers/create/createContainerController.js @@ -30,7 +30,6 @@ angular.module('portainer.docker').controller('CreateContainerController', [ 'SettingsService', 'PluginService', 'HttpRequestHelper', - 'ExtensionService', function ( $q, $scope, @@ -56,8 +55,7 @@ angular.module('portainer.docker').controller('CreateContainerController', [ SystemService, SettingsService, PluginService, - HttpRequestHelper, - ExtensionService + HttpRequestHelper ) { $scope.create = create; @@ -649,7 +647,7 @@ angular.module('portainer.docker').controller('CreateContainerController', [ $scope.isAdmin = Authentication.isAdmin(); $scope.showDeviceMapping = await shouldShowDevices(); $scope.areContainerCapabilitiesEnabled = await checkIfContainerCapabilitiesEnabled(); - $scope.isAdminOrEndpointAdmin = await checkIfAdminOrEndpointAdmin(); + $scope.isAdminOrEndpointAdmin = Authentication.isAdmin(); Volume.query( {}, @@ -935,35 +933,16 @@ angular.module('portainer.docker').controller('CreateContainerController', [ } } - async function isAdminOrEndpointAdmin() { - const isAdmin = Authentication.isAdmin(); - if (isAdmin) { - return true; - } - - const rbacEnabled = await ExtensionService.extensionEnabled(ExtensionService.EXTENSIONS.RBAC); - return rbacEnabled ? Authentication.hasAuthorizations(['EndpointResourcesAccess']) : false; - } - async function shouldShowDevices() { const { allowDeviceMappingForRegularUsers } = $scope.applicationState.application; - return allowDeviceMappingForRegularUsers || isAdminOrEndpointAdmin(); + return allowDeviceMappingForRegularUsers || Authentication.isAdmin(); } async function checkIfContainerCapabilitiesEnabled() { const { allowContainerCapabilitiesForRegularUsers } = $scope.applicationState.application; - return allowContainerCapabilitiesForRegularUsers || isAdminOrEndpointAdmin(); - } - - async function checkIfAdminOrEndpointAdmin() { - if (Authentication.isAdmin()) { - return true; - } - - const rbacEnabled = await ExtensionService.extensionEnabled(ExtensionService.EXTENSIONS.RBAC); - return rbacEnabled ? Authentication.hasAuthorizations(['EndpointResourcesAccess']) : false; + return allowContainerCapabilitiesForRegularUsers || Authentication.isAdmin(); } initView(); diff --git a/app/docker/views/containers/edit/containerController.js b/app/docker/views/containers/edit/containerController.js index 028fa89b7..1b0cf5a81 100644 --- a/app/docker/views/containers/edit/containerController.js +++ b/app/docker/views/containers/edit/containerController.js @@ -9,7 +9,6 @@ angular.module('portainer.docker').controller('ContainerController', [ '$transition$', '$filter', '$async', - 'ExtensionService', 'Commit', 'ContainerHelper', 'ContainerService', @@ -30,7 +29,6 @@ angular.module('portainer.docker').controller('ContainerController', [ $transition$, $filter, $async, - ExtensionService, Commit, ContainerHelper, ContainerService, @@ -115,9 +113,7 @@ angular.module('portainer.docker').controller('ContainerController', [ !allowHostNamespaceForRegularUsers || !allowPrivilegedModeForRegularUsers; - ExtensionService.extensionEnabled(ExtensionService.EXTENSIONS.RBAC).then((rbacEnabled) => { - $scope.displayRecreateButton = !inSwarm && !autoRemove && (settingRestrictsRegularUsers || rbacEnabled ? admin : true); - }); + $scope.displayRecreateButton = !inSwarm && !autoRemove && (admin || !settingRestrictsRegularUsers); }) .catch(function error(err) { Notifications.error('Failure', err, 'Unable to retrieve container info'); diff --git a/app/docker/views/dashboard/dashboardController.js b/app/docker/views/dashboard/dashboardController.js index f0c9722a7..9eaa97596 100644 --- a/app/docker/views/dashboard/dashboardController.js +++ b/app/docker/views/dashboard/dashboardController.js @@ -12,7 +12,6 @@ angular.module('portainer.docker').controller('DashboardController', [ 'EndpointService', 'Notifications', 'EndpointProvider', - 'ExtensionService', 'StateManager', function ( $scope, @@ -28,7 +27,6 @@ angular.module('portainer.docker').controller('DashboardController', [ EndpointService, Notifications, EndpointProvider, - ExtensionService, StateManager ) { $scope.dismissInformationPanel = function (id) { @@ -75,13 +73,7 @@ angular.module('portainer.docker').controller('DashboardController', [ const isAdmin = Authentication.isAdmin(); const { allowStackManagementForRegularUsers } = $scope.applicationState.application; - if (isAdmin || allowStackManagementForRegularUsers) { - return true; - } - const rbacEnabled = await ExtensionService.extensionEnabled(ExtensionService.EXTENSIONS.RBAC); - if (rbacEnabled) { - return Authentication.hasAuthorizations(['EndpointResourcesAccess']); - } + return isAdmin || allowStackManagementForRegularUsers; } initView(); diff --git a/app/docker/views/services/create/createServiceController.js b/app/docker/views/services/create/createServiceController.js index facba38b5..f836a0b66 100644 --- a/app/docker/views/services/create/createServiceController.js +++ b/app/docker/views/services/create/createServiceController.js @@ -33,7 +33,6 @@ angular.module('portainer.docker').controller('CreateServiceController', [ 'SettingsService', 'WebhookService', 'EndpointProvider', - 'ExtensionService', function ( $q, $scope, @@ -59,8 +58,7 @@ angular.module('portainer.docker').controller('CreateServiceController', [ NodeService, SettingsService, WebhookService, - EndpointProvider, - ExtensionService + EndpointProvider ) { $scope.formValues = { Name: '', @@ -592,15 +590,7 @@ angular.module('portainer.docker').controller('CreateServiceController', [ const settings = await SettingsService.publicSettings(); const { AllowBindMountsForRegularUsers } = settings; - if (isAdmin || AllowBindMountsForRegularUsers) { - return true; - } - const rbacEnabled = await ExtensionService.extensionEnabled(ExtensionService.EXTENSIONS.RBAC); - if (rbacEnabled) { - return Authentication.hasAuthorizations(['EndpointResourcesAccess']); - } - - return false; + return isAdmin || AllowBindMountsForRegularUsers; } }, ]); diff --git a/app/docker/views/volumes/volumesController.js b/app/docker/views/volumes/volumesController.js index b990df43f..bfd0a14b7 100644 --- a/app/docker/views/volumes/volumesController.js +++ b/app/docker/views/volumes/volumesController.js @@ -9,8 +9,7 @@ angular.module('portainer.docker').controller('VolumesController', [ 'HttpRequestHelper', 'EndpointProvider', 'Authentication', - 'ExtensionService', - function ($q, $scope, $state, VolumeService, ServiceService, VolumeHelper, Notifications, HttpRequestHelper, EndpointProvider, Authentication, ExtensionService) { + function ($q, $scope, $state, VolumeService, ServiceService, VolumeHelper, Notifications, HttpRequestHelper, EndpointProvider, Authentication) { $scope.removeAction = function (selectedItems) { var actionCount = selectedItems.length; angular.forEach(selectedItems, function (volume) { @@ -71,16 +70,8 @@ angular.module('portainer.docker').controller('VolumesController', [ function initView() { getVolumes(); - $scope.showBrowseAction = $scope.applicationState.endpoint.mode.agentProxy; - - ExtensionService.extensionEnabled(ExtensionService.EXTENSIONS.RBAC).then(function success(extensionEnabled) { - if (!extensionEnabled) { - var isAdmin = Authentication.isAdmin(); - if (!$scope.applicationState.application.enableVolumeBrowserForNonAdminUsers && !isAdmin) { - $scope.showBrowseAction = false; - } - } - }); + $scope.showBrowseAction = + $scope.applicationState.endpoint.mode.agentProxy && (Authentication.isAdmin() || $scope.applicationState.application.enableVolumeBrowserForNonAdminUsers); } initView(); diff --git a/app/extensions/_module.js b/app/extensions/_module.js deleted file mode 100644 index 9afe56617..000000000 --- a/app/extensions/_module.js +++ /dev/null @@ -1 +0,0 @@ -angular.module('portainer.extensions', ['portainer.extensions.rbac']); diff --git a/app/extensions/rbac/__module.js b/app/extensions/rbac/__module.js deleted file mode 100644 index 11713ef6f..000000000 --- a/app/extensions/rbac/__module.js +++ /dev/null @@ -1,23 +0,0 @@ -angular - .module('portainer.extensions.rbac', ['ngResource']) - .constant('API_ENDPOINT_ROLES', 'api/roles') - .config([ - '$stateRegistryProvider', - function ($stateRegistryProvider) { - 'use strict'; - - var roles = { - name: 'portainer.roles', - url: '/roles', - views: { - 'content@': { - templateUrl: './views/roles/roles.html', - controller: 'RolesController', - controllerAs: 'ctrl', - }, - }, - }; - - $stateRegistryProvider.register(roles); - }, - ]); diff --git a/app/extensions/rbac/components/access-viewer/accessViewer.html b/app/extensions/rbac/components/access-viewer/accessViewer.html deleted file mode 100644 index 0a933f8fe..000000000 --- a/app/extensions/rbac/components/access-viewer/accessViewer.html +++ /dev/null @@ -1,38 +0,0 @@ -
- - - -
-
- User -
-
-
- - No user available - - - - {{ $select.selected.Username }} - - - {{ item.Username }} - - -
-
- -
- Access -
-
-
- - Effective role for each endpoint will be displayed for the selected user -
-
- -
-
-
-
diff --git a/app/extensions/rbac/components/access-viewer/accessViewer.js b/app/extensions/rbac/components/access-viewer/accessViewer.js deleted file mode 100644 index 1bfc722b5..000000000 --- a/app/extensions/rbac/components/access-viewer/accessViewer.js +++ /dev/null @@ -1,5 +0,0 @@ -angular.module('portainer.app').component('accessViewer', { - templateUrl: './accessViewer.html', - controller: 'AccessViewerController', - controllerAs: 'ctrl', -}); diff --git a/app/extensions/rbac/components/access-viewer/accessViewerController.js b/app/extensions/rbac/components/access-viewer/accessViewerController.js deleted file mode 100644 index c11e8724a..000000000 --- a/app/extensions/rbac/components/access-viewer/accessViewerController.js +++ /dev/null @@ -1,126 +0,0 @@ -import _ from 'lodash-es'; -import angular from 'angular'; - -import AccessViewerPolicyModel from '../../models/access'; - -class AccessViewerController { - /* @ngInject */ - constructor(Notifications, ExtensionService, RoleService, UserService, EndpointService, GroupService, TeamService, TeamMembershipService) { - this.Notifications = Notifications; - this.ExtensionService = ExtensionService; - this.RoleService = RoleService; - this.UserService = UserService; - this.EndpointService = EndpointService; - this.GroupService = GroupService; - this.TeamService = TeamService; - this.TeamMembershipService = TeamMembershipService; - } - - onUserSelect() { - this.userRoles = []; - const userRoles = {}; - const user = this.selectedUser; - const userMemberships = _.filter(this.teamMemberships, { UserId: user.Id }); - - for (const [, endpoint] of _.entries(this.endpoints)) { - let role = this.getRoleFromUserEndpointPolicy(user, endpoint); - if (role) { - userRoles[endpoint.Id] = role; - continue; - } - - role = this.getRoleFromUserEndpointGroupPolicy(user, endpoint); - if (role) { - userRoles[endpoint.Id] = role; - continue; - } - - role = this.getRoleFromTeamEndpointPolicies(userMemberships, endpoint); - if (role) { - userRoles[endpoint.Id] = role; - continue; - } - - role = this.getRoleFromTeamEndpointGroupPolicies(userMemberships, endpoint); - if (role) { - userRoles[endpoint.Id] = role; - } - } - - this.userRoles = _.values(userRoles); - } - - findLowestRole(policies) { - return _.first(_.orderBy(policies, 'RoleId', 'desc')); - } - - getRoleFromUserEndpointPolicy(user, endpoint) { - const policyRoles = []; - const policy = endpoint.UserAccessPolicies[user.Id]; - if (policy) { - const accessPolicy = new AccessViewerPolicyModel(policy, endpoint, this.roles, null, null); - policyRoles.push(accessPolicy); - } - return this.findLowestRole(policyRoles); - } - - getRoleFromUserEndpointGroupPolicy(user, endpoint) { - const policyRoles = []; - const policy = this.groupUserAccessPolicies[endpoint.GroupId][user.Id]; - if (policy) { - const accessPolicy = new AccessViewerPolicyModel(policy, endpoint, this.roles, this.groups[endpoint.GroupId], null); - policyRoles.push(accessPolicy); - } - return this.findLowestRole(policyRoles); - } - - getRoleFromTeamEndpointPolicies(memberships, endpoint) { - const policyRoles = []; - for (const membership of memberships) { - const policy = endpoint.TeamAccessPolicies[membership.TeamId]; - if (policy) { - const accessPolicy = new AccessViewerPolicyModel(policy, endpoint, this.roles, null, this.teams[membership.TeamId]); - policyRoles.push(accessPolicy); - } - } - return this.findLowestRole(policyRoles); - } - - getRoleFromTeamEndpointGroupPolicies(memberships, endpoint) { - const policyRoles = []; - for (const membership of memberships) { - const policy = this.groupTeamAccessPolicies[endpoint.GroupId][membership.TeamId]; - if (policy) { - const accessPolicy = new AccessViewerPolicyModel(policy, endpoint, this.roles, this.groups[endpoint.GroupId], this.teams[membership.TeamId]); - policyRoles.push(accessPolicy); - } - } - return this.findLowestRole(policyRoles); - } - - async $onInit() { - try { - this.rbacEnabled = await this.ExtensionService.extensionEnabled(this.ExtensionService.EXTENSIONS.RBAC); - if (this.rbacEnabled) { - this.users = await this.UserService.users(); - this.endpoints = _.keyBy((await this.EndpointService.endpoints()).value, 'Id'); - const groups = await this.GroupService.groups(); - this.groupUserAccessPolicies = {}; - this.groupTeamAccessPolicies = {}; - _.forEach(groups, (group) => { - this.groupUserAccessPolicies[group.Id] = group.UserAccessPolicies; - this.groupTeamAccessPolicies[group.Id] = group.TeamAccessPolicies; - }); - this.groups = _.keyBy(groups, 'Id'); - this.roles = _.keyBy(await this.RoleService.roles(), 'Id'); - this.teams = _.keyBy(await this.TeamService.teams(), 'Id'); - this.teamMemberships = await this.TeamMembershipService.memberships(); - } - } catch (err) { - this.Notifications.error('Failure', err, 'Unable to retrieve accesses'); - } - } -} - -export default AccessViewerController; -angular.module('portainer.app').controller('AccessViewerController', AccessViewerController); diff --git a/app/extensions/rbac/components/access-viewer/datatable/accessViewerDatatable.html b/app/extensions/rbac/components/access-viewer/datatable/accessViewerDatatable.html deleted file mode 100644 index 9d91cd098..000000000 --- a/app/extensions/rbac/components/access-viewer/datatable/accessViewerDatatable.html +++ /dev/null @@ -1,73 +0,0 @@ -
- -
- - - - - - - - - - - - - - - - - - - - - -
- - Endpoint - - - - - - Role - - - - Access origin
{{ item.EndpointName }}{{ item.RoleName }}{{ item.TeamName ? 'Team' : 'User' }} {{ item.TeamName }} access defined on {{ item.AccessLocation }} - {{ item.GroupName }} - Manage access - - Manage access - -
Select a user to show associated access and role
The selected user does not have access to any endpoint(s)
-
- -
diff --git a/app/extensions/rbac/components/access-viewer/datatable/accessViewerDatatable.js b/app/extensions/rbac/components/access-viewer/datatable/accessViewerDatatable.js deleted file mode 100644 index 39bf39f70..000000000 --- a/app/extensions/rbac/components/access-viewer/datatable/accessViewerDatatable.js +++ /dev/null @@ -1,11 +0,0 @@ -angular.module('portainer.app').component('accessViewerDatatable', { - templateUrl: './accessViewerDatatable.html', - controller: 'GenericDatatableController', - bindings: { - titleText: '@', - titleIcon: '@', - tableKey: '@', - orderBy: '@', - dataset: '<', - }, -}); diff --git a/app/extensions/rbac/components/roles-datatable/rolesDatatable.html b/app/extensions/rbac/components/roles-datatable/rolesDatatable.html deleted file mode 100644 index 2e756b4b7..000000000 --- a/app/extensions/rbac/components/roles-datatable/rolesDatatable.html +++ /dev/null @@ -1,79 +0,0 @@ -
- - -
-
{{ $ctrl.titleText }}
-
- -
- - - - - - - - - - - - - - - - - - - -
- - Name - - - - - - Description - - - -
{{ item.Name }}{{ item.Description }}
Loading...
No role available.
-
- -
-
-
diff --git a/app/extensions/rbac/components/roles-datatable/rolesDatatable.js b/app/extensions/rbac/components/roles-datatable/rolesDatatable.js deleted file mode 100644 index b1234dfc4..000000000 --- a/app/extensions/rbac/components/roles-datatable/rolesDatatable.js +++ /dev/null @@ -1,13 +0,0 @@ -angular.module('portainer.extensions.rbac').component('rolesDatatable', { - templateUrl: './rolesDatatable.html', - controller: 'GenericDatatableController', - bindings: { - titleText: '@', - titleIcon: '@', - dataset: '<', - tableKey: '@', - orderBy: '@', - reverseOrder: '<', - rbacEnabled: '<', - }, -}); diff --git a/app/extensions/rbac/directives/authorization.js b/app/extensions/rbac/directives/authorization.js deleted file mode 100644 index 3a69174d4..000000000 --- a/app/extensions/rbac/directives/authorization.js +++ /dev/null @@ -1,40 +0,0 @@ -angular.module('portainer.extensions.rbac').directive('authorization', [ - 'Authentication', - 'ExtensionService', - '$async', - function (Authentication, ExtensionService, $async) { - async function linkAsync(scope, elem, attrs) { - elem.hide(); - try { - const rbacEnabled = await ExtensionService.extensionEnabled(ExtensionService.EXTENSIONS.RBAC); - if (!rbacEnabled) { - elem.show(); - return; - } - } catch (err) { - elem.show(); - return; - } - var authorizations = attrs.authorization.split(','); - for (var i = 0; i < authorizations.length; i++) { - authorizations[i] = authorizations[i].trim(); - } - - var hasAuthorizations = Authentication.hasAuthorizations(authorizations); - - if (hasAuthorizations) { - elem.show(); - } else if (!hasAuthorizations && elem[0].tagName === 'A') { - elem.show(); - elem.addClass('portainer-disabled-link'); - } - } - - return { - restrict: 'A', - link: function (scope, elem, attrs) { - return $async(linkAsync, scope, elem, attrs); - }, - }; - }, -]); diff --git a/app/extensions/rbac/directives/disable-authorization.js b/app/extensions/rbac/directives/disable-authorization.js deleted file mode 100644 index ec65ddaf1..000000000 --- a/app/extensions/rbac/directives/disable-authorization.js +++ /dev/null @@ -1,36 +0,0 @@ -angular.module('portainer.extensions.rbac').directive('disableAuthorization', [ - 'Authentication', - 'ExtensionService', - '$async', - function (Authentication, ExtensionService, $async) { - async function linkAsync(scope, elem, attrs) { - try { - const rbacEnabled = await ExtensionService.extensionEnabled(ExtensionService.EXTENSIONS.RBAC); - if (!rbacEnabled) { - return; - } - } catch (err) { - return; - } - - var authorizations = attrs.disableAuthorization.split(','); - for (var i = 0; i < authorizations.length; i++) { - authorizations[i] = authorizations[i].trim(); - } - - if (!Authentication.hasAuthorizations(authorizations)) { - elem.attr('disabled', true); - if (elem.is('Slider')) { - elem.css('pointer-events', 'none'); - } - } - } - - return { - restrict: 'A', - link: function (scope, elem, attrs) { - return $async(linkAsync, scope, elem, attrs); - }, - }; - }, -]); diff --git a/app/extensions/rbac/models/access.js b/app/extensions/rbac/models/access.js deleted file mode 100644 index 6326352f8..000000000 --- a/app/extensions/rbac/models/access.js +++ /dev/null @@ -1,15 +0,0 @@ -export default function AccessViewerPolicyModel(policy, endpoint, roles, group, team) { - this.EndpointId = endpoint.Id; - this.EndpointName = endpoint.Name; - this.RoleId = policy.RoleId; - this.RoleName = roles[policy.RoleId].Name; - if (group) { - this.GroupId = group.Id; - this.GroupName = group.Name; - } - if (team) { - this.TeamId = team.Id; - this.TeamName = team.Name; - } - this.AccessLocation = group ? 'endpoint group' : 'endpoint'; -} diff --git a/app/extensions/rbac/models/role.js b/app/extensions/rbac/models/role.js deleted file mode 100644 index 349bc0946..000000000 --- a/app/extensions/rbac/models/role.js +++ /dev/null @@ -1,6 +0,0 @@ -export function RoleViewModel(data) { - this.ID = data.Id; - this.Name = data.Name; - this.Description = data.Description; - this.Authorizations = data.Authorizations; -} diff --git a/app/extensions/rbac/rest/role.js b/app/extensions/rbac/rest/role.js deleted file mode 100644 index bca280af3..000000000 --- a/app/extensions/rbac/rest/role.js +++ /dev/null @@ -1,18 +0,0 @@ -angular.module('portainer.app').factory('Roles', [ - '$resource', - 'API_ENDPOINT_ROLES', - function RolesFactory($resource, API_ENDPOINT_ROLES) { - 'use strict'; - return $resource( - API_ENDPOINT_ROLES + '/:id', - {}, - { - create: { method: 'POST', ignoreLoadingBar: true }, - query: { method: 'GET', isArray: true }, - get: { method: 'GET', params: { id: '@id' } }, - update: { method: 'PUT', params: { id: '@id' } }, - remove: { method: 'DELETE', params: { id: '@id' } }, - } - ); - }, -]); diff --git a/app/extensions/rbac/services/roleService.js b/app/extensions/rbac/services/roleService.js deleted file mode 100644 index 5700fe0aa..000000000 --- a/app/extensions/rbac/services/roleService.js +++ /dev/null @@ -1,49 +0,0 @@ -import { - RoleViewModel, - // EndpointRoleCreateRequest, - // EndpointRoleUpdateRequest -} from '../models/role'; - -angular.module('portainer.extensions.rbac').factory('RoleService', [ - '$q', - 'Roles', - function RoleService($q, Roles) { - 'use strict'; - var service = {}; - - service.role = function (roleId) { - var deferred = $q.defer(); - - Roles.get({ id: roleId }) - .$promise.then(function success(data) { - var role = new RoleViewModel(data); - deferred.resolve(role); - }) - .catch(function error(err) { - deferred.reject({ msg: 'Unable to retrieve role', err: err }); - }); - - return deferred.promise; - }; - - service.roles = function () { - return Roles.query({}).$promise; - }; - - // service.createRole = function(model, endpoints) { - // var payload = new EndpointRoleCreateRequest(model, endpoints); - // return EndpointRoles.create(payload).$promise; - // }; - // - // service.updateRole = function(model, endpoints) { - // var payload = new EndpointRoleUpdateRequest(model, endpoints); - // return EndpointRoles.update(payload).$promise; - // }; - - service.deleteRole = function (roleId) { - return Roles.remove({ id: roleId }).$promise; - }; - - return service; - }, -]); diff --git a/app/extensions/rbac/views/roles/roles.html b/app/extensions/rbac/views/roles/roles.html deleted file mode 100644 index a1cafdb24..000000000 --- a/app/extensions/rbac/views/roles/roles.html +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - Role management - - - - -

- - The - Role-Based Access Control extension - is required to use this feature. -

-
-
- -
-
- -
-
- -
- -
diff --git a/app/extensions/rbac/views/roles/rolesController.js b/app/extensions/rbac/views/roles/rolesController.js deleted file mode 100644 index b5411a0c6..000000000 --- a/app/extensions/rbac/views/roles/rolesController.js +++ /dev/null @@ -1,24 +0,0 @@ -import angular from 'angular'; - -class RolesController { - /* @ngInject */ - constructor(Notifications, RoleService, ExtensionService) { - this.Notifications = Notifications; - this.RoleService = RoleService; - this.ExtensionService = ExtensionService; - } - - async $onInit() { - this.roles = []; - this.rbacEnabled = false; - - try { - this.rbacEnabled = await this.ExtensionService.extensionEnabled(this.ExtensionService.EXTENSIONS.RBAC); - this.roles = await this.RoleService.roles(); - } catch (err) { - this.Notifications.error('Failure', err, 'Unable to retrieve roles'); - } - } -} -export default RolesController; -angular.module('portainer.extensions.rbac').controller('RolesController', RolesController); diff --git a/app/kubernetes/views/resource-pools/access/resourcePoolAccess.html b/app/kubernetes/views/resource-pools/access/resourcePoolAccess.html index 3044762b0..6b6b0f862 100644 --- a/app/kubernetes/views/resource-pools/access/resourcePoolAccess.html +++ b/app/kubernetes/views/resource-pools/access/resourcePoolAccess.html @@ -57,21 +57,6 @@ - - -
@@ -96,11 +81,6 @@
- Remove -
-
- -
- - - The Role-Based Access Control extension is required to select a specific role. - -
-
+
@@ -67,10 +57,8 @@ title-icon="fa-user-lock" table-key="{{ 'access_' + ctrl.entityType }}" order-by="Name" - rbac-enabled="ctrl.rbacEnabled && ctrl.entityType !== 'registry'" inherit-from="ctrl.inheritFrom" dataset="ctrl.authorizedUsersAndTeams" - roles="ctrl.roles" update-action="ctrl.updateAction" remove-action="ctrl.unauthorizeAccess" > diff --git a/app/portainer/components/accessManagement/porAccessManagementController.js b/app/portainer/components/accessManagement/porAccessManagementController.js index 1234d3f3c..460c302d9 100644 --- a/app/portainer/components/accessManagement/porAccessManagementController.js +++ b/app/portainer/components/accessManagement/porAccessManagementController.js @@ -4,11 +4,9 @@ import angular from 'angular'; class PorAccessManagementController { /* @ngInject */ - constructor(Notifications, ExtensionService, AccessService, RoleService) { + constructor(Notifications, AccessService) { this.Notifications = Notifications; - this.ExtensionService = ExtensionService; this.AccessService = AccessService; - this.RoleService = RoleService; this.unauthorizeAccess = this.unauthorizeAccess.bind(this); this.updateAction = this.updateAction.bind(this); @@ -31,11 +29,10 @@ class PorAccessManagementController { const entity = this.accessControlledEntity; const oldUserAccessPolicies = entity.UserAccessPolicies; const oldTeamAccessPolicies = entity.TeamAccessPolicies; - const selectedRoleId = this.rbacEnabled ? this.formValues.selectedRole.Id : 0; const selectedUserAccesses = _.filter(this.formValues.multiselectOutput, (access) => access.Type === 'user'); const selectedTeamAccesses = _.filter(this.formValues.multiselectOutput, (access) => access.Type === 'team'); - const accessPolicies = this.AccessService.generateAccessPolicies(oldUserAccessPolicies, oldTeamAccessPolicies, selectedUserAccesses, selectedTeamAccesses, selectedRoleId); + const accessPolicies = this.AccessService.generateAccessPolicies(oldUserAccessPolicies, oldTeamAccessPolicies, selectedUserAccesses, selectedTeamAccesses, 0); this.accessControlledEntity.UserAccessPolicies = accessPolicies.userAccessPolicies; this.accessControlledEntity.TeamAccessPolicies = accessPolicies.teamAccessPolicies; this.updateAccess(); @@ -56,18 +53,7 @@ class PorAccessManagementController { try { const entity = this.accessControlledEntity; const parent = this.inheritFrom; - // TODO: refactor - // extract this code and locate it in AccessService.accesses() function - // see resourcePoolAccessController for another usage of AccessService.accesses() - // which needs RBAC support - this.roles = []; - this.rbacEnabled = await this.ExtensionService.extensionEnabled(this.ExtensionService.EXTENSIONS.RBAC); - if (this.rbacEnabled) { - this.roles = await this.RoleService.roles(); - this.formValues = { - selectedRole: this.roles[0], - }; - } + const data = await this.AccessService.accesses(entity, parent, this.roles); this.availableUsersAndTeams = _.orderBy(data.availableUsersAndTeams, 'Name', 'asc'); this.authorizedUsersAndTeams = data.authorizedUsersAndTeams; diff --git a/app/portainer/components/extension-list/extension-item/extension-item.js b/app/portainer/components/extension-list/extension-item/extension-item.js deleted file mode 100644 index e2d4b0723..000000000 --- a/app/portainer/components/extension-list/extension-item/extension-item.js +++ /dev/null @@ -1,8 +0,0 @@ -angular.module('portainer.app').component('extensionItem', { - templateUrl: './extensionItem.html', - controller: 'ExtensionItemController', - bindings: { - model: '<', - currentDate: '<', - }, -}); diff --git a/app/portainer/components/extension-list/extension-item/extensionItem.html b/app/portainer/components/extension-list/extension-item/extensionItem.html deleted file mode 100644 index 18a41ccc4..000000000 --- a/app/portainer/components/extension-list/extension-item/extensionItem.html +++ /dev/null @@ -1,47 +0,0 @@ - -
-
- - - - - - - - - - -
- - - {{ $ctrl.model.Name }} - - - - coming soon - deal - expired - enabled - update available - -
- - -
- - - {{ $ctrl.model.ShortDescription }} - - - - Licensed to {{ $ctrl.model.License.Company }} - Expires on {{ $ctrl.model.License.Expiration }} - -
- -
- -
- -
diff --git a/app/portainer/components/extension-list/extension-item/extensionItemController.js b/app/portainer/components/extension-list/extension-item/extensionItemController.js deleted file mode 100644 index 8bf0d218c..000000000 --- a/app/portainer/components/extension-list/extension-item/extensionItemController.js +++ /dev/null @@ -1,13 +0,0 @@ -angular.module('portainer.app').controller('ExtensionItemController', [ - '$state', - function ($state) { - var ctrl = this; - ctrl.goToExtensionView = goToExtensionView; - - function goToExtensionView() { - if (ctrl.model.Available) { - $state.go('portainer.extensions.extension', { id: ctrl.model.Id }); - } - } - }, -]); diff --git a/app/portainer/components/extension-list/extension-list.js b/app/portainer/components/extension-list/extension-list.js deleted file mode 100644 index 5b9b0a055..000000000 --- a/app/portainer/components/extension-list/extension-list.js +++ /dev/null @@ -1,7 +0,0 @@ -angular.module('portainer.app').component('extensionList', { - templateUrl: './extensionList.html', - bindings: { - extensions: '<', - currentDate: '<', - }, -}); diff --git a/app/portainer/components/extension-list/extensionList.html b/app/portainer/components/extension-list/extensionList.html deleted file mode 100644 index 2673b28d3..000000000 --- a/app/portainer/components/extension-list/extensionList.html +++ /dev/null @@ -1,13 +0,0 @@ -
- - -
-
Available extensions
-
- -
- -
-
-
-
diff --git a/app/portainer/models/extension.js b/app/portainer/models/extension.js deleted file mode 100644 index 2d64182d5..000000000 --- a/app/portainer/models/extension.js +++ /dev/null @@ -1,17 +0,0 @@ -export function ExtensionViewModel(data) { - this.Id = data.Id; - this.Name = data.Name; - this.Enabled = data.Enabled; - this.Description = data.Description; - this.Price = data.Price; - this.PriceDescription = data.PriceDescription; - this.Available = data.Available; - this.Deal = data.Deal; - this.ShortDescription = data.ShortDescription; - this.License = data.License; - this.Version = data.Version; - this.UpdateAvailable = data.UpdateAvailable; - this.ShopURL = data.ShopURL; - this.Images = data.Images; - this.Logo = data.Logo; -} diff --git a/app/portainer/rest/extension.js b/app/portainer/rest/extension.js deleted file mode 100644 index d93f5e873..000000000 --- a/app/portainer/rest/extension.js +++ /dev/null @@ -1,18 +0,0 @@ -angular.module('portainer.app').factory('Extension', [ - '$resource', - 'API_ENDPOINT_EXTENSIONS', - function ExtensionFactory($resource, API_ENDPOINT_EXTENSIONS) { - 'use strict'; - return $resource( - API_ENDPOINT_EXTENSIONS + '/:id/:action', - {}, - { - create: { method: 'POST' }, - query: { method: 'GET', isArray: true }, - get: { method: 'GET', params: { id: '@id' } }, - delete: { method: 'DELETE', params: { id: '@id' } }, - update: { method: 'POST', params: { id: '@id', action: 'update' } }, - } - ); - }, -]); diff --git a/app/portainer/services/api/accessService.js b/app/portainer/services/api/accessService.js index 2c9acd0dd..d09f4bdfe 100644 --- a/app/portainer/services/api/accessService.js +++ b/app/portainer/services/api/accessService.js @@ -11,15 +11,7 @@ angular.module('portainer.app').factory('AccessService', [ 'use strict'; var service = {}; - function _getRole(roles, roleId) { - if (roles.length) { - const role = _.find(roles, (role) => role.Id === roleId); - return role ? role : { Id: 0, Name: '-' }; - } - return { Id: 0, Name: '-' }; - } - - function _mapAccessData(accesses, authorizedPolicies, inheritedPolicies, roles) { + function _mapAccessData(accesses, authorizedPolicies, inheritedPolicies) { var availableAccesses = []; var authorizedAccesses = []; @@ -30,14 +22,11 @@ angular.module('portainer.app').factory('AccessService', [ const inherited = inheritedPolicies && inheritedPolicies[access.Id]; if (authorized && inherited) { - access.Role = _getRole(roles, authorizedPolicies[access.Id].RoleId); access.Override = true; authorizedAccesses.push(access); } else if (authorized && !inherited) { - access.Role = _getRole(roles, authorizedPolicies[access.Id].RoleId); authorizedAccesses.push(access); } else if (!authorized && inherited) { - access.Role = _getRole(roles, inheritedPolicies[access.Id].RoleId); access.Inherited = true; authorizedAccesses.push(access); availableAccesses.push(access); @@ -52,7 +41,7 @@ angular.module('portainer.app').factory('AccessService', [ }; } - function getAccesses(authorizedUserPolicies, authorizedTeamPolicies, inheritedUserPolicies, inheritedTeamPolicies, roles) { + function getAccesses(authorizedUserPolicies, authorizedTeamPolicies, inheritedUserPolicies, inheritedTeamPolicies) { var deferred = $q.defer(); $q.all({ @@ -67,8 +56,8 @@ angular.module('portainer.app').factory('AccessService', [ return new TeamAccessViewModel(team); }); - var userAccessData = _mapAccessData(userAccesses, authorizedUserPolicies, inheritedUserPolicies, roles); - var teamAccessData = _mapAccessData(teamAccesses, authorizedTeamPolicies, inheritedTeamPolicies, roles); + var userAccessData = _mapAccessData(userAccesses, authorizedUserPolicies, inheritedUserPolicies); + var teamAccessData = _mapAccessData(teamAccesses, authorizedTeamPolicies, inheritedTeamPolicies); var accessData = { availableUsersAndTeams: userAccessData.available.concat(teamAccessData.available), @@ -84,7 +73,7 @@ angular.module('portainer.app').factory('AccessService', [ return deferred.promise; } - async function accessesAsync(entity, parent, roles) { + async function accessesAsync(entity, parent) { try { if (!entity) { throw { msg: 'Unable to retrieve accesses' }; @@ -101,14 +90,14 @@ angular.module('portainer.app').factory('AccessService', [ if (parent && !parent.TeamAccessPolicies) { parent.TeamAccessPolicies = {}; } - return await getAccesses(entity.UserAccessPolicies, entity.TeamAccessPolicies, parent ? parent.UserAccessPolicies : {}, parent ? parent.TeamAccessPolicies : {}, roles); + return await getAccesses(entity.UserAccessPolicies, entity.TeamAccessPolicies, parent ? parent.UserAccessPolicies : {}, parent ? parent.TeamAccessPolicies : {}); } catch (err) { throw err; } } - function accesses(entity, parent, roles) { - return $async(accessesAsync, entity, parent, roles); + function accesses(entity, parent) { + return $async(accessesAsync, entity, parent); } service.accesses = accesses; diff --git a/app/portainer/services/api/extensionService.js b/app/portainer/services/api/extensionService.js deleted file mode 100644 index 576dc3e12..000000000 --- a/app/portainer/services/api/extensionService.js +++ /dev/null @@ -1,102 +0,0 @@ -import _ from 'lodash-es'; -import { ExtensionViewModel } from '../../models/extension'; - -angular.module('portainer.app').factory('ExtensionService', [ - '$q', - 'Extension', - 'StateManager', - '$async', - 'FileUploadService', - function ExtensionServiceFactory($q, Extension, StateManager, $async, FileUploadService) { - 'use strict'; - var service = {}; - - service.EXTENSIONS = Object.freeze({ - REGISTRY_MANAGEMENT: 1, - OAUTH_AUTHENTICATION: 2, - RBAC: 3, - }); - - service.enable = enable; - service.update = update; - service.delete = _delete; - service.extensions = extensions; - service.extension = extension; - service.extensionEnabled = extensionEnabled; - service.retrieveAndSaveEnabledExtensions = retrieveAndSaveEnabledExtensions; - - function enable(license, extensionFile) { - if (extensionFile) { - return FileUploadService.uploadExtension(license, extensionFile); - } else { - return Extension.create({ license: license }).$promise; - } - } - - function update(id, version) { - return Extension.update({ id: id, version: version }).$promise; - } - - function _delete(id) { - return Extension.delete({ id: id }).$promise; - } - - function extensions(store) { - var deferred = $q.defer(); - - Extension.query({ store: store }) - .$promise.then(function success(data) { - var extensions = data.map(function (item) { - return new ExtensionViewModel(item); - }); - deferred.resolve(extensions); - }) - .catch(function error(err) { - deferred.reject({ msg: 'Unable to retrieve extensions', err: err }); - }); - - return deferred.promise; - } - - function extension(id) { - var deferred = $q.defer(); - - Extension.get({ id: id }) - .$promise.then(function success(data) { - var extension = new ExtensionViewModel(data); - deferred.resolve(extension); - }) - .catch(function error(err) { - deferred.reject({ msg: 'Unable to retrieve extension details', err: err }); - }); - - return deferred.promise; - } - - function extensionEnabled(extensionId) { - return $async(extensionsEnabledAsync, extensionId); - } - - async function extensionsEnabledAsync(extensionId) { - if (extensionId === service.EXTENSIONS.RBAC) { - return StateManager.getExtension(extensionId) ? true : false; - } else { - const extensions = await service.extensions(false); - const extension = _.find(extensions, (ext) => ext.Id === extensionId); - return extension ? extension.Enabled : false; - } - } - - function retrieveAndSaveEnabledExtensions() { - return $async(retrieveAndSaveEnabledExtensionsAsync); - } - - async function retrieveAndSaveEnabledExtensionsAsync() { - const extensions = await service.extensions(false); - _.forEach(extensions, (ext) => delete ext.License); - StateManager.saveExtensions(extensions); - } - - return service; - }, -]); diff --git a/app/portainer/services/fileUpload.js b/app/portainer/services/fileUpload.js index fe581645a..b39a151a8 100644 --- a/app/portainer/services/fileUpload.js +++ b/app/portainer/services/fileUpload.js @@ -197,19 +197,6 @@ angular.module('portainer.app').factory('FileUploadService', [ return $q.all(queue); }; - service.uploadExtension = function (license, extensionFile) { - const payload = { - License: license, - file: extensionFile, - ArchiveFileName: extensionFile.name, - }; - return Upload.upload({ - url: 'api/extensions/upload', - data: payload, - ignoreLoadingBar: true, - }); - }; - return service; }, ]); diff --git a/app/portainer/services/localStorage.js b/app/portainer/services/localStorage.js index 95bc618d5..5026331bc 100644 --- a/app/portainer/services/localStorage.js +++ b/app/portainer/services/localStorage.js @@ -50,12 +50,6 @@ angular.module('portainer.app').factory('LocalStorage', [ getUIState: function () { return localStorageService.get('UI_STATE'); }, - storeExtensionState: function (state) { - localStorageService.set('EXTENSION_STATE', state); - }, - getExtensionState: function () { - return localStorageService.get('EXTENSION_STATE'); - }, storeJWT: function (jwt) { localStorageService.set('JWT', jwt); }, @@ -145,7 +139,7 @@ angular.module('portainer.app').factory('LocalStorage', [ localStorageService.clearAll(); }, cleanAuthData() { - localStorageService.remove('JWT', 'EXTENSION_STATE', 'APPLICATION_STATE', 'LOGIN_STATE_UUID'); + localStorageService.remove('JWT', 'APPLICATION_STATE', 'LOGIN_STATE_UUID'); }, cleanEndpointData() { localStorageService.remove('ENDPOINT_ID', 'ENDPOINT_PUBLIC_URL', 'ENDPOINT_OFFLINE_MODE', 'ENDPOINTS_DATA', 'ENDPOINT_STATE'); diff --git a/app/portainer/services/stateManager.js b/app/portainer/services/stateManager.js index 600ee21a5..70520cb7d 100644 --- a/app/portainer/services/stateManager.js +++ b/app/portainer/services/stateManager.js @@ -1,4 +1,3 @@ -import _ from 'lodash-es'; import moment from 'moment'; angular.module('portainer.app').factory('StateManager', [ @@ -60,7 +59,6 @@ angular.module('portainer.app').factory('StateManager', [ manager.clean = function () { state.endpoint = {}; - state.extensions = []; }; manager.updateLogo = function (logoURL) { @@ -174,11 +172,6 @@ angular.module('portainer.app').factory('StateManager', [ state.UI = UIState; } - const extensionState = LocalStorage.getExtensionState(); - if (extensionState) { - state.extensions = extensionState; - } - var endpointState = LocalStorage.getEndpointState(); if (endpointState) { state.endpoint = endpointState; @@ -276,19 +269,6 @@ angular.module('portainer.app').factory('StateManager', [ return state.endpoint.agentApiVersion; }; - manager.saveExtensions = function (extensions) { - state.extensions = extensions; - LocalStorage.storeExtensionState(state.extensions); - }; - - manager.getExtensions = function () { - return state.extensions; - }; - - manager.getExtension = function (extensionId) { - return _.find(state.extensions, { Id: extensionId, Enabled: true }); - }; - return manager; }, ]); diff --git a/app/portainer/views/auth/authController.js b/app/portainer/views/auth/authController.js index a2acfc0be..ff44d2669 100644 --- a/app/portainer/views/auth/authController.js +++ b/app/portainer/views/auth/authController.js @@ -12,7 +12,6 @@ class AuthenticationController { Authentication, UserService, EndpointService, - ExtensionService, StateManager, Notifications, SettingsService, @@ -28,7 +27,6 @@ class AuthenticationController { this.Authentication = Authentication; this.UserService = UserService; this.EndpointService = EndpointService; - this.ExtensionService = ExtensionService; this.StateManager = StateManager; this.Notifications = Notifications; this.SettingsService = SettingsService; @@ -47,7 +45,6 @@ class AuthenticationController { OAuthProvider: '', }; - this.retrieveAndSaveEnabledExtensionsAsync = this.retrieveAndSaveEnabledExtensionsAsync.bind(this); this.checkForEndpointsAsync = this.checkForEndpointsAsync.bind(this); this.checkForLatestVersionAsync = this.checkForLatestVersionAsync.bind(this); this.postLoginSteps = this.postLoginSteps.bind(this); @@ -117,14 +114,6 @@ class AuthenticationController { * POST LOGIN STEPS SECTION */ - async retrieveAndSaveEnabledExtensionsAsync() { - try { - await this.ExtensionService.retrieveAndSaveEnabledExtensions(); - } catch (err) { - this.error(err, 'Unable to retrieve enabled extensions'); - } - } - async checkForEndpointsAsync() { try { const endpoints = await this.EndpointService.endpoints(0, 1); @@ -158,7 +147,6 @@ class AuthenticationController { } async postLoginSteps() { - await this.retrieveAndSaveEnabledExtensionsAsync(); await this.checkForEndpointsAsync(); await this.checkForLatestVersionAsync(); } diff --git a/app/portainer/views/extensions/extensions.html b/app/portainer/views/extensions/extensions.html deleted file mode 100644 index 2f50d4138..000000000 --- a/app/portainer/views/extensions/extensions.html +++ /dev/null @@ -1,129 +0,0 @@ - - - Portainer extensions - - - - -

- Portainer CE is a great way of managing clusters, provisioning containers and services and managing container environment lifecycles. To extend the benefit of Portainer CE - even more, and to address the needs of larger, complex or critical environments, the Portainer team provides a growing range of low-cost Extensions. -

- -

- To ensure that Portainer remains the best choice for managing production container platforms, the Portainer team have chosen a modular, extensible design approach, where - additional capability can be added to the Portainer CE core as needed, and at very low cost. -

- -

- Available through a simple subscription process from the list below, Portainer Extensions provide a simple way to enhance Portainer CE’s core functionality through - incremental capability in important areas. -

- -

- For additional information on Portainer Extensions, see our website - here. -

-
-
- -
-
- - -
-
- Enable extension -
- -
-
-

- Portainer will download the latest version of the extension. Ensure that you have a valid license. -

-

- You will need to upload the extension archive manually. Ensure that you have a valid license. -

-

- You can download the latest version of our extensions here. -

-

- - Switch to offline activation - - - Switch to online activation - -

-
-
- -
- -
- -
-
- -
-
-
-

This field is required.

-

Invalid license format.

-
-
-
- -
-
- - - {{ formValues.ExtensionFile.name }} - - -
-
- -
-
- -
-
-
-
-
-
-
- -
-
- -
-
- - - -

- - Portainer must be connected to the Internet to fetch the list of available extensions. -

-
-
diff --git a/app/portainer/views/extensions/extensionsController.js b/app/portainer/views/extensions/extensionsController.js deleted file mode 100644 index 1404ec7fa..000000000 --- a/app/portainer/views/extensions/extensionsController.js +++ /dev/null @@ -1,67 +0,0 @@ -import moment from 'moment'; - -angular.module('portainer.app').controller('ExtensionsController', [ - '$scope', - '$state', - 'ExtensionService', - 'Notifications', - function ($scope, $state, ExtensionService, Notifications) { - $scope.state = { - actionInProgress: false, - currentDate: moment().format('YYYY-MM-dd'), - }; - - $scope.formValues = { - License: '', - ExtensionFile: null, - }; - - function initView() { - ExtensionService.extensions(true) - .then(function onSuccess(data) { - $scope.extensions = data; - }) - .catch(function onError(err) { - $scope.extensions = []; - Notifications.error('Failure', err, 'Unable to access extension store'); - }); - } - - $scope.enableExtension = function () { - const license = $scope.formValues.License; - const extensionFile = $scope.formValues.ExtensionFile; - - $scope.state.actionInProgress = true; - ExtensionService.enable(license, extensionFile) - .then(function onSuccess() { - return ExtensionService.retrieveAndSaveEnabledExtensions(); - }) - .then(function () { - Notifications.success('Extension successfully enabled'); - $state.reload(); - }) - .catch(function onError(err) { - Notifications.error('Failure', err, 'Unable to enable extension'); - }) - .finally(function final() { - $scope.state.actionInProgress = false; - }); - }; - - $scope.isValidLicenseFormat = function (form) { - var valid = true; - - if (!$scope.formValues.License) { - return; - } - - if (isNaN($scope.formValues.License[0])) { - valid = false; - } - - form.extension_license.$setValidity('invalidLicense', valid); - }; - - initView(); - }, -]); diff --git a/app/portainer/views/extensions/inspect/extension.html b/app/portainer/views/extensions/inspect/extension.html deleted file mode 100644 index 4ac2b782e..000000000 --- a/app/portainer/views/extensions/inspect/extension.html +++ /dev/null @@ -1,196 +0,0 @@ - - - Portainer extensions > {{ extension.Name }} - - -
-
- - -
-
-
-
{{ extension.Name }} extension
- - -
- -
-
- {{ extension.ShortDescription }} -
-
-
- -
-
-
- Enabled - Expired - {{ extension.Price }} -
- -
- {{ extension.PriceDescription }} -
- -
- -
- -
-
- - - - - -
- Coming soon -
- -
- -
- -
- -
- - -
-
-
-
-
-
-
- -
-
- - -
-
- - Offline update - -
-
-
-

- You will need to upload the extension archive manually. You can download the latest version of our extensions - here. -

-
-
-
-
- - - {{ formValues.ExtensionFile.name }} - - -
-
-
-
- -
-
-
-
-
-
-
- -
-
- - -
- - Description - -
-
-
-
-
-
-

- - Unable to provide a description in an offline environment. -

-
-
-
-
-
-
- -
-
- - -
- - Screenshots - -
-
-
- -
-
-
-
-
-
diff --git a/app/portainer/views/extensions/inspect/extensionController.js b/app/portainer/views/extensions/inspect/extensionController.js deleted file mode 100644 index 13e383d58..000000000 --- a/app/portainer/views/extensions/inspect/extensionController.js +++ /dev/null @@ -1,90 +0,0 @@ -angular.module('portainer.app').controller('ExtensionController', [ - '$q', - '$scope', - '$transition$', - '$state', - 'ExtensionService', - 'Notifications', - 'ModalService', - function ($q, $scope, $transition$, $state, ExtensionService, Notifications, ModalService) { - $scope.state = { - onlineUpdateInProgress: false, - offlineUpdateInProgress: false, - deleteInProgress: false, - offlineUpdate: false, - }; - - $scope.formValues = { - instances: 1, - extensionFile: null, - }; - - $scope.updateExtensionOnline = updateExtensionOnline; - $scope.updateExtensionOffline = updateExtensionOffline; - $scope.deleteExtension = deleteExtension; - $scope.enlargeImage = enlargeImage; - - function enlargeImage(image) { - ModalService.enlargeImage(image); - } - - function deleteExtension(extension) { - $scope.state.deleteInProgress = true; - ExtensionService.delete(extension.Id) - .then(function onSuccess() { - Notifications.success('Extension successfully deleted'); - $state.go('portainer.extensions'); - }) - .catch(function onError(err) { - Notifications.error('Failure', err, 'Unable to delete extension'); - }) - .finally(function final() { - $scope.state.deleteInProgress = false; - }); - } - - function updateExtensionOnline(extension) { - $scope.state.onlineUpdateInProgress = true; - ExtensionService.update(extension.Id, extension.Version) - .then(function onSuccess() { - Notifications.success('Extension successfully updated'); - $state.reload(); - }) - .catch(function onError(err) { - Notifications.error('Failure', err, 'Unable to update extension'); - }) - .finally(function final() { - $scope.state.onlineUpdateInProgress = false; - }); - } - - function updateExtensionOffline(extension) { - $scope.state.offlineUpdateInProgress = true; - const extensionFile = $scope.formValues.ExtensionFile; - - ExtensionService.enable(extension.License.LicenseKey, extensionFile) - .then(function onSuccess() { - Notifications.success('Extension successfully updated'); - $state.reload(); - }) - .catch(function onError(err) { - Notifications.error('Failure', err, 'Unable to update extension'); - }) - .finally(function final() { - $scope.state.offlineUpdateInProgress = false; - }); - } - - function initView() { - ExtensionService.extension($transition$.params().id) - .then(function onSuccess(extension) { - $scope.extension = extension; - }) - .catch(function onError(err) { - Notifications.error('Failure', err, 'Unable to retrieve extension information'); - }); - } - - initView(); - }, -]); diff --git a/app/portainer/views/init/admin/initAdminController.js b/app/portainer/views/init/admin/initAdminController.js index 1f7826019..65ccb4bb5 100644 --- a/app/portainer/views/init/admin/initAdminController.js +++ b/app/portainer/views/init/admin/initAdminController.js @@ -8,8 +8,7 @@ angular.module('portainer.app').controller('InitAdminController', [ 'SettingsService', 'UserService', 'EndpointService', - 'ExtensionService', - function ($async, $scope, $state, Notifications, Authentication, StateManager, SettingsService, UserService, EndpointService, ExtensionService) { + function ($async, $scope, $state, Notifications, Authentication, StateManager, SettingsService, UserService, EndpointService) { $scope.logo = StateManager.getState().application.logo; $scope.formValues = { @@ -23,18 +22,6 @@ angular.module('portainer.app').controller('InitAdminController', [ actionInProgress: false, }; - function retrieveAndSaveEnabledExtensions() { - return $async(retrieveAndSaveEnabledExtensionsAsync); - } - - async function retrieveAndSaveEnabledExtensionsAsync() { - try { - await ExtensionService.retrieveAndSaveEnabledExtensions(); - } catch (err) { - Notifications.error('Failure', err, 'Unable to retrieve enabled extensions'); - } - } - $scope.createAdminUser = function () { var username = $scope.formValues.Username; var password = $scope.formValues.Password; @@ -44,9 +31,6 @@ angular.module('portainer.app').controller('InitAdminController', [ .then(function success() { return Authentication.login(username, password); }) - .then(function success() { - return retrieveAndSaveEnabledExtensions(); - }) .then(function success() { StateManager.updateEnableTelemetry($scope.formValues.enableTelemetry); return SettingsService.update({ enableTelemetry: $scope.formValues.enableTelemetry }); diff --git a/app/portainer/views/settings/settings.html b/app/portainer/views/settings/settings.html index d5091ef43..f0a297d6b 100644 --- a/app/portainer/views/settings/settings.html +++ b/app/portainer/views/settings/settings.html @@ -101,10 +101,7 @@
diff --git a/app/portainer/views/sidebar/sidebar.html b/app/portainer/views/sidebar/sidebar.html index 012e86ab4..2b974b4a3 100644 --- a/app/portainer/views/sidebar/sidebar.html +++ b/app/portainer/views/sidebar/sidebar.html @@ -111,9 +111,6 @@ -