Merge pull request #2049 from mattermost/plt-1856
PLT-1856 Add basic server audit tab to system console for EE
Этот коммит содержится в:
29
api/admin.go
29
api/admin.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.
|
// See License.txt for license information.
|
||||||
|
|
||||||
package api
|
package api
|
||||||
@@ -21,6 +21,7 @@ func InitAdmin(r *mux.Router) {
|
|||||||
|
|
||||||
sr := r.PathPrefix("/admin").Subrouter()
|
sr := r.PathPrefix("/admin").Subrouter()
|
||||||
sr.Handle("/logs", ApiUserRequired(getLogs)).Methods("GET")
|
sr.Handle("/logs", ApiUserRequired(getLogs)).Methods("GET")
|
||||||
|
sr.Handle("/audits", ApiUserRequired(getAllAudits)).Methods("GET")
|
||||||
sr.Handle("/config", ApiUserRequired(getConfig)).Methods("GET")
|
sr.Handle("/config", ApiUserRequired(getConfig)).Methods("GET")
|
||||||
sr.Handle("/save_config", ApiUserRequired(saveConfig)).Methods("POST")
|
sr.Handle("/save_config", ApiUserRequired(saveConfig)).Methods("POST")
|
||||||
sr.Handle("/test_email", ApiUserRequired(testEmail)).Methods("POST")
|
sr.Handle("/test_email", ApiUserRequired(testEmail)).Methods("POST")
|
||||||
@@ -58,6 +59,32 @@ func getLogs(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
w.Write([]byte(model.ArrayToJson(lines)))
|
w.Write([]byte(model.ArrayToJson(lines)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getAllAudits(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
if !c.HasSystemAdminPermissions("getAllAudits") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if result := <-Srv.Store.Audit().Get("", 200); result.Err != nil {
|
||||||
|
c.Err = result.Err
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
audits := result.Data.(model.Audits)
|
||||||
|
etag := audits.Etag()
|
||||||
|
|
||||||
|
if HandleEtag(etag, w, r) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(etag) > 0 {
|
||||||
|
w.Header().Set(model.HEADER_ETAG_SERVER, etag)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write([]byte(audits.ToJson()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func getClientConfig(c *Context, w http.ResponseWriter, r *http.Request) {
|
func getClientConfig(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
w.Write([]byte(model.MapToJson(utils.ClientCfg)))
|
w.Write([]byte(model.MapToJson(utils.ClientCfg)))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,36 @@ func TestGetLogs(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetAllAudits(t *testing.T) {
|
||||||
|
Setup()
|
||||||
|
|
||||||
|
team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
|
||||||
|
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
|
||||||
|
|
||||||
|
user := &model.User{TeamId: team.Id, Email: model.NewId() + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "pwd"}
|
||||||
|
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
|
||||||
|
store.Must(Srv.Store.User().VerifyEmail(user.Id))
|
||||||
|
|
||||||
|
Client.LoginByEmail(team.Name, user.Email, "pwd")
|
||||||
|
|
||||||
|
if _, err := Client.GetAllAudits(); err == nil {
|
||||||
|
t.Fatal("Shouldn't have permissions")
|
||||||
|
}
|
||||||
|
|
||||||
|
c := &Context{}
|
||||||
|
c.RequestId = model.NewId()
|
||||||
|
c.IpAddress = "cmd_line"
|
||||||
|
UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN)
|
||||||
|
|
||||||
|
Client.LoginByEmail(team.Name, user.Email, "pwd")
|
||||||
|
|
||||||
|
if audits, err := Client.GetAllAudits(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if len(audits.Data.(model.Audits)) <= 0 {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetClientProperties(t *testing.T) {
|
func TestGetClientProperties(t *testing.T) {
|
||||||
Setup()
|
Setup()
|
||||||
|
|
||||||
|
|||||||
@@ -406,6 +406,15 @@ func (c *Client) GetLogs() (*Result, *AppError) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) GetAllAudits() (*Result, *AppError) {
|
||||||
|
if r, err := c.DoApiGet("/admin/audits", "", ""); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||||
|
r.Header.Get(HEADER_ETAG_SERVER), AuditsFromJson(r.Body)}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) GetClientProperties() (*Result, *AppError) {
|
func (c *Client) GetClientProperties() (*Result, *AppError) {
|
||||||
if r, err := c.DoApiGet("/admin/client_props", "", ""); err != nil {
|
if r, err := c.DoApiGet("/admin/client_props", "", ""); err != nil {
|
||||||
return nil, err
|
return nil, 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
|
||||||
@@ -72,9 +72,16 @@ func (s SqlAuditStore) Get(user_id string, limit int) StoreChannel {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
query := "SELECT * FROM Audits"
|
||||||
|
|
||||||
|
if len(user_id) != 0 {
|
||||||
|
query += " WHERE UserId = :user_id"
|
||||||
|
}
|
||||||
|
|
||||||
|
query += " ORDER BY CreateAt DESC LIMIT :limit"
|
||||||
|
|
||||||
var audits model.Audits
|
var audits model.Audits
|
||||||
if _, err := s.GetReplica().Select(&audits, "SELECT * FROM Audits WHERE UserId = :user_id ORDER BY CreateAt DESC LIMIT :limit",
|
if _, err := s.GetReplica().Select(&audits, query, map[string]interface{}{"user_id": user_id, "limit": limit}); err != nil {
|
||||||
map[string]interface{}{"user_id": user_id, "limit": limit}); err != nil {
|
|
||||||
result.Err = model.NewLocAppError("SqlAuditStore.Get", "store.sql_audit.get.finding.app_error", nil, "user_id="+user_id)
|
result.Err = model.NewLocAppError("SqlAuditStore.Get", "store.sql_audit.get.finding.app_error", nil, "user_id="+user_id)
|
||||||
} else {
|
} else {
|
||||||
result.Data = audits
|
result.Data = audits
|
||||||
|
|||||||
@@ -45,6 +45,14 @@ func TestSqlAuditStore(t *testing.T) {
|
|||||||
t.Fatal("Should have returned empty because user_id is missing")
|
t.Fatal("Should have returned empty because user_id is missing")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c = store.Audit().Get("", 100)
|
||||||
|
result = <-c
|
||||||
|
audits = result.Data.(model.Audits)
|
||||||
|
|
||||||
|
if len(audits) <= 4 {
|
||||||
|
t.Fatal("Failed to save and retrieve 4 audit logs")
|
||||||
|
}
|
||||||
|
|
||||||
if r2 := <-store.Audit().PermanentDeleteByUser(audit.UserId); r2.Err != nil {
|
if r2 := <-store.Audit().PermanentDeleteByUser(audit.UserId); r2.Err != nil {
|
||||||
t.Fatal(r2.Err)
|
t.Fatal(r2.Err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,204 +1,24 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
var Modal = ReactBootstrap.Modal;
|
var Modal = ReactBootstrap.Modal;
|
||||||
import UserStore from '../stores/user_store.jsx';
|
|
||||||
import ChannelStore from '../stores/channel_store.jsx';
|
|
||||||
import * as AsyncClient from '../utils/async_client.jsx';
|
|
||||||
import LoadingScreen from './loading_screen.jsx';
|
import LoadingScreen from './loading_screen.jsx';
|
||||||
|
import AuditTable from './audit_table.jsx';
|
||||||
|
|
||||||
|
import UserStore from '../stores/user_store.jsx';
|
||||||
|
|
||||||
|
import * as AsyncClient from '../utils/async_client.jsx';
|
||||||
import * as Utils from '../utils/utils.jsx';
|
import * as Utils from '../utils/utils.jsx';
|
||||||
|
|
||||||
import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
|
import {intlShape, injectIntl, FormattedMessage} from 'mm-intl';
|
||||||
|
|
||||||
const holders = defineMessages({
|
|
||||||
sessionRevoked: {
|
|
||||||
id: 'access_history.sessionRevoked',
|
|
||||||
defaultMessage: 'The session with id {sessionId} was revoked'
|
|
||||||
},
|
|
||||||
channelCreated: {
|
|
||||||
id: 'access_history.channelCreated',
|
|
||||||
defaultMessage: 'Created the {channelName} channel/group'
|
|
||||||
},
|
|
||||||
establishedDM: {
|
|
||||||
id: 'access_history.establishedDM',
|
|
||||||
defaultMessage: 'Established a direct message channel with {username}'
|
|
||||||
},
|
|
||||||
nameUpdated: {
|
|
||||||
id: 'access_history.nameUpdated',
|
|
||||||
defaultMessage: 'Updated the {channelName} channel/group name'
|
|
||||||
},
|
|
||||||
headerUpdated: {
|
|
||||||
id: 'access_history.headerUpdated',
|
|
||||||
defaultMessage: 'Updated the {channelName} channel/group header'
|
|
||||||
},
|
|
||||||
channelDeleted: {
|
|
||||||
id: 'access_history.channelDeleted',
|
|
||||||
defaultMessage: 'Deleted the channel/group with the URL {url}'
|
|
||||||
},
|
|
||||||
userAdded: {
|
|
||||||
id: 'access_history.userAdded',
|
|
||||||
defaultMessage: 'Added {username} to the {channelName} channel/group'
|
|
||||||
},
|
|
||||||
userRemoved: {
|
|
||||||
id: 'access_history.userRemoved',
|
|
||||||
defaultMessage: 'Removed {username} to the {channelName} channel/group'
|
|
||||||
},
|
|
||||||
attemptedRegisterApp: {
|
|
||||||
id: 'access_history.attemptedRegisterApp',
|
|
||||||
defaultMessage: 'Attempted to register a new OAuth Application with ID {id}'
|
|
||||||
},
|
|
||||||
attemptedAllowOAuthAccess: {
|
|
||||||
id: 'access_history.attemptedAllowOAuthAccess',
|
|
||||||
defaultMessage: 'Attempted to allow a new OAuth service access'
|
|
||||||
},
|
|
||||||
successfullOAuthAccess: {
|
|
||||||
id: 'access_history.successfullOAuthAccess',
|
|
||||||
defaultMessage: 'Successfully gave a new OAuth service access'
|
|
||||||
},
|
|
||||||
failedOAuthAccess: {
|
|
||||||
id: 'access_history.failedOAuthAccess',
|
|
||||||
defaultMessage: 'Failed to allow a new OAuth service access - the redirect URI did not match the previously registered callback'
|
|
||||||
},
|
|
||||||
attemptedOAuthToken: {
|
|
||||||
id: 'access_history.attemptedOAuthToken',
|
|
||||||
defaultMessage: 'Attempted to get an OAuth access token'
|
|
||||||
},
|
|
||||||
successfullOAuthToken: {
|
|
||||||
id: 'access_history.successfullOAuthToken',
|
|
||||||
defaultMessage: 'Successfully added a new OAuth service'
|
|
||||||
},
|
|
||||||
oauthTokenFailed: {
|
|
||||||
id: 'access_history.oauthTokenFailed',
|
|
||||||
defaultMessage: 'Failed to get an OAuth access token - {token}'
|
|
||||||
},
|
|
||||||
attemptedLogin: {
|
|
||||||
id: 'access_history.attemptedLogin',
|
|
||||||
defaultMessage: 'Attempted to login'
|
|
||||||
},
|
|
||||||
successfullLogin: {
|
|
||||||
id: 'access_history.successfullLogin',
|
|
||||||
defaultMessage: 'Successfully logged in'
|
|
||||||
},
|
|
||||||
failedLogin: {
|
|
||||||
id: 'access_history.failedLogin',
|
|
||||||
defaultMessage: 'FAILED login attempt'
|
|
||||||
},
|
|
||||||
updatePicture: {
|
|
||||||
id: 'access_history.updatePicture',
|
|
||||||
defaultMessage: 'Updated your profile picture'
|
|
||||||
},
|
|
||||||
updateGeneral: {
|
|
||||||
id: 'access_history.updateGeneral',
|
|
||||||
defaultMessage: 'Updated the general settings of your account'
|
|
||||||
},
|
|
||||||
attemptedPassword: {
|
|
||||||
id: 'access_history.attemptedPassword',
|
|
||||||
defaultMessage: 'Attempted to change password'
|
|
||||||
},
|
|
||||||
successfullPassword: {
|
|
||||||
id: 'access_history.successfullPassword',
|
|
||||||
defaultMessage: 'Successfully changed password'
|
|
||||||
},
|
|
||||||
failedPassword: {
|
|
||||||
id: 'access_history.failedPassword',
|
|
||||||
defaultMessage: 'Failed to change password - tried to update user password who was logged in through oauth'
|
|
||||||
},
|
|
||||||
updatedRol: {
|
|
||||||
id: 'access_history.updatedRol',
|
|
||||||
defaultMessage: 'Updated user role(s) to '
|
|
||||||
},
|
|
||||||
member: {
|
|
||||||
id: 'access_history.member',
|
|
||||||
defaultMessage: 'member'
|
|
||||||
},
|
|
||||||
accountActive: {
|
|
||||||
id: 'access_history.accountActive',
|
|
||||||
defaultMessage: 'Account made active'
|
|
||||||
},
|
|
||||||
accountInactive: {
|
|
||||||
id: 'access_history.accountInactive',
|
|
||||||
defaultMessage: 'Account made inactive'
|
|
||||||
},
|
|
||||||
by: {
|
|
||||||
id: 'access_history.by',
|
|
||||||
defaultMessage: ' by {username}'
|
|
||||||
},
|
|
||||||
byAdmin: {
|
|
||||||
id: 'access_history.byAdmin',
|
|
||||||
defaultMessage: ' by an admin'
|
|
||||||
},
|
|
||||||
sentEmail: {
|
|
||||||
id: 'access_history.sentEmail',
|
|
||||||
defaultMessage: 'Sent an email to {email} to reset your password'
|
|
||||||
},
|
|
||||||
attemptedReset: {
|
|
||||||
id: 'access_history.attemptedReset',
|
|
||||||
defaultMessage: 'Attempted to reset password'
|
|
||||||
},
|
|
||||||
successfullReset: {
|
|
||||||
id: 'access_history.successfullReset',
|
|
||||||
defaultMessage: 'Successfully reset password'
|
|
||||||
},
|
|
||||||
updateGlobalNotifications: {
|
|
||||||
id: 'access_history.updateGlobalNotifications',
|
|
||||||
defaultMessage: 'Updated your global notification settings'
|
|
||||||
},
|
|
||||||
attemptedWebhookCreate: {
|
|
||||||
id: 'access_history.attemptedWebhookCreate',
|
|
||||||
defaultMessage: 'Attempted to create a webhook'
|
|
||||||
},
|
|
||||||
succcessfullWebhookCreate: {
|
|
||||||
id: 'access_history.successfullWebhookCreate',
|
|
||||||
defaultMessage: 'Successfully created a webhook'
|
|
||||||
},
|
|
||||||
failedWebhookCreate: {
|
|
||||||
id: 'access_history.failedWebhookCreate',
|
|
||||||
defaultMessage: 'Failed to create a webhook - bad channel permissions'
|
|
||||||
},
|
|
||||||
attemptedWebhookDelete: {
|
|
||||||
id: 'access_history.attemptedWebhookDelete',
|
|
||||||
defaultMessage: 'Attempted to delete a webhook'
|
|
||||||
},
|
|
||||||
successfullWebhookDelete: {
|
|
||||||
id: 'access_history.successfullWebhookDelete',
|
|
||||||
defaultMessage: 'Successfully deleted a webhook'
|
|
||||||
},
|
|
||||||
failedWebhookDelete: {
|
|
||||||
id: 'access_history.failedWebhookDelete',
|
|
||||||
defaultMessage: 'Failed to delete a webhook - inappropriate conditions'
|
|
||||||
},
|
|
||||||
logout: {
|
|
||||||
id: 'access_history.logout',
|
|
||||||
defaultMessage: 'Logged out of your account'
|
|
||||||
},
|
|
||||||
verified: {
|
|
||||||
id: 'access_history.verified',
|
|
||||||
defaultMessage: 'Sucessfully verified your email address'
|
|
||||||
},
|
|
||||||
revokedAll: {
|
|
||||||
id: 'access_history.revokedAll',
|
|
||||||
defaultMessage: 'Revoked all current sessions for the team'
|
|
||||||
},
|
|
||||||
loginAttempt: {
|
|
||||||
id: 'access_history.loginAttempt',
|
|
||||||
defaultMessage: ' (Login attempt)'
|
|
||||||
},
|
|
||||||
loginFailure: {
|
|
||||||
id: 'access_history.loginFailure',
|
|
||||||
defaultMessage: ' (Login failure)'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
class AccessHistoryModal extends React.Component {
|
class AccessHistoryModal extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.onAuditChange = this.onAuditChange.bind(this);
|
this.onAuditChange = this.onAuditChange.bind(this);
|
||||||
this.handleMoreInfo = this.handleMoreInfo.bind(this);
|
|
||||||
this.onShow = this.onShow.bind(this);
|
this.onShow = this.onShow.bind(this);
|
||||||
this.onHide = this.onHide.bind(this);
|
this.onHide = this.onHide.bind(this);
|
||||||
this.formatAuditInfo = this.formatAuditInfo.bind(this);
|
|
||||||
this.handleRevokedSession = this.handleRevokedSession.bind(this);
|
|
||||||
|
|
||||||
const state = this.getStateFromStoresForAudits();
|
const state = this.getStateFromStoresForAudits();
|
||||||
state.moreInfo = [];
|
state.moreInfo = [];
|
||||||
@@ -245,359 +65,17 @@ class AccessHistoryModal extends React.Component {
|
|||||||
this.setState(newState);
|
this.setState(newState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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);
|
|
||||||
const 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'}) + ' | ' + currentAuditDesc;
|
|
||||||
return currentAuditInfo;
|
|
||||||
}
|
|
||||||
render() {
|
render() {
|
||||||
var accessList = [];
|
|
||||||
|
|
||||||
const {formatMessage} = this.props.intl;
|
|
||||||
for (var i = 0; i < this.state.audits.length; i++) {
|
|
||||||
const currentAudit = this.state.audits[i];
|
|
||||||
const currentAuditInfo = this.formatAuditInfo(currentAudit);
|
|
||||||
|
|
||||||
var moreInfo = (
|
|
||||||
<a
|
|
||||||
href='#'
|
|
||||||
className='theme'
|
|
||||||
onClick={this.handleMoreInfo.bind(this, i)}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id='access_history.moreInfo'
|
|
||||||
defaultMessage='More info'
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (this.state.moreInfo[i]) {
|
|
||||||
if (!currentAudit.session_id) {
|
|
||||||
currentAudit.session_id = 'N/A';
|
|
||||||
|
|
||||||
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='access_history.ip'
|
|
||||||
defaultMessage='IP: {ip}'
|
|
||||||
values={{
|
|
||||||
ip: currentAudit.ip_address
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<FormattedMessage
|
|
||||||
id='access_history.session'
|
|
||||||
defaultMessage='Session ID: {id}'
|
|
||||||
values={{
|
|
||||||
id: currentAudit.session_id
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
var divider = null;
|
|
||||||
if (i < this.state.audits.length - 1) {
|
|
||||||
divider = (<div className='divider-light'></div>);
|
|
||||||
}
|
|
||||||
|
|
||||||
accessList[i] = (
|
|
||||||
<div
|
|
||||||
key={'accessHistoryEntryKey' + i}
|
|
||||||
className='access-history__table'
|
|
||||||
>
|
|
||||||
<div className='access__report'>
|
|
||||||
<div className='report__time'>{currentAuditInfo}</div>
|
|
||||||
<div className='report__info'>
|
|
||||||
{moreInfo}
|
|
||||||
</div>
|
|
||||||
{divider}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
var content;
|
var content;
|
||||||
if (this.state.audits.loading) {
|
if (this.state.audits.loading) {
|
||||||
content = (<LoadingScreen />);
|
content = (<LoadingScreen />);
|
||||||
} else {
|
} else {
|
||||||
content = (<form role='form'>{accessList}</form>);
|
content = (
|
||||||
|
<AuditTable
|
||||||
|
audits={this.state.audits}
|
||||||
|
moreInfo={this.state.moreInfo}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -628,4 +106,4 @@ AccessHistoryModal.propTypes = {
|
|||||||
onHide: React.PropTypes.func.isRequired
|
onHide: React.PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default injectIntl(AccessHistoryModal);
|
export default injectIntl(AccessHistoryModal);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import * as Utils from '../../utils/utils.jsx';
|
|||||||
import EmailSettingsTab from './email_settings.jsx';
|
import EmailSettingsTab from './email_settings.jsx';
|
||||||
import LogSettingsTab from './log_settings.jsx';
|
import LogSettingsTab from './log_settings.jsx';
|
||||||
import LogsTab from './logs.jsx';
|
import LogsTab from './logs.jsx';
|
||||||
|
import AuditsTab from './audits.jsx';
|
||||||
import FileSettingsTab from './image_settings.jsx';
|
import FileSettingsTab from './image_settings.jsx';
|
||||||
import PrivacySettingsTab from './privacy_settings.jsx';
|
import PrivacySettingsTab from './privacy_settings.jsx';
|
||||||
import RateSettingsTab from './rate_settings.jsx';
|
import RateSettingsTab from './rate_settings.jsx';
|
||||||
@@ -138,6 +139,8 @@ export default class AdminController extends React.Component {
|
|||||||
tab = <LogSettingsTab config={this.state.config} />;
|
tab = <LogSettingsTab config={this.state.config} />;
|
||||||
} else if (this.state.selected === 'logs') {
|
} else if (this.state.selected === 'logs') {
|
||||||
tab = <LogsTab />;
|
tab = <LogsTab />;
|
||||||
|
} else if (this.state.selected === 'audits') {
|
||||||
|
tab = <AuditsTab />;
|
||||||
} else if (this.state.selected === 'image_settings') {
|
} else if (this.state.selected === 'image_settings') {
|
||||||
tab = <FileSettingsTab config={this.state.config} />;
|
tab = <FileSettingsTab config={this.state.config} />;
|
||||||
} else if (this.state.selected === 'privacy_settings') {
|
} else if (this.state.selected === 'privacy_settings') {
|
||||||
|
|||||||
@@ -214,6 +214,24 @@ export default class AdminSidebar extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let audits;
|
||||||
|
if (global.window.mm_license.IsLicensed === 'true') {
|
||||||
|
audits = (
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href='#'
|
||||||
|
className={this.isSelected('audits')}
|
||||||
|
onClick={this.handleClick.bind(this, 'audits', null)}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.sidebar.audits'
|
||||||
|
defaultMessage='Audits'
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='sidebar--left sidebar--collapsable'>
|
<div className='sidebar--left sidebar--collapsable'>
|
||||||
<div>
|
<div>
|
||||||
@@ -448,6 +466,7 @@ export default class AdminSidebar extends React.Component {
|
|||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
{audits}
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
94
web/react/components/admin_console/audits.jsx
Обычный файл
94
web/react/components/admin_console/audits.jsx
Обычный файл
@@ -0,0 +1,94 @@
|
|||||||
|
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
import LoadingScreen from '../loading_screen.jsx';
|
||||||
|
import AuditTable from '../audit_table.jsx';
|
||||||
|
|
||||||
|
import AdminStore from '../../stores/admin_store.jsx';
|
||||||
|
|
||||||
|
import * as AsyncClient from '../../utils/async_client.jsx';
|
||||||
|
|
||||||
|
import {FormattedMessage} from 'mm-intl';
|
||||||
|
|
||||||
|
export default class Audits extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.onAuditListenerChange = this.onAuditListenerChange.bind(this);
|
||||||
|
this.reload = this.reload.bind(this);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
audits: AdminStore.getAudits()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
AdminStore.addAuditChangeListener(this.onAuditListenerChange);
|
||||||
|
AsyncClient.getServerAudits();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
AdminStore.removeAuditChangeListener(this.onAuditListenerChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
onAuditListenerChange() {
|
||||||
|
this.setState({
|
||||||
|
audits: AdminStore.getAudits()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
reload() {
|
||||||
|
AdminStore.saveAudits(null);
|
||||||
|
this.setState({
|
||||||
|
audits: null
|
||||||
|
});
|
||||||
|
|
||||||
|
AsyncClient.getServerAudits();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
var content = null;
|
||||||
|
|
||||||
|
if (global.window.mm_license.IsLicensed !== 'true') {
|
||||||
|
return <div/>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.state.audits === null) {
|
||||||
|
content = <LoadingScreen />;
|
||||||
|
} else {
|
||||||
|
content = (
|
||||||
|
<div style={{margin: '10px'}}>
|
||||||
|
<AuditTable
|
||||||
|
audits={this.state.audits}
|
||||||
|
oneLine={true}
|
||||||
|
showUserId={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='panel'>
|
||||||
|
<h3>
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.audits.title'
|
||||||
|
defaultMessage='Server Audits'
|
||||||
|
/>
|
||||||
|
</h3>
|
||||||
|
<button
|
||||||
|
type='submit'
|
||||||
|
className='btn btn-primary'
|
||||||
|
onClick={this.reload}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.audits.reload'
|
||||||
|
defaultMessage='Reload'
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<div className='log__panel'>
|
||||||
|
{content}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
571
web/react/components/audit_table.jsx
Обычный файл
571
web/react/components/audit_table.jsx
Обычный файл
@@ -0,0 +1,571 @@
|
|||||||
|
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
import UserStore from '../stores/user_store.jsx';
|
||||||
|
import ChannelStore from '../stores/channel_store.jsx';
|
||||||
|
import * as Utils from '../utils/utils.jsx';
|
||||||
|
|
||||||
|
import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
|
||||||
|
|
||||||
|
const holders = defineMessages({
|
||||||
|
sessionRevoked: {
|
||||||
|
id: 'audit_table.sessionRevoked',
|
||||||
|
defaultMessage: 'The session with id {sessionId} was revoked'
|
||||||
|
},
|
||||||
|
channelCreated: {
|
||||||
|
id: 'audit_table.channelCreated',
|
||||||
|
defaultMessage: 'Created the {channelName} channel/group'
|
||||||
|
},
|
||||||
|
establishedDM: {
|
||||||
|
id: 'audit_table.establishedDM',
|
||||||
|
defaultMessage: 'Established a direct message channel with {username}'
|
||||||
|
},
|
||||||
|
nameUpdated: {
|
||||||
|
id: 'audit_table.nameUpdated',
|
||||||
|
defaultMessage: 'Updated the {channelName} channel/group name'
|
||||||
|
},
|
||||||
|
headerUpdated: {
|
||||||
|
id: 'audit_table.headerUpdated',
|
||||||
|
defaultMessage: 'Updated the {channelName} channel/group header'
|
||||||
|
},
|
||||||
|
channelDeleted: {
|
||||||
|
id: 'audit_table.channelDeleted',
|
||||||
|
defaultMessage: 'Deleted the channel/group with the URL {url}'
|
||||||
|
},
|
||||||
|
userAdded: {
|
||||||
|
id: 'audit_table.userAdded',
|
||||||
|
defaultMessage: 'Added {username} to the {channelName} channel/group'
|
||||||
|
},
|
||||||
|
userRemoved: {
|
||||||
|
id: 'audit_table.userRemoved',
|
||||||
|
defaultMessage: 'Removed {username} to the {channelName} channel/group'
|
||||||
|
},
|
||||||
|
attemptedRegisterApp: {
|
||||||
|
id: 'audit_table.attemptedRegisterApp',
|
||||||
|
defaultMessage: 'Attempted to register a new OAuth Application with ID {id}'
|
||||||
|
},
|
||||||
|
attemptedAllowOAuthAccess: {
|
||||||
|
id: 'audit_table.attemptedAllowOAuthAccess',
|
||||||
|
defaultMessage: 'Attempted to allow a new OAuth service access'
|
||||||
|
},
|
||||||
|
successfullOAuthAccess: {
|
||||||
|
id: 'audit_table.successfullOAuthAccess',
|
||||||
|
defaultMessage: 'Successfully gave a new OAuth service access'
|
||||||
|
},
|
||||||
|
failedOAuthAccess: {
|
||||||
|
id: 'audit_table.failedOAuthAccess',
|
||||||
|
defaultMessage: 'Failed to allow a new OAuth service access - the redirect URI did not match the previously registered callback'
|
||||||
|
},
|
||||||
|
attemptedOAuthToken: {
|
||||||
|
id: 'audit_table.attemptedOAuthToken',
|
||||||
|
defaultMessage: 'Attempted to get an OAuth access token'
|
||||||
|
},
|
||||||
|
successfullOAuthToken: {
|
||||||
|
id: 'audit_table.successfullOAuthToken',
|
||||||
|
defaultMessage: 'Successfully added a new OAuth service'
|
||||||
|
},
|
||||||
|
oauthTokenFailed: {
|
||||||
|
id: 'audit_table.oauthTokenFailed',
|
||||||
|
defaultMessage: 'Failed to get an OAuth access token - {token}'
|
||||||
|
},
|
||||||
|
attemptedLogin: {
|
||||||
|
id: 'audit_table.attemptedLogin',
|
||||||
|
defaultMessage: 'Attempted to login'
|
||||||
|
},
|
||||||
|
successfullLogin: {
|
||||||
|
id: 'audit_table.successfullLogin',
|
||||||
|
defaultMessage: 'Successfully logged in'
|
||||||
|
},
|
||||||
|
failedLogin: {
|
||||||
|
id: 'audit_table.failedLogin',
|
||||||
|
defaultMessage: 'FAILED login attempt'
|
||||||
|
},
|
||||||
|
updatePicture: {
|
||||||
|
id: 'audit_table.updatePicture',
|
||||||
|
defaultMessage: 'Updated your profile picture'
|
||||||
|
},
|
||||||
|
updateGeneral: {
|
||||||
|
id: 'audit_table.updateGeneral',
|
||||||
|
defaultMessage: 'Updated the general settings of your account'
|
||||||
|
},
|
||||||
|
attemptedPassword: {
|
||||||
|
id: 'audit_table.attemptedPassword',
|
||||||
|
defaultMessage: 'Attempted to change password'
|
||||||
|
},
|
||||||
|
successfullPassword: {
|
||||||
|
id: 'audit_table.successfullPassword',
|
||||||
|
defaultMessage: 'Successfully changed password'
|
||||||
|
},
|
||||||
|
failedPassword: {
|
||||||
|
id: 'audit_table.failedPassword',
|
||||||
|
defaultMessage: 'Failed to change password - tried to update user password who was logged in through oauth'
|
||||||
|
},
|
||||||
|
updatedRol: {
|
||||||
|
id: 'audit_table.updatedRol',
|
||||||
|
defaultMessage: 'Updated user role(s) to '
|
||||||
|
},
|
||||||
|
member: {
|
||||||
|
id: 'audit_table.member',
|
||||||
|
defaultMessage: 'member'
|
||||||
|
},
|
||||||
|
accountActive: {
|
||||||
|
id: 'audit_table.accountActive',
|
||||||
|
defaultMessage: 'Account made active'
|
||||||
|
},
|
||||||
|
accountInactive: {
|
||||||
|
id: 'audit_table.accountInactive',
|
||||||
|
defaultMessage: 'Account made inactive'
|
||||||
|
},
|
||||||
|
by: {
|
||||||
|
id: 'audit_table.by',
|
||||||
|
defaultMessage: ' by {username}'
|
||||||
|
},
|
||||||
|
byAdmin: {
|
||||||
|
id: 'audit_table.byAdmin',
|
||||||
|
defaultMessage: ' by an admin'
|
||||||
|
},
|
||||||
|
sentEmail: {
|
||||||
|
id: 'audit_table.sentEmail',
|
||||||
|
defaultMessage: 'Sent an email to {email} to reset your password'
|
||||||
|
},
|
||||||
|
attemptedReset: {
|
||||||
|
id: 'audit_table.attemptedReset',
|
||||||
|
defaultMessage: 'Attempted to reset password'
|
||||||
|
},
|
||||||
|
successfullReset: {
|
||||||
|
id: 'audit_table.successfullReset',
|
||||||
|
defaultMessage: 'Successfully reset password'
|
||||||
|
},
|
||||||
|
updateGlobalNotifications: {
|
||||||
|
id: 'audit_table.updateGlobalNotifications',
|
||||||
|
defaultMessage: 'Updated your global notification settings'
|
||||||
|
},
|
||||||
|
attemptedWebhookCreate: {
|
||||||
|
id: 'audit_table.attemptedWebhookCreate',
|
||||||
|
defaultMessage: 'Attempted to create a webhook'
|
||||||
|
},
|
||||||
|
succcessfullWebhookCreate: {
|
||||||
|
id: 'audit_table.successfullWebhookCreate',
|
||||||
|
defaultMessage: 'Successfully created a webhook'
|
||||||
|
},
|
||||||
|
failedWebhookCreate: {
|
||||||
|
id: 'audit_table.failedWebhookCreate',
|
||||||
|
defaultMessage: 'Failed to create a webhook - bad channel permissions'
|
||||||
|
},
|
||||||
|
attemptedWebhookDelete: {
|
||||||
|
id: 'audit_table.attemptedWebhookDelete',
|
||||||
|
defaultMessage: 'Attempted to delete a webhook'
|
||||||
|
},
|
||||||
|
successfullWebhookDelete: {
|
||||||
|
id: 'audit_table.successfullWebhookDelete',
|
||||||
|
defaultMessage: 'Successfully deleted a webhook'
|
||||||
|
},
|
||||||
|
failedWebhookDelete: {
|
||||||
|
id: 'audit_table.failedWebhookDelete',
|
||||||
|
defaultMessage: 'Failed to delete a webhook - inappropriate conditions'
|
||||||
|
},
|
||||||
|
logout: {
|
||||||
|
id: 'audit_table.logout',
|
||||||
|
defaultMessage: 'Logged out of your account'
|
||||||
|
},
|
||||||
|
verified: {
|
||||||
|
id: 'audit_table.verified',
|
||||||
|
defaultMessage: 'Sucessfully verified your email address'
|
||||||
|
},
|
||||||
|
revokedAll: {
|
||||||
|
id: 'audit_table.revokedAll',
|
||||||
|
defaultMessage: 'Revoked all current sessions for the team'
|
||||||
|
},
|
||||||
|
loginAttempt: {
|
||||||
|
id: 'audit_table.loginAttempt',
|
||||||
|
defaultMessage: ' (Login attempt)'
|
||||||
|
},
|
||||||
|
loginFailure: {
|
||||||
|
id: 'audit_table.loginFailure',
|
||||||
|
defaultMessage: ' (Login failure)'
|
||||||
|
},
|
||||||
|
userId: {
|
||||||
|
id: 'audit_table.userId',
|
||||||
|
defaultMessage: 'User ID'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
class AuditTable extends React.Component {
|
||||||
|
constructor(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() {
|
||||||
|
var accessList = [];
|
||||||
|
|
||||||
|
const {formatMessage} = this.props.intl;
|
||||||
|
for (var i = 0; i < this.props.audits.length; i++) {
|
||||||
|
const currentAudit = this.props.audits[i];
|
||||||
|
const currentAuditInfo = this.formatAuditInfo(currentAudit);
|
||||||
|
|
||||||
|
let moreInfo;
|
||||||
|
if (!this.props.oneLine) {
|
||||||
|
moreInfo = (
|
||||||
|
<a
|
||||||
|
href='#'
|
||||||
|
className='theme'
|
||||||
|
onClick={this.handleMoreInfo.bind(this, i)}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id='audit_table.moreInfo'
|
||||||
|
defaultMessage='More info'
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.state.moreInfo[i]) {
|
||||||
|
if (!currentAudit.session_id) {
|
||||||
|
currentAudit.session_id = 'N/A';
|
||||||
|
|
||||||
|
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;
|
||||||
|
if (i < this.props.audits.length - 1) {
|
||||||
|
divider = (<div className='divider-light'></div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
accessList[i] = (
|
||||||
|
<div
|
||||||
|
key={'accessHistoryEntryKey' + i}
|
||||||
|
className='access-history__table'
|
||||||
|
>
|
||||||
|
<div className='access__report'>
|
||||||
|
<div className='report__time'>{currentAuditInfo}</div>
|
||||||
|
<div className='report__info'>
|
||||||
|
{moreInfo}
|
||||||
|
</div>
|
||||||
|
{divider}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <form role='form'>{accessList}</form>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AuditTable.propTypes = {
|
||||||
|
intl: intlShape.isRequired,
|
||||||
|
audits: React.PropTypes.array.isRequired,
|
||||||
|
oneLine: React.PropTypes.bool,
|
||||||
|
showUserId: React.PropTypes.bool
|
||||||
|
};
|
||||||
|
|
||||||
|
export default injectIntl(AuditTable);
|
||||||
@@ -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.
|
||||||
|
|
||||||
import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
|
import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
|
||||||
@@ -10,6 +10,7 @@ import Constants from '../utils/constants.jsx';
|
|||||||
const ActionTypes = Constants.ActionTypes;
|
const ActionTypes = Constants.ActionTypes;
|
||||||
|
|
||||||
const LOG_CHANGE_EVENT = 'log_change';
|
const LOG_CHANGE_EVENT = 'log_change';
|
||||||
|
const SERVER_AUDIT_CHANGE_EVENT = 'server_audit_change';
|
||||||
const CONFIG_CHANGE_EVENT = 'config_change';
|
const CONFIG_CHANGE_EVENT = 'config_change';
|
||||||
const ALL_TEAMS_EVENT = 'all_team_change';
|
const ALL_TEAMS_EVENT = 'all_team_change';
|
||||||
|
|
||||||
@@ -18,6 +19,7 @@ class AdminStoreClass extends EventEmitter {
|
|||||||
super();
|
super();
|
||||||
|
|
||||||
this.logs = null;
|
this.logs = null;
|
||||||
|
this.audits = null;
|
||||||
this.config = null;
|
this.config = null;
|
||||||
this.teams = null;
|
this.teams = null;
|
||||||
|
|
||||||
@@ -25,6 +27,10 @@ class AdminStoreClass extends EventEmitter {
|
|||||||
this.addLogChangeListener = this.addLogChangeListener.bind(this);
|
this.addLogChangeListener = this.addLogChangeListener.bind(this);
|
||||||
this.removeLogChangeListener = this.removeLogChangeListener.bind(this);
|
this.removeLogChangeListener = this.removeLogChangeListener.bind(this);
|
||||||
|
|
||||||
|
this.emitAuditChange = this.emitAuditChange.bind(this);
|
||||||
|
this.addAuditChangeListener = this.addAuditChangeListener.bind(this);
|
||||||
|
this.removeAuditChangeListener = this.removeAuditChangeListener.bind(this);
|
||||||
|
|
||||||
this.emitConfigChange = this.emitConfigChange.bind(this);
|
this.emitConfigChange = this.emitConfigChange.bind(this);
|
||||||
this.addConfigChangeListener = this.addConfigChangeListener.bind(this);
|
this.addConfigChangeListener = this.addConfigChangeListener.bind(this);
|
||||||
this.removeConfigChangeListener = this.removeConfigChangeListener.bind(this);
|
this.removeConfigChangeListener = this.removeConfigChangeListener.bind(this);
|
||||||
@@ -46,6 +52,18 @@ class AdminStoreClass extends EventEmitter {
|
|||||||
this.removeListener(LOG_CHANGE_EVENT, callback);
|
this.removeListener(LOG_CHANGE_EVENT, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
emitAuditChange() {
|
||||||
|
this.emit(SERVER_AUDIT_CHANGE_EVENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
addAuditChangeListener(callback) {
|
||||||
|
this.on(SERVER_AUDIT_CHANGE_EVENT, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeAuditChangeListener(callback) {
|
||||||
|
this.removeListener(SERVER_AUDIT_CHANGE_EVENT, callback);
|
||||||
|
}
|
||||||
|
|
||||||
emitConfigChange() {
|
emitConfigChange() {
|
||||||
this.emit(CONFIG_CHANGE_EVENT);
|
this.emit(CONFIG_CHANGE_EVENT);
|
||||||
}
|
}
|
||||||
@@ -78,6 +96,14 @@ class AdminStoreClass extends EventEmitter {
|
|||||||
this.logs = logs;
|
this.logs = logs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getAudits() {
|
||||||
|
return this.audits;
|
||||||
|
}
|
||||||
|
|
||||||
|
saveAudits(audits) {
|
||||||
|
this.audits = audits;
|
||||||
|
}
|
||||||
|
|
||||||
getConfig() {
|
getConfig() {
|
||||||
return this.config;
|
return this.config;
|
||||||
}
|
}
|
||||||
@@ -113,6 +139,10 @@ AdminStoreClass.dispatchToken = AppDispatcher.register((payload) => {
|
|||||||
AdminStore.saveLogs(action.logs);
|
AdminStore.saveLogs(action.logs);
|
||||||
AdminStore.emitLogChange();
|
AdminStore.emitLogChange();
|
||||||
break;
|
break;
|
||||||
|
case ActionTypes.RECIEVED_SERVER_AUDITS:
|
||||||
|
AdminStore.saveAudits(action.audits);
|
||||||
|
AdminStore.emitAuditChange();
|
||||||
|
break;
|
||||||
case ActionTypes.RECIEVED_CONFIG:
|
case ActionTypes.RECIEVED_CONFIG:
|
||||||
AdminStore.saveConfig(action.config);
|
AdminStore.saveConfig(action.config);
|
||||||
AdminStore.emitConfigChange();
|
AdminStore.emitConfigChange();
|
||||||
|
|||||||
@@ -312,6 +312,32 @@ export function getLogs() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getServerAudits() {
|
||||||
|
if (isCallInProgress('getServerAudits')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
callTracker.getServerAudits = utils.getTimestamp();
|
||||||
|
client.getServerAudits(
|
||||||
|
(data, textStatus, xhr) => {
|
||||||
|
callTracker.getServerAudits = 0;
|
||||||
|
|
||||||
|
if (xhr.status === 304 || !data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AppDispatcher.handleServerAction({
|
||||||
|
type: ActionTypes.RECIEVED_SERVER_AUDITS,
|
||||||
|
audits: data
|
||||||
|
});
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
callTracker.getServerAudits = 0;
|
||||||
|
dispatchError(err, 'getServerAudits');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function getConfig() {
|
export function getConfig() {
|
||||||
if (isCallInProgress('getConfig')) {
|
if (isCallInProgress('getConfig')) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -407,6 +407,20 @@ export function getLogs(success, error) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getServerAudits(success, error) {
|
||||||
|
$.ajax({
|
||||||
|
url: '/api/v1/admin/audits',
|
||||||
|
dataType: 'json',
|
||||||
|
contentType: 'application/json',
|
||||||
|
type: 'GET',
|
||||||
|
success,
|
||||||
|
error: function onError(xhr, status, err) {
|
||||||
|
var e = handleError('getServerAudits', xhr, status, err);
|
||||||
|
error(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function getConfig(success, error) {
|
export function getConfig(success, error) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: '/api/v1/admin/config',
|
url: '/api/v1/admin/config',
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ export default {
|
|||||||
|
|
||||||
RECIEVED_CONFIG: null,
|
RECIEVED_CONFIG: null,
|
||||||
RECIEVED_LOGS: null,
|
RECIEVED_LOGS: null,
|
||||||
|
RECIEVED_SERVER_AUDITS: null,
|
||||||
RECIEVED_ALL_TEAMS: null,
|
RECIEVED_ALL_TEAMS: null,
|
||||||
|
|
||||||
SHOW_SEARCH: null,
|
SHOW_SEARCH: null,
|
||||||
|
|||||||
@@ -8,53 +8,54 @@
|
|||||||
"about.date": "Build Date:",
|
"about.date": "Build Date:",
|
||||||
"about.hash": "Build Hash:",
|
"about.hash": "Build Hash:",
|
||||||
"about.close": "Close",
|
"about.close": "Close",
|
||||||
"access_history.sessionRevoked": "The session with id {sessionId} was revoked",
|
"audit_table.sessionRevoked": "The session with id {sessionId} was revoked",
|
||||||
"access_history.channelCreated": "Created the {channelName} channel/group",
|
"audit_table.channelCreated": "Created the {channelName} channel/group",
|
||||||
"access_history.establishedDM": "Established a direct message channel with {username}",
|
"audit_table.establishedDM": "Established a direct message channel with {username}",
|
||||||
"access_history.nameUpdated": "Updated the {channelName} channel/group name",
|
"audit_table.nameUpdated": "Updated the {channelName} channel/group name",
|
||||||
"access_history.headerUpdated": "Updated the {channelName} channel/group header",
|
"audit_table.headerUpdated": "Updated the {channelName} channel/group header",
|
||||||
"access_history.channelDeleted": "Deleted the channel/group with the URL {url}",
|
"audit_table.channelDeleted": "Deleted the channel/group with the URL {url}",
|
||||||
"access_history.userAdded": "Added {username} to the {channelName} channel/group",
|
"audit_table.userAdded": "Added {username} to the {channelName} channel/group",
|
||||||
"access_history.userRemoved": "Removed {username} to the {channelName} channel/group",
|
"audit_table.userRemoved": "Removed {username} to the {channelName} channel/group",
|
||||||
"access_history.attemptedRegisterApp": "Attempted to register a new OAuth Application with ID {id}",
|
"audit_table.attemptedRegisterApp": "Attempted to register a new OAuth Application with ID {id}",
|
||||||
"access_history.attemptedAllowOAuthAccess": "Attempted to allow a new OAuth service access",
|
"audit_table.attemptedAllowOAuthAccess": "Attempted to allow a new OAuth service access",
|
||||||
"access_history.successfullOAuthAccess": "Successfully gave a new OAuth service access",
|
"audit_table.successfullOAuthAccess": "Successfully gave a new OAuth service access",
|
||||||
"access_history.failedOAuthAccess": "Failed to allow a new OAuth service access - the redirect URI did not match the previously registered callback",
|
"audit_table.failedOAuthAccess": "Failed to allow a new OAuth service access - the redirect URI did not match the previously registered callback",
|
||||||
"access_history.attemptedOAuthToken": "Attempted to get an OAuth access token",
|
"audit_table.attemptedOAuthToken": "Attempted to get an OAuth access token",
|
||||||
"access_history.successfullOAuthToken": "Successfully added a new OAuth service",
|
"audit_table.successfullOAuthToken": "Successfully added a new OAuth service",
|
||||||
"access_history.oauthTokenFailed": "Failed to get an OAuth access token - {token}",
|
"audit_table.oauthTokenFailed": "Failed to get an OAuth access token - {token}",
|
||||||
"access_history.attemptedLogin": "Attempted to login",
|
"audit_table.attemptedLogin": "Attempted to login",
|
||||||
"access_history.successfullLogin": "Successfully logged in",
|
"audit_table.successfullLogin": "Successfully logged in",
|
||||||
"access_history.failedLogin": "FAILED login attempt",
|
"audit_table.failedLogin": "FAILED login attempt",
|
||||||
"access_history.updatePicture": "Updated your profile picture",
|
"audit_table.updatePicture": "Updated your profile picture",
|
||||||
"access_history.updateGeneral": "Updated the general settings of your account",
|
"audit_table.updateGeneral": "Updated the general settings of your account",
|
||||||
"access_history.attemptedPassword": "Attempted to change password",
|
"audit_table.attemptedPassword": "Attempted to change password",
|
||||||
"access_history.successfullPassword": "Successfully changed password",
|
"audit_table.successfullPassword": "Successfully changed password",
|
||||||
"access_history.failedPassword": "Failed to change password - tried to update user password who was logged in through oauth",
|
"audit_table.failedPassword": "Failed to change password - tried to update user password who was logged in through oauth",
|
||||||
"access_history.updatedRol": "Updated user role(s) to ",
|
"audit_table.updatedRol": "Updated user role(s) to ",
|
||||||
"access_history.member": "member",
|
"audit_table.member": "member",
|
||||||
"access_history.accountActive": "Account made active",
|
"audit_table.accountActive": "Account made active",
|
||||||
"access_history.accountInactive": "Account made inactive",
|
"audit_table.accountInactive": "Account made inactive",
|
||||||
"access_history.by": " by {username}",
|
"audit_table.by": " by {username}",
|
||||||
"access_history.byAdmin": " by an admin",
|
"audit_table.byAdmin": " by an admin",
|
||||||
"access_history.sentEmail": "Sent an email to {email} to reset your password",
|
"audit_table.sentEmail": "Sent an email to {email} to reset your password",
|
||||||
"access_history.attemptedReset": "Attempted to reset password",
|
"audit_table.attemptedReset": "Attempted to reset password",
|
||||||
"access_history.successfullReset": "Successfully reset password",
|
"audit_table.successfullReset": "Successfully reset password",
|
||||||
"access_history.updateGlobalNotifications": "Updated your global notification settings",
|
"audit_table.updateGlobalNotifications": "Updated your global notification settings",
|
||||||
"access_history.attemptedWebhookCreate": "Attempted to create a webhook",
|
"audit_table.attemptedWebhookCreate": "Attempted to create a webhook",
|
||||||
"access_history.successfullWebhookCreate": "Successfully created a webhook",
|
"audit_table.successfullWebhookCreate": "Successfully created a webhook",
|
||||||
"access_history.failedWebhookCreate": "Failed to create a webhook - bad channel permissions",
|
"audit_table.failedWebhookCreate": "Failed to create a webhook - bad channel permissions",
|
||||||
"access_history.attemptedWebhookDelete": "Attempted to delete a webhook",
|
"audit_table.attemptedWebhookDelete": "Attempted to delete a webhook",
|
||||||
"access_history.successfullWebhookDelete": "Successfully deleted a webhook",
|
"audit_table.successfullWebhookDelete": "Successfully deleted a webhook",
|
||||||
"access_history.failedWebhookDelete": "Failed to delete a webhook - inappropriate conditions",
|
"audit_table.failedWebhookDelete": "Failed to delete a webhook - inappropriate conditions",
|
||||||
"access_history.logout": "Logged out of your account",
|
"audit_table.logout": "Logged out of your account",
|
||||||
"access_history.verified": "Sucessfully verified your email address",
|
"audit_table.verified": "Sucessfully verified your email address",
|
||||||
"access_history.revokedAll": "Revoked all current sessions for the team",
|
"audit_table.revokedAll": "Revoked all current sessions for the team",
|
||||||
"access_history.loginAttempt": " (Login attempt)",
|
"audit_table.loginAttempt": " (Login attempt)",
|
||||||
"access_history.loginFailure": " (Login failure)",
|
"audit_table.loginFailure": " (Login failure)",
|
||||||
"access_history.moreInfo": "More info",
|
"audit_table.moreInfo": "More info",
|
||||||
"access_history.ip": "IP: {ip}",
|
"audit_table.ip": "IP: {ip}",
|
||||||
"access_history.session": "Session ID: {id}",
|
"audit_table.session": "Session ID: {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",
|
||||||
@@ -96,6 +97,7 @@
|
|||||||
"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": "Audits",
|
||||||
"admin.analytics.loading": "Loading...",
|
"admin.analytics.loading": "Loading...",
|
||||||
"admin.analytics.totalUsers": "Total Users",
|
"admin.analytics.totalUsers": "Total Users",
|
||||||
"admin.analytics.publicChannels": "Public Channels",
|
"admin.analytics.publicChannels": "Public Channels",
|
||||||
@@ -337,6 +339,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": "Server Audits",
|
||||||
|
"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: ",
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user