diff --git a/api4/license.go b/api4/license.go index ab63883502..fdd8e958f6 100644 --- a/api4/license.go +++ b/api4/license.go @@ -5,7 +5,9 @@ package api4 import ( "bytes" + "encoding/json" "io" + "io/ioutil" "net/http" "github.com/mattermost/mattermost-server/v5/audit" @@ -13,6 +15,7 @@ import ( ) func (api *API) InitLicense() { + api.BaseRoutes.ApiRoot.Handle("/trial-license", api.ApiSessionRequired(requestTrialLicense)).Methods("POST") api.BaseRoutes.ApiRoot.Handle("/license", api.ApiSessionRequired(addLicense)).Methods("POST") api.BaseRoutes.ApiRoot.Handle("/license", api.ApiSessionRequired(removeLicense)).Methods("DELETE") api.BaseRoutes.ApiRoot.Handle("/license/client", api.ApiHandler(getClientLicense)).Methods("GET") @@ -138,3 +141,59 @@ func removeLicense(c *Context, w http.ResponseWriter, r *http.Request) { ReturnStatusOK(w) } + +func requestTrialLicense(c *Context, w http.ResponseWriter, r *http.Request) { + auditRec := c.MakeAuditRecord("requestTrialLicense", audit.Fail) + defer c.LogAuditRec(auditRec) + c.LogAudit("attempt") + + if !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_MANAGE_SYSTEM) { + c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) + return + } + + if *c.App.Config().ExperimentalSettings.RestrictSystemAdmin { + c.Err = model.NewAppError("removeLicense", "api.restricted_system_admin", nil, "", http.StatusForbidden) + return + } + + var usersNumber struct { + Users int `json:"users"` + } + + b, readErr := ioutil.ReadAll(r.Body) + if readErr != nil { + c.Err = model.NewAppError("removeLicense", "api.license.request-trial.bad-request", nil, "", http.StatusBadRequest) + return + } + json.Unmarshal(b, &usersNumber) + if usersNumber.Users == 0 { + c.Err = model.NewAppError("removeLicense", "api.license.request-trial.bad-request", nil, "", http.StatusBadRequest) + return + } + + currentUser, err := c.App.GetUser(c.App.Session().UserId) + if err != nil { + c.Err = err + return + } + + trialLicenseRequest := &model.TrialLicenseRequest{ + ServerID: c.App.DiagnosticId(), + Name: currentUser.GetDisplayName(model.SHOW_FULLNAME), + Email: currentUser.Email, + SiteName: *c.App.Config().TeamSettings.SiteName, + SiteURL: *c.App.Config().ServiceSettings.SiteURL, + Users: usersNumber.Users, + } + + if err := c.App.RequestTrialLicense(trialLicenseRequest); err != nil { + c.Err = err + return + } + + auditRec.Success() + c.LogAudit("success") + + ReturnStatusOK(w) +} diff --git a/app/app_iface.go b/app/app_iface.go index 5a6e392603..dce0e3015b 100644 --- a/app/app_iface.go +++ b/app/app_iface.go @@ -253,6 +253,8 @@ type AppIface interface { RenameChannel(channel *model.Channel, newChannelName string, newDisplayName string) (*model.Channel, *model.AppError) // RenameTeam is used to rename the team Name and the DisplayName fields RenameTeam(team *model.Team, newTeamName string, newDisplayName string) (*model.Team, *model.AppError) + // RequestTrialLicense request a trial license from the mattermost offical license server + RequestTrialLicense(trialRequest *model.TrialLicenseRequest) *model.AppError // RevokeSessionsFromAllUsers will go through all the sessions active // in the server and revoke them RevokeSessionsFromAllUsers() *model.AppError diff --git a/app/license.go b/app/license.go index d09c2abd4d..a8441ceda2 100644 --- a/app/license.go +++ b/app/license.go @@ -4,6 +4,7 @@ package app import ( + "bytes" "net/http" "strings" @@ -12,6 +13,8 @@ import ( "github.com/mattermost/mattermost-server/v5/utils" ) +const requestTrialURL = "https://customers.mattermost.com/api/v1/trials" + func (a *App) LoadLicense() { licenseId := "" props, err := a.Srv().Store.System().Get() @@ -214,3 +217,22 @@ func (a *App) GetSanitizedClientLicense() map[string]string { return sanitizedLicense } + +// RequestTrialLicense request a trial license from the mattermost offical license server +func (a *App) RequestTrialLicense(trialRequest *model.TrialLicenseRequest) *model.AppError { + resp, err := http.Post(requestTrialURL, "application/json", bytes.NewBuffer([]byte(trialRequest.ToJson()))) + if err != nil { + return model.NewAppError("RequestTrialLicense", "api.license.request_trial_license.app_error", nil, err.Error(), http.StatusBadRequest) + } + defer resp.Body.Close() + licenseResponse := model.MapFromJson(resp.Body) + + if _, err := a.SaveLicense([]byte(licenseResponse["license"])); err != nil { + return err + } + + a.ReloadConfig() + a.InvalidateAllCaches() + + return nil +} diff --git a/app/opentracing_layer.go b/app/opentracing_layer.go index 940b60d5b9..c71d68b6d8 100644 --- a/app/opentracing_layer.go +++ b/app/opentracing_layer.go @@ -11395,6 +11395,28 @@ func (a *OpenTracingAppLayer) RenameTeam(team *model.Team, newTeamName string, n return resultVar0, resultVar1 } +func (a *OpenTracingAppLayer) RequestTrialLicense(trialRequest *model.TrialLicenseRequest) *model.AppError { + origCtx := a.ctx + span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.RequestTrialLicense") + + a.ctx = newCtx + a.app.Srv().Store.SetContext(newCtx) + defer func() { + a.app.Srv().Store.SetContext(origCtx) + a.ctx = origCtx + }() + + defer span.Finish() + resultVar0 := a.app.RequestTrialLicense(trialRequest) + + if resultVar0 != nil { + span.LogFields(spanlog.Error(resultVar0)) + ext.Error.Set(span, true) + } + + return resultVar0 +} + func (a *OpenTracingAppLayer) ResetPasswordFromToken(userSuppliedTokenString string, newPassword string) *model.AppError { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.ResetPasswordFromToken") diff --git a/i18n/en.json b/i18n/en.json index 9b48530670..dc604868f8 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -1488,6 +1488,14 @@ "id": "api.license.remove_expired_license.failed.error", "translation": "Failed to send the disable license email successfully." }, + { + "id": "api.license.request-trial.bad-request", + "translation": "The number of users requested is not correct." + }, + { + "id": "api.license.request_trial_license.app_error", + "translation": "Unable to get a trial license, please try again or contact with support@mattermost.com." + }, { "id": "api.marshal_error", "translation": "marshal error" diff --git a/model/client4.go b/model/client4.go index 3eacb46176..8a8d55fd9b 100644 --- a/model/client4.go +++ b/model/client4.go @@ -5102,3 +5102,14 @@ func (c *Client4) GetChannelMemberCountsByGroup(channelID string, includeTimezon defer closeBody(r) return ChannelMemberCountsByGroupFromJson(r.Body), BuildResponse(r) } + +// RequestTrialLicense will request a trial license and install it in the server +func (c *Client4) RequestTrialLicense(users int) (bool, *Response) { + b, _ := json.Marshal(map[string]int{"users": users}) + r, err := c.DoApiPost("/trial-license", string(b)) + if err != nil { + return false, BuildErrorResponse(r, err) + } + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) +} diff --git a/model/license.go b/model/license.go index 80e7961494..c0c401e240 100644 --- a/model/license.go +++ b/model/license.go @@ -40,6 +40,20 @@ type Customer struct { Company string `json:"company"` } +type TrialLicenseRequest struct { + ServerID string `json:"server_id"` + Email string `json:"email"` + Name string `json:"name"` + SiteURL string `json:"site_url"` + SiteName string `json:"site_name"` + Users int `json:"users"` +} + +func (tlr *TrialLicenseRequest) ToJson() string { + b, _ := json.Marshal(tlr) + return string(b) +} + type Features struct { Users *int `json:"users"` LDAP *bool `json:"ldap"`