diff options
author | JoramWilander <jwawilander@gmail.com> | 2016-02-04 13:00:03 -0500 |
---|---|---|
committer | JoramWilander <jwawilander@gmail.com> | 2016-02-04 13:35:44 -0500 |
commit | e45282deaa1d78d7ff3a125e9fd11e3fdc120b07 (patch) | |
tree | c116beae6abed1b703c481dfd63df36d689a8af5 | |
parent | 7e8389cd0538fb6aff3931fb23714158d3f24449 (diff) | |
download | chat-e45282deaa1d78d7ff3a125e9fd11e3fdc120b07.tar.gz chat-e45282deaa1d78d7ff3a125e9fd11e3fdc120b07.tar.bz2 chat-e45282deaa1d78d7ff3a125e9fd11e3fdc120b07.zip |
Move license storage to database
-rw-r--r-- | api/license.go | 32 | ||||
-rw-r--r-- | i18n/en.json | 26 | ||||
-rw-r--r-- | i18n/es.json | 6 | ||||
-rw-r--r-- | mattermost.go | 22 | ||||
-rw-r--r-- | model/license.go | 26 | ||||
-rw-r--r-- | model/system.go | 1 | ||||
-rw-r--r-- | store/sql_license_store.go | 83 | ||||
-rw-r--r-- | store/sql_license_store_test.go | 43 | ||||
-rw-r--r-- | store/sql_store.go | 8 | ||||
-rw-r--r-- | store/sql_system_store.go | 26 | ||||
-rw-r--r-- | store/sql_system_store_test.go | 16 | ||||
-rw-r--r-- | store/store.go | 9 | ||||
-rw-r--r-- | utils/license.go | 37 | ||||
-rw-r--r-- | web/react/components/admin_console/admin_sidebar.jsx | 2 |
14 files changed, 284 insertions, 53 deletions
diff --git a/api/license.go b/api/license.go index 4077c0e46..23e7946c8 100644 --- a/api/license.go +++ b/api/license.go @@ -81,9 +81,24 @@ func addLicense(c *Context, w http.ResponseWriter, r *http.Request) { return } - if err := writeFileLocally(data, utils.LicenseLocation()); err != nil { - c.LogAudit("failed - could not save license file") - c.Err = model.NewLocAppError("addLicense", "api.license.add_license.save.app_error", nil, "path="+utils.LicenseLocation()) + record := &model.LicenseRecord{} + record.Id = license.Id + record.Bytes = string(data) + rchan := Srv.Store.License().Save(record) + + sysVar := &model.System{} + sysVar.Name = model.SYSTEM_ACTIVE_LICENSE_ID + sysVar.Value = license.Id + schan := Srv.Store.System().SaveOrUpdate(sysVar) + + if result := <-rchan; result.Err != nil { + c.Err = model.NewLocAppError("addLicense", "api.license.add_license.save.app_error", nil, "err="+result.Err.Error()) + utils.RemoveLicense() + return + } + + if result := <-schan; result.Err != nil { + c.Err = model.NewLocAppError("addLicense", "api.license.add_license.save_active.app_error", nil, "") utils.RemoveLicense() return } @@ -100,9 +115,14 @@ func addLicense(c *Context, w http.ResponseWriter, r *http.Request) { func removeLicense(c *Context, w http.ResponseWriter, r *http.Request) { c.LogAudit("") - if ok := utils.RemoveLicense(); !ok { - c.LogAudit("failed - could not remove license file") - c.Err = model.NewLocAppError("removeLicense", "api.license.remove_license.remove.app_error", nil, "") + utils.RemoveLicense() + + sysVar := &model.System{} + sysVar.Name = model.SYSTEM_ACTIVE_LICENSE_ID + sysVar.Value = "" + + if result := <-Srv.Store.System().Update(sysVar); result.Err != nil { + c.Err = model.NewLocAppError("removeLicense", "api.license.remove_license.update.app_error", nil, "") return } diff --git a/i18n/en.json b/i18n/en.json index 74a873204..ad84ec15c 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -612,6 +612,10 @@ "translation": "License did not save properly." }, { + "id": "api.license.add_license.save_active.app_error", + "translation": "Active license ID did not save properly." + }, + { "id": "api.license.add_license.unique_users.app_error", "translation": "This license only supports {{.Users}} users, when your system has {{.Count}} unique users. Unique users are counted distinctly by email address. You can see total user count under Site Reports -> View Statistics." }, @@ -1732,6 +1736,10 @@ "translation": "Unable to get channels" }, { + "id": "mattermost.load_license.find.warn", + "translation": "Unable to find active license" + }, + { "id": "mattermost.bulletin.subject", "translation": "Mattermost Security Bulletin" }, @@ -2916,6 +2924,18 @@ "translation": "We encountered an error updating the system property" }, { + "id": "store.sql_license.save.app_error", + "translation": "We encountered an error saving the license" + }, + { + "id": "store.sql_license.get.app_error", + "translation": "We encountered an error getting the license" + }, + { + "id": "store.sql_license.get.missing.app_error", + "translation": "A license with that ID was not found" + }, + { "id": "store.sql_team.get.find.app_error", "translation": "We couldn't find the existing team" }, @@ -3204,10 +3224,6 @@ "translation": "No valid enterprise license found" }, { - "id": "utils.license.load_license.open_find.warn", - "translation": "Unable to open/find license file" - }, - { "id": "utils.license.remove_license.unable.error", "translation": "Unable to remove license file, err=%v" }, @@ -3531,4 +3547,4 @@ "id": "web.watcher_fail.error", "translation": "Failed to add directory to watcher %v" } -]
\ No newline at end of file +] diff --git a/i18n/es.json b/i18n/es.json index 2fe02b2b0..946cf424d 100644 --- a/i18n/es.json +++ b/i18n/es.json @@ -3204,10 +3204,6 @@ "translation": "No se encontrĂ³ una licencia enterprise vĂ¡lida" }, { - "id": "utils.license.load_license.open_find.warn", - "translation": "No pudimos encontrar/abrir el achivo de licencia" - }, - { "id": "utils.license.remove_license.unable.error", "translation": "No se pudo remover el archivo de la licencia, err=%v" }, @@ -3531,4 +3527,4 @@ "id": "web.watcher_fail.error", "translation": "Falla al agregar el directorio a ser vigilado %v" } -]
\ No newline at end of file +] diff --git a/mattermost.go b/mattermost.go index 43fa06601..5a18e2f40 100644 --- a/mattermost.go +++ b/mattermost.go @@ -69,7 +69,7 @@ func main() { web.InitWeb() if model.BuildEnterpriseReady == "true" { - utils.LoadLicense() + loadLicense() } if flagRunCmds { @@ -95,6 +95,26 @@ func main() { } } +func loadLicense() { + licenseId := "" + if result := <-api.Srv.Store.System().Get(); result.Err == nil { + props := result.Data.(model.StringMap) + licenseId = props[model.SYSTEM_ACTIVE_LICENSE_ID] + } + + if len(licenseId) != 26 { + l4g.Warn(utils.T("mattermost.load_license.find.warn")) + return + } + + if result := <-api.Srv.Store.License().Get(licenseId); result.Err == nil { + record := result.Data.(*model.LicenseRecord) + utils.LoadLicense([]byte(record.Bytes)) + } else { + l4g.Warn(utils.T("mattermost.load_license.find.warn")) + } +} + func setDiagnosticId() { if result := <-api.Srv.Store.System().Get(); result.Err == nil { props := result.Data.(model.StringMap) diff --git a/model/license.go b/model/license.go index a271b46b7..ea66fef0d 100644 --- a/model/license.go +++ b/model/license.go @@ -8,6 +8,12 @@ import ( "io" ) +type LicenseRecord struct { + Id string `json:"id"` + CreateAt int64 `json:"create_at"` + Bytes string `json:"-"` +} + type License struct { Id string `json:"id"` IssuedAt int64 `json:"issued_at"` @@ -83,3 +89,23 @@ func LicenseFromJson(data io.Reader) *License { return nil } } + +func (lr *LicenseRecord) IsValid() *AppError { + if len(lr.Id) != 26 { + return NewLocAppError("LicenseRecord.IsValid", "model.license_record.is_valid.id.app_error", nil, "") + } + + if lr.CreateAt == 0 { + return NewLocAppError("LicenseRecord.IsValid", "model.license_record.is_valid.create_at.app_error", nil, "") + } + + if len(lr.Bytes) == 0 || len(lr.Bytes) > 10000 { + return NewLocAppError("LicenseRecord.IsValid", "model.license_record.is_valid.create_at.app_error", nil, "") + } + + return nil +} + +func (lr *LicenseRecord) PreSave() { + lr.CreateAt = GetMillis() +} diff --git a/model/system.go b/model/system.go index 70db529d5..b387749f6 100644 --- a/model/system.go +++ b/model/system.go @@ -12,6 +12,7 @@ const ( SYSTEM_DIAGNOSTIC_ID = "DiagnosticId" SYSTEM_RAN_UNIT_TESTS = "RanUnitTests" SYSTEM_LAST_SECURITY_TIME = "LastSecurityTime" + SYSTEM_ACTIVE_LICENSE_ID = "ActiveLicenseId" ) type System struct { diff --git a/store/sql_license_store.go b/store/sql_license_store.go new file mode 100644 index 000000000..f5d67bc5d --- /dev/null +++ b/store/sql_license_store.go @@ -0,0 +1,83 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package store + +import ( + "github.com/mattermost/platform/model" +) + +type SqlLicenseStore struct { + *SqlStore +} + +func NewSqlLicenseStore(sqlStore *SqlStore) LicenseStore { + ls := &SqlLicenseStore{sqlStore} + + for _, db := range sqlStore.GetAllConns() { + table := db.AddTableWithName(model.LicenseRecord{}, "Licenses").SetKeys(false, "Id") + table.ColMap("Id").SetMaxSize(26) + table.ColMap("Bytes").SetMaxSize(10000) + } + + return ls +} + +func (ls SqlLicenseStore) UpgradeSchemaIfNeeded() { +} + +func (ls SqlLicenseStore) CreateIndexesIfNotExists() { +} + +func (ls SqlLicenseStore) Save(license *model.LicenseRecord) StoreChannel { + + storeChannel := make(StoreChannel) + + go func() { + result := StoreResult{} + + license.PreSave() + if result.Err = license.IsValid(); result.Err != nil { + storeChannel <- result + close(storeChannel) + return + } + + // Only insert if not exists + if err := ls.GetReplica().SelectOne(&model.LicenseRecord{}, "SELECT * FROM Licenses WHERE Id = :Id", map[string]interface{}{"Id": license.Id}); err != nil { + if err := ls.GetMaster().Insert(license); err != nil { + result.Err = model.NewLocAppError("SqlLicenseStore.Save", "store.sql_license.save.app_error", nil, "license_id="+license.Id+", "+err.Error()) + } else { + result.Data = license + } + } + + storeChannel <- result + close(storeChannel) + }() + + return storeChannel +} + +func (ls SqlLicenseStore) Get(id string) StoreChannel { + + storeChannel := make(StoreChannel) + + go func() { + result := StoreResult{} + + if obj, err := ls.GetReplica().Get(model.LicenseRecord{}, id); err != nil { + result.Err = model.NewLocAppError("SqlLicenseStore.Get", "store.sql_license.get.app_error", nil, "license_id="+id+", "+err.Error()) + } else if obj == nil { + result.Err = model.NewLocAppError("SqlLicenseStore.Get", "store.sql_license.get.missing.app_error", nil, "license_id="+id) + } else { + result.Data = obj.(*model.LicenseRecord) + } + + storeChannel <- result + close(storeChannel) + + }() + + return storeChannel +} diff --git a/store/sql_license_store_test.go b/store/sql_license_store_test.go new file mode 100644 index 000000000..ad24a6af7 --- /dev/null +++ b/store/sql_license_store_test.go @@ -0,0 +1,43 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package store + +import ( + "github.com/mattermost/platform/model" + "testing" +) + +func TestLicenseStoreSave(t *testing.T) { + Setup() + + l1 := model.LicenseRecord{} + l1.Id = model.NewId() + l1.Bytes = "junk" + + if err := (<-store.License().Save(&l1)).Err; err != nil { + t.Fatal("couldn't save license record", err) + } + + if err := (<-store.License().Save(&l1)).Err; err != nil { + t.Fatal("shouldn't fail on trying to save existing license record", err) + } +} + +func TestLicenseStoreGet(t *testing.T) { + Setup() + + l1 := model.LicenseRecord{} + l1.Id = model.NewId() + l1.Bytes = "junk" + + Must(store.License().Save(&l1)) + + if r := <-store.License().Get(l1.Id); r.Err != nil { + t.Fatal("couldn't get license", r.Err) + } else { + if r.Data.(*model.LicenseRecord).Bytes != l1.Bytes { + t.Fatal("license bytes didn't match") + } + } +} diff --git a/store/sql_store.go b/store/sql_store.go index 8517eb1a2..a994ec57e 100644 --- a/store/sql_store.go +++ b/store/sql_store.go @@ -49,6 +49,7 @@ type SqlStore struct { webhook WebhookStore command CommandStore preference PreferenceStore + license LicenseStore } func NewSqlStore() Store { @@ -103,6 +104,7 @@ func NewSqlStore() Store { sqlStore.webhook = NewSqlWebhookStore(sqlStore) sqlStore.command = NewSqlCommandStore(sqlStore) sqlStore.preference = NewSqlPreferenceStore(sqlStore) + sqlStore.license = NewSqlLicenseStore(sqlStore) err := sqlStore.master.CreateTablesIfNotExists() if err != nil { @@ -120,6 +122,7 @@ func NewSqlStore() Store { sqlStore.webhook.(*SqlWebhookStore).UpgradeSchemaIfNeeded() sqlStore.command.(*SqlCommandStore).UpgradeSchemaIfNeeded() sqlStore.preference.(*SqlPreferenceStore).UpgradeSchemaIfNeeded() + sqlStore.license.(*SqlLicenseStore).UpgradeSchemaIfNeeded() sqlStore.team.(*SqlTeamStore).CreateIndexesIfNotExists() sqlStore.channel.(*SqlChannelStore).CreateIndexesIfNotExists() @@ -132,6 +135,7 @@ func NewSqlStore() Store { sqlStore.webhook.(*SqlWebhookStore).CreateIndexesIfNotExists() sqlStore.command.(*SqlCommandStore).CreateIndexesIfNotExists() sqlStore.preference.(*SqlPreferenceStore).CreateIndexesIfNotExists() + sqlStore.license.(*SqlLicenseStore).CreateIndexesIfNotExists() sqlStore.preference.(*SqlPreferenceStore).DeleteUnusedFeatures() @@ -523,6 +527,10 @@ func (ss SqlStore) Preference() PreferenceStore { return ss.preference } +func (ss SqlStore) License() LicenseStore { + return ss.license +} + type mattermConverter struct{} func (me mattermConverter) ToDb(val interface{}) (interface{}, error) { diff --git a/store/sql_system_store.go b/store/sql_system_store.go index cfd4a670f..f8da06cec 100644 --- a/store/sql_system_store.go +++ b/store/sql_system_store.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package store @@ -47,6 +47,30 @@ func (s SqlSystemStore) Save(system *model.System) StoreChannel { return storeChannel } +func (s SqlSystemStore) SaveOrUpdate(system *model.System) StoreChannel { + + storeChannel := make(StoreChannel) + + go func() { + result := StoreResult{} + + if err := s.GetReplica().SelectOne(&model.System{}, "SELECT * FROM Systems WHERE Name = :Name", map[string]interface{}{"Name": system.Name}); err == nil { + if _, err := s.GetMaster().Update(system); err != nil { + result.Err = model.NewLocAppError("SqlSystemStore.SaveOrUpdate", "store.sql_system.update.app_error", nil, "") + } + } else { + if err := s.GetMaster().Insert(system); err != nil { + result.Err = model.NewLocAppError("SqlSystemStore.SaveOrUpdate", "store.sql_system.save.app_error", nil, "") + } + } + + storeChannel <- result + close(storeChannel) + }() + + return storeChannel +} + func (s SqlSystemStore) Update(system *model.System) StoreChannel { storeChannel := make(StoreChannel) diff --git a/store/sql_system_store_test.go b/store/sql_system_store_test.go index 8ff5445cc..ce149e97a 100644 --- a/store/sql_system_store_test.go +++ b/store/sql_system_store_test.go @@ -31,3 +31,19 @@ func TestSqlSystemStore(t *testing.T) { t.Fatal() } } + +func TestSqlSystemStoreSaveOrUpdate(t *testing.T) { + Setup() + + system := &model.System{Name: model.NewId(), Value: "value"} + + if err := (<-store.System().SaveOrUpdate(system)).Err; err != nil { + t.Fatal(err) + } + + system.Value = "value2" + + if r := <-store.System().SaveOrUpdate(system); r.Err != nil { + t.Fatal(r.Err) + } +} diff --git a/store/store.go b/store/store.go index b6b86e0d9..952b96e87 100644 --- a/store/store.go +++ b/store/store.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package store @@ -39,6 +39,7 @@ type Store interface { Webhook() WebhookStore Command() CommandStore Preference() PreferenceStore + License() LicenseStore MarkSystemRanUnitTests() Close() } @@ -164,6 +165,7 @@ type OAuthStore interface { type SystemStore interface { Save(system *model.System) StoreChannel + SaveOrUpdate(system *model.System) StoreChannel Update(system *model.System) StoreChannel Get() StoreChannel } @@ -203,3 +205,8 @@ type PreferenceStore interface { PermanentDeleteByUser(userId string) StoreChannel IsFeatureEnabled(feature, userId string) StoreChannel } + +type LicenseStore interface { + Save(license *model.LicenseRecord) StoreChannel + Get(id string) StoreChannel +} diff --git a/utils/license.go b/utils/license.go index 0d1cd597c..b773a163e 100644 --- a/utils/license.go +++ b/utils/license.go @@ -1,19 +1,15 @@ -// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package utils import ( - "bytes" "crypto" "crypto/rsa" "crypto/sha512" "crypto/x509" "encoding/base64" "encoding/pem" - "io" - "os" - "path/filepath" "strconv" "strings" @@ -22,10 +18,6 @@ import ( "github.com/mattermost/platform/model" ) -const ( - LICENSE_FILENAME = "active.dat" -) - var IsLicensed bool = false var License *model.License = &model.License{} var ClientLicense map[string]string = make(map[string]string) @@ -41,18 +33,8 @@ NxpC+5KFhU+xSeeklNqwCgnlOyZ7qSTxmdJHb+60SwuYnnGIYzLJhY4LYDr4J+KR 1wIDAQAB -----END PUBLIC KEY-----`) -func LoadLicense() { - file, err := os.Open(LicenseLocation()) - if err != nil { - l4g.Warn(T("utils.license.load_license.open_find.warn")) - return - } - defer file.Close() - - buf := bytes.NewBuffer(nil) - io.Copy(buf, file) - - if success, licenseStr := ValidateLicense(buf.Bytes()); success { +func LoadLicense(licenseBytes []byte) { + if success, licenseStr := ValidateLicense(licenseBytes); success { license := model.LicenseFromJson(strings.NewReader(licenseStr)) SetLicense(license) return @@ -74,21 +56,10 @@ func SetLicense(license *model.License) bool { return false } -func LicenseLocation() string { - return filepath.Dir(CfgFileName) + "/" + LICENSE_FILENAME -} - -func RemoveLicense() bool { +func RemoveLicense() { License = &model.License{} IsLicensed = false ClientLicense = getClientLicense(License) - - if err := os.Remove(LicenseLocation()); err != nil { - l4g.Error(T("utils.license.remove_license.unable.error"), err.Error()) - return false - } - - return true } func ValidateLicense(signed []byte) (bool, string) { diff --git a/web/react/components/admin_console/admin_sidebar.jsx b/web/react/components/admin_console/admin_sidebar.jsx index 7d74013f2..eadd8d412 100644 --- a/web/react/components/admin_console/admin_sidebar.jsx +++ b/web/react/components/admin_console/admin_sidebar.jsx @@ -454,6 +454,7 @@ export default class AdminSidebar extends React.Component { </ul> <ul className='nav nav__sub-menu padded'> {licenseSettings} + {audits} <li> <a href='#' @@ -466,7 +467,6 @@ export default class AdminSidebar extends React.Component { /> </a> </li> - {audits} </ul> </li> </ul> |