chore(code): use cmp.Or() EE-7333 (#12009)

pull/10989/merge
andres-portainer 2024-07-04 19:23:53 -03:00 committed by GitHub
parent 854474478c
commit faca64442f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 111 additions and 218 deletions

View File

@ -1,6 +1,7 @@
package main package main
import ( import (
"cmp"
"context" "context"
"crypto/sha256" "crypto/sha256"
"os" "os"
@ -258,21 +259,10 @@ func updateSettingsFromFlags(dataStore dataservices.DataStore, flags *portainer.
return err return err
} }
if *flags.SnapshotInterval != "" { settings.SnapshotInterval = *cmp.Or(flags.SnapshotInterval, &settings.SnapshotInterval)
settings.SnapshotInterval = *flags.SnapshotInterval settings.LogoURL = *cmp.Or(flags.Logo, &settings.LogoURL)
} settings.EnableEdgeComputeFeatures = *cmp.Or(flags.EnableEdgeComputeFeatures, &settings.EnableEdgeComputeFeatures)
settings.TemplatesURL = *cmp.Or(flags.Templates, &settings.TemplatesURL)
if *flags.Logo != "" {
settings.LogoURL = *flags.Logo
}
if *flags.EnableEdgeComputeFeatures {
settings.EnableEdgeComputeFeatures = *flags.EnableEdgeComputeFeatures
}
if *flags.Templates != "" {
settings.TemplatesURL = *flags.Templates
}
if *flags.Labels != nil { if *flags.Labels != nil {
settings.BlackListedLabels = *flags.Labels settings.BlackListedLabels = *flags.Labels

View File

@ -67,18 +67,23 @@ func (payload *customTemplateUpdatePayload) Validate(r *http.Request) error {
if govalidator.IsNull(payload.Title) { if govalidator.IsNull(payload.Title) {
return errors.New("Invalid custom template title") return errors.New("Invalid custom template title")
} }
if govalidator.IsNull(payload.FileContent) && govalidator.IsNull(payload.RepositoryURL) { if govalidator.IsNull(payload.FileContent) && govalidator.IsNull(payload.RepositoryURL) {
return errors.New("Either file content or git repository url need to be provided") return errors.New("Either file content or git repository url need to be provided")
} }
if payload.Type != portainer.KubernetesStack && payload.Platform != portainer.CustomTemplatePlatformLinux && payload.Platform != portainer.CustomTemplatePlatformWindows { if payload.Type != portainer.KubernetesStack && payload.Platform != portainer.CustomTemplatePlatformLinux && payload.Platform != portainer.CustomTemplatePlatformWindows {
return errors.New("Invalid custom template platform") return errors.New("Invalid custom template platform")
} }
if payload.Type != portainer.KubernetesStack && payload.Type != portainer.DockerSwarmStack && payload.Type != portainer.DockerComposeStack { if payload.Type != portainer.KubernetesStack && payload.Type != portainer.DockerSwarmStack && payload.Type != portainer.DockerComposeStack {
return errors.New("Invalid custom template type") return errors.New("Invalid custom template type")
} }
if govalidator.IsNull(payload.Description) { if govalidator.IsNull(payload.Description) {
return errors.New("Invalid custom template description") return errors.New("Invalid custom template description")
} }
if !isValidNote(payload.Note) { if !isValidNote(payload.Note) {
return errors.New("Invalid note. <img> tag is not supported") return errors.New("Invalid note. <img> tag is not supported")
} }
@ -86,12 +91,12 @@ func (payload *customTemplateUpdatePayload) Validate(r *http.Request) error {
if payload.RepositoryAuthentication && (govalidator.IsNull(payload.RepositoryUsername) || govalidator.IsNull(payload.RepositoryPassword)) { if payload.RepositoryAuthentication && (govalidator.IsNull(payload.RepositoryUsername) || govalidator.IsNull(payload.RepositoryPassword)) {
return errors.New("Invalid repository credentials. Username and password must be specified when authentication is enabled") return errors.New("Invalid repository credentials. Username and password must be specified when authentication is enabled")
} }
if govalidator.IsNull(payload.ComposeFilePathInRepository) { if govalidator.IsNull(payload.ComposeFilePathInRepository) {
payload.ComposeFilePathInRepository = filesystem.ComposeFileDefaultName payload.ComposeFilePathInRepository = filesystem.ComposeFileDefaultName
} }
err := validateVariablesDefinitions(payload.Variables) if err := validateVariablesDefinitions(payload.Variables); err != nil {
if err != nil {
return err return err
} }
@ -122,8 +127,7 @@ func (handler *Handler) customTemplateUpdate(w http.ResponseWriter, r *http.Requ
} }
var payload customTemplateUpdatePayload var payload customTemplateUpdatePayload
err = request.DecodeAndValidateJSONPayload(r, &payload) if err := request.DecodeAndValidateJSONPayload(r, &payload); err != nil {
if err != nil {
return httperror.BadRequest("Invalid request payload", err) return httperror.BadRequest("Invalid request payload", err)
} }
@ -219,8 +223,7 @@ func (handler *Handler) customTemplateUpdate(w http.ResponseWriter, r *http.Requ
customTemplate.ProjectPath = projectPath customTemplate.ProjectPath = projectPath
} }
err = handler.DataStore.CustomTemplate().Update(customTemplate.ID, customTemplate) if err := handler.DataStore.CustomTemplate().Update(customTemplate.ID, customTemplate); err != nil {
if err != nil {
return httperror.InternalServerError("Unable to persist custom template changes inside the database", err) return httperror.InternalServerError("Unable to persist custom template changes inside the database", err)
} }

View File

@ -63,17 +63,15 @@ func (handler *Handler) edgeStackStatusUpdate(w http.ResponseWriter, r *http.Req
} }
var payload updateStatusPayload var payload updateStatusPayload
err = request.DecodeAndValidateJSONPayload(r, &payload) if err := request.DecodeAndValidateJSONPayload(r, &payload); err != nil {
if err != nil {
return httperror.BadRequest("Invalid request payload", err) return httperror.BadRequest("Invalid request payload", err)
} }
var stack *portainer.EdgeStack var stack *portainer.EdgeStack
err = handler.DataStore.UpdateTx(func(tx dataservices.DataStoreTx) error { if err := handler.DataStore.UpdateTx(func(tx dataservices.DataStoreTx) error {
stack, err = handler.updateEdgeStackStatus(tx, r, portainer.EdgeStackID(stackID), payload) stack, err = handler.updateEdgeStackStatus(tx, r, portainer.EdgeStackID(stackID), payload)
return err return err
}) }); err != nil {
if err != nil {
var httpErr *httperror.HandlerError var httpErr *httperror.HandlerError
if errors.As(err, &httpErr) { if errors.As(err, &httpErr) {
return httpErr return httpErr
@ -106,8 +104,7 @@ func (handler *Handler) updateEdgeStackStatus(tx dataservices.DataStoreTx, r *ht
return nil, handler.handlerDBErr(err, "Unable to find an environment with the specified identifier inside the database") return nil, handler.handlerDBErr(err, "Unable to find an environment with the specified identifier inside the database")
} }
err = handler.requestBouncer.AuthorizedEdgeEndpointOperation(r, endpoint) if err := handler.requestBouncer.AuthorizedEdgeEndpointOperation(r, endpoint); err != nil {
if err != nil {
return nil, httperror.Forbidden("Permission denied to access environment", err) return nil, httperror.Forbidden("Permission denied to access environment", err)
} }
@ -126,8 +123,7 @@ func (handler *Handler) updateEdgeStackStatus(tx dataservices.DataStoreTx, r *ht
updateEnvStatus(payload.EndpointID, stack, deploymentStatus) updateEnvStatus(payload.EndpointID, stack, deploymentStatus)
err = tx.EdgeStack().UpdateEdgeStack(stackID, stack) if err := tx.EdgeStack().UpdateEdgeStack(stackID, stack); err != nil {
if err != nil {
return nil, handler.handlerDBErr(err, "Unable to persist the stack changes inside the database") return nil, handler.handlerDBErr(err, "Unable to persist the stack changes inside the database")
} }
@ -137,6 +133,7 @@ func (handler *Handler) updateEdgeStackStatus(tx dataservices.DataStoreTx, r *ht
func updateEnvStatus(environmentId portainer.EndpointID, stack *portainer.EdgeStack, deploymentStatus portainer.EdgeStackDeploymentStatus) { func updateEnvStatus(environmentId portainer.EndpointID, stack *portainer.EdgeStack, deploymentStatus portainer.EdgeStackDeploymentStatus) {
if deploymentStatus.Type == portainer.EdgeStackStatusRemoved { if deploymentStatus.Type == portainer.EdgeStackStatusRemoved {
delete(stack.Status, environmentId) delete(stack.Status, environmentId)
return return
} }

View File

@ -1,6 +1,7 @@
package endpoints package endpoints
import ( import (
"cmp"
"net/http" "net/http"
"reflect" "reflect"
"strconv" "strconv"
@ -97,12 +98,9 @@ func (handler *Handler) endpointUpdate(w http.ResponseWriter, r *http.Request) *
if payload.Name != nil { if payload.Name != nil {
name := *payload.Name name := *payload.Name
isUnique, err := handler.isNameUnique(name, endpoint.ID) if isUnique, err := handler.isNameUnique(name, endpoint.ID); err != nil {
if err != nil {
return httperror.InternalServerError("Unable to check if name is unique", err) return httperror.InternalServerError("Unable to check if name is unique", err)
} } else if !isUnique {
if !isUnique {
return httperror.Conflict("Name is not unique", nil) return httperror.Conflict("Name is not unique", nil)
} }
@ -114,17 +112,12 @@ func (handler *Handler) endpointUpdate(w http.ResponseWriter, r *http.Request) *
updateEndpointProxy = true updateEndpointProxy = true
} }
if payload.PublicURL != nil {
endpoint.PublicURL = *payload.PublicURL
}
if payload.Gpus != nil { if payload.Gpus != nil {
endpoint.Gpus = payload.Gpus endpoint.Gpus = payload.Gpus
} }
if payload.EdgeCheckinInterval != nil { endpoint.PublicURL = *cmp.Or(payload.PublicURL, &endpoint.PublicURL)
endpoint.EdgeCheckinInterval = *payload.EdgeCheckinInterval endpoint.EdgeCheckinInterval = *cmp.Or(payload.EdgeCheckinInterval, &endpoint.EdgeCheckinInterval)
}
updateRelations := false updateRelations := false
@ -304,9 +297,5 @@ func shouldReloadTLSConfiguration(endpoint *portainer.Endpoint, payload *endpoin
return true return true
} }
if payload.TLSSkipClientVerify != nil && !*payload.TLSSkipClientVerify { return payload.TLSSkipClientVerify != nil && !*payload.TLSSkipClientVerify
return true
}
return false
} }

View File

@ -1,6 +1,7 @@
package registries package registries
import ( import (
"cmp"
"errors" "errors"
"net/http" "net/http"
@ -83,18 +84,16 @@ func (handler *Handler) registryUpdate(w http.ResponseWriter, r *http.Request) *
} }
var payload registryUpdatePayload var payload registryUpdatePayload
err = request.DecodeAndValidateJSONPayload(r, &payload) if err := request.DecodeAndValidateJSONPayload(r, &payload); err != nil {
if err != nil {
return httperror.BadRequest("Invalid request payload", err) return httperror.BadRequest("Invalid request payload", err)
} }
if payload.Name != nil { registry.Name = *cmp.Or(payload.Name, &registry.Name)
registry.Name = *payload.Name
} // Enforce name uniqueness across registries check is performed even if Name
// enforce name uniqueness across registries // didn't change (Name not in payload) as we need to enforce this rule on
// check is performed even if Name didn't change (Name not in payload) as we need // updates not performed with frontend (e.g. on direct API requests)
// to enforce this rule on updates not performed with frontend (e.g. on direct API requests) // See https://portainer.atlassian.net/browse/EE-2706 for more details
// see https://portainer.atlassian.net/browse/EE-2706 for more details
for _, r := range registries { for _, r := range registries {
if r.ID != registry.ID && r.Name == registry.Name { if r.ID != registry.ID && r.Name == registry.Name {
return httperror.Conflict("Another registry with the same name already exists", errors.New("A registry is already defined with this name")) return httperror.Conflict("Another registry with the same name already exists", errors.New("A registry is already defined with this name"))
@ -172,12 +171,9 @@ func (handler *Handler) registryUpdate(w http.ResponseWriter, r *http.Request) *
} }
} }
if payload.Quay != nil { registry.Quay = *cmp.Or(payload.Quay, &registry.Quay)
registry.Quay = *payload.Quay
}
err = handler.DataStore.Registry().Update(registry.ID, registry) if err := handler.DataStore.Registry().Update(registry.ID, registry); err != nil {
if err != nil {
return httperror.InternalServerError("Unable to persist registry changes inside the database", err) return httperror.InternalServerError("Unable to persist registry changes inside the database", err)
} }
@ -185,10 +181,7 @@ func (handler *Handler) registryUpdate(w http.ResponseWriter, r *http.Request) *
} }
func syncConfig(registry *portainer.Registry) *portainer.RegistryManagementConfiguration { func syncConfig(registry *portainer.Registry) *portainer.RegistryManagementConfiguration {
config := registry.ManagementConfiguration config := cmp.Or(registry.ManagementConfiguration, &portainer.RegistryManagementConfiguration{})
if config == nil {
config = &portainer.RegistryManagementConfiguration{}
}
config.Authentication = registry.Authentication config.Authentication = registry.Authentication
config.Username = registry.Username config.Username = registry.Username
@ -200,20 +193,17 @@ func syncConfig(registry *portainer.Registry) *portainer.RegistryManagementConfi
} }
func (handler *Handler) updateEndpointRegistryAccess(endpoint *portainer.Endpoint, registry *portainer.Registry, endpointAccess portainer.RegistryAccessPolicies) error { func (handler *Handler) updateEndpointRegistryAccess(endpoint *portainer.Endpoint, registry *portainer.Registry, endpointAccess portainer.RegistryAccessPolicies) error {
cli, err := handler.K8sClientFactory.GetKubeClient(endpoint) cli, err := handler.K8sClientFactory.GetKubeClient(endpoint)
if err != nil { if err != nil {
return err return err
} }
for _, namespace := range endpointAccess.Namespaces { for _, namespace := range endpointAccess.Namespaces {
err := cli.DeleteRegistrySecret(registry.ID, namespace) if err := cli.DeleteRegistrySecret(registry.ID, namespace); err != nil {
if err != nil {
return err return err
} }
err = cli.CreateRegistrySecret(registry, namespace) if err := cli.CreateRegistrySecret(registry, namespace); err != nil {
if err != nil {
return err return err
} }
} }

View File

@ -1,6 +1,7 @@
package settings package settings
import ( import (
"cmp"
"net/http" "net/http"
"strings" "strings"
"time" "time"
@ -76,22 +77,19 @@ func (payload *settingsUpdatePayload) Validate(r *http.Request) error {
} }
if payload.UserSessionTimeout != nil { if payload.UserSessionTimeout != nil {
_, err := time.ParseDuration(*payload.UserSessionTimeout) if _, err := time.ParseDuration(*payload.UserSessionTimeout); err != nil {
if err != nil {
return errors.New("Invalid user session timeout") return errors.New("Invalid user session timeout")
} }
} }
if payload.KubeconfigExpiry != nil { if payload.KubeconfigExpiry != nil {
_, err := time.ParseDuration(*payload.KubeconfigExpiry) if _, err := time.ParseDuration(*payload.KubeconfigExpiry); err != nil {
if err != nil {
return errors.New("Invalid Kubeconfig Expiry") return errors.New("Invalid Kubeconfig Expiry")
} }
} }
if payload.EdgePortainerURL != nil && *payload.EdgePortainerURL != "" { if payload.EdgePortainerURL != nil && *payload.EdgePortainerURL != "" {
_, err := edge.ParseHostForEdge(*payload.EdgePortainerURL) if _, err := edge.ParseHostForEdge(*payload.EdgePortainerURL); err != nil {
if err != nil {
return err return err
} }
} }
@ -101,6 +99,7 @@ func (payload *settingsUpdatePayload) Validate(r *http.Request) error {
return errors.New("Invalid OAuth AuthStyle") return errors.New("Invalid OAuth AuthStyle")
} }
} }
return nil return nil
} }
@ -153,18 +152,11 @@ func (handler *Handler) updateSettings(tx dataservices.DataStoreTx, payload sett
settings.AuthenticationMethod = portainer.AuthenticationMethod(*payload.AuthenticationMethod) settings.AuthenticationMethod = portainer.AuthenticationMethod(*payload.AuthenticationMethod)
} }
if payload.LogoURL != nil { settings.LogoURL = *cmp.Or(payload.LogoURL, &settings.LogoURL)
settings.LogoURL = *payload.LogoURL settings.TemplatesURL = *cmp.Or(payload.TemplatesURL, &settings.TemplatesURL)
}
if payload.TemplatesURL != nil { // Update the global deployment options, and the environment deployment options if they have changed
settings.TemplatesURL = *payload.TemplatesURL settings.GlobalDeploymentOptions = *cmp.Or(payload.GlobalDeploymentOptions, &settings.GlobalDeploymentOptions)
}
// update the global deployment options, and the environment deployment options if they have changed
if payload.GlobalDeploymentOptions != nil {
settings.GlobalDeploymentOptions = *payload.GlobalDeploymentOptions
}
if payload.ShowKomposeBuildOption != nil { if payload.ShowKomposeBuildOption != nil {
settings.ShowKomposeBuildOption = *payload.ShowKomposeBuildOption settings.ShowKomposeBuildOption = *payload.ShowKomposeBuildOption
@ -197,16 +189,8 @@ func (handler *Handler) updateSettings(tx dataservices.DataStoreTx, payload sett
} }
if payload.LDAPSettings != nil { if payload.LDAPSettings != nil {
ldapReaderDN := settings.LDAPSettings.ReaderDN ldapReaderDN := cmp.Or(payload.LDAPSettings.ReaderDN, settings.LDAPSettings.ReaderDN)
ldapPassword := settings.LDAPSettings.Password ldapPassword := cmp.Or(payload.LDAPSettings.Password, settings.LDAPSettings.Password)
if payload.LDAPSettings.ReaderDN != "" {
ldapReaderDN = payload.LDAPSettings.ReaderDN
}
if payload.LDAPSettings.Password != "" {
ldapPassword = payload.LDAPSettings.Password
}
settings.LDAPSettings = *payload.LDAPSettings settings.LDAPSettings = *payload.LDAPSettings
settings.LDAPSettings.ReaderDN = ldapReaderDN settings.LDAPSettings.ReaderDN = ldapReaderDN
@ -229,36 +213,19 @@ func (handler *Handler) updateSettings(tx dataservices.DataStoreTx, payload sett
settings.OAuthSettings.AuthStyle = payload.OAuthSettings.AuthStyle settings.OAuthSettings.AuthStyle = payload.OAuthSettings.AuthStyle
} }
if payload.EnableEdgeComputeFeatures != nil { settings.EnableEdgeComputeFeatures = *cmp.Or(payload.EnableEdgeComputeFeatures, &settings.EnableEdgeComputeFeatures)
settings.EnableEdgeComputeFeatures = *payload.EnableEdgeComputeFeatures settings.TrustOnFirstConnect = *cmp.Or(payload.TrustOnFirstConnect, &settings.TrustOnFirstConnect)
} settings.EnforceEdgeID = *cmp.Or(payload.EnforceEdgeID, &settings.EnforceEdgeID)
settings.EdgePortainerURL = *cmp.Or(payload.EdgePortainerURL, &settings.EdgePortainerURL)
if payload.TrustOnFirstConnect != nil {
settings.TrustOnFirstConnect = *payload.TrustOnFirstConnect
}
if payload.EnforceEdgeID != nil {
settings.EnforceEdgeID = *payload.EnforceEdgeID
}
if payload.EdgePortainerURL != nil {
settings.EdgePortainerURL = *payload.EdgePortainerURL
}
if payload.SnapshotInterval != nil && *payload.SnapshotInterval != settings.SnapshotInterval { if payload.SnapshotInterval != nil && *payload.SnapshotInterval != settings.SnapshotInterval {
err := handler.updateSnapshotInterval(settings, *payload.SnapshotInterval) if err := handler.updateSnapshotInterval(settings, *payload.SnapshotInterval); err != nil {
if err != nil {
return nil, httperror.InternalServerError("Unable to update snapshot interval", err) return nil, httperror.InternalServerError("Unable to update snapshot interval", err)
} }
} }
if payload.EdgeAgentCheckinInterval != nil { settings.EdgeAgentCheckinInterval = *cmp.Or(payload.EdgeAgentCheckinInterval, &settings.EdgeAgentCheckinInterval)
settings.EdgeAgentCheckinInterval = *payload.EdgeAgentCheckinInterval settings.KubeconfigExpiry = *cmp.Or(payload.KubeconfigExpiry, &settings.KubeconfigExpiry)
}
if payload.KubeconfigExpiry != nil {
settings.KubeconfigExpiry = *payload.KubeconfigExpiry
}
if payload.UserSessionTimeout != nil { if payload.UserSessionTimeout != nil {
settings.UserSessionTimeout = *payload.UserSessionTimeout settings.UserSessionTimeout = *payload.UserSessionTimeout
@ -268,21 +235,15 @@ func (handler *Handler) updateSettings(tx dataservices.DataStoreTx, payload sett
handler.JWTService.SetUserSessionDuration(userSessionDuration) handler.JWTService.SetUserSessionDuration(userSessionDuration)
} }
if payload.EnableTelemetry != nil { settings.EnableTelemetry = *cmp.Or(payload.EnableTelemetry, &settings.EnableTelemetry)
settings.EnableTelemetry = *payload.EnableTelemetry
}
err = handler.updateTLS(settings) if err := handler.updateTLS(settings); err != nil {
if err != nil {
return nil, err return nil, err
} }
if payload.KubectlShellImage != nil { settings.KubectlShellImage = *cmp.Or(payload.KubectlShellImage, &settings.KubectlShellImage)
settings.KubectlShellImage = *payload.KubectlShellImage
}
err = tx.Settings().UpdateSettings(settings) if err := tx.Settings().UpdateSettings(settings); err != nil {
if err != nil {
return nil, httperror.InternalServerError("Unable to persist settings changes inside the database", err) return nil, httperror.InternalServerError("Unable to persist settings changes inside the database", err)
} }
@ -304,8 +265,8 @@ func (handler *Handler) updateTLS(settings *portainer.Settings) error {
} }
settings.LDAPSettings.TLSConfig.TLSCACertPath = "" settings.LDAPSettings.TLSConfig.TLSCACertPath = ""
err := handler.FileService.DeleteTLSFiles(filesystem.LDAPStorePath)
if err != nil { if err := handler.FileService.DeleteTLSFiles(filesystem.LDAPStorePath); err != nil {
return httperror.InternalServerError("Unable to remove TLS files from disk", err) return httperror.InternalServerError("Unable to remove TLS files from disk", err)
} }

View File

@ -41,21 +41,18 @@ func (payload *sslUpdatePayload) Validate(r *http.Request) error {
// @router /ssl [put] // @router /ssl [put]
func (handler *Handler) sslUpdate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError { func (handler *Handler) sslUpdate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
var payload sslUpdatePayload var payload sslUpdatePayload
err := request.DecodeAndValidateJSONPayload(r, &payload) if err := request.DecodeAndValidateJSONPayload(r, &payload); err != nil {
if err != nil {
return httperror.BadRequest("Invalid request payload", err) return httperror.BadRequest("Invalid request payload", err)
} }
if payload.Cert != nil { if payload.Cert != nil {
err = handler.SSLService.SetCertificates([]byte(*payload.Cert), []byte(*payload.Key)) if err := handler.SSLService.SetCertificates([]byte(*payload.Cert), []byte(*payload.Key)); err != nil {
if err != nil {
return httperror.InternalServerError("Failed to save certificate", err) return httperror.InternalServerError("Failed to save certificate", err)
} }
} }
if payload.HTTPEnabled != nil { if payload.HTTPEnabled != nil {
err = handler.SSLService.SetHTTPEnabled(*payload.HTTPEnabled) if err := handler.SSLService.SetHTTPEnabled(*payload.HTTPEnabled); err != nil {
if err != nil {
return httperror.InternalServerError("Failed to force https", err) return httperror.InternalServerError("Failed to force https", err)
} }
} }

View File

@ -153,7 +153,7 @@ func (handler *Handler) stackUpdate(w http.ResponseWriter, r *http.Request) *htt
} }
if stack.GitConfig != nil && stack.GitConfig.Authentication != nil && stack.GitConfig.Authentication.Password != "" { if stack.GitConfig != nil && stack.GitConfig.Authentication != nil && stack.GitConfig.Authentication.Password != "" {
// sanitize password in the http response to minimise possible security leaks // Sanitize password in the http response to minimise possible security leaks
stack.GitConfig.Authentication.Password = "" stack.GitConfig.Authentication.Password = ""
} }

View File

@ -30,10 +30,7 @@ type stackGitUpdatePayload struct {
} }
func (payload *stackGitUpdatePayload) Validate(r *http.Request) error { func (payload *stackGitUpdatePayload) Validate(r *http.Request) error {
if err := update.ValidateAutoUpdateSettings(payload.AutoUpdate); err != nil { return update.ValidateAutoUpdateSettings(payload.AutoUpdate)
return err
}
return nil
} }
// @id StackUpdateGit // @id StackUpdateGit
@ -61,8 +58,7 @@ func (handler *Handler) stackUpdateGit(w http.ResponseWriter, r *http.Request) *
} }
var payload stackGitUpdatePayload var payload stackGitUpdatePayload
err = request.DecodeAndValidateJSONPayload(r, &payload) if err := request.DecodeAndValidateJSONPayload(r, &payload); err != nil {
if err != nil {
return httperror.BadRequest("Invalid request payload", err) return httperror.BadRequest("Invalid request payload", err)
} }
@ -94,8 +90,7 @@ func (handler *Handler) stackUpdateGit(w http.ResponseWriter, r *http.Request) *
return httperror.InternalServerError("Unable to find the environment associated to the stack inside the database", err) return httperror.InternalServerError("Unable to find the environment associated to the stack inside the database", err)
} }
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint) if err := handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint); err != nil {
if err != nil {
return httperror.Forbidden("Permission denied to access environment", err) return httperror.Forbidden("Permission denied to access environment", err)
} }
@ -115,20 +110,16 @@ func (handler *Handler) stackUpdateGit(w http.ResponseWriter, r *http.Request) *
return httperror.InternalServerError("Unable to retrieve a resource control associated to the stack", err) return httperror.InternalServerError("Unable to retrieve a resource control associated to the stack", err)
} }
access, err := handler.userCanAccessStack(securityContext, endpoint.ID, resourceControl) if access, err := handler.userCanAccessStack(securityContext, endpoint.ID, resourceControl); err != nil {
if err != nil {
return httperror.InternalServerError("Unable to verify user authorizations to validate stack access", err) return httperror.InternalServerError("Unable to verify user authorizations to validate stack access", err)
} } else if !access {
if !access {
return httperror.Forbidden("Access denied to resource", httperrors.ErrResourceAccessDenied) return httperror.Forbidden("Access denied to resource", httperrors.ErrResourceAccessDenied)
} }
} }
canManage, err := handler.userCanManageStacks(securityContext, endpoint) if canManage, err := handler.userCanManageStacks(securityContext, endpoint); err != nil {
if err != nil {
return httperror.InternalServerError("Unable to verify user authorizations to validate stack deletion", err) return httperror.InternalServerError("Unable to verify user authorizations to validate stack deletion", err)
} } else if !canManage {
if !canManage {
errMsg := "Stack editing is disabled for non-admin users" errMsg := "Stack editing is disabled for non-admin users"
return httperror.Forbidden(errMsg, errors.New(errMsg)) return httperror.Forbidden(errMsg, errors.New(errMsg))
} }
@ -147,9 +138,7 @@ func (handler *Handler) stackUpdateGit(w http.ResponseWriter, r *http.Request) *
stack.UpdateDate = time.Now().Unix() stack.UpdateDate = time.Now().Unix()
if stack.Type == portainer.DockerSwarmStack { if stack.Type == portainer.DockerSwarmStack {
stack.Option = &portainer.StackOption{ stack.Option = &portainer.StackOption{Prune: payload.Prune}
Prune: payload.Prune,
}
} }
if payload.RepositoryAuthentication { if payload.RepositoryAuthentication {
@ -160,12 +149,13 @@ func (handler *Handler) stackUpdateGit(w http.ResponseWriter, r *http.Request) *
if password == "" && stack.GitConfig != nil && stack.GitConfig.Authentication != nil { if password == "" && stack.GitConfig != nil && stack.GitConfig.Authentication != nil {
password = stack.GitConfig.Authentication.Password password = stack.GitConfig.Authentication.Password
} }
stack.GitConfig.Authentication = &gittypes.GitAuthentication{ stack.GitConfig.Authentication = &gittypes.GitAuthentication{
Username: payload.RepositoryUsername, Username: payload.RepositoryUsername,
Password: password, Password: password,
} }
_, err = handler.GitService.LatestCommitID(stack.GitConfig.URL, stack.GitConfig.ReferenceName, stack.GitConfig.Authentication.Username, stack.GitConfig.Authentication.Password, stack.GitConfig.TLSSkipVerify)
if err != nil { if _, err := handler.GitService.LatestCommitID(stack.GitConfig.URL, stack.GitConfig.ReferenceName, stack.GitConfig.Authentication.Username, stack.GitConfig.Authentication.Password, stack.GitConfig.TLSSkipVerify); err != nil {
return httperror.InternalServerError("Unable to fetch git repository", err) return httperror.InternalServerError("Unable to fetch git repository", err)
} }
} else { } else {
@ -173,17 +163,15 @@ func (handler *Handler) stackUpdateGit(w http.ResponseWriter, r *http.Request) *
} }
if payload.AutoUpdate != nil && payload.AutoUpdate.Interval != "" { if payload.AutoUpdate != nil && payload.AutoUpdate.Interval != "" {
jobID, e := deployments.StartAutoupdate(stack.ID, stack.AutoUpdate.Interval, handler.Scheduler, handler.StackDeployer, handler.DataStore, handler.GitService) if jobID, err := deployments.StartAutoupdate(stack.ID, stack.AutoUpdate.Interval, handler.Scheduler, handler.StackDeployer, handler.DataStore, handler.GitService); err != nil {
if e != nil { return err
return e } else {
stack.AutoUpdate.JobID = jobID
} }
stack.AutoUpdate.JobID = jobID
} }
//save the updated stack to DB // Save the updated stack to DB
err = handler.DataStore.Stack().Update(stack.ID, stack) if err := handler.DataStore.Stack().Update(stack.ID, stack); err != nil {
if err != nil {
return httperror.InternalServerError("Unable to persist the stack changes inside the database", err) return httperror.InternalServerError("Unable to persist the stack changes inside the database", err)
} }

