From d153d661db7d4349d69824d318aa9ad571970606 Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Tue, 2 Feb 2016 16:49:27 -0500 Subject: Add basic server audit tab to system console for EE --- web/react/components/access_history_modal.jsx | 548 +------------------- .../components/admin_console/admin_controller.jsx | 3 + .../components/admin_console/admin_sidebar.jsx | 19 + web/react/components/admin_console/audits.jsx | 94 ++++ web/react/components/audit_table.jsx | 571 +++++++++++++++++++++ 5 files changed, 700 insertions(+), 535 deletions(-) create mode 100644 web/react/components/admin_console/audits.jsx create mode 100644 web/react/components/audit_table.jsx (limited to 'web/react/components') diff --git a/web/react/components/access_history_modal.jsx b/web/react/components/access_history_modal.jsx index 6319b5681..98b1d7cc1 100644 --- a/web/react/components/access_history_modal.jsx +++ b/web/react/components/access_history_modal.jsx @@ -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. var Modal = ReactBootstrap.Modal; +import LoadingScreen from './loading_screen.jsx'; +import AuditTable from './audit_table.jsx'; + 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 * as Utils from '../utils/utils.jsx'; -import {intlShape, injectIntl, defineMessages, 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)' - } -}); +import {intlShape, injectIntl, FormattedMessage} from 'mm-intl'; class AccessHistoryModal extends React.Component { constructor(props) { super(props); this.onAuditChange = this.onAuditChange.bind(this); - this.handleMoreInfo = this.handleMoreInfo.bind(this); this.onShow = this.onShow.bind(this); this.onHide = this.onHide.bind(this); - this.formatAuditInfo = this.formatAuditInfo.bind(this); - this.handleRevokedSession = this.handleRevokedSession.bind(this); const state = this.getStateFromStoresForAudits(); state.moreInfo = []; @@ -245,359 +65,17 @@ class AccessHistoryModal extends React.Component { 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() { - 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 = ( - - - - ); - - 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 = ( -
-
- -
-
- -
-
- ); - } - - var divider = null; - if (i < this.state.audits.length - 1) { - divider = (
); - } - - accessList[i] = ( -
-
-
{currentAuditInfo}
-
- {moreInfo} -
- {divider} -
-
- ); - } - var content; if (this.state.audits.loading) { content = (); } else { - content = (
{accessList}
); + content = ( + + ); } return ( @@ -628,4 +106,4 @@ AccessHistoryModal.propTypes = { onHide: React.PropTypes.func.isRequired }; -export default injectIntl(AccessHistoryModal); \ No newline at end of file +export default injectIntl(AccessHistoryModal); diff --git a/web/react/components/admin_console/admin_controller.jsx b/web/react/components/admin_console/admin_controller.jsx index efd163017..360ae3ef3 100644 --- a/web/react/components/admin_console/admin_controller.jsx +++ b/web/react/components/admin_console/admin_controller.jsx @@ -11,6 +11,7 @@ import * as Utils from '../../utils/utils.jsx'; import EmailSettingsTab from './email_settings.jsx'; import LogSettingsTab from './log_settings.jsx'; import LogsTab from './logs.jsx'; +import AuditsTab from './audits.jsx'; import FileSettingsTab from './image_settings.jsx'; import PrivacySettingsTab from './privacy_settings.jsx'; import RateSettingsTab from './rate_settings.jsx'; @@ -138,6 +139,8 @@ export default class AdminController extends React.Component { tab = ; } else if (this.state.selected === 'logs') { tab = ; + } else if (this.state.selected === 'audits') { + tab = ; } else if (this.state.selected === 'image_settings') { tab = ; } else if (this.state.selected === 'privacy_settings') { diff --git a/web/react/components/admin_console/admin_sidebar.jsx b/web/react/components/admin_console/admin_sidebar.jsx index d6bae1feb..642bfe9d7 100644 --- a/web/react/components/admin_console/admin_sidebar.jsx +++ b/web/react/components/admin_console/admin_sidebar.jsx @@ -214,6 +214,24 @@ export default class AdminSidebar extends React.Component { ); } + let audits; + if (global.window.mm_license.IsLicensed === 'true') { + audits = ( +
  • + + + +
  • + ); + } + return (
    @@ -448,6 +466,7 @@ export default class AdminSidebar extends React.Component { /> + {audits} diff --git a/web/react/components/admin_console/audits.jsx b/web/react/components/admin_console/audits.jsx new file mode 100644 index 000000000..866539b3d --- /dev/null +++ b/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
    ; + } + + if (this.state.audits === null) { + content = ; + } else { + content = ( +
    + +
    + ); + } + + return ( +
    +

    + +

    + +
    + {content} +
    +
    + ); + } +} diff --git a/web/react/components/audit_table.jsx b/web/react/components/audit_table.jsx new file mode 100644 index 000000000..cdca7e8d6 --- /dev/null +++ b/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 = ( + + + + ); + } + + 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 = ( +
    +
    + +
    +
    + +
    +
    + ); + } + + var divider = null; + if (i < this.props.audits.length - 1) { + divider = (
    ); + } + + accessList[i] = ( +
    +
    +
    {currentAuditInfo}
    +
    + {moreInfo} +
    + {divider} +
    +
    + ); + } + + return
    {accessList}
    ; + } +} + +AuditTable.propTypes = { + intl: intlShape.isRequired, + audits: React.PropTypes.array.isRequired, + oneLine: React.PropTypes.bool, + showUserId: React.PropTypes.bool +}; + +export default injectIntl(AuditTable); -- cgit v1.2.3-1-g7c22