diff --git a/services/web/scripts/update_group_policy.mjs b/services/web/scripts/update_group_policy.mjs new file mode 100644 index 0000000000..554ade4376 --- /dev/null +++ b/services/web/scripts/update_group_policy.mjs @@ -0,0 +1,145 @@ +import fs from 'node:fs' +import minimist from 'minimist' +import { ObjectId } from '../app/src/infrastructure/mongodb.mjs' +import GroupPoliciesHandler from '../modules/group-settings/app/src/GroupPoliciesHandler.mjs' +import { scriptRunner } from './lib/ScriptRunner.mjs' + +// Imported to ensure policies are registered before we attempt to update them +import '../modules/group-settings/app/src/managed-users/ManagedUsersPolicy.mjs' +import '../modules/institutions/app/src/InstitutionsPolicy.mjs' + +const USAGE = `Usage: + node scripts/update_group_policy.mjs --subscription-id= --policy-name= --policy-value= + node scripts/update_group_policy.mjs --file= --policy-name= --policy-value= + +Options: + --subscription-id The id of a single subscription to update + --file Path to a file with one subscription _id per line (ignored when --subscription-id is present) + --policy-name The policy name to update (e.g. userCannotUseAIFeatures) + --policy-value The policy value (true or false) + --dry-run Perform the search but only log the count of subscriptions to update. Defaults to true + --initiator-id The user id of the person running the script (for audit logs) + --verbose Log each subscription id being updated + --help Show this help message` + +function printUsageAndExit(code) { + console.log(USAGE) + process.exit(code) +} + +const argv = minimist(process.argv.slice(2), { + string: [ + 'subscription-id', + 'policy-name', + 'policy-value', + 'initiator-id', + 'file', + ], + boolean: ['dry-run', 'help', 'verbose'], + default: { 'dry-run': true, verbose: false }, +}) + +if (argv.help) { + printUsageAndExit(0) +} + +async function main() { + const subscriptionId = argv['subscription-id'] + const policyName = argv['policy-name'] + const policyValue = argv['policy-value'] + const filePath = argv.file + const dryRun = argv['dry-run'] + const initiatorId = argv['initiator-id'] + const verbose = argv.verbose + + if (!policyName) { + console.error('Missing --policy-name argument') + printUsageAndExit(1) + } + if (!subscriptionId && !filePath) { + console.error( + 'Missing --subscription-id or --file argument, one must be provided' + ) + printUsageAndExit(1) + } + if (!policyValue) { + console.error('Missing --policy-value argument') + printUsageAndExit(1) + } + if (policyValue !== 'true' && policyValue !== 'false') { + console.error('--policy-value must be "true" or "false"') + printUsageAndExit(1) + } + if (initiatorId && !ObjectId.isValid(initiatorId)) { + console.error(`Invalid ObjectId for --initiator-id: ${initiatorId}`) + printUsageAndExit(1) + } + + const policies = { [policyName]: policyValue === 'true' } + const auditLogInfo = { + initiatorId: initiatorId ? new ObjectId(initiatorId) : undefined, + ipAddress: '0.0.0.0', + } + + let subscriptionIds + if (subscriptionId) { + subscriptionIds = [subscriptionId] + } else { + const fileContent = fs.readFileSync(filePath, 'utf8') + subscriptionIds = fileContent + .split('\n') + .map(line => line.trim()) + .filter(line => line.length > 0) + } + + const invalidIds = subscriptionIds.filter(id => !ObjectId.isValid(id)) + if (invalidIds.length > 0) { + console.error(`Invalid ObjectId: ${invalidIds.join(', ')}`) + printUsageAndExit(1) + } + + console.log( + `${dryRun ? 'Dry run: would update' : 'Updating'} policy ${policyName}=${policyValue}. Total subscriptions to update: ${subscriptionIds.length}` + ) + + const failedSubscriptions = [] + for (const id of subscriptionIds) { + if (dryRun) { + if (verbose) { + console.log(` Dry run: would update subscription ${id}`) + } + } else { + if (verbose) { + console.log(` Updating subscription ${id}`) + } + try { + await GroupPoliciesHandler.promises.updateGroupPolicies( + new ObjectId(id), + policies, + auditLogInfo + ) + } catch (error) { + console.error(` Subscription ${id} update failed`) + console.error(error) + failedSubscriptions.push(id) + } + } + } + + console.log( + `Total subscriptions ${dryRun ? 'found' : 'updated'}: ${subscriptionIds.length - failedSubscriptions.length}` + ) + if (failedSubscriptions.length) { + console.log(`Total subscriptions failed: ${failedSubscriptions.length}:`) + failedSubscriptions.forEach(id => console.log(` ${id}`)) + } +} + +try { + await scriptRunner(main) + console.log('Done.') + process.exit(0) +} catch (error) { + console.error(error) + process.exit(1) +}