View File

@ -88,8 +88,7 @@ func (handler *Handler) stackGitRedeploy(w http.ResponseWriter, r *http.Request)
return httperror.InternalServerError("Unable to find the environment associated to the stack inside the database", err) return httperror.InternalServerError("Unable to find the environment associated to the stack inside the database", err)
} }
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint) if err := handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint); err != nil {
if err != nil {
return httperror.Forbidden("Permission denied to access environment", err) return httperror.Forbidden("Permission denied to access environment", err)
} }
@ -98,44 +97,36 @@ func (handler *Handler) stackGitRedeploy(w http.ResponseWriter, r *http.Request)
return httperror.InternalServerError("Unable to retrieve info from request context", err) return httperror.InternalServerError("Unable to retrieve info from request context", err)
} }
//only check resource control when it is a DockerSwarmStack or a DockerComposeStack // Only check resource control when it is a DockerSwarmStack or a DockerComposeStack
if stack.Type == portainer.DockerSwarmStack || stack.Type == portainer.DockerComposeStack { if stack.Type == portainer.DockerSwarmStack || stack.Type == portainer.DockerComposeStack {
resourceControl, err := handler.DataStore.ResourceControl().ResourceControlByResourceIDAndType(stackutils.ResourceControlID(stack.EndpointID, stack.Name), portainer.StackResourceControl) resourceControl, err := handler.DataStore.ResourceControl().ResourceControlByResourceIDAndType(stackutils.ResourceControlID(stack.EndpointID, stack.Name), portainer.StackResourceControl)
if err != nil { if err != nil {
return httperror.InternalServerError("Unable to retrieve a resource control associated to the stack", err) return httperror.InternalServerError("Unable to retrieve a resource control associated to the stack", err)
} }
access, err := handler.userCanAccessStack(securityContext, endpoint.ID, resourceControl) if access, err := handler.userCanAccessStack(securityContext, endpoint.ID, resourceControl); err != nil {
if err != nil {
return httperror.InternalServerError("Unable to verify user authorizations to validate stack access", err) return httperror.InternalServerError("Unable to verify user authorizations to validate stack access", err)
} } else if !access {
if !access {
return httperror.Forbidden("Access denied to resource", httperrors.ErrResourceAccessDenied) return httperror.Forbidden("Access denied to resource", httperrors.ErrResourceAccessDenied)
} }
} }
canManage, err := handler.userCanManageStacks(securityContext, endpoint) if canManage, err := handler.userCanManageStacks(securityContext, endpoint); err != nil {
if err != nil {
return httperror.InternalServerError("Unable to verify user authorizations to validate stack deletion", err) return httperror.InternalServerError("Unable to verify user authorizations to validate stack deletion", err)
} } else if !canManage {
if !canManage {
errMsg := "Stack management is disabled for non-admin users" errMsg := "Stack management is disabled for non-admin users"
return httperror.Forbidden(errMsg, errors.New(errMsg)) return httperror.Forbidden(errMsg, errors.New(errMsg))
} }
var payload stackGitRedployPayload var payload stackGitRedployPayload
err = request.DecodeAndValidateJSONPayload(r, &payload) if err := request.DecodeAndValidateJSONPayload(r, &payload); err != nil {
if err != nil {
return httperror.BadRequest("Invalid request payload", err) return httperror.BadRequest("Invalid request payload", err)
} }
stack.GitConfig.ReferenceName = payload.RepositoryReferenceName stack.GitConfig.ReferenceName = payload.RepositoryReferenceName
stack.Env = payload.Env stack.Env = payload.Env
if stack.Type == portainer.DockerSwarmStack { if stack.Type == portainer.DockerSwarmStack {
stack.Option = &portainer.StackOption{ stack.Option = &portainer.StackOption{Prune: payload.Prune}
Prune: payload.Prune,
}
} }
if stack.Type == portainer.KubernetesStack { if stack.Type == portainer.KubernetesStack {
@ -171,9 +162,8 @@ func (handler *Handler) stackGitRedeploy(w http.ResponseWriter, r *http.Request)
defer clean() defer clean()
httpErr := handler.deployStack(r, stack, payload.PullImage, endpoint) if err := handler.deployStack(r, stack, payload.PullImage, endpoint); err != nil {
if httpErr != nil { return err
return httpErr
} }
newHash, err := handler.GitService.LatestCommitID(stack.GitConfig.URL, stack.GitConfig.ReferenceName, repositoryUsername, repositoryPassword, stack.GitConfig.TLSSkipVerify) newHash, err := handler.GitService.LatestCommitID(stack.GitConfig.URL, stack.GitConfig.ReferenceName, repositoryUsername, repositoryPassword, stack.GitConfig.TLSSkipVerify)
@ -190,13 +180,12 @@ func (handler *Handler) stackGitRedeploy(w http.ResponseWriter, r *http.Request)
stack.UpdateDate = time.Now().Unix() stack.UpdateDate = time.Now().Unix()
stack.Status = portainer.StackStatusActive stack.Status = portainer.StackStatusActive
err = handler.DataStore.Stack().Update(stack.ID, stack) if err := handler.DataStore.Stack().Update(stack.ID, stack); err != nil {
if err != nil {
return httperror.InternalServerError("Unable to persist the stack changes inside the database", errors.Wrap(err, "failed to update the stack")) return httperror.InternalServerError("Unable to persist the stack changes inside the database", errors.Wrap(err, "failed to update the stack"))
} }
if stack.GitConfig != nil && stack.GitConfig.Authentication != nil && stack.GitConfig.Authentication.Password != "" { if stack.GitConfig != nil && stack.GitConfig.Authentication != nil && stack.GitConfig.Authentication.Password != "" {
// sanitize password in the http response to minimise possible security leaks // Sanitize password in the http response to minimise possible security leaks
stack.GitConfig.Authentication.Password = "" stack.GitConfig.Authentication.Password = ""
} }
@ -204,10 +193,7 @@ func (handler *Handler) stackGitRedeploy(w http.ResponseWriter, r *http.Request)
} }
func (handler *Handler) deployStack(r *http.Request, stack *portainer.Stack, pullImage bool, endpoint *portainer.Endpoint) *httperror.HandlerError { func (handler *Handler) deployStack(r *http.Request, stack *portainer.Stack, pullImage bool, endpoint *portainer.Endpoint) *httperror.HandlerError {
var ( var deploymentConfiger deployments.StackDeploymentConfiger
deploymentConfiger deployments.StackDeploymentConfiger
err error
)
switch stack.Type { switch stack.Type {
case portainer.DockerSwarmStack: case portainer.DockerSwarmStack:
@ -238,6 +224,7 @@ func (handler *Handler) deployStack(r *http.Request, stack *portainer.Stack, pul
if err != nil { if err != nil {
return httperror.InternalServerError(err.Error(), err) return httperror.InternalServerError(err.Error(), err)
} }
case portainer.KubernetesStack: case portainer.KubernetesStack:
handler.stackCreationMutex.Lock() handler.stackCreationMutex.Lock()
defer handler.stackCreationMutex.Unlock() defer handler.stackCreationMutex.Unlock()
@ -263,13 +250,14 @@ func (handler *Handler) deployStack(r *http.Request, stack *portainer.Stack, pul
if err != nil { if err != nil {
return httperror.InternalServerError(err.Error(), err) return httperror.InternalServerError(err.Error(), err)
} }
default: default:
return httperror.InternalServerError("Unsupported stack", errors.Errorf("unsupported stack type: %v", stack.Type)) return httperror.InternalServerError("Unsupported stack", errors.Errorf("unsupported stack type: %v", stack.Type))
} }
err = deploymentConfiger.Deploy() if err := deploymentConfiger.Deploy(); err != nil {
if err != nil {
return httperror.InternalServerError(err.Error(), err) return httperror.InternalServerError(err.Error(), err)
} }
return nil return nil
} }

View File

@ -43,8 +43,7 @@ func (handler *Handler) teamUpdate(w http.ResponseWriter, r *http.Request) *http
} }
var payload teamUpdatePayload var payload teamUpdatePayload
err = request.DecodeAndValidateJSONPayload(r, &payload) if err := request.DecodeAndValidateJSONPayload(r, &payload); err != nil {
if err != nil {
return httperror.BadRequest("Invalid request payload", err) return httperror.BadRequest("Invalid request payload", err)
} }
@ -59,8 +58,7 @@ func (handler *Handler) teamUpdate(w http.ResponseWriter, r *http.Request) *http
team.Name = payload.Name team.Name = payload.Name
} }
err = handler.DataStore.Team().Update(team.ID, team) if err := handler.DataStore.Team().Update(team.ID, team); err != nil {
if err != nil {
return httperror.NotFound("Unable to persist team changes inside the database", err) return httperror.NotFound("Unable to persist team changes inside the database", err)
} }

