This commit is contained in:
Pavel Zeman 2026-05-25 10:10:27 +00:00 committed by GitHub
commit 33fc7af27c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 312 additions and 43 deletions

View file

@ -65,8 +65,7 @@ func ensureCloudInterface(c *Context, where string) bool {
return true
}
func getPreviewSubscription(c *Context, w http.ResponseWriter, r *http.Request) {
license := c.App.Channels().License()
func getPreviewSubscription(c *Context, w http.ResponseWriter, r *http.Request, license *model.License) {
subscription := &model.Subscription{
ID: "cloud-preview",
ProductID: license.SkuName,
@ -90,8 +89,9 @@ func getPreviewSubscription(c *Context, w http.ResponseWriter, r *http.Request)
func getSubscription(c *Context, w http.ResponseWriter, r *http.Request) {
// Preview subscription is a special case for cloud preview licenses.
if c.App.Channels().License().IsCloudPreview() {
getPreviewSubscription(c, w, r)
license := c.App.Channels().License()
if license != nil && license.IsCloudPreview() {
getPreviewSubscription(c, w, r, license)
return
}
@ -100,7 +100,7 @@ func getSubscription(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
if !c.App.Channels().License().IsCloud() {
if license == nil || !license.IsCloud() {
c.Err = model.NewAppError("Api4.getSubscription", "api.cloud.license_error", nil, "", http.StatusForbidden)
return
}
@ -199,7 +199,8 @@ func validateWorkspaceBusinessEmail(c *Context, w http.ResponseWriter, r *http.R
return
}
if !c.App.Channels().License().IsCloud() {
license := c.App.Channels().License()
if license == nil || !license.IsCloud() {
c.Err = model.NewAppError("Api4.validateWorkspaceBusinessEmail", "api.cloud.license_error", nil, "", http.StatusForbidden)
return
}
@ -250,7 +251,8 @@ func getCloudProducts(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
if !c.App.Channels().License().IsCloud() {
license := c.App.Channels().License()
if license == nil || !license.IsCloud() {
c.Err = model.NewAppError("Api4.getCloudProducts", "api.cloud.license_error", nil, "", http.StatusForbidden)
return
}
@ -300,7 +302,8 @@ func getCloudLimits(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
if !c.App.Channels().License().IsCloud() {
license := c.App.Channels().License()
if license == nil || !license.IsCloud() {
c.Err = model.NewAppError("Api4.getCloudLimits", "api.cloud.license_error", nil, "", http.StatusForbidden)
return
}
@ -328,7 +331,8 @@ func getCloudCustomer(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
if !c.App.Channels().License().IsCloud() {
license := c.App.Channels().License()
if license == nil || !license.IsCloud() {
c.Err = model.NewAppError("Api4.getCloudCustomer", "api.cloud.license_error", nil, "", http.StatusForbidden)
return
}
@ -384,7 +388,8 @@ func updateCloudCustomer(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
if !c.App.Channels().License().IsCloud() {
license := c.App.Channels().License()
if license == nil || !license.IsCloud() {
c.Err = model.NewAppError("Api4.updateCloudCustomer", "api.cloud.license_error", nil, "", http.StatusForbidden)
return
}
@ -429,7 +434,8 @@ func updateCloudCustomerAddress(c *Context, w http.ResponseWriter, r *http.Reque
return
}
if !c.App.Channels().License().IsCloud() {
license := c.App.Channels().License()
if license == nil || !license.IsCloud() {
c.Err = model.NewAppError("Api4.updateCloudCustomerAddress", "api.cloud.license_error", nil, "", http.StatusForbidden)
return
}
@ -474,7 +480,8 @@ func getInvoicesForSubscription(c *Context, w http.ResponseWriter, r *http.Reque
return
}
if !c.App.Channels().License().IsCloud() {
license := c.App.Channels().License()
if license == nil || !license.IsCloud() {
c.Err = model.NewAppError("Api4.getInvoicesForSubscription", "api.cloud.license_error", nil, "", http.StatusForbidden)
return
}
@ -507,7 +514,8 @@ func getSubscriptionInvoicePDF(c *Context, w http.ResponseWriter, r *http.Reques
return
}
if !c.App.Channels().License().IsCloud() {
license := c.App.Channels().License()
if license == nil || !license.IsCloud() {
c.Err = model.NewAppError("Api4.getSubscriptionInvoicePDF", "api.cloud.license_error", nil, "", http.StatusForbidden)
return
}
@ -547,7 +555,8 @@ func handleCWSWebhook(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
if !c.App.Channels().License().IsCloud() {
license := c.App.Channels().License()
if license == nil || !license.IsCloud() {
c.Err = model.NewAppError("Api4.handleCWSWebhook", "api.cloud.license_error", nil, "", http.StatusForbidden)
return
}

View file

@ -486,3 +486,20 @@ func TestCheckCWSConnection(t *testing.T) {
assert.Equal(t, "unavailable", response["status"])
})
}
func TestGetSubscriptionNilLicense(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t)
t.Run("nil license does not panic", func(t *testing.T) {
th.App.Srv().SetLicense(nil)
// getSubscription calls License().IsCloudPreview() and License().IsCloud()
// — must not panic when license is nil. The exact error depends on the
// test environment (cloud interface may not be available), but the key
// assertion is no panic and no 500.
resp, err := th.SystemAdminClient.DoAPIGet(context.Background(), "/cloud/subscription", "")
require.Error(t, err)
require.NotEqual(t, http.StatusInternalServerError, resp.StatusCode)
})
}

View file

@ -80,7 +80,8 @@ func getConfig(c *Context, w http.ResponseWriter, r *http.Request) {
RemoveMasked: filterMasked,
},
}
if c.App.Channels().License().IsCloud() {
license := c.App.Channels().License()
if license != nil && license.IsCloud() {
filterOpts.TagFilters = append(filterOpts.TagFilters, model.FilterTag{
TagType: model.ConfigAccessTagType,
TagName: model.ConfigAccessTagCloudRestrictable,
@ -168,7 +169,8 @@ func updateConfig(c *Context, w http.ResponseWriter, r *http.Request) {
}
// There are some settings that cannot be changed in a cloud env
if c.App.Channels().License().IsCloud() {
license := c.App.Channels().License()
if license != nil && license.IsCloud() {
// Both of them cannot be nil since cfg.SetDefaults is called earlier for cfg,
// and appCfg is the existing earlier config and if it's nil, server sets a default value.
if *appCfg.ComplianceSettings.Directory != *cfg.ComplianceSettings.Directory {
@ -233,7 +235,8 @@ func updateConfig(c *Context, w http.ResponseWriter, r *http.Request) {
c.LogAudit("updateConfig")
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
if c.App.Channels().License().IsCloud() {
license = c.App.Channels().License()
if license != nil && license.IsCloud() {
js, err := cfg.ToJSONFiltered(model.ConfigAccessTagType, model.ConfigAccessTagCloudRestrictable)
if err != nil {
c.Err = model.NewAppError("updateConfig", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
@ -324,7 +327,8 @@ func patchConfig(c *Context, w http.ResponseWriter, r *http.Request) {
}
// There are some settings that cannot be changed in a cloud env
if c.App.Channels().License().IsCloud() {
license := c.App.Channels().License()
if license != nil && license.IsCloud() {
if cfg.ComplianceSettings.Directory != nil && *appCfg.ComplianceSettings.Directory != *cfg.ComplianceSettings.Directory {
c.Err = model.NewAppError("patchConfig", "api.config.update_config.not_allowed_security.app_error", map[string]any{"Name": "ComplianceSettings.Directory"}, "", http.StatusForbidden)
return
@ -383,7 +387,8 @@ func patchConfig(c *Context, w http.ResponseWriter, r *http.Request) {
}
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
if c.App.Channels().License().IsCloud() {
license = c.App.Channels().License()
if license != nil && license.IsCloud() {
js, err := cfg.ToJSONFiltered(model.ConfigAccessTagType, model.ConfigAccessTagCloudRestrictable)
if err != nil {
c.Err = model.NewAppError("patchConfig", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)

View file

@ -66,6 +66,14 @@ func TestGetConfig(t *testing.T) {
require.NotEqual(t, model.FakeSetting, *cfg.SqlSettings.DataSource)
require.NotEqual(t, model.FakeSetting, *cfg.FileSettings.PublicLinkSalt)
})
t.Run("nil license does not panic", func(t *testing.T) {
th.App.Srv().SetLicense(nil)
// GetConfig calls License().IsCloud() — must not panic when license is nil
_, _, err := th.SystemAdminClient.GetConfig(context.Background())
require.NoError(t, err)
})
}
func TestGetConfigWithAccessTag(t *testing.T) {

View file

@ -124,7 +124,7 @@ func addLicense(c *Context, w http.ResponseWriter, r *http.Request) {
}
}
license, appErr = c.App.Srv().SaveLicense(licenseBytes)
_, appErr = c.App.Srv().SaveLicense(licenseBytes)
if appErr != nil {
if appErr.Id == model.ExpiredLicenseError {
c.LogAudit("failed - expired or non-started license")
@ -137,7 +137,8 @@ func addLicense(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
if c.App.Channels().License().IsCloud() {
license = c.App.Channels().License()
if license != nil && license.IsCloud() {
// If cloud, invalidate the caches when a new license is loaded
defer func() {
if err := c.App.Srv().Cloud.HandleLicenseChange(); err != nil {

View file

@ -618,3 +618,18 @@ func TestGetLicenseLoadMetric(t *testing.T) {
require.Equal(t, 1500, loadValue)
})
}
func TestAddLicenseNilLicense(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
t.Run("nil license does not panic", func(t *testing.T) {
th.App.Srv().SetLicense(nil)
// addLicense checks License().IsCloud() after saving — but the upload
// itself will fail validation. Key assertion: no panic, no 500.
resp, err := th.SystemAdminClient.DoAPIPost(context.Background(), "/license", "not-a-real-license")
require.Error(t, err)
require.NotEqual(t, http.StatusInternalServerError, resp.StatusCode)
})
}

View file

@ -99,7 +99,8 @@ func createTeam(c *Context, w http.ResponseWriter, r *http.Request) {
}
// On a cloud license, we must check limits before allowing to create
if c.App.Channels().License().IsCloud() {
license = c.App.Channels().License()
if license != nil && license.IsCloud() {
limits, err := c.App.Cloud().GetCloudLimits(c.AppContext.Session().UserId)
if err != nil {
c.Err = model.NewAppError("Api4.createTeam", "api.cloud.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
@ -405,7 +406,8 @@ func restoreTeam(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
// On a cloud license, we must check limits before allowing to restore
if c.App.Channels().License().IsCloud() {
license := c.App.Channels().License()
if license != nil && license.IsCloud() {
limits, err := c.App.Cloud().GetCloudLimits(c.AppContext.Session().UserId)
if err != nil {
c.Err = model.NewAppError("Api4.restoreTeam", "api.cloud.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
@ -1462,7 +1464,8 @@ func teamExists(c *Context, w http.ResponseWriter, r *http.Request) {
}
func importTeam(c *Context, w http.ResponseWriter, r *http.Request) {
if c.App.Channels().License().IsCloud() {
license := c.App.Channels().License()
if license != nil && license.IsCloud() {
c.Err = model.NewAppError("importTeam", "api.restricted_system_admin", nil, "", http.StatusForbidden)
return
}
@ -1701,7 +1704,8 @@ func inviteGuestsToChannels(c *Context, w http.ResponseWriter, r *http.Request)
return
}
guestEnabled := c.App.Channels().License() != nil && *c.App.Channels().License().Features.GuestAccounts
license := c.App.Channels().License()
guestEnabled := license != nil && license.Features != nil && license.Features.GuestAccounts != nil && *license.Features.GuestAccounts
if !guestEnabled {
c.Err = model.NewAppError("Api4.InviteGuestsToChannels", "api.team.invite_guests_to_channels.disabled.error", nil, "", http.StatusForbidden)

View file

@ -4,11 +4,13 @@
package api4
import (
"bytes"
"context"
"encoding/base64"
"encoding/binary"
"encoding/json"
"fmt"
"mime/multipart"
"net/http"
"strconv"
"strings"
@ -235,6 +237,17 @@ func TestCreateTeam(t *testing.T) {
require.NoError(t, err)
CheckCreatedStatus(t, resp)
})
t.Run("nil license does not panic", func(t *testing.T) {
th.App.Srv().SetLicense(nil)
team := &model.Team{Name: GenerateTestUsername(), DisplayName: "No License Team", Type: model.TeamOpen}
_, resp, err := th.Client.CreateTeam(context.Background(), team)
// The request may succeed or fail depending on permissions,
// but it must NOT panic due to nil License().IsCloud()
require.NoError(t, err)
require.NotEqual(t, http.StatusInternalServerError, resp.StatusCode)
})
}
func TestCreateTeamSanitization(t *testing.T) {
@ -5062,3 +5075,49 @@ func TestGetTeamMembersForUserRoleDataSanitization(t *testing.T) {
require.Fail(t, "basic team membership not found")
})
}
func TestRestoreTeamNilLicense(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
t.Run("nil license does not panic", func(t *testing.T) {
th.App.Srv().SetLicense(nil)
// restoreTeam checks License().IsCloud() — must not panic when nil.
resp, err := th.SystemAdminClient.DoAPIPost(context.Background(), "/teams/"+th.BasicTeam.Id+"/restore", "")
// May return 403/404 depending on team state, but must not return 500.
if err != nil {
require.NotEqual(t, http.StatusInternalServerError, resp.StatusCode)
}
})
}
func TestImportTeamNilLicense(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
t.Run("nil license does not panic", func(t *testing.T) {
th.App.Srv().SetLicense(nil)
// importTeam checks License().IsCloud() — must not panic when nil.
// We send a minimal multipart request that will fail validation but should not panic.
var b bytes.Buffer
w := multipart.NewWriter(&b)
require.NoError(t, w.WriteField("importFrom", "slack"))
require.NoError(t, w.WriteField("filesize", "100"))
part, err := w.CreateFormFile("file", "import.zip")
require.NoError(t, err)
_, err = part.Write([]byte("fake"))
require.NoError(t, err)
w.Close()
req, _ := http.NewRequest("POST", th.SystemAdminClient.APIURL+"/teams/"+th.BasicTeam.Id+"/import", &b)
req.Header.Set("Content-Type", w.FormDataContentType())
req.Header.Set(model.HeaderAuth, model.HeaderBearer+" "+th.SystemAdminClient.AuthToken)
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
resp.Body.Close()
require.NotEqual(t, http.StatusInternalServerError, resp.StatusCode,
"nil license must not cause a 500 panic in importTeam")
})
}

View file

@ -52,7 +52,8 @@ func createUpload(c *Context, w http.ResponseWriter, r *http.Request) {
c.SetPermissionError(model.PermissionManageSystem)
return
}
if c.App.Srv().License().IsCloud() {
license := c.App.Srv().License()
if license != nil && license.IsCloud() {
c.Err = model.NewAppError("createUpload", "api.file.cloud_upload.app_error", nil, "", http.StatusBadRequest)
return
}
@ -147,7 +148,8 @@ func uploadData(c *Context, w http.ResponseWriter, r *http.Request) {
c.SetPermissionError(model.PermissionManageSystem)
return
}
if c.App.Srv().License().IsCloud() {
license := c.App.Srv().License()
if license != nil && license.IsCloud() {
c.Err = model.NewAppError("UploadData", "api.file.cloud_upload.app_error", nil, "", http.StatusBadRequest)
return
}

View file

@ -551,3 +551,22 @@ func TestUploadDataMultipart(t *testing.T) {
require.Equal(t, file, data)
})
}
func TestCreateUploadNilLicense(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
t.Run("nil license does not panic on import upload", func(t *testing.T) {
th.App.Srv().SetLicense(nil)
us := &model.UploadSession{
Type: model.UploadTypeImport,
Filename: "import.zip",
FileSize: 1024,
}
_, resp, err := th.SystemAdminClient.CreateUpload(context.Background(), us)
require.Error(t, err)
require.NotEqual(t, http.StatusInternalServerError, resp.StatusCode,
"nil license must not cause a 500 panic in createUpload")
})
}

View file

@ -2276,7 +2276,8 @@ func loginCWS(c *Context, w http.ResponseWriter, r *http.Request) {
"cyber-defense": "/cyber-defense-hq",
}
if !c.App.Channels().License().IsCloud() {
license := c.App.Channels().License()
if license == nil || !license.IsCloud() {
c.Err = model.NewAppError("loginCWS", "api.user.login_cws.license.error", nil, "", http.StatusUnauthorized)
return
}
@ -2335,7 +2336,8 @@ func loginCWS(c *Context, w http.ResponseWriter, r *http.Request) {
}
// If a cloud preview, redirect to the correct use case URL
if c.App.License().IsCloudPreview() && useCase != "" {
license = c.App.License()
if license != nil && license.IsCloudPreview() && useCase != "" {
if url, ok := useCaseToURL[useCase]; ok {
redirectURL += url
}
@ -3282,7 +3284,8 @@ func demoteUserToGuest(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
guestEnabled := c.App.Channels().License() != nil && *c.App.Channels().License().Features.GuestAccounts
license := c.App.Channels().License()
guestEnabled := license != nil && license.Features != nil && license.Features.GuestAccounts != nil && *license.Features.GuestAccounts
if !guestEnabled {
c.Err = model.NewAppError("Api4.demoteUserToGuest", "api.team.invite_guests_to_channels.disabled.error", nil, "", http.StatusForbidden)
@ -3577,7 +3580,8 @@ func migrateAuthToLDAP(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
if c.App.Channels().License() == nil || !*c.App.Channels().License().Features.LDAP {
license := c.App.Channels().License()
if license == nil || license.Features == nil || license.Features.LDAP == nil || !*license.Features.LDAP {
c.Err = model.NewAppError("api.migrateAuthToLDAP", "api.admin.ldap.not_available.app_error", nil, "", http.StatusNotImplemented)
return
}
@ -3636,7 +3640,8 @@ func migrateAuthToSaml(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
if c.App.Channels().License() == nil || !*c.App.Channels().License().Features.SAML {
license := c.App.Channels().License()
if license == nil || license.Features == nil || license.Features.SAML == nil || !*license.Features.SAML {
c.Err = model.NewAppError("api.migrateAuthToSaml", "api.admin.saml.not_available.app_error", nil, "", http.StatusNotImplemented)
return
}

View file

@ -10268,3 +10268,61 @@ func TestSearchUsersWithMfaEnforced(t *testing.T) {
CheckForbiddenStatus(t, resp)
})
}
func TestLoginNilLicense(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
t.Run("nil license does not panic on login", func(t *testing.T) {
th.App.Srv().SetLicense(nil)
_, err := th.Client.Logout(context.Background())
require.NoError(t, err)
// Login calls isCWSLogin and AttachSessionCookies, both of which
// reference License().IsCloud() — must not panic when license is nil.
_, _, err = th.Client.Login(context.Background(), th.BasicUser.Email, th.BasicUser.Password)
require.NoError(t, err)
})
}
func TestDemoteUserToGuestNilLicense(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
t.Run("nil license does not panic", func(t *testing.T) {
th.App.Srv().SetLicense(nil)
// demoteUserToGuest checks License() == nil — should return 501, not panic.
resp, err := th.SystemAdminClient.DoAPIPost(context.Background(), "/users/"+th.BasicUser.Id+"/demote", "")
require.Error(t, err)
require.NotEqual(t, http.StatusInternalServerError, resp.StatusCode)
})
}
func TestMigrateAuthToLDAPNilLicense(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
t.Run("nil license does not panic", func(t *testing.T) {
th.App.Srv().SetLicense(nil)
jsonBody := `{"from":"email","force":false,"match_field":"email"}`
resp, err := th.SystemAdminClient.DoAPIPost(context.Background(), "/users/migrate_auth/ldap", jsonBody)
require.Error(t, err)
require.NotEqual(t, http.StatusInternalServerError, resp.StatusCode)
})
}
func TestMigrateAuthToSamlNilLicense(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
t.Run("nil license does not panic", func(t *testing.T) {
th.App.Srv().SetLicense(nil)
jsonBody := `{"from":"email","auto":false,"matches":{}}`
resp, err := th.SystemAdminClient.DoAPIPost(context.Background(), "/users/migrate_auth/saml", jsonBody)
require.Error(t, err)
require.NotEqual(t, http.StatusInternalServerError, resp.StatusCode)
})
}

View file

@ -198,7 +198,8 @@ func (a *App) AddAuditLogCertificate(rctx request.CTX, fileData *multipart.FileH
a.UpdateConfig(func(dest *model.Config) { *dest = *cfg })
if a.License().IsCloud() {
license := a.License()
if license != nil && license.IsCloud() {
err = a.Cloud().CreateAuditLoggingCert(rctx.Session().UserId, fileData)
if err != nil {
return model.NewAppError("AddAuditLogCertificate", "api.admin.add_certificate.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
@ -224,7 +225,8 @@ func (a *App) RemoveAuditLogCertificate(rctx request.CTX) *model.AppError {
a.UpdateConfig(func(dest *model.Config) { *dest = *cfg })
if a.License().IsCloud() {
license := a.License()
if license != nil && license.IsCloud() {
err = a.Cloud().RemoveAuditLoggingCert(rctx.Session().UserId)
if err != nil {
return model.NewAppError("RemoveAuditLogCertificate", "api.admin.remove_certificate.app_error", nil, "", http.StatusInternalServerError).Wrap(err)

View file

@ -61,7 +61,8 @@ func (a *App) ExportFileBackend() filestore.FileBackend {
func (a *App) CheckMandatoryS3Fields(settings *model.FileSettings) *model.AppError {
var fileBackendSettings filestore.FileBackendSettings
if a.License().IsCloud() && a.Config().FeatureFlags.CloudDedicatedExportUI && a.Config().FileSettings.DedicatedExportStore != nil && *a.Config().FileSettings.DedicatedExportStore {
license := a.License()
if license != nil && license.IsCloud() && a.Config().FeatureFlags.CloudDedicatedExportUI && a.Config().FileSettings.DedicatedExportStore != nil && *a.Config().FileSettings.DedicatedExportStore {
fileBackendSettings = filestore.NewExportFileBackendSettingsFromConfig(settings, false, false)
} else {
fileBackendSettings = filestore.NewFileBackendSettingsFromConfig(settings, false, false)

View file

@ -1207,3 +1207,18 @@ func TestFilterFilesByChannelPermissions_ABAC(t *testing.T) {
mockACS.AssertNotCalled(t, "AccessEvaluation")
})
}
func TestCheckMandatoryS3FieldsNilLicense(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t)
t.Run("nil license does not panic", func(t *testing.T) {
th.App.Srv().SetLicense(nil)
settings := &model.FileSettings{}
settings.SetDefaults(false)
// CheckMandatoryS3Fields calls License().IsCloud() — must not panic when nil.
_ = th.App.CheckMandatoryS3Fields(settings)
// If we get here without a panic, the test passes.
})
}

View file

@ -10,7 +10,8 @@ import (
func (a *App) SendIPFiltersChangedEmail(rctx request.CTX, userID string) error {
cloudWorkspaceOwnerEmailAddress := ""
if a.License().IsCloud() {
license := a.License()
if license != nil && license.IsCloud() {
portalUserCustomer, cErr := a.Cloud().GetCloudCustomer(userID)
if cErr != nil {
rctx.Logger().Error("Failed to get portal user customer", mlog.Err(cErr))

View file

@ -0,0 +1,25 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package app
import (
"testing"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/shared/request"
)
func TestSendIPFiltersChangedEmailNilLicense(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t)
t.Run("nil license does not panic", func(t *testing.T) {
th.App.Srv().SetLicense(nil)
rctx := request.TestContext(t)
// SendIPFiltersChangedEmail checks License().IsCloud() — must not panic when nil.
// It may return an error (e.g., no SMTP configured), but must not panic.
_ = th.App.SendIPFiltersChangedEmail(rctx, model.NewId())
})
}

View file

@ -197,7 +197,8 @@ func (a *App) DoLogin(rctx request.CTX, w http.ResponseWriter, r *http.Request,
rctx = rctx.WithSession(session)
if a.Srv().License() != nil && *a.Srv().License().Features.LDAP && a.Ldap() != nil {
license := a.Srv().License()
if license != nil && *license.Features.LDAP && a.Ldap() != nil {
userVal := *user
sessionVal := *session
a.Srv().Go(func() {
@ -313,7 +314,8 @@ func (a *App) AttachSessionCookies(rctx request.CTX, w http.ResponseWriter, r *h
http.SetCookie(w, csrfCookie)
// For context see: https://mattermost.atlassian.net/browse/MM-39583
if a.License().IsCloud() {
license := a.License()
if license != nil && license.IsCloud() {
a.AttachCloudSessionCookie(rctx, w, r)
}
}
@ -326,5 +328,6 @@ func GetProtocol(r *http.Request) string {
}
func isCWSLogin(a *App, token string) bool {
return a.License().IsCloud() && token != ""
license := a.License()
return license != nil && license.IsCloud() && token != ""
}

View file

@ -1445,7 +1445,8 @@ func (s *Server) sendLicenseUpForRenewalEmail(users map[string]*model.User, lice
func (s *Server) doReportUserCountForCloudSubscriptionJob() {
s.LoadLicense()
if !s.License().IsCloud() {
license := s.License()
if license == nil || !license.IsCloud() {
return
}

View file

@ -217,7 +217,8 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
subpath, _ := utils.GetSubpathFromConfig(c.App.Config())
siteURLHeader := app.GetProtocol(r) + "://" + r.Host + subpath
if c.App.Channels().License().IsCloud() {
license := c.App.Channels().License()
if license != nil && license.IsCloud() {
siteURLHeader = *c.App.Config().ServiceSettings.SiteURL + subpath
}
c.SetSiteURLHeader(siteURLHeader)
@ -289,7 +290,7 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
c.RemoveSessionCookie(w, r)
c.Err = model.NewAppError("ServeHTTP", "api.context.session_expired.app_error", nil, "token="+token+" Appears to be a CSRF attempt", http.StatusUnauthorized)
}
} else if token != "" && c.App.Channels().License().IsCloud() && tokenLocation == app.TokenLocationCloudHeader {
} else if token != "" && license != nil && license.IsCloud() && tokenLocation == app.TokenLocationCloudHeader {
// Check to see if this provided token matches our CWS Token
session, err := c.App.GetCloudSession(token)
if err != nil {
@ -298,7 +299,7 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
} else {
c.AppContext = c.AppContext.WithSession(session)
}
} else if token != "" && c.App.Channels().License() != nil && c.App.Channels().License().HasRemoteClusterService() && tokenLocation == app.TokenLocationRemoteClusterHeader {
} else if token != "" && license != nil && license.HasRemoteClusterService() && tokenLocation == app.TokenLocationRemoteClusterHeader {
// Get the remote cluster
if remoteId := c.GetRemoteID(r); remoteId == "" {
c.Logger.Warn("Missing remote cluster id") //

View file

@ -1223,3 +1223,21 @@ func TestHandleContextErrorZeroStatusCode(t *testing.T) {
assert.Equal(t, http.StatusBadRequest, response.Code)
})
}
func TestHandlerServeHTTPNilLicense(t *testing.T) {
th := Setup(t)
th.App.Srv().SetLicense(nil)
web := New(th.Server)
handler := web.NewHandler(handlerForServeDefaultSecurityHeaders)
// ServeHTTP checks License().IsCloud() for siteURL and CWS token — must not panic when nil.
request := httptest.NewRequest("GET", "/api/v4/test", nil)
response := httptest.NewRecorder()
handler.ServeHTTP(response, request)
// Should complete without panic. Any non-500 status is acceptable.
require.NotEqual(t, http.StatusInternalServerError, response.Code,
"nil license must not cause a 500 panic in ServeHTTP")
}