diff --git a/dist/.publish b/dist/.publish index 2703863ee9..1990d717ea 160000 --- a/dist/.publish +++ b/dist/.publish @@ -1 +1 @@ -Subproject commit 2703863ee9fa02a2443fd2cb2f4d32c67b2982e3 +Subproject commit 1990d717ea8706eeab2a194b27b1776ed509330e diff --git a/src/app/organization/organizationEventsController.js b/src/app/organization/organizationEventsController.js index afe0d3f741..e3f4fdaf5e 100644 --- a/src/app/organization/organizationEventsController.js +++ b/src/app/organization/organizationEventsController.js @@ -2,16 +2,15 @@ .module('bit.organization') .controller('organizationEventsController', function ($scope, $state, apiService, $uibModal, $filter, - toastr, $analytics, constants) { + toastr, $analytics, constants, eventService) { $scope.events = []; $scope.orgUsers = []; $scope.loading = true; $scope.continuationToken = null; - var d = new Date(); - $scope.filterEnd = new Date(d.getFullYear(), d.getMonth(), d.getDate(), 23, 59); - d.setDate(d.getDate() - 30); - $scope.filterStart = new Date(d.getFullYear(), d.getMonth(), d.getDate(), 0, 0); + var defaultFilters = eventService.getDefaultDateFilters(); + $scope.filterStart = defaultFilters.start; + $scope.filterEnd = defaultFilters.end; $scope.$on('$viewContentLoaded', function () { load(); @@ -54,15 +53,9 @@ } function loadEvents(clearExisting) { - var start = null, end = null; - try { - var format = 'yyyy-MM-ddTHH:mm'; - start = $filter('date')($scope.filterStart, format + 'Z', 'UTC'); - end = $filter('date')($scope.filterEnd, format + ':59.999Z', 'UTC'); - } catch (e) { } - - if (!start || !end || end < start) { - alert('Invalid date range.'); + var filterResult = eventService.formatDateFilters($scope.filterStart, $scope.filterEnd); + if (filterResult.error) { + alert(filterResult.error); return; } @@ -74,8 +67,8 @@ $scope.loading = true; return apiService.events.listOrganization({ orgId: $state.params.orgId, - start: start, - end: end, + start: filterResult.start, + end: filterResult.end, continuationToken: $scope.continuationToken }).$promise.then(function (list) { $scope.continuationToken = list.ContinuationToken; @@ -83,10 +76,11 @@ var events = []; for (i = 0; i < list.Data.length; i++) { var userId = list.Data[i].ActingUserId || list.Data[i].UserId; + var eventInfo = eventService.getEventInfo(list.Data[i]); events.push({ - message: eventMessage(list.Data[i]), - appIcon: 'fa-globe', - appName: 'Web', + message: eventInfo.message, + appIcon: eventInfo.appIcon, + appName: eventInfo.appName, userId: userId, userName: userId ? (orgUsersUserIdDict[userId] || '-') : '-', date: list.Data[i].Date @@ -101,98 +95,4 @@ $scope.loading = false; }); } - - function eventMessage(ev) { - var msg = ''; - switch (ev.Type) { - // User - case constants.eventType.User_LoggedIn: - msg = 'Logged in.'; - break; - case constants.eventType.User_ChangedPassword: - msg = 'Changed account password.'; - break; - case constants.eventType.User_Enabled2fa: - msg = 'Enabled two-step login.'; - break; - case constants.eventType.User_Disabled2fa: - msg = 'Disabled two-step login.'; - break; - case constants.eventType.User_Recovered2fa: - msg = 'Recovered account from two-step login.'; - break; - case constants.eventType.User_FailedLogIn: - msg = 'Login attempt failed with incorrect password.'; - break; - case constants.eventType.User_FailedLogIn2fa: - msg = 'Login attempt failed with incorrect two-step login.'; - break; - // Cipher - case constants.eventType.Cipher_Created: - msg = 'Created item ' + ev.CipherId + '.'; - break; - case constants.eventType.Cipher_Updated: - msg = 'Edited item ' + ev.CipherId + '.'; - break; - case constants.eventType.Cipher_Deleted: - msg = 'Deleted item ' + ev.CipherId + '.'; - break; - case constants.eventType.Cipher_AttachmentCreated: - msg = 'Created attachment for item ' + ev.CipherId + '.'; - break; - case constants.eventType.Cipher_AttachmentDeleted: - msg = 'Deleted attachment for item ' + ev.CipherId + '.'; - break; - case constants.eventType.Cipher_Shared: - msg = 'Shared item ' + ev.CipherId + '.'; - break; - case constants.eventType.Cipher_UpdatedCollections: - msg = 'Update collections for item ' + ev.CipherId + '.'; - break; - // Collection - case constants.eventType.Collection_Created: - msg = 'Created collection ' + ev.CollectionId + '.'; - break; - case constants.eventType.Collection_Updated: - msg = 'Edited collection ' + ev.CollectionId + '.'; - break; - case constants.eventType.Collection_Deleted: - msg = 'Deleted collection ' + ev.CollectionId + '.'; - break; - // Group - case constants.eventType.Group_Created: - msg = 'Created group ' + ev.GroupId + '.'; - break; - case constants.eventType.Group_Updated: - msg = 'Edited group ' + ev.GroupId + '.'; - break; - case constants.eventType.Group_Deleted: - msg = 'Deleted group ' + ev.GroupId + '.'; - break; - // Org user - case constants.eventType.OrganizationUser_Invited: - msg = 'Invited user ' + ev.OrganizationUserId + '.'; - break; - case constants.eventType.OrganizationUser_Confirmed: - msg = 'Confirmed user ' + ev.OrganizationUserId + '.'; - break; - case constants.eventType.OrganizationUser_Updated: - msg = 'Edited user ' + ev.OrganizationUserId + '.'; - break; - case constants.eventType.OrganizationUser_Removed: - msg = 'Removed user ' + ev.OrganizationUserId + '.'; - break; - case constants.eventType.OrganizationUser_UpdatedGroups: - msg = 'Edited groups for user ' + ev.OrganizationUserId + '.'; - break; - // Org - case constants.eventType.Organization_Updated: - msg = 'Edited organization settings.'; - break; - default: - break; - } - - return msg === '' ? null : msg; - } }); diff --git a/src/app/organization/organizationVaultCipherEventsController.js b/src/app/organization/organizationVaultCipherEventsController.js new file mode 100644 index 0000000000..9e3a8ca104 --- /dev/null +++ b/src/app/organization/organizationVaultCipherEventsController.js @@ -0,0 +1,103 @@ +angular + .module('bit.organization') + + .controller('organizationVaultCipherEventsController', function ($scope, apiService, $uibModalInstance, cipherService, + cipher, $analytics, eventService) { + $analytics.eventTrack('organizationVaultCipherEventsController', { category: 'Modal' }); + $scope.cipher = cipher; + $scope.events = []; + $scope.loading = true; + $scope.continuationToken = null; + + var defaultFilters = eventService.getDefaultDateFilters(); + $scope.filterStart = defaultFilters.start; + $scope.filterEnd = defaultFilters.end; + + $uibModalInstance.opened.then(function () { + load(); + }); + + $scope.refresh = function () { + loadEvents(true); + }; + + $scope.next = function () { + loadEvents(false); + }; + + var i = 0, + orgUsersUserIdDict = {}, + orgUsersIdDict = {}; + + function load() { + apiService.organizationUsers.list({ orgId: cipher.organizationId }).$promise.then(function (list) { + var users = []; + for (i = 0; i < list.Data.length; i++) { + var user = { + id: list.Data[i].Id, + userId: list.Data[i].UserId, + name: list.Data[i].Name, + email: list.Data[i].Email + }; + + users.push(user); + + var displayName = user.name || user.email; + orgUsersUserIdDict[user.userId] = displayName; + orgUsersIdDict[user.id] = displayName; + } + + $scope.orgUsers = users; + + return loadEvents(true); + }); + } + + function loadEvents(clearExisting) { + var filterResult = eventService.formatDateFilters($scope.filterStart, $scope.filterEnd); + if (filterResult.error) { + alert(filterResult.error); + return; + } + + if (clearExisting) { + $scope.continuationToken = null; + $scope.events = []; + } + + $scope.loading = true; + return apiService.events.listCipher({ + id: cipher.id, + start: filterResult.start, + end: filterResult.end, + continuationToken: $scope.continuationToken + }).$promise.then(function (list) { + $scope.continuationToken = list.ContinuationToken; + + var events = []; + for (i = 0; i < list.Data.length; i++) { + var userId = list.Data[i].ActingUserId || list.Data[i].UserId; + var eventInfo = eventService.getEventInfo(list.Data[i], { cipherInfo: false }); + events.push({ + message: eventInfo.message, + appIcon: eventInfo.appIcon, + appName: eventInfo.appName, + userId: userId, + userName: userId ? (orgUsersUserIdDict[userId] || '-') : '-', + date: list.Data[i].Date + }); + } + if ($scope.events && $scope.events.length > 0) { + $scope.events = $scope.events.concat(events); + } + else { + $scope.events = events; + } + $scope.loading = false; + }); + } + + $scope.close = function () { + $uibModalInstance.dismiss('cancel'); + }; + }); diff --git a/src/app/organization/organizationVaultController.js b/src/app/organization/organizationVaultController.js index b9d84a04e1..675c7fee9d 100644 --- a/src/app/organization/organizationVaultController.js +++ b/src/app/organization/organizationVaultController.js @@ -141,6 +141,17 @@ }); }; + $scope.viewEvents = function (cipher) { + $uibModal.open({ + animation: true, + templateUrl: 'app/organization/views/organizationVaultCipherEvents.html', + controller: 'organizationVaultCipherEventsController', + resolve: { + cipher: function () { return cipher; } + } + }); + }; + $scope.attachments = function (cipher) { authService.getUserProfile().then(function (profile) { return !!profile.organizations[cipher.organizationId].maxStorageGb; diff --git a/src/app/organization/views/organizationVault.html b/src/app/organization/views/organizationVault.html index 5d854df650..500796afb2 100644 --- a/src/app/organization/views/organizationVault.html +++ b/src/app/organization/views/organizationVault.html @@ -56,6 +56,11 @@ Collections +
  • + + Event Logs + +
  • diff --git a/src/app/organization/views/organizationVaultCipherEvents.html b/src/app/organization/views/organizationVaultCipherEvents.html new file mode 100644 index 0000000000..f3bd5c29fc --- /dev/null +++ b/src/app/organization/views/organizationVaultCipherEvents.html @@ -0,0 +1,60 @@ + + + diff --git a/src/app/services/apiService.js b/src/app/services/apiService.js index 00b6d7eb9d..00d59c46dd 100644 --- a/src/app/services/apiService.js +++ b/src/app/services/apiService.js @@ -181,7 +181,9 @@ _service.events = $resource(_apiUri + '/events', {}, { list: { method: 'GET', params: {} }, - listOrganization: { url: _apiUri + '/organizations/:orgId/events', method: 'GET', params: { id: '@orgId' } } + listOrganization: { url: _apiUri + '/organizations/:orgId/events', method: 'GET', params: { id: '@orgId' } }, + listCipher: { url: _apiUri + '/ciphers/:id/events', method: 'GET', params: { id: '@id' } }, + listOrganizationUser: { url: _apiUri + '/organizations/:orgId/users/:id/events', method: 'GET', params: { orgId: '@orgId', id: '@id' } } }); _service.identity = $resource(_identityUri + '/connect', {}, { diff --git a/src/app/services/eventService.js b/src/app/services/eventService.js new file mode 100644 index 0000000000..16925abd7f --- /dev/null +++ b/src/app/services/eventService.js @@ -0,0 +1,145 @@ +angular + .module('bit.services') + + .factory('eventService', function (constants, $filter) { + var _service = {}; + + _service.getDefaultDateFilters = function () { + var d = new Date(); + var filterEnd = new Date(d.getFullYear(), d.getMonth(), d.getDate(), 23, 59); + d.setDate(d.getDate() - 30); + var filterStart = new Date(d.getFullYear(), d.getMonth(), d.getDate(), 0, 0); + + return { + start: filterStart, + end: filterEnd + }; + }; + + _service.formatDateFilters = function (filterStart, filterEnd) { + var result = { + start: null, + end: null, + error: null + }; + + try { + var format = 'yyyy-MM-ddTHH:mm'; + result.start = $filter('date')(filterStart, format + 'Z', 'UTC'); + result.end = $filter('date')(filterEnd, format + ':59.999Z', 'UTC'); + } catch (e) { } + + if (!result.start || !result.end || result.end < result.start) { + result.error = 'Invalid date range.'; + } + + return result; + }; + + _service.getEventInfo = function (ev, options) { + options = options || { + cipherInfo: true + }; + return { + message: getEventMessage(ev, options), + appIcon: 'fa-globe', + appName: 'Web' + }; + }; + + function getEventMessage(ev, options) { + var msg = ''; + switch (ev.Type) { + // User + case constants.eventType.User_LoggedIn: + msg = 'Logged in.'; + break; + case constants.eventType.User_ChangedPassword: + msg = 'Changed account password.'; + break; + case constants.eventType.User_Enabled2fa: + msg = 'Enabled two-step login.'; + break; + case constants.eventType.User_Disabled2fa: + msg = 'Disabled two-step login.'; + break; + case constants.eventType.User_Recovered2fa: + msg = 'Recovered account from two-step login.'; + break; + case constants.eventType.User_FailedLogIn: + msg = 'Login attempt failed with incorrect password.'; + break; + case constants.eventType.User_FailedLogIn2fa: + msg = 'Login attempt failed with incorrect two-step login.'; + break; + // Cipher + case constants.eventType.Cipher_Created: + msg = options.cipherInfo ? 'Created item ' + ev.CipherId + '.' : 'Created.'; + break; + case constants.eventType.Cipher_Updated: + msg = options.cipherInfo ? 'Edited item ' + ev.CipherId + '.' : 'Edited.'; + break; + case constants.eventType.Cipher_Deleted: + msg = options.cipherInfo ? 'Deleted item ' + ev.CipherId + '.' : 'Deleted'; + break; + case constants.eventType.Cipher_AttachmentCreated: + msg = options.cipherInfo ? 'Created attachment for item ' + ev.CipherId + '.' : 'Created attachment.'; + break; + case constants.eventType.Cipher_AttachmentDeleted: + msg = options.cipherInfo ? 'Deleted attachment for item ' + ev.CipherId + '.' : 'Deleted attachment.'; + break; + case constants.eventType.Cipher_Shared: + msg = options.cipherInfo ? 'Shared item ' + ev.CipherId + '.' : 'Shared.'; + break; + case constants.eventType.Cipher_UpdatedCollections: + msg = options.cipherInfo ? 'Update collections for item ' + ev.CipherId + '.' : 'Updated collections.'; + break; + // Collection + case constants.eventType.Collection_Created: + msg = 'Created collection ' + ev.CollectionId + '.'; + break; + case constants.eventType.Collection_Updated: + msg = 'Edited collection ' + ev.CollectionId + '.'; + break; + case constants.eventType.Collection_Deleted: + msg = 'Deleted collection ' + ev.CollectionId + '.'; + break; + // Group + case constants.eventType.Group_Created: + msg = 'Created group ' + ev.GroupId + '.'; + break; + case constants.eventType.Group_Updated: + msg = 'Edited group ' + ev.GroupId + '.'; + break; + case constants.eventType.Group_Deleted: + msg = 'Deleted group ' + ev.GroupId + '.'; + break; + // Org user + case constants.eventType.OrganizationUser_Invited: + msg = 'Invited user ' + ev.OrganizationUserId + '.'; + break; + case constants.eventType.OrganizationUser_Confirmed: + msg = 'Confirmed user ' + ev.OrganizationUserId + '.'; + break; + case constants.eventType.OrganizationUser_Updated: + msg = 'Edited user ' + ev.OrganizationUserId + '.'; + break; + case constants.eventType.OrganizationUser_Removed: + msg = 'Removed user ' + ev.OrganizationUserId + '.'; + break; + case constants.eventType.OrganizationUser_UpdatedGroups: + msg = 'Edited groups for user ' + ev.OrganizationUserId + '.'; + break; + // Org + case constants.eventType.Organization_Updated: + msg = 'Edited organization settings.'; + break; + default: + break; + } + + return msg === '' ? null : msg; + } + + return _service; + }); diff --git a/src/index.html b/src/index.html index 2f6b337a63..e22d876f05 100644 --- a/src/index.html +++ b/src/index.html @@ -146,6 +146,7 @@ + @@ -209,6 +210,7 @@ +