View File

@ -1,6 +1,7 @@
package users package users
import ( import (
"cmp"
"errors" "errors"
"net/http" "net/http"
"time" "time"
@ -78,8 +79,7 @@ func (handler *Handler) userUpdate(w http.ResponseWriter, r *http.Request) *http
} }
var payload userUpdatePayload var payload userUpdatePayload
err = request.DecodeAndValidateJSONPayload(r, &payload) if err := request.DecodeAndValidateJSONPayload(r, &payload); err != nil {
if err != nil {
return httperror.BadRequest("Invalid request payload", err) return httperror.BadRequest("Invalid request payload", err)
} }
@ -99,11 +99,9 @@ func (handler *Handler) userUpdate(w http.ResponseWriter, r *http.Request) *http
return httperror.Forbidden("Permission denied. Unable to update username", httperrors.ErrResourceAccessDenied) return httperror.Forbidden("Permission denied. Unable to update username", httperrors.ErrResourceAccessDenied)
} }
sameNameUser, err := handler.DataStore.User().UserByUsername(payload.Username) if sameNameUser, err := handler.DataStore.User().UserByUsername(payload.Username); err != nil && !handler.DataStore.IsErrObjectNotFound(err) {
if err != nil && !handler.DataStore.IsErrObjectNotFound(err) {
return httperror.InternalServerError("Unable to retrieve users from the database", err) return httperror.InternalServerError("Unable to retrieve users from the database", err)
} } else if sameNameUser != nil && sameNameUser.ID != portainer.UserID(userID) {
if sameNameUser != nil && sameNameUser.ID != portainer.UserID(userID) {
return httperror.Conflict("Another user with the same username already exists", errUserAlreadyExists) return httperror.Conflict("Another user with the same username already exists", errUserAlreadyExists)
} }
@ -121,8 +119,7 @@ func (handler *Handler) userUpdate(w http.ResponseWriter, r *http.Request) *http
if payload.NewPassword != "" { if payload.NewPassword != "" {
// Non-admins need to supply the previous password // Non-admins need to supply the previous password
if tokenData.Role != portainer.AdministratorRole { if tokenData.Role != portainer.AdministratorRole {
err := handler.CryptoService.CompareHashAndData(user.Password, payload.Password) if err := handler.CryptoService.CompareHashAndData(user.Password, payload.Password); err != nil {
if err != nil {
return httperror.Forbidden("Current password doesn't match. Password left unchanged", errors.New("Current password does not match the password provided. Please try again")) return httperror.Forbidden("Current password doesn't match. Password left unchanged", errors.New("Current password does not match the password provided. Please try again"))
} }
} }
@ -139,22 +136,17 @@ func (handler *Handler) userUpdate(w http.ResponseWriter, r *http.Request) *http
} }
if payload.Theme != nil { if payload.Theme != nil {
if payload.Theme.Color != nil { user.ThemeSettings.Color = *cmp.Or(payload.Theme.Color, &user.ThemeSettings.Color)
user.ThemeSettings.Color = *payload.Theme.Color
}
} }
if payload.UseCache != nil { user.UseCache = *cmp.Or(payload.UseCache, &user.UseCache)
user.UseCache = *payload.UseCache
}
if payload.Role != 0 { if payload.Role != 0 {
user.Role = portainer.UserRole(payload.Role) user.Role = portainer.UserRole(payload.Role)
user.TokenIssueAt = time.Now().Unix() user.TokenIssueAt = time.Now().Unix()
} }
err = handler.DataStore.User().Update(user.ID, user) if err := handler.DataStore.User().Update(user.ID, user); err != nil {
if err != nil {
return httperror.InternalServerError("Unable to persist user changes inside the database", err) return httperror.InternalServerError("Unable to persist user changes inside the database", err)
} }