mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-05-23 09:09:36 +02:00
[web] Add User logs to Group Audit Logs view (#29155)
* [web] Add User logs to Group Audit Logs view GitOrigin-RevId: c455edc5a700ba24e73a16b2a66b50cb92f24176
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) {
|
async getGroupsWithEmailInvite(email) {
|
||||||
return await Subscription.find({ invited_emails: email }).exec()
|
return await Subscription.find({ invited_emails: email }).exec()
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
const OError = require('@overleaf/o-error')
|
const OError = require('@overleaf/o-error')
|
||||||
|
const logger = require('@overleaf/logger')
|
||||||
const { UserAuditLogEntry } = require('../../models/UserAuditLogEntry')
|
const { UserAuditLogEntry } = require('../../models/UserAuditLogEntry')
|
||||||
const { callbackify } = require('util')
|
const { callbackify } = require('util')
|
||||||
|
const SubscriptionLocator = require('../Subscription/SubscriptionLocator')
|
||||||
|
|
||||||
function _canHaveNoIpAddressId(operation, info) {
|
function _canHaveNoIpAddressId(operation, info) {
|
||||||
if (operation === 'join-group-subscription') return true
|
if (operation === 'join-group-subscription') return true
|
||||||
@@ -27,6 +29,9 @@ function _canHaveNoInitiatorId(operation, info) {
|
|||||||
if (operation === 'release-managed-user' && info.script) return true
|
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
|
* Add an audit log entry
|
||||||
*
|
*
|
||||||
@@ -68,10 +73,25 @@ async function addEntry(userId, operation, initiatorId, ipAddress, info = {}) {
|
|||||||
ipAddress,
|
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)
|
await UserAuditLogEntry.create(entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
const UserAuditLogHandler = {
|
const UserAuditLogHandler = {
|
||||||
|
MANAGED_GROUP_USER_EVENTS,
|
||||||
addEntry: callbackify(addEntry),
|
addEntry: callbackify(addEntry),
|
||||||
promises: {
|
promises: {
|
||||||
addEntry,
|
addEntry,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ const { Schema } = mongoose
|
|||||||
const UserAuditLogEntrySchema = new Schema(
|
const UserAuditLogEntrySchema = new Schema(
|
||||||
{
|
{
|
||||||
userId: { type: Schema.Types.ObjectId, index: true },
|
userId: { type: Schema.Types.ObjectId, index: true },
|
||||||
|
managedSubscriptionId: { type: Schema.Types.ObjectId, index: true },
|
||||||
info: { type: Object },
|
info: { type: Object },
|
||||||
initiatorId: { type: Schema.Types.ObjectId },
|
initiatorId: { type: Schema.Types.ObjectId },
|
||||||
ipAddress: { type: String },
|
ipAddress: { type: String },
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ describe('UserAuditLogHandler', function () {
|
|||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
this.userId = new ObjectId()
|
this.userId = new ObjectId()
|
||||||
this.initiatorId = new ObjectId()
|
this.initiatorId = new ObjectId()
|
||||||
|
this.subscriptionId = new ObjectId()
|
||||||
this.action = {
|
this.action = {
|
||||||
operation: 'clear-sessions',
|
operation: 'clear-sessions',
|
||||||
initiatorId: this.initiatorId,
|
initiatorId: this.initiatorId,
|
||||||
@@ -24,9 +25,16 @@ describe('UserAuditLogHandler', function () {
|
|||||||
ip: '0:0:0:0',
|
ip: '0:0:0:0',
|
||||||
}
|
}
|
||||||
this.UserAuditLogEntryMock = sinon.mock(UserAuditLogEntry)
|
this.UserAuditLogEntryMock = sinon.mock(UserAuditLogEntry)
|
||||||
|
this.getUniqueManagedSubscriptionMemberOfMock = sinon.stub().resolves()
|
||||||
this.UserAuditLogHandler = SandboxedModule.require(MODULE_PATH, {
|
this.UserAuditLogHandler = SandboxedModule.require(MODULE_PATH, {
|
||||||
requires: {
|
requires: {
|
||||||
'../../models/UserAuditLogEntry': { UserAuditLogEntry },
|
'../../models/UserAuditLogEntry': { UserAuditLogEntry },
|
||||||
|
'../Subscription/SubscriptionLocator': {
|
||||||
|
promises: {
|
||||||
|
getUniqueManagedSubscriptionMemberOf:
|
||||||
|
this.getUniqueManagedSubscriptionMemberOfMock,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -53,37 +61,33 @@ describe('UserAuditLogHandler', function () {
|
|||||||
this.UserAuditLogEntryMock.verify()
|
this.UserAuditLogEntryMock.verify()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('updates the log for password reset operation witout a initiatorId', async function () {
|
it('updates the log for password reset operation without a initiatorId', async function () {
|
||||||
await expect(
|
await this.UserAuditLogHandler.promises.addEntry(
|
||||||
this.UserAuditLogHandler.promises.addEntry(
|
this.userId,
|
||||||
this.userId,
|
'reset-password',
|
||||||
'reset-password',
|
undefined,
|
||||||
undefined,
|
this.action.ip,
|
||||||
this.action.ip,
|
this.action.info
|
||||||
this.action.info
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
this.UserAuditLogEntryMock.verify()
|
this.UserAuditLogEntryMock.verify()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('updates the log for a email removal via script', async function () {
|
it('updates the log for a email removal via script', async function () {
|
||||||
await expect(
|
await this.UserAuditLogHandler.promises.addEntry(
|
||||||
this.UserAuditLogHandler.promises.addEntry(
|
this.userId,
|
||||||
this.userId,
|
'remove-email',
|
||||||
'remove-email',
|
undefined,
|
||||||
undefined,
|
this.action.ip,
|
||||||
this.action.ip,
|
{
|
||||||
{
|
removedEmail: 'foo',
|
||||||
removedEmail: 'foo',
|
script: true,
|
||||||
script: true,
|
}
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
this.UserAuditLogEntryMock.verify()
|
this.UserAuditLogEntryMock.verify()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('updates the log when no ip address or initiatorId is specified for a group join event', async function () {
|
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,
|
this.userId,
|
||||||
'join-group-subscription',
|
'join-group-subscription',
|
||||||
undefined,
|
undefined,
|
||||||
@@ -92,6 +96,31 @@ describe('UserAuditLogHandler', function () {
|
|||||||
subscriptionId: 'foo',
|
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