diff --git a/app/app_iface.go b/app/app_iface.go
index bae6bc8c88..1740a0a892 100644
--- a/app/app_iface.go
+++ b/app/app_iface.go
@@ -848,6 +848,7 @@ type AppIface interface {
SendNotifications(post *model.Post, team *model.Team, channel *model.Channel, sender *model.User, parentPostList *model.PostList) ([]string, error)
SendPasswordReset(email string, siteURL string) (bool, *model.AppError)
SendPasswordResetEmail(email string, token *model.Token, locale, siteURL string) (bool, *model.AppError)
+ SendRemoveExpiredLicenseEmail(email string, locale, siteURL string, licenseId string) *model.AppError
SendSignInChangeEmail(email, method, locale, siteURL string) *model.AppError
ServeInterPluginRequest(w http.ResponseWriter, r *http.Request, sourcePluginId, destinationPluginId string)
ServePluginRequest(w http.ResponseWriter, r *http.Request)
diff --git a/app/email.go b/app/email.go
index 738621af74..ae00690283 100644
--- a/app/email.go
+++ b/app/email.go
@@ -513,6 +513,24 @@ func (a *App) SendDeactivateAccountEmail(email string, locale, siteURL string) *
return nil
}
+func (a *App) SendRemoveExpiredLicenseEmail(email string, locale, siteURL string, licenseId string) *model.AppError {
+ T := utils.GetUserTranslations(locale)
+ subject := T("api.templates.remove_expired_license.subject",
+ map[string]interface{}{"SiteName": a.ClientConfig()["SiteName"]})
+
+ bodyPage := a.newEmailTemplate("remove_expired_license", locale)
+ bodyPage.Props["SiteURL"] = siteURL
+ bodyPage.Props["Title"] = T("api.templates.remove_expired_license.body.title")
+ bodyPage.Props["Link"] = fmt.Sprintf("%s?id=%s", model.LICENSE_RENEWAL_LINK, licenseId)
+ bodyPage.Props["LinkButton"] = T("api.templates.remove_expired_license.body.renew_button")
+
+ if err := a.sendMail(email, subject, bodyPage.Render()); err != nil {
+ return model.NewAppError("SendRemoveExpiredLicenseEmail", "api.license.remove_expired_license.failed.error", nil, err.Error(), http.StatusInternalServerError)
+ }
+
+ return nil
+}
+
func (a *App) sendNotificationMail(to, subject, htmlBody string) *model.AppError {
if !*a.Config().EmailSettings.SendEmailNotifications {
return nil
diff --git a/app/license.go b/app/license.go
index 1e162e2543..2dc9b40d32 100644
--- a/app/license.go
+++ b/app/license.go
@@ -158,6 +158,8 @@ func (a *App) RemoveLicense() *model.AppError {
return nil
}
+ mlog.Info("Remove license.", mlog.String("id", model.SYSTEM_ACTIVE_LICENSE_ID))
+
sysVar := &model.System{}
sysVar.Name = model.SYSTEM_ACTIVE_LICENSE_ID
sysVar.Value = ""
diff --git a/app/opentracing_layer.go b/app/opentracing_layer.go
index 85be8bf41e..8311eb1554 100644
--- a/app/opentracing_layer.go
+++ b/app/opentracing_layer.go
@@ -12402,6 +12402,28 @@ func (a *OpenTracingAppLayer) SendPasswordResetEmail(email string, token *model.
return resultVar0, resultVar1
}
+func (a *OpenTracingAppLayer) SendRemoveExpiredLicenseEmail(email string, locale string, siteURL string, licenseId string) *model.AppError {
+ origCtx := a.ctx
+ span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.SendRemoveExpiredLicenseEmail")
+
+ 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.SendRemoveExpiredLicenseEmail(email, locale, siteURL, licenseId)
+
+ if resultVar0 != nil {
+ span.LogFields(spanlog.Error(resultVar0))
+ ext.Error.Set(span, true)
+ }
+
+ return resultVar0
+}
+
func (a *OpenTracingAppLayer) SendSignInChangeEmail(email string, method string, locale string, siteURL string) *model.AppError {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.SendSignInChangeEmail")
diff --git a/app/server.go b/app/server.go
index 43e8564763..06d01330ec 100644
--- a/app/server.go
+++ b/app/server.go
@@ -348,6 +348,9 @@ func NewServer(options ...Option) (*Server, error) {
s.Go(func() {
runCommandWebhookCleanupJob(s)
})
+ s.Go(func() {
+ runLicenseExpirationCheckJob(s)
+ })
if complianceI := s.Compliance; complianceI != nil {
complianceI.StartComplianceDailyJob()
@@ -777,6 +780,13 @@ func runSessionCleanupJob(s *Server) {
}, time.Hour*24)
}
+func runLicenseExpirationCheckJob(s *Server) {
+ doLicenseExpirationCheck(s)
+ model.CreateRecurringTask("License Expiration Check", func() {
+ doLicenseExpirationCheck(s)
+ }, time.Hour*24)
+}
+
func doSecurity(s *Server) {
s.DoSecurityUpdateCheck()
}
@@ -804,6 +814,46 @@ func doSessionCleanup(s *Server) {
s.Store.Session().Cleanup(model.GetMillis(), SESSIONS_CLEANUP_BATCH_SIZE)
}
+func doLicenseExpirationCheck(s *Server) {
+ s.FakeApp().LoadLicense()
+ license := s.License()
+
+ if license == nil {
+ mlog.Debug("License cannot be found.")
+ return
+ }
+
+ if !license.IsPastGracePeriod() {
+ mlog.Debug("License is not past the grace period.")
+ return
+ }
+
+ users, err := s.Store.User().GetSystemAdminProfiles()
+ if err != nil {
+ mlog.Error("Failed to get system admins for license expired message from Mattermost.")
+ return
+ }
+
+ //send email to admin(s)
+ for _, user := range users {
+ user := user
+ if user.Email == "" {
+ mlog.Error("Invalid system admin email.", mlog.String("user_email", user.Email))
+ continue
+ }
+
+ mlog.Debug("Sending license expired email.", mlog.String("user_email", user.Email))
+ s.Go(func() {
+ if err := s.FakeApp().SendRemoveExpiredLicenseEmail(user.Email, user.Locale, *s.Config().ServiceSettings.SiteURL, license.Id); err != nil {
+ mlog.Error("Error while sending the license expired email.", mlog.String("user_email", user.Email), mlog.Err(err))
+ }
+ })
+ }
+
+ //remove the license
+ s.FakeApp().RemoveLicense()
+}
+
func (s *Server) StartSearchEngine() (string, string) {
if s.SearchEngine.ElasticsearchEngine != nil && s.SearchEngine.ElasticsearchEngine.IsActive() {
s.Go(func() {
diff --git a/i18n/en.json b/i18n/en.json
index 6b60f6096a..650b9a5676 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -1464,6 +1464,10 @@
"id": "api.license.client.old_format.app_error",
"translation": "New format for the client license is not supported yet. Please specify format=old in the query string."
},
+ {
+ "id": "api.license.remove_expired_license.failed.error",
+ "translation": "Failed to send the disable license email successfully."
+ },
{
"id": "api.marshal_error",
"translation": "marshal error"
@@ -2298,6 +2302,18 @@
"id": "api.templates.post_body.button",
"translation": "Go To Post"
},
+ {
+ "id": "api.templates.remove_expired_license.body.renew_button",
+ "translation": "Renew License"
+ },
+ {
+ "id": "api.templates.remove_expired_license.body.title",
+ "translation": "Enterprise license has expired and some features may be disabled. Please renew."
+ },
+ {
+ "id": "api.templates.remove_expired_license.subject",
+ "translation": "Mattermost Enterprise license has been disabled."
+ },
{
"id": "api.templates.reset_body.button",
"translation": "Reset Password"
diff --git a/model/license.go b/model/license.go
index 16466e5340..6a7c75f611 100644
--- a/model/license.go
+++ b/model/license.go
@@ -12,6 +12,8 @@ import (
const (
EXPIRED_LICENSE_ERROR = "api.license.add_license.expired.app_error"
INVALID_LICENSE_ERROR = "api.license.add_license.invalid.app_error"
+ LICENSE_GRACE_PERIOD = 1000 * 60 * 60 * 24 * 10 //10 days
+ LICENSE_RENEWAL_LINK = "https://licensing.mattermost.com/renew"
)
type LicenseRecord struct {
@@ -194,6 +196,11 @@ func (l *License) IsExpired() bool {
return l.ExpiresAt < GetMillis()
}
+func (l *License) IsPastGracePeriod() bool {
+ timeDiff := GetMillis() - l.ExpiresAt
+ return timeDiff > LICENSE_GRACE_PERIOD
+}
+
func (l *License) IsStarted() bool {
return l.StartsAt < GetMillis()
}
diff --git a/model/license_test.go b/model/license_test.go
index e5a0caaac5..7d1625660b 100644
--- a/model/license_test.go
+++ b/model/license_test.go
@@ -116,6 +116,15 @@ func TestLicenseIsExpired(t *testing.T) {
assert.False(t, l1.IsExpired())
}
+func TestLicenseIsPastGracePeriod(t *testing.T) {
+ l1 := License{}
+ l1.ExpiresAt = GetMillis() - LICENSE_GRACE_PERIOD - 1000
+ assert.True(t, l1.IsPastGracePeriod())
+
+ l1.ExpiresAt = GetMillis() + 1000
+ assert.False(t, l1.IsPastGracePeriod())
+}
+
func TestLicenseIsStarted(t *testing.T) {
l1 := License{}
l1.StartsAt = GetMillis() - 1000
diff --git a/templates/remove_expired_license.html b/templates/remove_expired_license.html
new file mode 100644
index 0000000000..fd8adcdd0b
--- /dev/null
+++ b/templates/remove_expired_license.html
@@ -0,0 +1,50 @@
+{{define "remove_expired_license"}}
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+
+
+
+
+ {{.Props.Title}}
+ {{.Props.Info}}
+
+ {{.Props.LinkButton}}
+
+ |
+
+
+ {{template "email_info" . }}
+
+
+ |
+
+
+ {{template "email_footer" . }}
+
+
+ |
+
+
+ |
+
+
+
+{{end}}
\ No newline at end of file