diff --git a/app/channels.go b/app/channels.go index fffbb6b95d..3604980582 100644 --- a/app/channels.go +++ b/app/channels.go @@ -31,6 +31,13 @@ type configSvc interface { SaveConfig(newCfg *model.Config, sendConfigChangeClusterMessage bool) (*model.Config, *model.Config, *model.AppError) } +// licenseSvc is added to act as a starting point for future integrated products. +// It has the same signature and functionality with the license related APIs of the plugin-api. +type licenseSvc interface { // nolint: unused,deadcode + GetLicense() *model.License + RequestTrialLicense(requesterID string, users int, termsAccepted bool, receiveEmailsAccepted bool) *model.AppError +} + // namer is an interface which enforces that // all services can return their names. type namer interface { @@ -120,6 +127,7 @@ func NewChannels(s *Server, services map[ServiceKey]interface{}) (*Channels, err } ch.cfgSvc = cfgSvc } + } // We are passing a partially filled Channels struct so that the enterprise // methods can have access to app methods. diff --git a/app/license.go b/app/license.go index 155a669c93..af295dee73 100644 --- a/app/license.go +++ b/app/license.go @@ -17,6 +17,7 @@ import ( "github.com/mattermost/mattermost-server/v6/jobs" "github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/v6/shared/mlog" + "github.com/mattermost/mattermost-server/v6/store" "github.com/mattermost/mattermost-server/v6/utils" ) @@ -28,6 +29,62 @@ const ( var RequestTrialURL = "https://customers.mattermost.com/api/v1/trials" +// licenseWrapper is an adapter struct that only exposes the +// config related functionality to be passed down to other products. +type licenseWrapper struct { + srv *Server +} + +func (w *licenseWrapper) Name() ServiceKey { + return LicenseKey +} + +func (w *licenseWrapper) GetLicense() *model.License { + return w.srv.License() +} + +func (w *licenseWrapper) RequestTrialLicense(requesterID string, users int, termsAccepted bool, receiveEmailsAccepted bool) *model.AppError { + if *w.srv.Config().ExperimentalSettings.RestrictSystemAdmin { + return model.NewAppError("RequestTrialLicense", "api.restricted_system_admin", nil, "", http.StatusForbidden) + } + + if !termsAccepted { + return model.NewAppError("RequestTrialLicense", "api.license.request-trial.bad-request.terms-not-accepted", nil, "", http.StatusBadRequest) + } + + if users == 0 { + return model.NewAppError("RequestTrialLicense", "api.license.request-trial.bad-request", nil, "", http.StatusBadRequest) + } + + requester, err := w.srv.userService.GetUser(requesterID) + if err != nil { + var nfErr *store.ErrNotFound + switch { + case errors.As(err, &nfErr): + return model.NewAppError("RequestTrialLicense", MissingAccountError, nil, nfErr.Error(), http.StatusNotFound) + default: + return model.NewAppError("RequestTrialLicense", "app.user.get_by_username.app_error", nil, err.Error(), http.StatusInternalServerError) + } + } + + if *w.srv.Config().ServiceSettings.SiteURL == "" { + return model.NewAppError("RequestTrialLicense", "api.license.request_trial_license.no-site-url.app_error", nil, "", http.StatusBadRequest) + } + + trialLicenseRequest := &model.TrialLicenseRequest{ + ServerID: w.srv.TelemetryId(), + Name: requester.GetDisplayName(model.ShowFullName), + Email: requester.Email, + SiteName: *w.srv.Config().TeamSettings.SiteName, + SiteURL: *w.srv.Config().ServiceSettings.SiteURL, + Users: users, + TermsAccepted: termsAccepted, + ReceiveEmailsAccepted: receiveEmailsAccepted, + } + + return w.srv.RequestTrialLicense(trialLicenseRequest) +} + // JWTClaims custom JWT claims with the needed information for the // renewal process type JWTClaims struct { diff --git a/app/notification_push_test.go b/app/notification_push_test.go index 237e841853..93898d24cd 100644 --- a/app/notification_push_test.go +++ b/app/notification_push_test.go @@ -1409,7 +1409,8 @@ func TestPushNotificationRace(t *testing.T) { } s.configStore = &configWrapper{srv: s, Store: memoryStore} serviceMap := map[ServiceKey]interface{}{ - ConfigKey: s.configStore, + ConfigKey: s.configStore, + LicenseKey: &licenseWrapper{s}, } ch, err := NewChannels(s, serviceMap) require.NoError(t, err) diff --git a/app/server.go b/app/server.go index 1ea0a6b914..79668f9a36 100644 --- a/app/server.go +++ b/app/server.go @@ -89,7 +89,8 @@ var SentryDSN = "placeholder_sentry_dsn" type ServiceKey string const ( - ConfigKey ServiceKey = "config" + ConfigKey ServiceKey = "config" + LicenseKey ServiceKey = "license" ) type Server struct { @@ -141,6 +142,7 @@ type Server struct { licenseValue atomic.Value clientLicenseValue atomic.Value licenseListeners map[string]func(*model.License, *model.License) + licenseWrapper *licenseWrapper timezones *timezones.Timezones @@ -255,10 +257,15 @@ func NewServer(options ...Option) (*Server, error) { mlog.Info("Server is initializing...", mlog.String("go_version", runtime.Version())) s.httpService = httpservice.MakeHTTPService(s) + s.licenseWrapper = &licenseWrapper{ + srv: s, + } serviceMap := map[ServiceKey]interface{}{ - ConfigKey: s.configStore, + ConfigKey: s.configStore, + LicenseKey: s.licenseWrapper, } + // Step 3: Initialize products. // Depends on s.httpService. for name, initializer := range products {