2019-11-29 06:59:40 -05:00
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
2019-03-08 13:15:28 -05:00
package api4
import (
"bytes"
2020-06-02 15:34:15 -04:00
"encoding/json"
2019-03-08 13:15:28 -05:00
"io"
2025-04-17 16:29:46 -04:00
"math"
2019-03-08 13:15:28 -05:00
"net/http"
2023-06-11 01:24:35 -04:00
"github.com/mattermost/mattermost/server/public/shared/mlog"
"github.com/mattermost/mattermost/server/v8/channels/utils"
2021-06-17 08:07:34 -04:00
2023-06-11 01:24:35 -04:00
"github.com/mattermost/mattermost/server/public/model"
2025-04-17 16:29:46 -04:00
"github.com/mattermost/mattermost/server/v8/channels/app"
2019-03-08 13:15:28 -05:00
)
func ( api * API ) InitLicense ( ) {
2024-07-15 10:52:03 -04:00
api . BaseRoutes . APIRoot . Handle ( "/trial-license" , api . APISessionRequired ( requestTrialLicense ) ) . Methods ( http . MethodPost )
api . BaseRoutes . APIRoot . Handle ( "/trial-license/prev" , api . APISessionRequired ( getPrevTrialLicense ) ) . Methods ( http . MethodGet )
api . BaseRoutes . APIRoot . Handle ( "/license" , api . APISessionRequired ( addLicense , handlerParamFileAPI ) ) . Methods ( http . MethodPost )
api . BaseRoutes . APIRoot . Handle ( "/license" , api . APISessionRequired ( removeLicense ) ) . Methods ( http . MethodDelete )
api . BaseRoutes . APIRoot . Handle ( "/license/client" , api . APIHandler ( getClientLicense ) ) . Methods ( http . MethodGet )
2025-04-17 16:29:46 -04:00
api . BaseRoutes . APIRoot . Handle ( "/license/load_metric" , api . APISessionRequired ( getLicenseLoadMetric ) ) . Methods ( http . MethodGet )
2019-03-08 13:15:28 -05:00
}
func getClientLicense ( c * Context , w http . ResponseWriter , r * http . Request ) {
format := r . URL . Query ( ) . Get ( "format" )
if format == "" {
2022-04-04 00:57:29 -04:00
c . Err = model . NewAppError ( "getClientLicense" , "api.license.client.old_format.app_error" , nil , "" , http . StatusBadRequest )
2019-03-08 13:15:28 -05:00
return
}
if format != "old" {
c . SetInvalidParam ( "format" )
return
}
var clientLicense map [ string ] string
2021-07-12 14:05:36 -04:00
if c . App . SessionHasPermissionTo ( * c . AppContext . Session ( ) , model . PermissionReadLicenseInformation ) {
2020-06-12 07:43:50 -04:00
clientLicense = c . App . Srv ( ) . ClientLicense ( )
2019-03-08 13:15:28 -05:00
} else {
2020-06-12 07:43:50 -04:00
clientLicense = c . App . Srv ( ) . GetSanitizedClientLicense ( )
2019-03-08 13:15:28 -05:00
}
2024-10-16 03:45:41 -04:00
if _ , err := w . Write ( [ ] byte ( model . MapToJSON ( clientLicense ) ) ) ; err != nil {
c . Logger . Warn ( "Error while writing response" , mlog . Err ( err ) )
}
2019-03-08 13:15:28 -05:00
}
func addLicense ( c * Context , w http . ResponseWriter , r * http . Request ) {
2025-07-16 00:47:03 -04:00
auditRec := c . MakeAuditRecord ( model . AuditEventAddLicense , model . AuditStatusFail )
2020-03-12 15:50:21 -04:00
defer c . LogAuditRec ( auditRec )
2019-03-08 13:15:28 -05:00
c . LogAudit ( "attempt" )
2025-04-10 15:22:03 -04:00
if ! c . App . SessionHasPermissionToAndNotRestrictedAdmin ( * c . AppContext . Session ( ) , model . PermissionManageLicenseInformation ) {
2021-07-12 14:05:36 -04:00
c . SetPermissionError ( model . PermissionManageLicenseInformation )
2019-03-08 13:15:28 -05:00
return
}
err := r . ParseMultipartForm ( * c . App . Config ( ) . FileSettings . MaxFileSize )
if err != nil {
http . Error ( w , err . Error ( ) , http . StatusBadRequest )
return
}
m := r . MultipartForm
fileArray , ok := m . File [ "license" ]
if ! ok {
c . Err = model . NewAppError ( "addLicense" , "api.license.add_license.no_file.app_error" , nil , "" , http . StatusBadRequest )
return
}
if len ( fileArray ) <= 0 {
c . Err = model . NewAppError ( "addLicense" , "api.license.add_license.array.app_error" , nil , "" , http . StatusBadRequest )
return
}
fileData := fileArray [ 0 ]
2025-06-25 20:37:32 -04:00
model . AddEventParameterToAuditRec ( auditRec , "filename" , fileData . Filename )
2019-03-08 13:15:28 -05:00
file , err := fileData . Open ( )
if err != nil {
2024-04-22 06:03:28 -04:00
c . Err = model . NewAppError ( "addLicense" , "api.license.add_license.open.app_error" , nil , "" , http . StatusBadRequest ) . Wrap ( err )
2019-03-08 13:15:28 -05:00
return
}
defer file . Close ( )
buf := bytes . NewBuffer ( nil )
2024-10-16 03:45:41 -04:00
if _ , err := io . Copy ( buf , file ) ; err != nil {
c . Err = model . NewAppError ( "addLicense" , "api.license.add_license.copy.app_error" , nil , "" , http . StatusInternalServerError ) . Wrap ( err )
return
}
2019-03-08 13:15:28 -05:00
2021-06-17 08:07:34 -04:00
licenseBytes := buf . Bytes ( )
license , appErr := utils . LicenseValidator . LicenseFromBytes ( licenseBytes )
if appErr != nil {
c . Err = appErr
return
}
// skip the restrictions if license is a sanctioned trial
if ! license . IsSanctionedTrial ( ) && license . IsTrialLicense ( ) {
2022-10-18 06:30:22 -04:00
lm := c . App . Srv ( ) . Platform ( ) . LicenseManager ( )
if lm == nil {
c . Err = model . NewAppError ( "addLicense" , "api.license.upgrade_needed.app_error" , nil , "" , http . StatusInternalServerError )
return
}
canStartTrialLicense , err := lm . CanStartTrial ( )
2021-06-17 08:07:34 -04:00
if err != nil {
2024-12-12 00:03:46 -05:00
c . Err = model . NewAppError ( "addLicense" , "api.license.add_license.open.app_error" , nil , "" , http . StatusInternalServerError ) . Wrap ( err )
2021-06-17 08:07:34 -04:00
return
}
if ! canStartTrialLicense {
c . Err = model . NewAppError ( "addLicense" , "api.license.request-trial.can-start-trial.not-allowed" , nil , "" , http . StatusBadRequest )
return
}
}
license , appErr = c . App . Srv ( ) . SaveLicense ( licenseBytes )
2019-03-08 13:15:28 -05:00
if appErr != nil {
2021-07-12 14:05:36 -04:00
if appErr . Id == model . ExpiredLicenseError {
2019-03-08 13:15:28 -05:00
c . LogAudit ( "failed - expired or non-started license" )
2021-07-12 14:05:36 -04:00
} else if appErr . Id == model . InvalidLicenseError {
2019-03-08 13:15:28 -05:00
c . LogAudit ( "failed - invalid license" )
} else {
c . LogAudit ( "failed - unable to save license" )
}
c . Err = appErr
return
}
2022-11-02 16:40:26 -04:00
if c . App . Channels ( ) . License ( ) . IsCloud ( ) {
// If cloud, invalidate the caches when a new license is loaded
2024-10-16 03:45:41 -04:00
defer func ( ) {
if err := c . App . Srv ( ) . Cloud . HandleLicenseChange ( ) ; err != nil {
c . Logger . Warn ( "Error while handling license change" , mlog . Err ( err ) )
}
} ( )
2022-11-02 16:40:26 -04:00
}
2020-03-12 15:50:21 -04:00
auditRec . Success ( )
2019-03-08 13:15:28 -05:00
c . LogAudit ( "success" )
2020-03-12 15:50:21 -04:00
2021-07-26 04:11:02 -04:00
if err := json . NewEncoder ( w ) . Encode ( license ) ; err != nil {
2022-07-28 11:05:03 -04:00
c . Logger . Warn ( "Error while writing response" , mlog . Err ( err ) )
2021-07-26 04:11:02 -04:00
}
2019-03-08 13:15:28 -05:00
}
func removeLicense ( c * Context , w http . ResponseWriter , r * http . Request ) {
2025-07-16 00:47:03 -04:00
auditRec := c . MakeAuditRecord ( model . AuditEventRemoveLicense , model . AuditStatusFail )
2020-03-12 15:50:21 -04:00
defer c . LogAuditRec ( auditRec )
2019-03-08 13:15:28 -05:00
c . LogAudit ( "attempt" )
2025-04-10 15:22:03 -04:00
if ! c . App . SessionHasPermissionToAndNotRestrictedAdmin ( * c . AppContext . Session ( ) , model . PermissionManageLicenseInformation ) {
2021-07-12 14:05:36 -04:00
c . SetPermissionError ( model . PermissionManageLicenseInformation )
2019-03-08 13:15:28 -05:00
return
}
2020-06-12 07:43:50 -04:00
if err := c . App . Srv ( ) . RemoveLicense ( ) ; err != nil {
2019-03-08 13:15:28 -05:00
c . Err = err
return
}
2020-03-12 15:50:21 -04:00
auditRec . Success ( )
2019-03-08 13:15:28 -05:00
c . LogAudit ( "success" )
2020-03-12 15:50:21 -04:00
2019-03-08 13:15:28 -05:00
ReturnStatusOK ( w )
}
2020-06-02 15:34:15 -04:00
func requestTrialLicense ( c * Context , w http . ResponseWriter , r * http . Request ) {
2025-07-16 00:47:03 -04:00
auditRec := c . MakeAuditRecord ( model . AuditEventRequestTrialLicense , model . AuditStatusFail )
2020-06-02 15:34:15 -04:00
defer c . LogAuditRec ( auditRec )
c . LogAudit ( "attempt" )
2025-04-10 15:22:03 -04:00
if ! c . App . SessionHasPermissionToAndNotRestrictedAdmin ( * c . AppContext . Session ( ) , model . PermissionManageLicenseInformation ) {
2021-07-12 14:05:36 -04:00
c . SetPermissionError ( model . PermissionManageLicenseInformation )
2020-06-02 15:34:15 -04:00
return
}
2022-10-06 04:04:21 -04:00
if c . App . Srv ( ) . Platform ( ) . LicenseManager ( ) == nil {
2021-07-27 07:07:40 -04:00
c . Err = model . NewAppError ( "requestTrialLicense" , "api.license.upgrade_needed.app_error" , nil , "" , http . StatusForbidden )
return
}
2022-10-06 04:04:21 -04:00
canStartTrialLicense , err := c . App . Srv ( ) . Platform ( ) . LicenseManager ( ) . CanStartTrial ( )
2021-06-17 08:07:34 -04:00
if err != nil {
2024-04-22 06:03:28 -04:00
c . Err = model . NewAppError ( "requestTrialLicense" , "api.license.request-trial.can-start-trial.error" , nil , "" , http . StatusInternalServerError ) . Wrap ( err )
2021-06-17 08:07:34 -04:00
return
}
if ! canStartTrialLicense {
c . Err = model . NewAppError ( "requestTrialLicense" , "api.license.request-trial.can-start-trial.not-allowed" , nil , "" , http . StatusBadRequest )
return
}
2023-03-30 14:57:07 -04:00
var trialRequest * model . TrialLicenseRequest
2020-06-02 15:34:15 -04:00
2022-08-09 07:25:46 -04:00
b , readErr := io . ReadAll ( r . Body )
2020-06-02 15:34:15 -04:00
if readErr != nil {
2020-07-03 06:17:34 -04:00
c . Err = model . NewAppError ( "requestTrialLicense" , "api.license.request-trial.bad-request" , nil , "" , http . StatusBadRequest )
return
}
2024-08-29 09:24:55 -04:00
err = json . Unmarshal ( b , & trialRequest )
if err != nil {
c . Err = model . NewAppError ( "requestTrialLicense" , "api.license.request-trial.bad-request" , nil , "" , http . StatusBadRequest ) . Wrap ( err )
return
}
2020-06-29 11:22:50 -04:00
2023-03-30 14:57:07 -04:00
var appErr * model . AppError
// If any of the newly supported trial request fields are set (ie, not a legacy request), process this as a new trial request (requiring the new fields) otherwise fall back on the old method.
if ! trialRequest . IsLegacy ( ) {
appErr = c . App . Channels ( ) . RequestTrialLicenseWithExtraFields ( c . AppContext . Session ( ) . UserId , trialRequest )
} else {
appErr = c . App . Channels ( ) . RequestTrialLicense ( c . AppContext . Session ( ) . UserId , trialRequest . Users , trialRequest . TermsAccepted , trialRequest . ReceiveEmailsAccepted )
}
if appErr != nil {
c . Err = appErr
2020-06-02 15:34:15 -04:00
return
}
auditRec . Success ( )
c . LogAudit ( "success" )
ReturnStatusOK ( w )
}
2020-12-18 10:40:46 -05:00
2021-06-17 08:07:34 -04:00
func getPrevTrialLicense ( c * Context , w http . ResponseWriter , r * http . Request ) {
2022-10-06 04:04:21 -04:00
if c . App . Srv ( ) . Platform ( ) . LicenseManager ( ) == nil {
2021-07-27 07:07:40 -04:00
c . Err = model . NewAppError ( "getPrevTrialLicense" , "api.license.upgrade_needed.app_error" , nil , "" , http . StatusForbidden )
return
}
2022-10-06 04:04:21 -04:00
license , err := c . App . Srv ( ) . Platform ( ) . LicenseManager ( ) . GetPrevTrial ( )
2021-06-17 08:07:34 -04:00
if err != nil {
http . Error ( w , err . Error ( ) , http . StatusInternalServerError )
return
}
var clientLicense map [ string ] string
2021-07-12 14:05:36 -04:00
if c . App . SessionHasPermissionTo ( * c . AppContext . Session ( ) , model . PermissionReadLicenseInformation ) {
2021-06-17 08:07:34 -04:00
clientLicense = utils . GetClientLicense ( license )
} else {
clientLicense = utils . GetSanitizedClientLicense ( utils . GetClientLicense ( license ) )
}
2024-10-16 03:45:41 -04:00
w . Header ( ) . Set ( "Content-Type" , "application/json" )
if _ , err := w . Write ( [ ] byte ( model . MapToJSON ( clientLicense ) ) ) ; err != nil {
c . Logger . Warn ( "Error while writing response" , mlog . Err ( err ) )
}
2021-06-17 08:07:34 -04:00
}
2025-04-17 16:29:46 -04:00
// getLicenseLoadMetric returns a load metric computed as (mau / licensed) * 1000.
func getLicenseLoadMetric ( c * Context , w http . ResponseWriter , r * http . Request ) {
var loadMetric int
var licenseUsers int
license := c . App . Srv ( ) . License ( )
if license != nil && license . Features != nil {
licenseUsers = * license . Features . Users
}
if licenseUsers > 0 {
monthlyActiveUsers , err := c . App . Srv ( ) . Store ( ) . User ( ) . AnalyticsActiveCount ( app . MonthMilliseconds , model . UserCountOptions { IncludeBotAccounts : false , IncludeDeleted : false } )
if err != nil {
c . Err = model . NewAppError ( "getLicenseLoad" , "api.license.load_metric.app_error" , nil , "" , http . StatusInternalServerError ) . Wrap ( err )
return
}
loadMetric = int ( math . Round ( ( float64 ( monthlyActiveUsers ) / float64 ( licenseUsers ) * float64 ( 1000 ) ) ) )
}
// Create response object
data := map [ string ] int {
"load" : loadMetric ,
}
w . Header ( ) . Set ( "Content-Type" , "application/json" )
if err := json . NewEncoder ( w ) . Encode ( data ) ; err != nil {
c . Logger . Warn ( "Error while writing response" , mlog . Err ( err ) )
}
}