mirror of
https://github.com/overleaf/overleaf.git
synced 2025-12-05 01:10:29 +00:00
[web] Add User logs to Group Audit Logs view (#29480)
* Revert "Revert "[web] Add User logs to Group Audit Logs view (#29155)" (#29479)" This reverts commit 40a1516ab9cec690d0487a0a870b9fab17598d60. * Fix `managedUsersEnabled` flag in frontend GitOrigin-RevId: ae3edf5bcbc01ec46bc18028e758d3364072c307
This commit is contained in:
@@ -98,6 +98,13 @@ const SubscriptionLocator = {
|
||||
)
|
||||
},
|
||||
|
||||
async getUniqueManagedSubscriptionMemberOf(userId) {
|
||||
return await Subscription.findOne(
|
||||
{ member_ids: userId, managedUsersEnabled: true },
|
||||
{ _id: 1 }
|
||||
)
|
||||
},
|
||||
|
||||
async getGroupsWithEmailInvite(email) {
|
||||
return await Subscription.find({ invited_emails: email }).exec()
|
||||
},
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
const OError = require('@overleaf/o-error')
|
||||
const logger = require('@overleaf/logger')
|
||||
const { UserAuditLogEntry } = require('../../models/UserAuditLogEntry')
|
||||
const { callbackify } = require('util')
|
||||
const SubscriptionLocator = require('../Subscription/SubscriptionLocator')
|
||||
|
||||
function _canHaveNoIpAddressId(operation, info) {
|
||||
if (operation === 'join-group-subscription') return true
|
||||
@@ -27,6 +29,9 @@ function _canHaveNoInitiatorId(operation, info) {
|
||||
if (operation === 'release-managed-user' && info.script) return true
|
||||
}
|
||||
|
||||
// events that are visible to managed user admins in Group Audit Logs view
|
||||
const MANAGED_GROUP_USER_EVENTS = ['login', 'reset-password', 'update-password']
|
||||
|
||||
/**
|
||||
* Add an audit log entry
|
||||
*
|
||||
@@ -68,10 +73,25 @@ async function addEntry(userId, operation, initiatorId, ipAddress, info = {}) {
|
||||
ipAddress,
|
||||
}
|
||||
|
||||
if (MANAGED_GROUP_USER_EVENTS.includes(operation)) {
|
||||
try {
|
||||
const managedSubscription =
|
||||
await SubscriptionLocator.promises.getUniqueManagedSubscriptionMemberOf(
|
||||
userId
|
||||
)
|
||||
if (managedSubscription) {
|
||||
entry.managedSubscriptionId = managedSubscription._id
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error({ err, userId }, 'failed to lookup managed subscription')
|
||||
}
|
||||
}
|
||||
|
||||
await UserAuditLogEntry.create(entry)
|
||||
}
|
||||
|
||||
const UserAuditLogHandler = {
|
||||
MANAGED_GROUP_USER_EVENTS,
|
||||
addEntry: callbackify(addEntry),
|
||||
promises: {
|
||||
addEntry,
|
||||
|
||||
@@ -4,6 +4,7 @@ const { Schema } = mongoose
|
||||
const UserAuditLogEntrySchema = new Schema(
|
||||
{
|
||||
userId: { type: Schema.Types.ObjectId, index: true },
|
||||
managedSubscriptionId: { type: Schema.Types.ObjectId, index: true },
|
||||
info: { type: Object },
|
||||
initiatorId: { type: Schema.Types.ObjectId },
|
||||
ipAddress: { type: String },
|
||||
|
||||
@@ -10,6 +10,7 @@ describe('UserAuditLogHandler', function () {
|
||||
beforeEach(function () {
|
||||
this.userId = new ObjectId()
|
||||
this.initiatorId = new ObjectId()
|
||||
this.subscriptionId = new ObjectId()
|
||||
this.action = {
|
||||
operation: 'clear-sessions',
|
||||
initiatorId: this.initiatorId,
|
||||
@@ -24,9 +25,16 @@ describe('UserAuditLogHandler', function () {
|
||||
ip: '0:0:0:0',
|
||||
}
|
||||
this.UserAuditLogEntryMock = sinon.mock(UserAuditLogEntry)
|
||||
this.getUniqueManagedSubscriptionMemberOfMock = sinon.stub().resolves()
|
||||
this.UserAuditLogHandler = SandboxedModule.require(MODULE_PATH, {
|
||||
requires: {
|
||||
'../../models/UserAuditLogEntry': { UserAuditLogEntry },
|
||||
'../Subscription/SubscriptionLocator': {
|
||||
promises: {
|
||||
getUniqueManagedSubscriptionMemberOf:
|
||||
this.getUniqueManagedSubscriptionMemberOfMock,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
@@ -53,37 +61,33 @@ describe('UserAuditLogHandler', function () {
|
||||
this.UserAuditLogEntryMock.verify()
|
||||
})
|
||||
|
||||
it('updates the log for password reset operation witout a initiatorId', async function () {
|
||||
await expect(
|
||||
this.UserAuditLogHandler.promises.addEntry(
|
||||
this.userId,
|
||||
'reset-password',
|
||||
undefined,
|
||||
this.action.ip,
|
||||
this.action.info
|
||||
)
|
||||
it('updates the log for password reset operation without a initiatorId', async function () {
|
||||
await this.UserAuditLogHandler.promises.addEntry(
|
||||
this.userId,
|
||||
'reset-password',
|
||||
undefined,
|
||||
this.action.ip,
|
||||
this.action.info
|
||||
)
|
||||
this.UserAuditLogEntryMock.verify()
|
||||
})
|
||||
|
||||
it('updates the log for a email removal via script', async function () {
|
||||
await expect(
|
||||
this.UserAuditLogHandler.promises.addEntry(
|
||||
this.userId,
|
||||
'remove-email',
|
||||
undefined,
|
||||
this.action.ip,
|
||||
{
|
||||
removedEmail: 'foo',
|
||||
script: true,
|
||||
}
|
||||
)
|
||||
await this.UserAuditLogHandler.promises.addEntry(
|
||||
this.userId,
|
||||
'remove-email',
|
||||
undefined,
|
||||
this.action.ip,
|
||||
{
|
||||
removedEmail: 'foo',
|
||||
script: true,
|
||||
}
|
||||
)
|
||||
this.UserAuditLogEntryMock.verify()
|
||||
})
|
||||
|
||||
it('updates the log when no ip address or initiatorId is specified for a group join event', async function () {
|
||||
this.UserAuditLogHandler.promises.addEntry(
|
||||
await this.UserAuditLogHandler.promises.addEntry(
|
||||
this.userId,
|
||||
'join-group-subscription',
|
||||
undefined,
|
||||
@@ -92,6 +96,31 @@ describe('UserAuditLogHandler', function () {
|
||||
subscriptionId: 'foo',
|
||||
}
|
||||
)
|
||||
this.UserAuditLogEntryMock.verify()
|
||||
})
|
||||
|
||||
it('includes managedSubscriptionId for managed group user events ', async function () {
|
||||
await this.UserAuditLogHandler.promises.addEntry(
|
||||
this.userId,
|
||||
'reset-password',
|
||||
undefined,
|
||||
this.action.ip
|
||||
)
|
||||
this.UserAuditLogEntryMock.verify()
|
||||
expect(this.getUniqueManagedSubscriptionMemberOfMock).to.have.been
|
||||
.called
|
||||
})
|
||||
|
||||
it('does not includes managedSubscriptionId for events not in the managed group event list', async function () {
|
||||
await this.UserAuditLogHandler.promises.addEntry(
|
||||
this.userId,
|
||||
'foo',
|
||||
this.action.initiatorId,
|
||||
this.action.ip
|
||||
)
|
||||
this.UserAuditLogEntryMock.verify()
|
||||
expect(this.getUniqueManagedSubscriptionMemberOfMock).not.to.have.been
|
||||
.called
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
|
||||
import Helpers from './lib/helpers.mjs'
|
||||
|
||||
const tags = ['saas']
|
||||
|
||||
const indexes = [
|
||||
{
|
||||
key: {
|
||||
managedSubscriptionId: 1,
|
||||
timestamp: 1,
|
||||
},
|
||||
name: 'managedSubscriptionId_1_timestamp_1',
|
||||
},
|
||||
]
|
||||
|
||||
const migrate = async client => {
|
||||
const { db } = client
|
||||
await Helpers.addIndexesToCollection(db.userAuditLogEntries, indexes)
|
||||
}
|
||||
|
||||
const rollback = async client => {
|
||||
const { db } = client
|
||||
try {
|
||||
await Helpers.dropIndexesFromCollection(db.userAuditLogEntries, indexes)
|
||||
} catch (err) {
|
||||
console.error('Something went wrong rolling back the migrations', err)
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
tags,
|
||||
migrate,
|
||||
rollback,
|
||||
}
|
||||
Reference in New Issue
Block a user