Merge pull request #2073 from mattermost/ee-updates
Some general updates to EE
Этот коммит содержится в:
@@ -81,9 +81,24 @@ func addLicense(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := writeFileLocally(data, utils.LicenseLocation()); err != nil {
|
record := &model.LicenseRecord{}
|
||||||
c.LogAudit("failed - could not save license file")
|
record.Id = license.Id
|
||||||
c.Err = model.NewLocAppError("addLicense", "api.license.add_license.save.app_error", nil, "path="+utils.LicenseLocation())
|
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()
|
utils.RemoveLicense()
|
||||||
return
|
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) {
|
func removeLicense(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
c.LogAudit("")
|
c.LogAudit("")
|
||||||
|
|
||||||
if ok := utils.RemoveLicense(); !ok {
|
utils.RemoveLicense()
|
||||||
c.LogAudit("failed - could not remove license file")
|
|
||||||
c.Err = model.NewLocAppError("removeLicense", "api.license.remove_license.remove.app_error", nil, "")
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
24
i18n/en.json
24
i18n/en.json
@@ -611,6 +611,10 @@
|
|||||||
"id": "api.license.add_license.save.app_error",
|
"id": "api.license.add_license.save.app_error",
|
||||||
"translation": "License did not save properly."
|
"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",
|
"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."
|
"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."
|
||||||
@@ -1743,6 +1747,10 @@
|
|||||||
"id": "manaultesting.test_autolink.unable.app_error",
|
"id": "manaultesting.test_autolink.unable.app_error",
|
||||||
"translation": "Unable to get channels"
|
"translation": "Unable to get channels"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "mattermost.load_license.find.warn",
|
||||||
|
"translation": "Unable to find active license"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "mattermost.bulletin.subject",
|
"id": "mattermost.bulletin.subject",
|
||||||
"translation": "Mattermost Security Bulletin"
|
"translation": "Mattermost Security Bulletin"
|
||||||
@@ -2927,6 +2935,18 @@
|
|||||||
"id": "store.sql_system.update.app_error",
|
"id": "store.sql_system.update.app_error",
|
||||||
"translation": "We encountered an error updating the system property"
|
"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",
|
"id": "store.sql_team.get.find.app_error",
|
||||||
"translation": "We couldn't find the existing team"
|
"translation": "We couldn't find the existing team"
|
||||||
@@ -3215,10 +3235,6 @@
|
|||||||
"id": "utils.license.load_license.invalid.warn",
|
"id": "utils.license.load_license.invalid.warn",
|
||||||
"translation": "No valid enterprise license found"
|
"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",
|
"id": "utils.license.remove_license.unable.error",
|
||||||
"translation": "Unable to remove license file, err=%v"
|
"translation": "Unable to remove license file, err=%v"
|
||||||
|
|||||||
@@ -3203,10 +3203,6 @@
|
|||||||
"id": "utils.license.load_license.invalid.warn",
|
"id": "utils.license.load_license.invalid.warn",
|
||||||
"translation": "No se encontró una licencia enterprise válida"
|
"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",
|
"id": "utils.license.remove_license.unable.error",
|
||||||
"translation": "No se pudo remover el archivo de la licencia, err=%v"
|
"translation": "No se pudo remover el archivo de la licencia, err=%v"
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ func main() {
|
|||||||
web.InitWeb()
|
web.InitWeb()
|
||||||
|
|
||||||
if model.BuildEnterpriseReady == "true" {
|
if model.BuildEnterpriseReady == "true" {
|
||||||
utils.LoadLicense()
|
loadLicense()
|
||||||
}
|
}
|
||||||
|
|
||||||
if flagRunCmds {
|
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() {
|
func setDiagnosticId() {
|
||||||
if result := <-api.Srv.Store.System().Get(); result.Err == nil {
|
if result := <-api.Srv.Store.System().Get(); result.Err == nil {
|
||||||
props := result.Data.(model.StringMap)
|
props := result.Data.(model.StringMap)
|
||||||
|
|||||||
@@ -8,6 +8,12 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type LicenseRecord struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
CreateAt int64 `json:"create_at"`
|
||||||
|
Bytes string `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
type License struct {
|
type License struct {
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
IssuedAt int64 `json:"issued_at"`
|
IssuedAt int64 `json:"issued_at"`
|
||||||
@@ -83,3 +89,23 @@ func LicenseFromJson(data io.Reader) *License {
|
|||||||
return nil
|
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()
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ const (
|
|||||||
SYSTEM_DIAGNOSTIC_ID = "DiagnosticId"
|
SYSTEM_DIAGNOSTIC_ID = "DiagnosticId"
|
||||||
SYSTEM_RAN_UNIT_TESTS = "RanUnitTests"
|
SYSTEM_RAN_UNIT_TESTS = "RanUnitTests"
|
||||||
SYSTEM_LAST_SECURITY_TIME = "LastSecurityTime"
|
SYSTEM_LAST_SECURITY_TIME = "LastSecurityTime"
|
||||||
|
SYSTEM_ACTIVE_LICENSE_ID = "ActiveLicenseId"
|
||||||
)
|
)
|
||||||
|
|
||||||
type System struct {
|
type System struct {
|
||||||
|
|||||||
83
store/sql_license_store.go
Обычный файл
83
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
|
||||||
|
}
|
||||||
43
store/sql_license_store_test.go
Обычный файл
43
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -49,6 +49,7 @@ type SqlStore struct {
|
|||||||
webhook WebhookStore
|
webhook WebhookStore
|
||||||
command CommandStore
|
command CommandStore
|
||||||
preference PreferenceStore
|
preference PreferenceStore
|
||||||
|
license LicenseStore
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSqlStore() Store {
|
func NewSqlStore() Store {
|
||||||
@@ -103,6 +104,7 @@ func NewSqlStore() Store {
|
|||||||
sqlStore.webhook = NewSqlWebhookStore(sqlStore)
|
sqlStore.webhook = NewSqlWebhookStore(sqlStore)
|
||||||
sqlStore.command = NewSqlCommandStore(sqlStore)
|
sqlStore.command = NewSqlCommandStore(sqlStore)
|
||||||
sqlStore.preference = NewSqlPreferenceStore(sqlStore)
|
sqlStore.preference = NewSqlPreferenceStore(sqlStore)
|
||||||
|
sqlStore.license = NewSqlLicenseStore(sqlStore)
|
||||||
|
|
||||||
err := sqlStore.master.CreateTablesIfNotExists()
|
err := sqlStore.master.CreateTablesIfNotExists()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -120,6 +122,7 @@ func NewSqlStore() Store {
|
|||||||
sqlStore.webhook.(*SqlWebhookStore).UpgradeSchemaIfNeeded()
|
sqlStore.webhook.(*SqlWebhookStore).UpgradeSchemaIfNeeded()
|
||||||
sqlStore.command.(*SqlCommandStore).UpgradeSchemaIfNeeded()
|
sqlStore.command.(*SqlCommandStore).UpgradeSchemaIfNeeded()
|
||||||
sqlStore.preference.(*SqlPreferenceStore).UpgradeSchemaIfNeeded()
|
sqlStore.preference.(*SqlPreferenceStore).UpgradeSchemaIfNeeded()
|
||||||
|
sqlStore.license.(*SqlLicenseStore).UpgradeSchemaIfNeeded()
|
||||||
|
|
||||||
sqlStore.team.(*SqlTeamStore).CreateIndexesIfNotExists()
|
sqlStore.team.(*SqlTeamStore).CreateIndexesIfNotExists()
|
||||||
sqlStore.channel.(*SqlChannelStore).CreateIndexesIfNotExists()
|
sqlStore.channel.(*SqlChannelStore).CreateIndexesIfNotExists()
|
||||||
@@ -132,6 +135,7 @@ func NewSqlStore() Store {
|
|||||||
sqlStore.webhook.(*SqlWebhookStore).CreateIndexesIfNotExists()
|
sqlStore.webhook.(*SqlWebhookStore).CreateIndexesIfNotExists()
|
||||||
sqlStore.command.(*SqlCommandStore).CreateIndexesIfNotExists()
|
sqlStore.command.(*SqlCommandStore).CreateIndexesIfNotExists()
|
||||||
sqlStore.preference.(*SqlPreferenceStore).CreateIndexesIfNotExists()
|
sqlStore.preference.(*SqlPreferenceStore).CreateIndexesIfNotExists()
|
||||||
|
sqlStore.license.(*SqlLicenseStore).CreateIndexesIfNotExists()
|
||||||
|
|
||||||
sqlStore.preference.(*SqlPreferenceStore).DeleteUnusedFeatures()
|
sqlStore.preference.(*SqlPreferenceStore).DeleteUnusedFeatures()
|
||||||
|
|
||||||
@@ -523,6 +527,10 @@ func (ss SqlStore) Preference() PreferenceStore {
|
|||||||
return ss.preference
|
return ss.preference
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ss SqlStore) License() LicenseStore {
|
||||||
|
return ss.license
|
||||||
|
}
|
||||||
|
|
||||||
type mattermConverter struct{}
|
type mattermConverter struct{}
|
||||||
|
|
||||||
func (me mattermConverter) ToDb(val interface{}) (interface{}, error) {
|
func (me mattermConverter) ToDb(val interface{}) (interface{}, error) {
|
||||||
|
|||||||
@@ -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.
|
// See License.txt for license information.
|
||||||
|
|
||||||
package store
|
package store
|
||||||
@@ -47,6 +47,30 @@ func (s SqlSystemStore) Save(system *model.System) StoreChannel {
|
|||||||
return 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 {
|
func (s SqlSystemStore) Update(system *model.System) StoreChannel {
|
||||||
|
|
||||||
storeChannel := make(StoreChannel)
|
storeChannel := make(StoreChannel)
|
||||||
|
|||||||
@@ -31,3 +31,19 @@ func TestSqlSystemStore(t *testing.T) {
|
|||||||
t.Fatal()
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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.
|
// See License.txt for license information.
|
||||||
|
|
||||||
package store
|
package store
|
||||||
@@ -39,6 +39,7 @@ type Store interface {
|
|||||||
Webhook() WebhookStore
|
Webhook() WebhookStore
|
||||||
Command() CommandStore
|
Command() CommandStore
|
||||||
Preference() PreferenceStore
|
Preference() PreferenceStore
|
||||||
|
License() LicenseStore
|
||||||
MarkSystemRanUnitTests()
|
MarkSystemRanUnitTests()
|
||||||
Close()
|
Close()
|
||||||
}
|
}
|
||||||
@@ -164,6 +165,7 @@ type OAuthStore interface {
|
|||||||
|
|
||||||
type SystemStore interface {
|
type SystemStore interface {
|
||||||
Save(system *model.System) StoreChannel
|
Save(system *model.System) StoreChannel
|
||||||
|
SaveOrUpdate(system *model.System) StoreChannel
|
||||||
Update(system *model.System) StoreChannel
|
Update(system *model.System) StoreChannel
|
||||||
Get() StoreChannel
|
Get() StoreChannel
|
||||||
}
|
}
|
||||||
@@ -203,3 +205,8 @@ type PreferenceStore interface {
|
|||||||
PermanentDeleteByUser(userId string) StoreChannel
|
PermanentDeleteByUser(userId string) StoreChannel
|
||||||
IsFeatureEnabled(feature, userId string) StoreChannel
|
IsFeatureEnabled(feature, userId string) StoreChannel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LicenseStore interface {
|
||||||
|
Save(license *model.LicenseRecord) StoreChannel
|
||||||
|
Get(id string) StoreChannel
|
||||||
|
}
|
||||||
|
|||||||
@@ -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.
|
// See License.txt for license information.
|
||||||
|
|
||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"crypto"
|
"crypto"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"crypto/sha512"
|
"crypto/sha512"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -22,10 +18,6 @@ import (
|
|||||||
"github.com/mattermost/platform/model"
|
"github.com/mattermost/platform/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
LICENSE_FILENAME = "active.dat"
|
|
||||||
)
|
|
||||||
|
|
||||||
var IsLicensed bool = false
|
var IsLicensed bool = false
|
||||||
var License *model.License = &model.License{}
|
var License *model.License = &model.License{}
|
||||||
var ClientLicense map[string]string = make(map[string]string)
|
var ClientLicense map[string]string = make(map[string]string)
|
||||||
@@ -41,18 +33,8 @@ NxpC+5KFhU+xSeeklNqwCgnlOyZ7qSTxmdJHb+60SwuYnnGIYzLJhY4LYDr4J+KR
|
|||||||
1wIDAQAB
|
1wIDAQAB
|
||||||
-----END PUBLIC KEY-----`)
|
-----END PUBLIC KEY-----`)
|
||||||
|
|
||||||
func LoadLicense() {
|
func LoadLicense(licenseBytes []byte) {
|
||||||
file, err := os.Open(LicenseLocation())
|
if success, licenseStr := ValidateLicense(licenseBytes); success {
|
||||||
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 {
|
|
||||||
license := model.LicenseFromJson(strings.NewReader(licenseStr))
|
license := model.LicenseFromJson(strings.NewReader(licenseStr))
|
||||||
SetLicense(license)
|
SetLicense(license)
|
||||||
return
|
return
|
||||||
@@ -74,21 +56,10 @@ func SetLicense(license *model.License) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func LicenseLocation() string {
|
func RemoveLicense() {
|
||||||
return filepath.Dir(CfgFileName) + "/" + LICENSE_FILENAME
|
|
||||||
}
|
|
||||||
|
|
||||||
func RemoveLicense() bool {
|
|
||||||
License = &model.License{}
|
License = &model.License{}
|
||||||
IsLicensed = false
|
IsLicensed = false
|
||||||
ClientLicense = getClientLicense(License)
|
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) {
|
func ValidateLicense(signed []byte) (bool, string) {
|
||||||
|
|||||||
@@ -73,7 +73,8 @@ class AccessHistoryModal extends React.Component {
|
|||||||
content = (
|
content = (
|
||||||
<AuditTable
|
<AuditTable
|
||||||
audits={this.state.audits}
|
audits={this.state.audits}
|
||||||
moreInfo={this.state.moreInfo}
|
showIp={true}
|
||||||
|
showSession={true}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ export default class AdminController extends React.Component {
|
|||||||
} else if (this.state.selected === 'ldap_settings') {
|
} else if (this.state.selected === 'ldap_settings') {
|
||||||
tab = <LdapSettingsTab config={this.state.config} />;
|
tab = <LdapSettingsTab config={this.state.config} />;
|
||||||
} else if (this.state.selected === 'license') {
|
} else if (this.state.selected === 'license') {
|
||||||
tab = <LicenseSettingsTab />;
|
tab = <LicenseSettingsTab config={this.state.config} />;
|
||||||
} else if (this.state.selected === 'team_users') {
|
} else if (this.state.selected === 'team_users') {
|
||||||
if (this.state.teams) {
|
if (this.state.teams) {
|
||||||
tab = <TeamUsersTab team={this.state.teams[this.state.selectedTeam]} />;
|
tab = <TeamUsersTab team={this.state.teams[this.state.selectedTeam]} />;
|
||||||
|
|||||||
@@ -225,7 +225,7 @@ export default class AdminSidebar extends React.Component {
|
|||||||
>
|
>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.sidebar.audits'
|
id='admin.sidebar.audits'
|
||||||
defaultMessage='Audits'
|
defaultMessage='Compliance and Auditing'
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -454,6 +454,7 @@ export default class AdminSidebar extends React.Component {
|
|||||||
</ul>
|
</ul>
|
||||||
<ul className='nav nav__sub-menu padded'>
|
<ul className='nav nav__sub-menu padded'>
|
||||||
{licenseSettings}
|
{licenseSettings}
|
||||||
|
{audits}
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href='#'
|
href='#'
|
||||||
@@ -466,7 +467,6 @@ export default class AdminSidebar extends React.Component {
|
|||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{audits}
|
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -60,8 +60,9 @@ export default class Audits extends React.Component {
|
|||||||
<div style={{margin: '10px'}}>
|
<div style={{margin: '10px'}}>
|
||||||
<AuditTable
|
<AuditTable
|
||||||
audits={this.state.audits}
|
audits={this.state.audits}
|
||||||
oneLine={true}
|
|
||||||
showUserId={true}
|
showUserId={true}
|
||||||
|
showIp={true}
|
||||||
|
showSession={true}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -72,7 +73,7 @@ export default class Audits extends React.Component {
|
|||||||
<h3>
|
<h3>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.audits.title'
|
id='admin.audits.title'
|
||||||
defaultMessage='Server Audits'
|
defaultMessage='User Activity'
|
||||||
/>
|
/>
|
||||||
</h3>
|
</h3>
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -109,7 +109,17 @@ class LicenseSettings extends React.Component {
|
|||||||
);
|
);
|
||||||
licenseType = (
|
licenseType = (
|
||||||
<FormattedHTMLMessage
|
<FormattedHTMLMessage
|
||||||
id='admin.license.entrepriseType'
|
id='admin.license.enterpriseType'
|
||||||
|
values={{
|
||||||
|
terms: global.window.mm_config.TermsOfServiceLink,
|
||||||
|
name: global.window.mm_license.Name,
|
||||||
|
company: global.window.mm_license.Company,
|
||||||
|
users: global.window.mm_license.Users,
|
||||||
|
issued: Utils.displayDate(parseInt(global.window.mm_license.IssuedAt, 10)) + ' ' + Utils.displayTime(parseInt(global.window.mm_license.IssuedAt, 10), true),
|
||||||
|
start: Utils.displayDate(parseInt(global.window.mm_license.StartsAt, 10)),
|
||||||
|
expires: Utils.displayDate(parseInt(global.window.mm_license.ExpiresAt, 10)),
|
||||||
|
ldap: global.window.mm_license.LDAP
|
||||||
|
}}
|
||||||
defaultMessage='<div><p>This compiled release of Mattermost platform is provided under a <a href="http://mattermost.com" target="_blank">commercial license</a>
|
defaultMessage='<div><p>This compiled release of Mattermost platform is provided under a <a href="http://mattermost.com" target="_blank">commercial license</a>
|
||||||
from Mattermost, Inc. based on your subscription level and is subject to the <a href="{terms}" target="_blank">Terms of Service.</a></p>
|
from Mattermost, Inc. based on your subscription level and is subject to the <a href="{terms}" target="_blank">Terms of Service.</a></p>
|
||||||
<p>Your subscription details are as follows:</p>
|
<p>Your subscription details are as follows:</p>
|
||||||
@@ -126,6 +136,7 @@ class LicenseSettings extends React.Component {
|
|||||||
licenseKey = (
|
licenseKey = (
|
||||||
<div className='col-sm-8'>
|
<div className='col-sm-8'>
|
||||||
<button
|
<button
|
||||||
|
disabled={this.props.config.LdapSettings.Enable}
|
||||||
className='btn btn-danger'
|
className='btn btn-danger'
|
||||||
onClick={this.handleRemove}
|
onClick={this.handleRemove}
|
||||||
id='remove-button'
|
id='remove-button'
|
||||||
@@ -256,7 +267,8 @@ class LicenseSettings extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
LicenseSettings.propTypes = {
|
LicenseSettings.propTypes = {
|
||||||
intl: intlShape.isRequired
|
intl: intlShape.isRequired,
|
||||||
|
config: React.PropTypes.object
|
||||||
};
|
};
|
||||||
|
|
||||||
export default injectIntl(LicenseSettings);
|
export default injectIntl(LicenseSettings);
|
||||||
|
|||||||
@@ -183,389 +183,388 @@ const holders = defineMessages({
|
|||||||
loginFailure: {
|
loginFailure: {
|
||||||
id: 'audit_table.loginFailure',
|
id: 'audit_table.loginFailure',
|
||||||
defaultMessage: ' (Login failure)'
|
defaultMessage: ' (Login failure)'
|
||||||
},
|
|
||||||
userId: {
|
|
||||||
id: 'audit_table.userId',
|
|
||||||
defaultMessage: 'User ID'
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
class AuditTable extends React.Component {
|
class AuditTable extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.handleMoreInfo = this.handleMoreInfo.bind(this);
|
|
||||||
this.formatAuditInfo = this.formatAuditInfo.bind(this);
|
|
||||||
this.handleRevokedSession = this.handleRevokedSession.bind(this);
|
|
||||||
|
|
||||||
this.state = {moreInfo: []};
|
|
||||||
}
|
|
||||||
handleMoreInfo(index) {
|
|
||||||
var newMoreInfo = this.state.moreInfo;
|
|
||||||
newMoreInfo[index] = true;
|
|
||||||
this.setState({moreInfo: newMoreInfo});
|
|
||||||
}
|
|
||||||
handleRevokedSession(sessionId) {
|
|
||||||
return this.props.intl.formatMessage(holders.sessionRevoked, {sessionId: sessionId});
|
|
||||||
}
|
|
||||||
formatAuditInfo(currentAudit) {
|
|
||||||
const currentActionURL = currentAudit.action.replace(/\/api\/v[1-9]/, '');
|
|
||||||
|
|
||||||
const {formatMessage} = this.props.intl;
|
|
||||||
let currentAuditDesc = '';
|
|
||||||
|
|
||||||
if (currentActionURL.indexOf('/channels') === 0) {
|
|
||||||
const channelInfo = currentAudit.extra_info.split(' ');
|
|
||||||
const channelNameField = channelInfo[0].split('=');
|
|
||||||
|
|
||||||
let channelURL = '';
|
|
||||||
let channelObj;
|
|
||||||
let channelName = '';
|
|
||||||
if (channelNameField.indexOf('name') >= 0) {
|
|
||||||
channelURL = channelNameField[channelNameField.indexOf('name') + 1];
|
|
||||||
channelObj = ChannelStore.getByName(channelURL);
|
|
||||||
if (channelObj) {
|
|
||||||
channelName = channelObj.display_name;
|
|
||||||
} else {
|
|
||||||
channelName = channelURL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (currentActionURL) {
|
|
||||||
case '/channels/create':
|
|
||||||
currentAuditDesc = formatMessage(holders.channelCreated, {channelName: channelName});
|
|
||||||
break;
|
|
||||||
case '/channels/create_direct':
|
|
||||||
currentAuditDesc = formatMessage(holders.establishedDM, {username: Utils.getDirectTeammate(channelObj.id).username});
|
|
||||||
break;
|
|
||||||
case '/channels/update':
|
|
||||||
currentAuditDesc = formatMessage(holders.nameUpdated, {channelName: channelName});
|
|
||||||
break;
|
|
||||||
case '/channels/update_desc': // support the old path
|
|
||||||
case '/channels/update_header':
|
|
||||||
currentAuditDesc = formatMessage(holders.headerUpdated, {channelName: channelName});
|
|
||||||
break;
|
|
||||||
default: {
|
|
||||||
let userIdField = [];
|
|
||||||
let userId = '';
|
|
||||||
let username = '';
|
|
||||||
|
|
||||||
if (channelInfo[1]) {
|
|
||||||
userIdField = channelInfo[1].split('=');
|
|
||||||
|
|
||||||
if (userIdField.indexOf('user_id') >= 0) {
|
|
||||||
userId = userIdField[userIdField.indexOf('user_id') + 1];
|
|
||||||
username = UserStore.getProfile(userId).username;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (/\/channels\/[A-Za-z0-9]+\/delete/.test(currentActionURL)) {
|
|
||||||
currentAuditDesc = formatMessage(holders.channelDeleted, {url: channelURL});
|
|
||||||
} else if (/\/channels\/[A-Za-z0-9]+\/add/.test(currentActionURL)) {
|
|
||||||
currentAuditDesc = formatMessage(holders.userAdded, {username: username, channelName: channelName});
|
|
||||||
} else if (/\/channels\/[A-Za-z0-9]+\/remove/.test(currentActionURL)) {
|
|
||||||
currentAuditDesc = formatMessage(holders.userRemoved, {username: username, channelName: channelName});
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (currentActionURL.indexOf('/oauth') === 0) {
|
|
||||||
const oauthInfo = currentAudit.extra_info.split(' ');
|
|
||||||
|
|
||||||
switch (currentActionURL) {
|
|
||||||
case '/oauth/register': {
|
|
||||||
const clientIdField = oauthInfo[0].split('=');
|
|
||||||
|
|
||||||
if (clientIdField[0] === 'client_id') {
|
|
||||||
currentAuditDesc = formatMessage(holders.attemptedRegisterApp, {id: clientIdField[1]});
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case '/oauth/allow':
|
|
||||||
if (oauthInfo[0] === 'attempt') {
|
|
||||||
currentAuditDesc = formatMessage(holders.attemptedAllowOAuthAccess);
|
|
||||||
} else if (oauthInfo[0] === 'success') {
|
|
||||||
currentAuditDesc = formatMessage(holders.successfullOAuthAccess);
|
|
||||||
} else if (oauthInfo[0] === 'fail - redirect_uri did not match registered callback') {
|
|
||||||
currentAuditDesc = formatMessage(holders.failedOAuthAccess);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case '/oauth/access_token':
|
|
||||||
if (oauthInfo[0] === 'attempt') {
|
|
||||||
currentAuditDesc = formatMessage(holders.attemptedOAuthToken);
|
|
||||||
} else if (oauthInfo[0] === 'success') {
|
|
||||||
currentAuditDesc = formatMessage(holders.successfullOAuthToken);
|
|
||||||
} else {
|
|
||||||
const oauthTokenFailure = oauthInfo[0].split('-');
|
|
||||||
|
|
||||||
if (oauthTokenFailure[0].trim() === 'fail' && oauthTokenFailure[1]) {
|
|
||||||
currentAuditDesc = formatMessage(oauthTokenFailure, {token: oauthTokenFailure[1].trim()});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else if (currentActionURL.indexOf('/users') === 0) {
|
|
||||||
const userInfo = currentAudit.extra_info.split(' ');
|
|
||||||
|
|
||||||
switch (currentActionURL) {
|
|
||||||
case '/users/login':
|
|
||||||
if (userInfo[0] === 'attempt') {
|
|
||||||
currentAuditDesc = formatMessage(holders.attemptedLogin);
|
|
||||||
} else if (userInfo[0] === 'success') {
|
|
||||||
currentAuditDesc = formatMessage(holders.successfullLogin);
|
|
||||||
} else if (userInfo[0]) {
|
|
||||||
currentAuditDesc = formatMessage(holders.failedLogin);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case '/users/revoke_session':
|
|
||||||
currentAuditDesc = this.handleRevokedSession(userInfo[0].split('=')[1]);
|
|
||||||
break;
|
|
||||||
case '/users/newimage':
|
|
||||||
currentAuditDesc = formatMessage(holders.updatePicture);
|
|
||||||
break;
|
|
||||||
case '/users/update':
|
|
||||||
currentAuditDesc = formatMessage(holders.updateGeneral);
|
|
||||||
break;
|
|
||||||
case '/users/newpassword':
|
|
||||||
if (userInfo[0] === 'attempted') {
|
|
||||||
currentAuditDesc = formatMessage(holders.attemptedPassword);
|
|
||||||
} else if (userInfo[0] === 'completed') {
|
|
||||||
currentAuditDesc = formatMessage(holders.successfullPassword);
|
|
||||||
} else if (userInfo[0] === 'failed - tried to update user password who was logged in through oauth') {
|
|
||||||
currentAuditDesc = formatMessage(holders.failedPassword);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case '/users/update_roles': {
|
|
||||||
const userRoles = userInfo[0].split('=')[1];
|
|
||||||
|
|
||||||
currentAuditDesc = formatMessage(holders.updatedRol);
|
|
||||||
if (userRoles.trim()) {
|
|
||||||
currentAuditDesc += userRoles;
|
|
||||||
} else {
|
|
||||||
currentAuditDesc += formatMessage(holders.member);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case '/users/update_active': {
|
|
||||||
const updateType = userInfo[0].split('=')[0];
|
|
||||||
const updateField = userInfo[0].split('=')[1];
|
|
||||||
|
|
||||||
/* Either describes account activation/deactivation or a revoked session as part of an account deactivation */
|
|
||||||
if (updateType === 'active') {
|
|
||||||
if (updateField === 'true') {
|
|
||||||
currentAuditDesc = formatMessage(holders.accountActive);
|
|
||||||
} else if (updateField === 'false') {
|
|
||||||
currentAuditDesc = formatMessage(holders.accountInactive);
|
|
||||||
}
|
|
||||||
|
|
||||||
const actingUserInfo = userInfo[1].split('=');
|
|
||||||
if (actingUserInfo[0] === 'session_user') {
|
|
||||||
const actingUser = UserStore.getProfile(actingUserInfo[1]);
|
|
||||||
const currentUser = UserStore.getCurrentUser();
|
|
||||||
if (currentUser && actingUser && (Utils.isAdmin(currentUser.roles) || Utils.isSystemAdmin(currentUser.roles))) {
|
|
||||||
currentAuditDesc += formatMessage(holders.by, {username: actingUser.username});
|
|
||||||
} else if (currentUser && actingUser) {
|
|
||||||
currentAuditDesc += formatMessage(holders.byAdmin);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (updateType === 'session_id') {
|
|
||||||
currentAuditDesc = this.handleRevokedSession(updateField);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case '/users/send_password_reset':
|
|
||||||
currentAuditDesc = formatMessage(holders.sentEmail, {email: userInfo[0].split('=')[1]});
|
|
||||||
break;
|
|
||||||
case '/users/reset_password':
|
|
||||||
if (userInfo[0] === 'attempt') {
|
|
||||||
currentAuditDesc = formatMessage(holders.attemptedReset);
|
|
||||||
} else if (userInfo[0] === 'success') {
|
|
||||||
currentAuditDesc = formatMessage(holders.successfullReset);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case '/users/update_notify':
|
|
||||||
currentAuditDesc = formatMessage(holders.updateGlobalNotifications);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else if (currentActionURL.indexOf('/hooks') === 0) {
|
|
||||||
const webhookInfo = currentAudit.extra_info.split(' ');
|
|
||||||
|
|
||||||
switch (currentActionURL) {
|
|
||||||
case '/hooks/incoming/create':
|
|
||||||
if (webhookInfo[0] === 'attempt') {
|
|
||||||
currentAuditDesc = formatMessage(holders.attemptedWebhookCreate);
|
|
||||||
} else if (webhookInfo[0] === 'success') {
|
|
||||||
currentAuditDesc = formatMessage(holders.succcessfullWebhookCreate);
|
|
||||||
} else if (webhookInfo[0] === 'fail - bad channel permissions') {
|
|
||||||
currentAuditDesc = formatMessage(holders.failedWebhookCreate);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case '/hooks/incoming/delete':
|
|
||||||
if (webhookInfo[0] === 'attempt') {
|
|
||||||
currentAuditDesc = formatMessage(holders.attemptedWebhookDelete);
|
|
||||||
} else if (webhookInfo[0] === 'success') {
|
|
||||||
currentAuditDesc = formatMessage(holders.successfullWebhookDelete);
|
|
||||||
} else if (webhookInfo[0] === 'fail - inappropriate conditions') {
|
|
||||||
currentAuditDesc = formatMessage(holders.failedWebhookDelete);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
switch (currentActionURL) {
|
|
||||||
case '/logout':
|
|
||||||
currentAuditDesc = formatMessage(holders.logout);
|
|
||||||
break;
|
|
||||||
case '/verify_email':
|
|
||||||
currentAuditDesc = formatMessage(holders.verified);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If all else fails... */
|
|
||||||
if (!currentAuditDesc) {
|
|
||||||
/* Currently not called anywhere */
|
|
||||||
if (currentAudit.extra_info.indexOf('revoked_all=') >= 0) {
|
|
||||||
currentAuditDesc = formatMessage(holders.revokedAll);
|
|
||||||
} else {
|
|
||||||
let currentActionDesc = '';
|
|
||||||
if (currentActionURL && currentActionURL.lastIndexOf('/') !== -1) {
|
|
||||||
currentActionDesc = currentActionURL.substring(currentActionURL.lastIndexOf('/') + 1).replace('_', ' ');
|
|
||||||
currentActionDesc = Utils.toTitleCase(currentActionDesc);
|
|
||||||
}
|
|
||||||
|
|
||||||
let currentExtraInfoDesc = '';
|
|
||||||
if (currentAudit.extra_info) {
|
|
||||||
currentExtraInfoDesc = currentAudit.extra_info;
|
|
||||||
|
|
||||||
if (currentExtraInfoDesc.indexOf('=') !== -1) {
|
|
||||||
currentExtraInfoDesc = currentExtraInfoDesc.substring(currentExtraInfoDesc.indexOf('=') + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
currentAuditDesc = currentActionDesc + ' ' + currentExtraInfoDesc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentDate = new Date(currentAudit.create_at);
|
|
||||||
let currentAuditInfo = currentDate.toLocaleDateString(global.window.mm_locale, {month: 'short', day: '2-digit', year: 'numeric'}) + ' - ' + currentDate.toLocaleTimeString(global.window.mm_locale, {hour: '2-digit', minute: '2-digit'});
|
|
||||||
|
|
||||||
if (this.props.showUserId) {
|
|
||||||
currentAuditInfo += ' | ' + formatMessage(holders.userId) + ': ' + currentAudit.user_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentAuditInfo += ' | ' + currentAuditDesc;
|
|
||||||
|
|
||||||
return currentAuditInfo;
|
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
var accessList = [];
|
var accessList = [];
|
||||||
|
|
||||||
const {formatMessage} = this.props.intl;
|
const {formatMessage} = this.props.intl;
|
||||||
for (var i = 0; i < this.props.audits.length; i++) {
|
for (var i = 0; i < this.props.audits.length; i++) {
|
||||||
const currentAudit = this.props.audits[i];
|
const audit = this.props.audits[i];
|
||||||
const currentAuditInfo = this.formatAuditInfo(currentAudit);
|
const auditInfo = formatAuditInfo(audit, formatMessage);
|
||||||
|
|
||||||
let moreInfo;
|
let uContent;
|
||||||
if (!this.props.oneLine) {
|
if (this.props.showUserId) {
|
||||||
moreInfo = (
|
uContent = <td>{auditInfo.userId}</td>;
|
||||||
<a
|
|
||||||
href='#'
|
|
||||||
className='theme'
|
|
||||||
onClick={this.handleMoreInfo.bind(this, i)}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='audit_table.moreInfo'
|
|
||||||
defaultMessage='More info'
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.moreInfo[i]) {
|
let iContent;
|
||||||
if (!currentAudit.session_id) {
|
if (this.props.showIp) {
|
||||||
currentAudit.session_id = 'N/A';
|
iContent = <td>{auditInfo.ip}</td>;
|
||||||
|
|
||||||
if (currentAudit.action.search('/users/login') >= 0) {
|
|
||||||
if (currentAudit.extra_info === 'attempt') {
|
|
||||||
currentAudit.session_id += formatMessage(holders.loginAttempt);
|
|
||||||
} else {
|
|
||||||
currentAudit.session_id += formatMessage(holders.loginFailure);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
moreInfo = (
|
|
||||||
<div>
|
|
||||||
<div>
|
|
||||||
<FormattedMessage
|
|
||||||
id='audit_table.ip'
|
|
||||||
defaultMessage='IP: {ip}'
|
|
||||||
values={{
|
|
||||||
ip: currentAudit.ip_address
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<FormattedMessage
|
|
||||||
id='audit_table.session'
|
|
||||||
defaultMessage='Session ID: {id}'
|
|
||||||
values={{
|
|
||||||
id: currentAudit.session_id
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var divider = null;
|
let sContent;
|
||||||
if (i < this.props.audits.length - 1) {
|
if (this.props.showSession) {
|
||||||
divider = (<div className='divider-light'></div>);
|
sContent = <td>{auditInfo.sessionId}</td>;
|
||||||
|
}
|
||||||
|
|
||||||
|
let descStyle = {};
|
||||||
|
if (auditInfo.desc.toLowerCase().indexOf('fail') !== -1) {
|
||||||
|
descStyle.color = 'red';
|
||||||
}
|
}
|
||||||
|
|
||||||
accessList[i] = (
|
accessList[i] = (
|
||||||
<div
|
<tr key={audit.id}>
|
||||||
key={'accessHistoryEntryKey' + i}
|
<td>{auditInfo.timestamp}</td>
|
||||||
className='access-history__table'
|
{uContent}
|
||||||
>
|
<td style={descStyle}>{auditInfo.desc}</td>
|
||||||
<div className='access__report'>
|
{iContent}
|
||||||
<div className='report__time'>{currentAuditInfo}</div>
|
{sContent}
|
||||||
<div className='report__info'>
|
</tr>
|
||||||
{moreInfo}
|
|
||||||
</div>
|
|
||||||
{divider}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <form role='form'>{accessList}</form>;
|
let userIdContent;
|
||||||
|
if (this.props.showUserId) {
|
||||||
|
userIdContent = (
|
||||||
|
<th>
|
||||||
|
<FormattedMessage
|
||||||
|
id='audit_table.userId'
|
||||||
|
defaultMessage='User ID'
|
||||||
|
/>
|
||||||
|
</th>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let ipContent;
|
||||||
|
if (this.props.showIp) {
|
||||||
|
ipContent = (
|
||||||
|
<th>
|
||||||
|
<FormattedMessage
|
||||||
|
id='audit_table.ip'
|
||||||
|
defaultMessage='IP Address'
|
||||||
|
/>
|
||||||
|
</th>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let sessionContent;
|
||||||
|
if (this.props.showSession) {
|
||||||
|
sessionContent = (
|
||||||
|
<th>
|
||||||
|
<FormattedMessage
|
||||||
|
id='audit_table.session'
|
||||||
|
defaultMessage='Session ID'
|
||||||
|
/>
|
||||||
|
</th>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<table className='table'>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
<FormattedMessage
|
||||||
|
id='audit_table.timestamp'
|
||||||
|
defaultMessage='Timestamp'
|
||||||
|
/>
|
||||||
|
</th>
|
||||||
|
{userIdContent}
|
||||||
|
<th>
|
||||||
|
<FormattedMessage
|
||||||
|
id='audit_table.action'
|
||||||
|
defaultMessage='Action'
|
||||||
|
/>
|
||||||
|
</th>
|
||||||
|
{ipContent}
|
||||||
|
{sessionContent}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{accessList}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AuditTable.propTypes = {
|
AuditTable.propTypes = {
|
||||||
intl: intlShape.isRequired,
|
intl: intlShape.isRequired,
|
||||||
audits: React.PropTypes.array.isRequired,
|
audits: React.PropTypes.array.isRequired,
|
||||||
oneLine: React.PropTypes.bool,
|
showUserId: React.PropTypes.bool,
|
||||||
showUserId: React.PropTypes.bool
|
showIp: React.PropTypes.bool,
|
||||||
|
showSession: React.PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
export default injectIntl(AuditTable);
|
export default injectIntl(AuditTable);
|
||||||
|
|
||||||
|
export function formatAuditInfo(audit, formatMessage) {
|
||||||
|
const actionURL = audit.action.replace(/\/api\/v[1-9]/, '');
|
||||||
|
let auditDesc = '';
|
||||||
|
|
||||||
|
if (actionURL.indexOf('/channels') === 0) {
|
||||||
|
const channelInfo = audit.extra_info.split(' ');
|
||||||
|
const channelNameField = channelInfo[0].split('=');
|
||||||
|
|
||||||
|
let channelURL = '';
|
||||||
|
let channelObj;
|
||||||
|
let channelName = '';
|
||||||
|
if (channelNameField.indexOf('name') >= 0) {
|
||||||
|
channelURL = channelNameField[channelNameField.indexOf('name') + 1];
|
||||||
|
channelObj = ChannelStore.getByName(channelURL);
|
||||||
|
if (channelObj) {
|
||||||
|
channelName = channelObj.display_name;
|
||||||
|
} else {
|
||||||
|
channelName = channelURL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (actionURL) {
|
||||||
|
case '/channels/create':
|
||||||
|
auditDesc = formatMessage(holders.channelCreated, {channelName: channelName});
|
||||||
|
break;
|
||||||
|
case '/channels/create_direct':
|
||||||
|
auditDesc = formatMessage(holders.establishedDM, {username: Utils.getDirectTeammate(channelObj.id).username});
|
||||||
|
break;
|
||||||
|
case '/channels/update':
|
||||||
|
auditDesc = formatMessage(holders.nameUpdated, {channelName: channelName});
|
||||||
|
break;
|
||||||
|
case '/channels/update_desc': // support the old path
|
||||||
|
case '/channels/update_header':
|
||||||
|
auditDesc = formatMessage(holders.headerUpdated, {channelName: channelName});
|
||||||
|
break;
|
||||||
|
default: {
|
||||||
|
let userIdField = [];
|
||||||
|
let userId = '';
|
||||||
|
let username = '';
|
||||||
|
|
||||||
|
if (channelInfo[1]) {
|
||||||
|
userIdField = channelInfo[1].split('=');
|
||||||
|
|
||||||
|
if (userIdField.indexOf('user_id') >= 0) {
|
||||||
|
userId = userIdField[userIdField.indexOf('user_id') + 1];
|
||||||
|
username = UserStore.getProfile(userId).username;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/\/channels\/[A-Za-z0-9]+\/delete/.test(actionURL)) {
|
||||||
|
auditDesc = formatMessage(holders.channelDeleted, {url: channelURL});
|
||||||
|
} else if (/\/channels\/[A-Za-z0-9]+\/add/.test(actionURL)) {
|
||||||
|
auditDesc = formatMessage(holders.userAdded, {username: username, channelName: channelName});
|
||||||
|
} else if (/\/channels\/[A-Za-z0-9]+\/remove/.test(actionURL)) {
|
||||||
|
auditDesc = formatMessage(holders.userRemoved, {username: username, channelName: channelName});
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (actionURL.indexOf('/oauth') === 0) {
|
||||||
|
const oauthInfo = audit.extra_info.split(' ');
|
||||||
|
|
||||||
|
switch (actionURL) {
|
||||||
|
case '/oauth/register': {
|
||||||
|
const clientIdField = oauthInfo[0].split('=');
|
||||||
|
|
||||||
|
if (clientIdField[0] === 'client_id') {
|
||||||
|
auditDesc = formatMessage(holders.attemptedRegisterApp, {id: clientIdField[1]});
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case '/oauth/allow':
|
||||||
|
if (oauthInfo[0] === 'attempt') {
|
||||||
|
auditDesc = formatMessage(holders.attemptedAllowOAuthAccess);
|
||||||
|
} else if (oauthInfo[0] === 'success') {
|
||||||
|
auditDesc = formatMessage(holders.successfullOAuthAccess);
|
||||||
|
} else if (oauthInfo[0] === 'fail - redirect_uri did not match registered callback') {
|
||||||
|
auditDesc = formatMessage(holders.failedOAuthAccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '/oauth/access_token':
|
||||||
|
if (oauthInfo[0] === 'attempt') {
|
||||||
|
auditDesc = formatMessage(holders.attemptedOAuthToken);
|
||||||
|
} else if (oauthInfo[0] === 'success') {
|
||||||
|
auditDesc = formatMessage(holders.successfullOAuthToken);
|
||||||
|
} else {
|
||||||
|
const oauthTokenFailure = oauthInfo[0].split('-');
|
||||||
|
|
||||||
|
if (oauthTokenFailure[0].trim() === 'fail' && oauthTokenFailure[1]) {
|
||||||
|
auditDesc = formatMessage(oauthTokenFailure, {token: oauthTokenFailure[1].trim()});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (actionURL.indexOf('/users') === 0) {
|
||||||
|
const userInfo = audit.extra_info.split(' ');
|
||||||
|
|
||||||
|
switch (actionURL) {
|
||||||
|
case '/users/login':
|
||||||
|
if (userInfo[0] === 'attempt') {
|
||||||
|
auditDesc = formatMessage(holders.attemptedLogin);
|
||||||
|
} else if (userInfo[0] === 'success') {
|
||||||
|
auditDesc = formatMessage(holders.successfullLogin);
|
||||||
|
} else if (userInfo[0]) {
|
||||||
|
auditDesc = formatMessage(holders.failedLogin);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '/users/revoke_session':
|
||||||
|
auditDesc = formatMessage(holders.sessionRevoked, {sessionId: userInfo[0].split('=')[1]});
|
||||||
|
break;
|
||||||
|
case '/users/newimage':
|
||||||
|
auditDesc = formatMessage(holders.updatePicture);
|
||||||
|
break;
|
||||||
|
case '/users/update':
|
||||||
|
auditDesc = formatMessage(holders.updateGeneral);
|
||||||
|
break;
|
||||||
|
case '/users/newpassword':
|
||||||
|
if (userInfo[0] === 'attempted') {
|
||||||
|
auditDesc = formatMessage(holders.attemptedPassword);
|
||||||
|
} else if (userInfo[0] === 'completed') {
|
||||||
|
auditDesc = formatMessage(holders.successfullPassword);
|
||||||
|
} else if (userInfo[0] === 'failed - tried to update user password who was logged in through oauth') {
|
||||||
|
auditDesc = formatMessage(holders.failedPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '/users/update_roles': {
|
||||||
|
const userRoles = userInfo[0].split('=')[1];
|
||||||
|
|
||||||
|
auditDesc = formatMessage(holders.updatedRol);
|
||||||
|
if (userRoles.trim()) {
|
||||||
|
auditDesc += userRoles;
|
||||||
|
} else {
|
||||||
|
auditDesc += formatMessage(holders.member);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case '/users/update_active': {
|
||||||
|
const updateType = userInfo[0].split('=')[0];
|
||||||
|
const updateField = userInfo[0].split('=')[1];
|
||||||
|
|
||||||
|
/* Either describes account activation/deactivation or a revoked session as part of an account deactivation */
|
||||||
|
if (updateType === 'active') {
|
||||||
|
if (updateField === 'true') {
|
||||||
|
auditDesc = formatMessage(holders.accountActive);
|
||||||
|
} else if (updateField === 'false') {
|
||||||
|
auditDesc = formatMessage(holders.accountInactive);
|
||||||
|
}
|
||||||
|
|
||||||
|
const actingUserInfo = userInfo[1].split('=');
|
||||||
|
if (actingUserInfo[0] === 'session_user') {
|
||||||
|
const actingUser = UserStore.getProfile(actingUserInfo[1]);
|
||||||
|
const user = UserStore.getCurrentUser();
|
||||||
|
if (user && actingUser && (Utils.isAdmin(user.roles) || Utils.isSystemAdmin(user.roles))) {
|
||||||
|
auditDesc += formatMessage(holders.by, {username: actingUser.username});
|
||||||
|
} else if (user && actingUser) {
|
||||||
|
auditDesc += formatMessage(holders.byAdmin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (updateType === 'session_id') {
|
||||||
|
auditDesc = formatMessage(holders.sessionRevoked, {sessionId: updateField});
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case '/users/send_password_reset':
|
||||||
|
auditDesc = formatMessage(holders.sentEmail, {email: userInfo[0].split('=')[1]});
|
||||||
|
break;
|
||||||
|
case '/users/reset_password':
|
||||||
|
if (userInfo[0] === 'attempt') {
|
||||||
|
auditDesc = formatMessage(holders.attemptedReset);
|
||||||
|
} else if (userInfo[0] === 'success') {
|
||||||
|
auditDesc = formatMessage(holders.successfullReset);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '/users/update_notify':
|
||||||
|
auditDesc = formatMessage(holders.updateGlobalNotifications);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (actionURL.indexOf('/hooks') === 0) {
|
||||||
|
const webhookInfo = audit.extra_info.split(' ');
|
||||||
|
|
||||||
|
switch (actionURL) {
|
||||||
|
case '/hooks/incoming/create':
|
||||||
|
if (webhookInfo[0] === 'attempt') {
|
||||||
|
auditDesc = formatMessage(holders.attemptedWebhookCreate);
|
||||||
|
} else if (webhookInfo[0] === 'success') {
|
||||||
|
auditDesc = formatMessage(holders.succcessfullWebhookCreate);
|
||||||
|
} else if (webhookInfo[0] === 'fail - bad channel permissions') {
|
||||||
|
auditDesc = formatMessage(holders.failedWebhookCreate);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case '/hooks/incoming/delete':
|
||||||
|
if (webhookInfo[0] === 'attempt') {
|
||||||
|
auditDesc = formatMessage(holders.attemptedWebhookDelete);
|
||||||
|
} else if (webhookInfo[0] === 'success') {
|
||||||
|
auditDesc = formatMessage(holders.successfullWebhookDelete);
|
||||||
|
} else if (webhookInfo[0] === 'fail - inappropriate conditions') {
|
||||||
|
auditDesc = formatMessage(holders.failedWebhookDelete);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch (actionURL) {
|
||||||
|
case '/logout':
|
||||||
|
auditDesc = formatMessage(holders.logout);
|
||||||
|
break;
|
||||||
|
case '/verify_email':
|
||||||
|
auditDesc = formatMessage(holders.verified);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If all else fails... */
|
||||||
|
if (!auditDesc) {
|
||||||
|
/* Currently not called anywhere */
|
||||||
|
if (audit.extra_info.indexOf('revoked_all=') >= 0) {
|
||||||
|
auditDesc = formatMessage(holders.revokedAll);
|
||||||
|
} else {
|
||||||
|
let actionDesc = '';
|
||||||
|
if (actionURL && actionURL.lastIndexOf('/') !== -1) {
|
||||||
|
actionDesc = actionURL.substring(actionURL.lastIndexOf('/') + 1).replace('_', ' ');
|
||||||
|
actionDesc = Utils.toTitleCase(actionDesc);
|
||||||
|
}
|
||||||
|
|
||||||
|
let extraInfoDesc = '';
|
||||||
|
if (audit.extra_info) {
|
||||||
|
extraInfoDesc = audit.extra_info;
|
||||||
|
|
||||||
|
if (extraInfoDesc.indexOf('=') !== -1) {
|
||||||
|
extraInfoDesc = extraInfoDesc.substring(extraInfoDesc.indexOf('=') + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auditDesc = actionDesc + ' ' + extraInfoDesc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const date = new Date(audit.create_at);
|
||||||
|
let auditInfo = {};
|
||||||
|
auditInfo.timestamp = date.toLocaleDateString(global.window.mm_locale, {month: 'short', day: '2-digit', year: 'numeric'}) + ' - ' + date.toLocaleTimeString(global.window.mm_locale, {hour: '2-digit', minute: '2-digit'});
|
||||||
|
auditInfo.userId = audit.user_id;
|
||||||
|
auditInfo.desc = auditDesc;
|
||||||
|
auditInfo.ip = audit.ip_address;
|
||||||
|
auditInfo.sessionId = audit.session_id;
|
||||||
|
|
||||||
|
return auditInfo;
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,6 +8,54 @@
|
|||||||
"about.date": "Build Date:",
|
"about.date": "Build Date:",
|
||||||
"about.hash": "Build Hash:",
|
"about.hash": "Build Hash:",
|
||||||
"about.close": "Close",
|
"about.close": "Close",
|
||||||
|
"audit_table.sessionRevoked": "The session with id {sessionId} was revoked",
|
||||||
|
"audit_table.channelCreated": "Created the {channelName} channel/group",
|
||||||
|
"audit_table.establishedDM": "Established a direct message channel with {username}",
|
||||||
|
"audit_table.nameUpdated": "Updated the {channelName} channel/group name",
|
||||||
|
"audit_table.headerUpdated": "Updated the {channelName} channel/group header",
|
||||||
|
"audit_table.channelDeleted": "Deleted the channel/group with the URL {url}",
|
||||||
|
"audit_table.userAdded": "Added {username} to the {channelName} channel/group",
|
||||||
|
"audit_table.userRemoved": "Removed {username} to the {channelName} channel/group",
|
||||||
|
"audit_table.attemptedRegisterApp": "Attempted to register a new OAuth Application with ID {id}",
|
||||||
|
"audit_table.attemptedAllowOAuthAccess": "Attempted to allow a new OAuth service access",
|
||||||
|
"audit_table.successfullOAuthAccess": "Successfully gave a new OAuth service access",
|
||||||
|
"audit_table.failedOAuthAccess": "Failed to allow a new OAuth service access - the redirect URI did not match the previously registered callback",
|
||||||
|
"audit_table.attemptedOAuthToken": "Attempted to get an OAuth access token",
|
||||||
|
"audit_table.successfullOAuthToken": "Successfully added a new OAuth service",
|
||||||
|
"audit_table.oauthTokenFailed": "Failed to get an OAuth access token - {token}",
|
||||||
|
"audit_table.attemptedLogin": "Attempted to login",
|
||||||
|
"audit_table.successfullLogin": "Successfully logged in",
|
||||||
|
"audit_table.failedLogin": "FAILED login attempt",
|
||||||
|
"audit_table.updatePicture": "Updated your profile picture",
|
||||||
|
"audit_table.updateGeneral": "Updated the general settings of your account",
|
||||||
|
"audit_table.attemptedPassword": "Attempted to change password",
|
||||||
|
"audit_table.successfullPassword": "Successfully changed password",
|
||||||
|
"audit_table.failedPassword": "Failed to change password - tried to update user password who was logged in through oauth",
|
||||||
|
"audit_table.updatedRol": "Updated user role(s) to ",
|
||||||
|
"audit_table.member": "member",
|
||||||
|
"audit_table.accountActive": "Account made active",
|
||||||
|
"audit_table.accountInactive": "Account made inactive",
|
||||||
|
"audit_table.by": " by {username}",
|
||||||
|
"audit_table.byAdmin": " by an admin",
|
||||||
|
"audit_table.sentEmail": "Sent an email to {email} to reset your password",
|
||||||
|
"audit_table.attemptedReset": "Attempted to reset password",
|
||||||
|
"audit_table.successfullReset": "Successfully reset password",
|
||||||
|
"audit_table.updateGlobalNotifications": "Updated your global notification settings",
|
||||||
|
"audit_table.attemptedWebhookCreate": "Attempted to create a webhook",
|
||||||
|
"audit_table.successfullWebhookCreate": "Successfully created a webhook",
|
||||||
|
"audit_table.failedWebhookCreate": "Failed to create a webhook - bad channel permissions",
|
||||||
|
"audit_table.attemptedWebhookDelete": "Attempted to delete a webhook",
|
||||||
|
"audit_table.successfullWebhookDelete": "Successfully deleted a webhook",
|
||||||
|
"audit_table.failedWebhookDelete": "Failed to delete a webhook - inappropriate conditions",
|
||||||
|
"audit_table.logout": "Logged out of your account",
|
||||||
|
"audit_table.verified": "Sucessfully verified your email address",
|
||||||
|
"audit_table.revokedAll": "Revoked all current sessions for the team",
|
||||||
|
"audit_table.loginAttempt": " (Login attempt)",
|
||||||
|
"audit_table.loginFailure": " (Login failure)",
|
||||||
|
"audit_table.moreInfo": "More info",
|
||||||
|
"audit_table.ip": "IP Address",
|
||||||
|
"audit_table.session": "Session ID",
|
||||||
|
"audit_table.userId": "User ID",
|
||||||
"access_history.title": "Access History",
|
"access_history.title": "Access History",
|
||||||
"activity_log_modal.iphoneNativeApp": "iPhone Native App",
|
"activity_log_modal.iphoneNativeApp": "iPhone Native App",
|
||||||
"activity_log_modal.androidNativeApp": "Android Native App",
|
"activity_log_modal.androidNativeApp": "Android Native App",
|
||||||
@@ -33,7 +81,6 @@
|
|||||||
"admin.sidebar.statistics": "- Statistics",
|
"admin.sidebar.statistics": "- Statistics",
|
||||||
"admin.sidebar.ldap": "LDAP Settings",
|
"admin.sidebar.ldap": "LDAP Settings",
|
||||||
"admin.sidebar.license": "Edition and License",
|
"admin.sidebar.license": "Edition and License",
|
||||||
"admin.sidebar.audits": "Audits",
|
|
||||||
"admin.sidebar.reports": "SITE REPORTS",
|
"admin.sidebar.reports": "SITE REPORTS",
|
||||||
"admin.sidebar.view_statistics": "View Statistics",
|
"admin.sidebar.view_statistics": "View Statistics",
|
||||||
"admin.sidebar.settings": "SETTINGS",
|
"admin.sidebar.settings": "SETTINGS",
|
||||||
@@ -50,6 +97,8 @@
|
|||||||
"admin.sidebar.teams": "TEAMS ({count})",
|
"admin.sidebar.teams": "TEAMS ({count})",
|
||||||
"admin.sidebar.other": "OTHER",
|
"admin.sidebar.other": "OTHER",
|
||||||
"admin.sidebar.logs": "Logs",
|
"admin.sidebar.logs": "Logs",
|
||||||
|
"admin.sidebar.audits": "Compliance and Auditing",
|
||||||
|
"admin.analytics.loading": "Loading...",
|
||||||
"admin.analytics.totalUsers": "Total Users",
|
"admin.analytics.totalUsers": "Total Users",
|
||||||
"admin.analytics.publicChannels": "Public Channels",
|
"admin.analytics.publicChannels": "Public Channels",
|
||||||
"admin.analytics.privateGroups": "Private Groups",
|
"admin.analytics.privateGroups": "Private Groups",
|
||||||
@@ -67,8 +116,6 @@
|
|||||||
"admin.analytics.recentActive": "Recent Active Users",
|
"admin.analytics.recentActive": "Recent Active Users",
|
||||||
"admin.analytics.newlyCreated": "Newly Created Users",
|
"admin.analytics.newlyCreated": "Newly Created Users",
|
||||||
"admin.analytics.title": "Statistics for {title}",
|
"admin.analytics.title": "Statistics for {title}",
|
||||||
"admin.audits.title": "Server Audits",
|
|
||||||
"admin.audits.reload": "Reload",
|
|
||||||
"admin.email.notificationDisplayExample": "Ex: \"Mattermost Notification\", \"System\", \"No-Reply\"",
|
"admin.email.notificationDisplayExample": "Ex: \"Mattermost Notification\", \"System\", \"No-Reply\"",
|
||||||
"admin.email.notificationEmailExample": "Ex: \"mattermost@yourcompany.com\", \"admin@yourcompany.com\"",
|
"admin.email.notificationEmailExample": "Ex: \"mattermost@yourcompany.com\", \"admin@yourcompany.com\"",
|
||||||
"admin.email.smtpUsernameExample": "Ex: \"admin@yourcompany.com\", \"AKIADTOVBGERKLCBV\"",
|
"admin.email.smtpUsernameExample": "Ex: \"admin@yourcompany.com\", \"AKIADTOVBGERKLCBV\"",
|
||||||
@@ -255,7 +302,7 @@
|
|||||||
"admin.license.removing": "Removing License...",
|
"admin.license.removing": "Removing License...",
|
||||||
"admin.license.uploading": "Uploading License...",
|
"admin.license.uploading": "Uploading License...",
|
||||||
"admin.license.enterpriseEdition": "Mattermost Enterprise Edition. Designed for enterprise-scale communication.",
|
"admin.license.enterpriseEdition": "Mattermost Enterprise Edition. Designed for enterprise-scale communication.",
|
||||||
"admin.license.entrepriseType": "<div><p>This compiled release of Mattermost platform is provided under a <a href=\"http://mattermost.com\" target=\"_blank\">commercial license</a>\n from Mattermost, Inc. based on your subscription level and is subject to the <a href=\"{terms}\" target=\"_blank\">Terms of Service.</a></p>\n <p>Your subscription details are as follows:</p>\n Name: {name}<br />\n Company or organization name: {company}<br/>\n Number of users: {users}<br/>\n License issued: {issued}<br/>\n Start date of license: {start}<br/>\n Expiry date of license: {expires}<br/>\n LDAP: {ldap}<br/></div>",
|
"admin.license.enterpriseType": "<div><p>This compiled release of Mattermost platform is provided under a <a href=\"http://mattermost.com\" target=\"_blank\">commercial license</a> from Mattermost, Inc. based on your subscription level and is subject to the <a href=\"{terms}\" target=\"_blank\">Terms of Service.</a></p><p>Your subscription details are as follows:</p>Name: {name}<br />Company or organization name: {company}<br/>Number of users: {users}<br/>License issued: {issued}<br/>Start date of license: {start}<br/>Expiry date of license: {expires}<br/>LDAP: {ldap}<br/></div>",
|
||||||
"admin.license.keyRemove": "Remove Enterprise License and Downgrade Server",
|
"admin.license.keyRemove": "Remove Enterprise License and Downgrade Server",
|
||||||
"admin.licence.keyMigration": "If you’re migrating servers you may need to remove your license key from this server in order to install it on a new server. To start,\n <a href=\"http://mattermost.com\" target=\"_blank\">disable all Enterprise Edition features on this server</a>.\n This will enable the ability to remove the license key and downgrade this server from Enterprise Edition to Team Edition.",
|
"admin.licence.keyMigration": "If you’re migrating servers you may need to remove your license key from this server in order to install it on a new server. To start,\n <a href=\"http://mattermost.com\" target=\"_blank\">disable all Enterprise Edition features on this server</a>.\n This will enable the ability to remove the license key and downgrade this server from Enterprise Edition to Team Edition.",
|
||||||
"admin.license.teamEdition": "Mattermost Team Edition. Designed for teams from 5 to 50 users.",
|
"admin.license.teamEdition": "Mattermost Team Edition. Designed for teams from 5 to 50 users.",
|
||||||
@@ -293,6 +340,8 @@
|
|||||||
"admin.log.save": "Save",
|
"admin.log.save": "Save",
|
||||||
"admin.logs.title": "Server Logs",
|
"admin.logs.title": "Server Logs",
|
||||||
"admin.logs.reload": "Reload",
|
"admin.logs.reload": "Reload",
|
||||||
|
"admin.audits.title": "User Activity",
|
||||||
|
"admin.audits.reload": "Reload",
|
||||||
"admin.privacy.saving": "Saving Config...",
|
"admin.privacy.saving": "Saving Config...",
|
||||||
"admin.privacy.title": "Privacy Settings",
|
"admin.privacy.title": "Privacy Settings",
|
||||||
"admin.privacy.showEmailTitle": "Show Email Address: ",
|
"admin.privacy.showEmailTitle": "Show Email Address: ",
|
||||||
@@ -440,54 +489,6 @@
|
|||||||
"admin.user_item.makeActive": "Make Active",
|
"admin.user_item.makeActive": "Make Active",
|
||||||
"admin.user_item.makeInactive": "Make Inactive",
|
"admin.user_item.makeInactive": "Make Inactive",
|
||||||
"admin.user_item.resetPwd": "Reset Password",
|
"admin.user_item.resetPwd": "Reset Password",
|
||||||
"audit_table.sessionRevoked": "The session with id {sessionId} was revoked",
|
|
||||||
"audit_table.channelCreated": "Created the {channelName} channel/group",
|
|
||||||
"audit_table.establishedDM": "Established a direct message channel with {username}",
|
|
||||||
"audit_table.nameUpdated": "Updated the {channelName} channel/group name",
|
|
||||||
"audit_table.headerUpdated": "Updated the {channelName} channel/group header",
|
|
||||||
"audit_table.channelDeleted": "Deleted the channel/group with the URL {url}",
|
|
||||||
"audit_table.userAdded": "Added {username} to the {channelName} channel/group",
|
|
||||||
"audit_table.userRemoved": "Removed {username} to the {channelName} channel/group",
|
|
||||||
"audit_table.attemptedRegisterApp": "Attempted to register a new OAuth Application with ID {id}",
|
|
||||||
"audit_table.attemptedAllowOAuthAccess": "Attempted to allow a new OAuth service access",
|
|
||||||
"audit_table.successfullOAuthAccess": "Successfully gave a new OAuth service access",
|
|
||||||
"audit_table.failedOAuthAccess": "Failed to allow a new OAuth service access - the redirect URI did not match the previously registered callback",
|
|
||||||
"audit_table.attemptedOAuthToken": "Attempted to get an OAuth access token",
|
|
||||||
"audit_table.successfullOAuthToken": "Successfully added a new OAuth service",
|
|
||||||
"audit_table.oauthTokenFailed": "Failed to get an OAuth access token - {token}",
|
|
||||||
"audit_table.attemptedLogin": "Attempted to login",
|
|
||||||
"audit_table.successfullLogin": "Successfully logged in",
|
|
||||||
"audit_table.failedLogin": "FAILED login attempt",
|
|
||||||
"audit_table.updatePicture": "Updated your profile picture",
|
|
||||||
"audit_table.updateGeneral": "Updated the general settings of your account",
|
|
||||||
"audit_table.attemptedPassword": "Attempted to change password",
|
|
||||||
"audit_table.successfullPassword": "Successfully changed password",
|
|
||||||
"audit_table.failedPassword": "Failed to change password - tried to update user password who was logged in through oauth",
|
|
||||||
"audit_table.updatedRol": "Updated user role(s) to ",
|
|
||||||
"audit_table.member": "member",
|
|
||||||
"audit_table.accountActive": "Account made active",
|
|
||||||
"audit_table.accountInactive": "Account made inactive",
|
|
||||||
"audit_table.by": " by {username}",
|
|
||||||
"audit_table.byAdmin": " by an admin",
|
|
||||||
"audit_table.sentEmail": "Sent an email to {email} to reset your password",
|
|
||||||
"audit_table.attemptedReset": "Attempted to reset password",
|
|
||||||
"audit_table.successfullReset": "Successfully reset password",
|
|
||||||
"audit_table.updateGlobalNotifications": "Updated your global notification settings",
|
|
||||||
"audit_table.attemptedWebhookCreate": "Attempted to create a webhook",
|
|
||||||
"audit_table.successfullWebhookCreate": "Successfully created a webhook",
|
|
||||||
"audit_table.failedWebhookCreate": "Failed to create a webhook - bad channel permissions",
|
|
||||||
"audit_table.attemptedWebhookDelete": "Attempted to delete a webhook",
|
|
||||||
"audit_table.successfullWebhookDelete": "Successfully deleted a webhook",
|
|
||||||
"audit_table.failedWebhookDelete": "Failed to delete a webhook - inappropriate conditions",
|
|
||||||
"audit_table.logout": "Logged out of your account",
|
|
||||||
"audit_table.verified": "Sucessfully verified your email address",
|
|
||||||
"audit_table.revokedAll": "Revoked all current sessions for the team",
|
|
||||||
"audit_table.loginAttempt": " (Login attempt)",
|
|
||||||
"audit_table.loginFailure": " (Login failure)",
|
|
||||||
"audit_table.userId": "User ID",
|
|
||||||
"audit_table.moreInfo": "More info",
|
|
||||||
"audit_table.ip": "IP: {ip}",
|
|
||||||
"audit_table.session": "Session ID: {id}",
|
|
||||||
"authorize.title": "An application would like to connect to your {teamName} account",
|
"authorize.title": "An application would like to connect to your {teamName} account",
|
||||||
"authorize.app": "The app <strong>{appName}</strong> would like the ability to access and modify your basic information.",
|
"authorize.app": "The app <strong>{appName}</strong> would like the ability to access and modify your basic information.",
|
||||||
"authorize.access": "Allow <strong>{appName}</strong> access?",
|
"authorize.access": "Allow <strong>{appName}</strong> access?",
|
||||||
|
|||||||
@@ -211,7 +211,7 @@
|
|||||||
"admin.licence.keyMigration": "Si estás migrando servidores es posible que necesites remover tu licencia de este servidor para poder instalarlo en un servidor nuevo. Para empezar,\n <a href=\"http://mattermost.com\" target=\"_blank\">deshabilita todas las características de la Edición Enterprise de este servidor</a>.\n Esta operación habilitará la opción para remover la licencia y degradar este servidor de la Edición Enterprise a la Edición Team.",
|
"admin.licence.keyMigration": "Si estás migrando servidores es posible que necesites remover tu licencia de este servidor para poder instalarlo en un servidor nuevo. Para empezar,\n <a href=\"http://mattermost.com\" target=\"_blank\">deshabilita todas las características de la Edición Enterprise de este servidor</a>.\n Esta operación habilitará la opción para remover la licencia y degradar este servidor de la Edición Enterprise a la Edición Team.",
|
||||||
"admin.license.edition": "Edición: ",
|
"admin.license.edition": "Edición: ",
|
||||||
"admin.license.enterpriseEdition": "Mattermost Edición Enterprise. Diseñada para comunicación de escala empresarial.",
|
"admin.license.enterpriseEdition": "Mattermost Edición Enterprise. Diseñada para comunicación de escala empresarial.",
|
||||||
"admin.license.entrepriseType": "<div><p>Esta versión compilada de la plataforma de Mattermost es proporcionada bajo una <a href=\"http://mattermost.com\" target=\"_blank\">licencia comercial</a>\n de Mattermost, Inc. basado en tu nivel de subscripción y sujeto a los <a href=\"{terms}\" target=\"_blank\">Términos del Servicio.</a></p>\n <p>Los detalles de tu subscripción son los siguientes:</p>\n Nombre: {name}<br />\n Nombre de compañia u organización: {company}<br/>\n Cantidad de usuarios: {users}<br/>\n Licencia emitida por: {issued}<br/>\n Inicio de la licencia: {start}<br/>\n Fecha de expiración: {expires}<br/>\n LDAP: {ldap}<br/></div>",
|
"admin.license.enterpriseType": "<div><p>Esta versión compilada de la plataforma de Mattermost es proporcionada bajo una <a href=\"http://mattermost.com\" target=\"_blank\">licencia comercial</a> de Mattermost, Inc. basado en tu nivel de subscripción y sujeto a los <a href=\"{terms}\" target=\"_blank\">Términos del Servicio.</a></p><p>Los detalles de tu subscripción son los siguientes:</p>Nombre: {name}<br />Nombre de compañia u organización: {company}<br/>Cantidad de usuarios: {users}<br/>Licencia emitida por: {issued}<br/>Inicio de la licencia: {start}<br/>Fecha de expiración: {expires}<br/>LDAP: {ldap}<br/></div>",
|
||||||
"admin.license.key": "Llave de la Licencia: ",
|
"admin.license.key": "Llave de la Licencia: ",
|
||||||
"admin.license.keyRemove": "Remover la Licencia Enterprise y Degradar el Servidor",
|
"admin.license.keyRemove": "Remover la Licencia Enterprise y Degradar el Servidor",
|
||||||
"admin.license.removing": "Removiendo Licencia...",
|
"admin.license.removing": "Removiendo Licencia...",
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user