feat(demo): remove demo mode EE-6769 (#11841)

pull/11848/head
andres-portainer 2024-05-17 20:00:01 -03:00 committed by GitHub
parent fbbf550730
commit 2b01136d03
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
45 changed files with 27 additions and 481 deletions

View File

@ -34,7 +34,6 @@ func (*Service) ParseFlags(version string) (*portainer.CLIFlags, error) {
TunnelPort: kingpin.Flag("tunnel-port", "Port to serve the tunnel server").Default(defaultTunnelServerPort).String(),
Assets: kingpin.Flag("assets", "Path to the assets").Default(defaultAssetsDirectory).Short('a').String(),
Data: kingpin.Flag("data", "Path to the folder where the data is stored").Default(defaultDataDirectory).Short('d').String(),
DemoEnvironment: kingpin.Flag("demo", "Demo environment").Bool(),
EndpointURL: kingpin.Flag("host", "Environment URL").Short('H').String(),
FeatureFlags: kingpin.Flag("feat", "List of feature flags").Strings(),
EnableEdgeComputeFeatures: kingpin.Flag("edge-compute", "Enable Edge Compute features").Bool(),

View File

@ -20,7 +20,6 @@ import (
"github.com/portainer/portainer/api/datastore"
"github.com/portainer/portainer/api/datastore/migrator"
"github.com/portainer/portainer/api/datastore/postinit"
"github.com/portainer/portainer/api/demo"
"github.com/portainer/portainer/api/docker"
dockerclient "github.com/portainer/portainer/api/docker/client"
"github.com/portainer/portainer/api/exec"
@ -509,14 +508,6 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
applicationStatus := initStatus(instanceID)
demoService := demo.NewService()
if *flags.DemoEnvironment {
err := demoService.Init(dataStore, cryptoService)
if err != nil {
log.Fatal().Err(err).Msg("failed initializing demo environment")
}
}
// channel to control when the admin user is created
adminCreationDone := make(chan struct{}, 1)
@ -631,7 +622,6 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server {
ShutdownCtx: shutdownCtx,
ShutdownTrigger: shutdownTrigger,
StackDeployer: stackDeployer,
DemoService: demoService,
UpgradeService: upgradeService,
AdminCreationDone: adminCreationDone,
PendingActionsService: pendingActionsService,

View File

@ -1,118 +0,0 @@
package demo
import (
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
)
type EnvironmentDetails struct {
Enabled bool `json:"enabled"`
Users []portainer.UserID `json:"users"`
Environments []portainer.EndpointID `json:"environments"`
}
type Service struct {
details EnvironmentDetails
}
func NewService() *Service {
return &Service{}
}
func (service *Service) Details() EnvironmentDetails {
return service.details
}
func (service *Service) Init(store dataservices.DataStore, cryptoService portainer.CryptoService) error {
log.Info().Msg("starting demo environment")
isClean, err := isCleanStore(store)
if err != nil {
return errors.WithMessage(err, "failed checking if store is clean")
}
if !isClean {
return errors.New(" Demo environment can only be initialized on a clean database")
}
id, err := initDemoUser(store, cryptoService)
if err != nil {
return errors.WithMessage(err, "failed creating demo user")
}
endpointIds, err := initDemoEndpoints(store)
if err != nil {
return errors.WithMessage(err, "failed creating demo endpoint")
}
err = initDemoSettings(store)
if err != nil {
return errors.WithMessage(err, "failed updating demo settings")
}
service.details = EnvironmentDetails{
Enabled: true,
Users: []portainer.UserID{id},
// endpoints 2,3 are created after deployment of portainer
Environments: endpointIds,
}
return nil
}
func isCleanStore(store dataservices.DataStore) (bool, error) {
endpoints, err := store.Endpoint().Endpoints()
if err != nil {
return false, err
}
if len(endpoints) > 0 {
return false, nil
}
users, err := store.User().ReadAll()
if err != nil {
return false, err
}
if len(users) > 0 {
return false, nil
}
return true, nil
}
func (service *Service) IsDemo() bool {
return service.details.Enabled
}
func (service *Service) IsDemoEnvironment(environmentID portainer.EndpointID) bool {
if !service.IsDemo() {
return false
}
for _, demoEndpointID := range service.details.Environments {
if environmentID == demoEndpointID {
return true
}
}
return false
}
func (service *Service) IsDemoUser(userID portainer.UserID) bool {
if !service.IsDemo() {
return false
}
for _, demoUserID := range service.details.Users {
if userID == demoUserID {
return true
}
}
return false
}

View File

@ -1,88 +0,0 @@
package demo
import (
"github.com/pkg/errors"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
)
func initDemoUser(
store dataservices.DataStore,
cryptoService portainer.CryptoService,
) (portainer.UserID, error) {
password, err := cryptoService.Hash("tryportainer")
if err != nil {
return 0, errors.WithMessage(err, "failed creating password hash")
}
admin := &portainer.User{
Username: "admin",
Password: password,
Role: portainer.AdministratorRole,
}
err = store.User().Create(admin)
return admin.ID, errors.WithMessage(err, "failed creating user")
}
func initDemoEndpoints(store dataservices.DataStore) ([]portainer.EndpointID, error) {
localEndpointId, err := initDemoLocalEndpoint(store)
if err != nil {
return nil, err
}
// second and third endpoints are going to be created with docker-compose as a part of the demo environment set up.
// ref: https://github.com/portainer/portainer-demo/blob/master/docker-compose.yml
return []portainer.EndpointID{localEndpointId, localEndpointId + 1, localEndpointId + 2}, nil
}
func initDemoLocalEndpoint(store dataservices.DataStore) (portainer.EndpointID, error) {
id := portainer.EndpointID(store.Endpoint().GetNextIdentifier())
localEndpoint := &portainer.Endpoint{
ID: id,
Name: "local",
URL: "unix:///var/run/docker.sock",
PublicURL: "demo.portainer.io",
Type: portainer.DockerEnvironment,
GroupID: portainer.EndpointGroupID(1),
TLSConfig: portainer.TLSConfiguration{
TLS: false,
},
AuthorizedUsers: []portainer.UserID{},
AuthorizedTeams: []portainer.TeamID{},
UserAccessPolicies: portainer.UserAccessPolicies{},
TeamAccessPolicies: portainer.TeamAccessPolicies{},
TagIDs: []portainer.TagID{},
Status: portainer.EndpointStatusUp,
Snapshots: []portainer.DockerSnapshot{},
Kubernetes: portainer.KubernetesDefault(),
}
err := store.Endpoint().Create(localEndpoint)
if err != nil {
return id, errors.WithMessage(err, "failed creating local endpoint")
}
err = store.Snapshot().Create(&portainer.Snapshot{EndpointID: id})
if err != nil {
return id, errors.WithMessage(err, "failed creating snapshot")
}
return id, errors.WithMessage(err, "failed creating local endpoint")
}
func initDemoSettings(
store dataservices.DataStore,
) error {
settings, err := store.Settings().Settings()
if err != nil {
return errors.WithMessage(err, "failed fetching settings")
}
settings.EnableTelemetry = false
settings.LogoURL = ""
err = store.Settings().UpdateSettings(settings)
return errors.WithMessage(err, "failed updating settings")
}

View File

@ -9,6 +9,4 @@ var (
ErrUnauthorized = errors.New("Unauthorized")
// ErrResourceAccessDenied Access denied to resource error
ErrResourceAccessDenied = errors.New("Access denied to resource")
// ErrNotAvailableInDemo feature is not allowed in demo
ErrNotAvailableInDemo = errors.New("This feature is not available in the demo version of Portainer")
)

View File

@ -16,7 +16,6 @@ import (
"github.com/portainer/portainer/api/adminmonitor"
"github.com/portainer/portainer/api/crypto"
"github.com/portainer/portainer/api/demo"
"github.com/portainer/portainer/api/http/offlinegate"
"github.com/portainer/portainer/api/internal/testhelpers"
@ -55,8 +54,7 @@ func Test_backupHandlerWithoutPassword_shouldCreateATarballArchive(t *testing.T)
gate,
"./test_assets/handler_test",
func() {},
adminMonitor,
&demo.Service{}).backup(w, r)
adminMonitor).backup(w, r)
assert.Nil(t, handlerErr, "Handler should not fail")
response := w.Result()
@ -99,8 +97,7 @@ func Test_backupHandlerWithPassword_shouldCreateEncryptedATarballArchive(t *test
gate,
"./test_assets/handler_test",
func() {},
adminMonitor,
&demo.Service{}).backup(w, r)
adminMonitor).backup(w, r)
assert.Nil(t, handlerErr, "Handler should not fail")
response := w.Result()

View File

@ -6,8 +6,6 @@ import (
"github.com/portainer/portainer/api/adminmonitor"
"github.com/portainer/portainer/api/dataservices"
"github.com/portainer/portainer/api/demo"
"github.com/portainer/portainer/api/http/middlewares"
"github.com/portainer/portainer/api/http/offlinegate"
"github.com/portainer/portainer/api/http/security"
httperror "github.com/portainer/portainer/pkg/libhttp/error"
@ -34,10 +32,7 @@ func NewHandler(
filestorePath string,
shutdownTrigger context.CancelFunc,
adminMonitor *adminmonitor.Monitor,
demoService *demo.Service,
) *Handler {
h := &Handler{
Router: mux.NewRouter(),
bouncer: bouncer,
@ -48,11 +43,8 @@ func NewHandler(
adminMonitor: adminMonitor,
}
demoRestrictedRouter := h.NewRoute().Subrouter()
demoRestrictedRouter.Use(middlewares.RestrictDemoEnv(demoService.IsDemo))
demoRestrictedRouter.Handle("/backup", bouncer.RestrictedAccess(adminAccess(httperror.LoggerHandler(h.backup)))).Methods(http.MethodPost)
demoRestrictedRouter.Handle("/restore", bouncer.PublicAccess(httperror.LoggerHandler(h.restore))).Methods(http.MethodPost)
h.Handle("/backup", bouncer.RestrictedAccess(adminAccess(httperror.LoggerHandler(h.backup)))).Methods(http.MethodPost)
h.Handle("/restore", bouncer.PublicAccess(httperror.LoggerHandler(h.restore))).Methods(http.MethodPost)
return h
}

View File

@ -14,7 +14,6 @@ import (
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/adminmonitor"
"github.com/portainer/portainer/api/demo"
"github.com/portainer/portainer/api/http/offlinegate"
"github.com/portainer/portainer/api/internal/testhelpers"
@ -63,7 +62,6 @@ func Test_restoreArchive_usingCombinationOfPasswords(t *testing.T) {
"./test_assets/handler_test",
func() {},
adminMonitor,
&demo.Service{},
)
//backup
@ -96,7 +94,6 @@ func Test_restoreArchive_shouldFailIfSystemWasAlreadyInitialized(t *testing.T) {
"./test_assets/handler_test",
func() {},
adminMonitor,
&demo.Service{},
)
//backup

View File

@ -10,10 +10,7 @@ import (
)
func TestEmptyGlobalKey(t *testing.T) {
handler := NewHandler(
helper.NewTestRequestBouncer(),
nil,
)
handler := NewHandler(helper.NewTestRequestBouncer())
req, err := http.NewRequest(http.MethodPost, "https://portainer.io:9443/endpoints/global-key", nil)
if err != nil {

View File

@ -9,7 +9,6 @@ import (
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
httperrors "github.com/portainer/portainer/api/http/errors"
"github.com/portainer/portainer/api/internal/endpointutils"
httperror "github.com/portainer/portainer/pkg/libhttp/error"
"github.com/portainer/portainer/pkg/libhttp/request"
@ -67,10 +66,6 @@ func (handler *Handler) endpointDelete(w http.ResponseWriter, r *http.Request) *
return httperror.BadRequest("Invalid boolean query parameter", err)
}
if handler.demoService.IsDemoEnvironment(portainer.EndpointID(endpointID)) {
return httperror.Forbidden(httperrors.ErrNotAvailableInDemo.Error(), httperrors.ErrNotAvailableInDemo)
}
err = handler.DataStore.UpdateTx(func(tx dataservices.DataStoreTx) error {
return handler.deleteEndpoint(tx, portainer.EndpointID(endpointID), deleteCluster)
})
@ -112,15 +107,6 @@ func (handler *Handler) endpointDeleteMultiple(w http.ResponseWriter, r *http.Re
err := handler.DataStore.UpdateTx(func(tx dataservices.DataStoreTx) error {
for _, e := range p.Endpoints {
// Demo endpoints cannot be deleted.
if handler.demoService.IsDemoEnvironment(portainer.EndpointID(e.ID)) {
resps = append(resps, DeleteMultipleResp{
Name: e.Name,
Err: httperrors.ErrNotAvailableInDemo,
})
continue
}
// Attempt deletion.
err := handler.deleteEndpoint(
tx,

View File

@ -9,7 +9,6 @@ import (
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/datastore"
"github.com/portainer/portainer/api/demo"
"github.com/portainer/portainer/api/http/proxy"
"github.com/portainer/portainer/api/internal/testhelpers"
)
@ -19,7 +18,7 @@ func TestEndpointDeleteEdgeGroupsConcurrently(t *testing.T) {
_, store := datastore.MustNewTestStore(t, true, false)
handler := NewHandler(testhelpers.NewTestRequestBouncer(), demo.NewService())
handler := NewHandler(testhelpers.NewTestRequestBouncer())
handler.DataStore = store
handler.ProxyManager = proxy.NewManager(nil)
handler.ProxyManager.NewProxyFactory(nil, nil, nil, nil, nil, nil, nil, nil)

View File

@ -194,7 +194,7 @@ func setupEndpointListHandler(t *testing.T, endpoints []portainer.Endpoint) *Han
bouncer := testhelpers.NewTestRequestBouncer()
handler := NewHandler(bouncer, nil)
handler := NewHandler(bouncer)
handler.DataStore = store
handler.ComposeStackManager = testhelpers.NewComposeStackManager()
handler.SnapshotService, _ = snapshot.NewService("1s", store, nil, nil, nil, nil)

View File

@ -194,7 +194,7 @@ func setupFilterTest(t *testing.T, endpoints []portainer.Endpoint) *Handler {
is.NoError(err, "error creating a user")
bouncer := testhelpers.NewTestRequestBouncer()
handler := NewHandler(bouncer, nil)
handler := NewHandler(bouncer)
handler.DataStore = store
handler.ComposeStackManager = testhelpers.NewComposeStackManager()

View File

@ -5,7 +5,6 @@ import (
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
"github.com/portainer/portainer/api/demo"
dockerclient "github.com/portainer/portainer/api/docker/client"
"github.com/portainer/portainer/api/http/proxy"
"github.com/portainer/portainer/api/http/security"
@ -28,7 +27,6 @@ func hideFields(endpoint *portainer.Endpoint) {
type Handler struct {
*mux.Router
requestBouncer security.BouncerService
demoService *demo.Service
DataStore dataservices.DataStore
FileService portainer.FileService
ProxyManager *proxy.Manager
@ -44,11 +42,10 @@ type Handler struct {
}
// NewHandler creates a handler to manage environment(endpoint) operations.
func NewHandler(bouncer security.BouncerService, demoService *demo.Service) *Handler {
func NewHandler(bouncer security.BouncerService) *Handler {
h := &Handler{
Router: mux.NewRouter(),
requestBouncer: bouncer,
demoService: demoService,
}
h.Handle("/endpoints",

View File

@ -5,7 +5,6 @@ import (
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
"github.com/portainer/portainer/api/demo"
"github.com/portainer/portainer/api/http/security"
httperror "github.com/portainer/portainer/pkg/libhttp/error"
@ -26,15 +25,14 @@ type Handler struct {
JWTService portainer.JWTService
LDAPService portainer.LDAPService
SnapshotService portainer.SnapshotService
demoService *demo.Service
}
// NewHandler creates a handler to manage settings operations.
func NewHandler(bouncer security.BouncerService, demoService *demo.Service) *Handler {
func NewHandler(bouncer security.BouncerService) *Handler {
h := &Handler{
Router: mux.NewRouter(),
demoService: demoService,
Router: mux.NewRouter(),
}
h.Handle("/settings",
bouncer.AdminAccess(httperror.LoggerHandler(h.settingsInspect))).Methods(http.MethodGet)
h.Handle("/settings",

View File

@ -149,11 +149,6 @@ func (handler *Handler) updateSettings(tx dataservices.DataStoreTx, payload sett
return nil, httperror.InternalServerError("Unable to retrieve the settings from the database", err)
}
if handler.demoService.IsDemo() {
payload.EnableTelemetry = nil
payload.LogoURL = nil
}
if payload.AuthenticationMethod != nil {
settings.AuthenticationMethod = portainer.AuthenticationMethod(*payload.AuthenticationMethod)
}

View File

@ -5,7 +5,6 @@ import (
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
"github.com/portainer/portainer/api/demo"
"github.com/portainer/portainer/api/http/security"
"github.com/portainer/portainer/api/internal/upgrade"
httperror "github.com/portainer/portainer/pkg/libhttp/error"
@ -18,21 +17,18 @@ type Handler struct {
*mux.Router
status *portainer.Status
dataStore dataservices.DataStore
demoService *demo.Service
upgradeService upgrade.Service
}
// NewHandler creates a handler to manage status operations.
func NewHandler(bouncer security.BouncerService,
status *portainer.Status,
demoService *demo.Service,
dataStore dataservices.DataStore,
upgradeService upgrade.Service) *Handler {
h := &Handler{
Router: mux.NewRouter(),
dataStore: dataStore,
demoService: demoService,
status: status,
upgradeService: upgradeService,
}

View File

@ -4,7 +4,6 @@ import (
"net/http"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/demo"
httperror "github.com/portainer/portainer/pkg/libhttp/error"
"github.com/portainer/portainer/pkg/libhttp/response"
@ -13,7 +12,6 @@ import (
type status struct {
*portainer.Status
DemoEnvironment demo.EnvironmentDetails
}
// @id systemStatus
@ -26,8 +24,7 @@ type status struct {
// @router /system/status [get]
func (handler *Handler) systemStatus(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
return response.JSON(w, &status{
Status: handler.status,
DemoEnvironment: handler.demoService.Details(),
Status: handler.status,
})
}

View File

@ -10,7 +10,6 @@ import (
"github.com/portainer/portainer/api/apikey"
"github.com/portainer/portainer/api/database/models"
"github.com/portainer/portainer/api/datastore"
"github.com/portainer/portainer/api/demo"
"github.com/portainer/portainer/api/http/security"
"github.com/portainer/portainer/api/internal/testhelpers"
"github.com/portainer/portainer/api/jwt"
@ -40,7 +39,7 @@ func Test_getSystemVersion(t *testing.T) {
apiKeyService := apikey.NewAPIKeyService(store.APIKeyRepository(), store.User())
requestBouncer := security.NewRequestBouncer(store, jwtService, apiKeyService)
h := NewHandler(requestBouncer, &portainer.Status{}, &demo.Service{}, store, nil)
h := NewHandler(requestBouncer, &portainer.Status{}, store, nil)
// generate standard and admin user tokens
jwt, _, _ := jwtService.GenerateToken(&portainer.TokenData{ID: adminUser.ID, Username: adminUser.Username, Role: adminUser.Role})

View File

@ -7,7 +7,6 @@ import (
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/apikey"
"github.com/portainer/portainer/api/dataservices"
"github.com/portainer/portainer/api/demo"
"github.com/portainer/portainer/api/http/security"
httperror "github.com/portainer/portainer/pkg/libhttp/error"
@ -31,7 +30,6 @@ type Handler struct {
*mux.Router
bouncer security.BouncerService
apiKeyService apikey.APIKeyService
demoService *demo.Service
DataStore dataservices.DataStore
CryptoService portainer.CryptoService
passwordStrengthChecker security.PasswordStrengthChecker
@ -40,12 +38,11 @@ type Handler struct {
}
// NewHandler creates a handler to manage user operations.
func NewHandler(bouncer security.BouncerService, rateLimiter *security.RateLimiter, apiKeyService apikey.APIKeyService, demoService *demo.Service, passwordStrengthChecker security.PasswordStrengthChecker) *Handler {
func NewHandler(bouncer security.BouncerService, rateLimiter *security.RateLimiter, apiKeyService apikey.APIKeyService, passwordStrengthChecker security.PasswordStrengthChecker) *Handler {
h := &Handler{
Router: mux.NewRouter(),
bouncer: bouncer,
apiKeyService: apiKeyService,
demoService: demoService,
passwordStrengthChecker: passwordStrengthChecker,
}

View File

@ -41,7 +41,7 @@ func Test_userCreateAccessToken(t *testing.T) {
rateLimiter := security.NewRateLimiter(10, 1*time.Second, 1*time.Hour)
passwordChecker := security.NewPasswordStrengthChecker(store.SettingsService)
h := NewHandler(requestBouncer, rateLimiter, apiKeyService, nil, passwordChecker)
h := NewHandler(requestBouncer, rateLimiter, apiKeyService, passwordChecker)
h.DataStore = store
h.CryptoService = testhelpers.NewCryptoService()

View File

@ -32,7 +32,7 @@ func Test_deleteUserRemovesAccessTokens(t *testing.T) {
rateLimiter := security.NewRateLimiter(10, 1*time.Second, 1*time.Hour)
passwordChecker := security.NewPasswordStrengthChecker(store.SettingsService)
h := NewHandler(requestBouncer, rateLimiter, apiKeyService, nil, passwordChecker)
h := NewHandler(requestBouncer, rateLimiter, apiKeyService, passwordChecker)
h.DataStore = store
t.Run("standard user deletion removes all associated access tokens", func(t *testing.T) {

View File

@ -40,7 +40,7 @@ func Test_userGetAccessTokens(t *testing.T) {
rateLimiter := security.NewRateLimiter(10, 1*time.Second, 1*time.Hour)
passwordChecker := security.NewPasswordStrengthChecker(store.SettingsService)
h := NewHandler(requestBouncer, rateLimiter, apiKeyService, nil, passwordChecker)
h := NewHandler(requestBouncer, rateLimiter, apiKeyService, passwordChecker)
h.DataStore = store
// generate standard and admin user tokens

View File

@ -12,7 +12,6 @@ import (
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/apikey"
"github.com/portainer/portainer/api/datastore"
"github.com/portainer/portainer/api/demo"
"github.com/portainer/portainer/api/http/security"
"github.com/portainer/portainer/api/internal/authorization"
"github.com/portainer/portainer/api/internal/testhelpers"
@ -40,7 +39,7 @@ func Test_userList(t *testing.T) {
rateLimiter := security.NewRateLimiter(10, 1*time.Second, 1*time.Hour)
passwordChecker := security.NewPasswordStrengthChecker(store.SettingsService)
h := NewHandler(requestBouncer, rateLimiter, apiKeyService, &demo.Service{}, passwordChecker)
h := NewHandler(requestBouncer, rateLimiter, apiKeyService, passwordChecker)
h.DataStore = store
// generate admin user tokens

View File

@ -38,7 +38,7 @@ func Test_userRemoveAccessToken(t *testing.T) {
rateLimiter := security.NewRateLimiter(10, 1*time.Second, 1*time.Hour)
passwordChecker := security.NewPasswordStrengthChecker(store.SettingsService)
h := NewHandler(requestBouncer, rateLimiter, apiKeyService, nil, passwordChecker)
h := NewHandler(requestBouncer, rateLimiter, apiKeyService, passwordChecker)
h.DataStore = store
// generate standard and admin user tokens

View File

@ -68,10 +68,6 @@ func (handler *Handler) userUpdate(w http.ResponseWriter, r *http.Request) *http
return httperror.BadRequest("Invalid user identifier route variable", err)
}
if handler.demoService.IsDemoUser(portainer.UserID(userID)) {
return httperror.Forbidden(httperrors.ErrNotAvailableInDemo.Error(), httperrors.ErrNotAvailableInDemo)
}
tokenData, err := security.RetrieveTokenData(r)
if err != nil {
return httperror.InternalServerError("Unable to retrieve user authentication token", err)

View File

@ -55,10 +55,6 @@ func (handler *Handler) userUpdatePassword(w http.ResponseWriter, r *http.Reques
return httperror.BadRequest("Invalid user identifier route variable", err)
}
if handler.demoService.IsDemoUser(portainer.UserID(userID)) {
return httperror.Forbidden(httperrors.ErrNotAvailableInDemo.Error(), httperrors.ErrNotAvailableInDemo)
}
tokenData, err := security.RetrieveTokenData(r)
if err != nil {
return httperror.InternalServerError("Unable to retrieve user authentication token", err)

View File

@ -32,7 +32,7 @@ func Test_updateUserRemovesAccessTokens(t *testing.T) {
rateLimiter := security.NewRateLimiter(10, 1*time.Second, 1*time.Hour)
passwordChecker := security.NewPasswordStrengthChecker(store.SettingsService)
h := NewHandler(requestBouncer, rateLimiter, apiKeyService, nil, passwordChecker)
h := NewHandler(requestBouncer, rateLimiter, apiKeyService, passwordChecker)
h.DataStore = store
t.Run("standard user deletion removes all associated access tokens", func(t *testing.T) {

View File

@ -1,24 +0,0 @@
package middlewares
import (
"net/http"
"github.com/portainer/portainer/api/http/errors"
httperror "github.com/portainer/portainer/pkg/libhttp/error"
"github.com/gorilla/mux"
)
// restrict functionality on demo environments
func RestrictDemoEnv(isDemo func() bool) mux.MiddlewareFunc {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !isDemo() {
next.ServeHTTP(w, r)
return
}
httperror.WriteError(w, http.StatusBadRequest, errors.ErrNotAvailableInDemo.Error(), errors.ErrNotAvailableInDemo)
})
}
}

View File

@ -1,41 +0,0 @@
package middlewares
import (
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func Test_demoEnvironment_shouldFail(t *testing.T) {
r := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(`{}`))
w := httptest.NewRecorder()
h := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {})
RestrictDemoEnv(func() bool { return true }).Middleware(h).ServeHTTP(w, r)
response := w.Result()
defer response.Body.Close()
assert.Equal(t, http.StatusBadRequest, response.StatusCode)
body, _ := io.ReadAll(response.Body)
assert.Contains(t, string(body), "This feature is not available in the demo version of Portainer")
}
func Test_notDemoEnvironment_shouldSucceed(t *testing.T) {
r := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(`{}`))
w := httptest.NewRecorder()
h := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {})
RestrictDemoEnv(func() bool { return false }).Middleware(h).ServeHTTP(w, r)
response := w.Result()
assert.Equal(t, http.StatusOK, response.StatusCode)
}

View File

@ -13,7 +13,6 @@ import (
"github.com/portainer/portainer/api/apikey"
"github.com/portainer/portainer/api/crypto"
"github.com/portainer/portainer/api/dataservices"
"github.com/portainer/portainer/api/demo"
"github.com/portainer/portainer/api/docker"
dockerclient "github.com/portainer/portainer/api/docker/client"
"github.com/portainer/portainer/api/http/csrf"
@ -109,7 +108,6 @@ type Server struct {
ShutdownCtx context.Context
ShutdownTrigger context.CancelFunc
StackDeployer deployments.StackDeployer
DemoService *demo.Service
UpgradeService upgrade.Service
AdminCreationDone chan struct{}
PendingActionsService *pendingactions.PendingActionsService
@ -145,7 +143,6 @@ func (server *Server) Start() error {
server.FileService.GetDatastorePath(),
server.ShutdownTrigger,
adminMonitor,
server.DemoService,
)
var roleHandler = roles.NewHandler(requestBouncer)
@ -170,7 +167,7 @@ func (server *Server) Start() error {
var edgeTemplatesHandler = edgetemplates.NewHandler(requestBouncer)
edgeTemplatesHandler.DataStore = server.DataStore
var endpointHandler = endpoints.NewHandler(requestBouncer, server.DemoService)
var endpointHandler = endpoints.NewHandler(requestBouncer)
endpointHandler.DataStore = server.DataStore
endpointHandler.FileService = server.FileService
endpointHandler.ProxyManager = server.ProxyManager
@ -226,7 +223,7 @@ func (server *Server) Start() error {
var resourceControlHandler = resourcecontrols.NewHandler(requestBouncer)
resourceControlHandler.DataStore = server.DataStore
var settingsHandler = settings.NewHandler(requestBouncer, server.DemoService)
var settingsHandler = settings.NewHandler(requestBouncer)
settingsHandler.DataStore = server.DataStore
settingsHandler.FileService = server.FileService
settingsHandler.JWTService = server.JWTService
@ -269,7 +266,6 @@ func (server *Server) Start() error {
var systemHandler = system.NewHandler(requestBouncer,
server.Status,
server.DemoService,
server.DataStore,
server.UpgradeService)
@ -281,7 +277,7 @@ func (server *Server) Start() error {
var uploadHandler = upload.NewHandler(requestBouncer)
uploadHandler.FileService = server.FileService
var userHandler = users.NewHandler(requestBouncer, rateLimiter, server.APIKeyService, server.DemoService, passwordStrengthChecker)
var userHandler = users.NewHandler(requestBouncer, rateLimiter, server.APIKeyService, passwordStrengthChecker)
userHandler.DataStore = server.DataStore
userHandler.CryptoService = server.CryptoService
userHandler.AdminCreationDone = server.AdminCreationDone

View File

@ -124,7 +124,6 @@ type (
Assets *string
Data *string
FeatureFlags *[]string
DemoEnvironment *bool
EnableEdgeComputeFeatures *bool
EndpointURL *string
Labels *[]Pair

View File

@ -1,16 +0,0 @@
class DemoFeatureIndicatorController {
/* @ngInject */
constructor(StateManager) {
Object.assign(this, { StateManager });
this.isDemo = false;
}
$onInit() {
const state = this.StateManager.getState();
this.isDemo = state.application.demoEnvironment.enabled;
}
}
export default DemoFeatureIndicatorController;

View File

@ -1,10 +0,0 @@
<div class="row" ng-if="$ctrl.isDemo">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="alert-triangle" title-text="Feature not available"> </rd-widget-header>
<rd-widget-body>
<span class="small text-muted">{{ $ctrl.content }}</span>
</rd-widget-body>
</rd-widget>
</div>
</div>

View File

@ -1,12 +0,0 @@
import angular from 'angular';
import controller from './demo-feature-indicator.controller.js';
export const demoFeatureIndicator = {
templateUrl: './demo-feature-indicator.html',
controller,
bindings: {
content: '<',
},
};
angular.module('portainer.app').component('demoFeatureIndicator', demoFeatureIndicator);

View File

@ -30,10 +30,8 @@ export default class ThemeSettingsController {
async updateThemeSettings(theme) {
try {
if (!this.state.isDemo) {
await this.UserService.updateUserTheme(this.state.userId, theme);
await queryClient.invalidateQueries(userQueryKeys.user(this.state.userId));
}
await this.UserService.updateUserTheme(this.state.userId, theme);
await queryClient.invalidateQueries(userQueryKeys.user(this.state.userId));
notifySuccess('Success', 'User theme settings successfully updated');
} catch (err) {
@ -43,12 +41,9 @@ export default class ThemeSettingsController {
$onInit() {
return this.$async(async () => {
const state = this.StateManager.getState();
this.state = {
userId: null,
themeColor: 'auto',
isDemo: state.application.demoEnvironment.enabled,
};
this.state.availableThemes = options;

View File

@ -4,7 +4,6 @@ export function StatusViewModel(data) {
this.Version = data.Version;
this.Edition = data.Edition;
this.InstanceID = data.InstanceID;
this.DemoEnvironment = data.DemoEnvironment;
}
export function StatusVersionViewModel(data) {

View File

@ -109,7 +109,6 @@ function StateManagerFactory(
state.application.version = status.Version;
state.application.edition = status.Edition;
state.application.instanceId = status.InstanceID;
state.application.demoEnvironment = status.DemoEnvironment;
state.application.enableTelemetry = settings.EnableTelemetry;
state.application.logo = settings.LogoURL;

View File

@ -1,7 +1,5 @@
<page-header title="'User settings'" breadcrumbs="['User settings']" reload="true"> </page-header>
<demo-feature-indicator ng-if="isDemoUser" content="'You cannot change the password of this account in the demo version of Portainer.'"> </demo-feature-indicator>
<div class="row">
<div class="col-lg-12 col-md-12 col-xs-12">
<theme-settings></theme-settings>
@ -56,7 +54,7 @@
<button
type="submit"
class="btn btn-primary btn-sm"
ng-disabled="isDemoUser || (AuthenticationMethod !== 1 && !isInitialAdmin) || !formValues.currentPassword || !formValues.newPassword || form.$invalid || formValues.newPassword !== formValues.confirmPassword"
ng-disabled="(AuthenticationMethod !== 1 && !isInitialAdmin) || !formValues.currentPassword || !formValues.newPassword || form.$invalid || formValues.newPassword !== formValues.confirmPassword"
ng-click="updatePassword()"
>
Update password

View File

@ -76,10 +76,6 @@ angular.module('portainer.app').controller('AccountController', [
$scope.forceChangePassword = userDetails.forceChangePassword;
$scope.isInitialAdmin = userDetails.ID === 1;
if (state.application.demoEnvironment.enabled) {
$scope.isDemoUser = state.application.demoEnvironment.users.includes($scope.userID);
}
SettingsService.publicSettings()
.then(function success(data) {
$scope.AuthenticationMethod = data.AuthenticationMethod;

View File

@ -1,16 +0,0 @@
import { useIsDemo } from '@/react/portainer/system/useSystemStatus';
export function DemoAlert() {
const isDemoQuery = useIsDemo();
if (!isDemoQuery.data) {
return null;
}
return (
<div className="col-sm-12 mt-2">
<span className="small text-muted">
You cannot use this feature in the demo version of Portainer.
</span>
</div>
);
}

View File

@ -1,13 +1,8 @@
import { useField } from 'formik';
import { useIsDemo } from '@/react/portainer/system/useSystemStatus';
import { SwitchField } from '@@/form-components/SwitchField';
import { DemoAlert } from './DemoAlert';
export function EnableTelemetryField() {
const isDemoQuery = useIsDemo();
const [{ value }, , { setValue }] = useField<boolean>('enableTelemetry');
return (
@ -20,12 +15,9 @@ export function EnableTelemetryField() {
checked={value}
name="toggle_enableTelemetry"
onChange={(checked) => setValue(checked)}
disabled={isDemoQuery.data}
/>
</div>
<DemoAlert />
<div className="col-sm-12 text-muted small mt-2">
You can find more information about this in our{' '}
<a

View File

@ -1,18 +1,13 @@
import { useField, Field } from 'formik';
import { useIsDemo } from '@/react/portainer/system/useSystemStatus';
import { FormControl } from '@@/form-components/FormControl';
import { Input } from '@@/form-components/Input';
import { SwitchField } from '@@/form-components/SwitchField';
import { useToggledValue } from '../useToggledValue';
import { DemoAlert } from './DemoAlert';
export function LogoFieldset() {
const [{ name }, { error }] = useField<string>('logo');
const isDemoQuery = useIsDemo();
const [isEnabled, setIsEnabled] = useToggledValue('logo');
@ -26,12 +21,9 @@ export function LogoFieldset() {
checked={isEnabled}
name="toggle_logo"
labelClass="col-sm-3 col-lg-2"
disabled={isDemoQuery.data}
onChange={(checked) => setIsEnabled(checked)}
/>
</div>
<DemoAlert />
</div>
{isEnabled && (

View File

@ -1,7 +1,6 @@
import { useField, Field } from 'formik';
import { FeatureId } from '@/react/portainer/feature-flags/enums';
import { useIsDemo } from '@/react/portainer/system/useSystemStatus';
import { FormControl } from '@@/form-components/FormControl';
import { TextArea } from '@@/form-components/Input/Textarea';
@ -9,10 +8,7 @@ import { SwitchField } from '@@/form-components/SwitchField';
import { useToggledValue } from '../useToggledValue';
import { DemoAlert } from './DemoAlert';
export function ScreenBannerFieldset() {
const isDemoQuery = useIsDemo();
const [{ name }, { error }] = useField<string>('loginBanner');
const [isEnabled, setIsEnabled] = useToggledValue('loginBanner');
@ -26,14 +22,11 @@ export function ScreenBannerFieldset() {
label="Login screen banner"
checked={isEnabled}
name="toggle_login_banner"
disabled={isDemoQuery.data}
onChange={(checked) => setIsEnabled(checked)}
featureId={FeatureId.CUSTOM_LOGIN_BANNER}
/>
</div>
<DemoAlert />
<div className="col-sm-12 text-muted small mt-2">
You can set a custom banner that will be shown to all users during
login.

View File

@ -1,10 +1,8 @@
import { UseQueryOptions, useQuery } from '@tanstack/react-query';
import axios, { parseAxiosError } from '@/portainer/services/axios';
import { UserId } from '@/portainer/users/types';
import { isBE } from '../feature-flags/feature-flags.service';
import { EnvironmentId } from '../environments/types';
import { buildUrl } from './build-url';
import { queryKeys } from './query-keys';
@ -15,11 +13,6 @@ export interface StatusResponse {
Edition: string;
Version: string;
InstanceID: string;
DemoEnvironment: {
Enabled: boolean;
Users: Array<UserId>;
Environments: Array<EnvironmentId>;
};
}
export async function getSystemStatus() {
@ -53,9 +46,3 @@ export function useSystemStatus<T = StatusResponse>({
onSuccess,
});
}
export function useIsDemo() {
return useSystemStatus({
select: (status) => status.DemoEnvironment.Enabled,
});
}