Files
upstream-mirror 30810eecd7
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 2s
snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
2026-09-16 01:21:02 +05:00

862 lines
31 KiB
TypeScript

import type { IServerEvents, LoginServiceConfiguration } from '@rocket.chat/core-typings';
import { expect } from 'chai';
import { before, describe, it, after, afterEach } from 'mocha';
import { getCredentials, api, request, credentials } from '../../data/api-data';
import { updatePermission, updateSetting, getSettingValueById } from '../../data/permissions.helper';
import { IS_EE } from '../../e2e/config/constants';
describe('[Settings]', () => {
before((done) => getCredentials(done));
describe('[/settings.public]', () => {
it('should return public settings', async () => {
const res = await request.get(api('settings.public')).expect('Content-Type', 'application/json').expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('settings');
expect(res.body).to.have.property('count');
});
it('should return public settings even requested with count and offset params', async () => {
const res = await request
.get(api('settings.public'))
.query({
count: 5,
offset: 0,
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success').and.to.be.true;
expect(res.body).to.have.property('settings').and.to.be.an('array').and.to.have.lengthOf(5);
expect(res.body).to.have.property('count').and.to.be.a('number').and.to.equal(5);
});
it('should return public settings even requested with _id param', async () => {
const res = await request
.get(api('settings.public'))
.query({
_id: 'Site_Url',
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success').and.to.be.true;
expect(res.body).to.have.property('settings').and.to.be.an('array').and.to.have.lengthOf(1);
expect(res.body).to.have.property('count').and.to.be.a('number').and.to.equal(1);
});
it('should return public settings even requested with _id param as an array', async () => {
const res = await request
.get(api('settings.public'))
.query({
_id: 'Site_Url,LDAP_Enable',
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success').and.to.be.true;
expect(res.body).to.have.property('settings').and.to.be.an('array').and.to.have.lengthOf(2);
expect(res.body).to.have.property('count').and.to.be.a('number').and.to.equal(2);
});
it('should return an empty response when requesting public settings with a broken _id param', async () => {
const res = await request
.get(api('settings.public'))
.query({
_id: 10,
})
.expect('Content-Type', 'application/json')
.expect(200);
expect(res.body).to.have.property('success').and.to.be.true;
expect(res.body).to.have.property('settings').and.to.be.an('array').and.to.be.empty;
expect(res.body).to.have.property('count').and.to.be.a('number').and.to.equal(0);
expect(res.body).to.have.property('offset').and.to.be.a('number').and.to.equal(0);
expect(res.body).to.have.property('total').and.to.be.a('number').and.to.equal(0);
});
});
describe('[/settings]', () => {
it('should return private settings', async () => {
const res = await request.get(api('settings')).set(credentials).expect('Content-Type', 'application/json').expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('settings');
expect(res.body).to.have.property('count');
});
it('should return the default values of the settings when includeDefaults is true', async () => {
return request
.get(api('settings'))
.query({ includeDefaults: true })
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('settings');
expect(res.body).to.have.property('count');
expect(res.body.settings[0]).to.have.property('packageValue');
});
});
describe('POST (bulk)', () => {
before(async () => {
await updatePermission('edit-privileged-setting', ['admin']);
});
after(async () => {
await updatePermission('edit-privileged-setting', ['admin']);
});
it('should return unauthorized when not authenticated', async () => {
await request
.post(api('settings'))
.send({ settings: [{ _id: 'LDAP_Enable', value: false }] })
.expect(401);
});
it('should return bad request when settings is missing', async () => {
await request.post(api('settings')).set(credentials).send({}).expect(400);
});
it('should return bad request when settings is empty', async () => {
await request.post(api('settings')).set(credentials).send({ settings: [] }).expect(400);
});
it('should return bad request when an item is missing value', async () => {
await request
.post(api('settings'))
.set(credentials)
.send({ settings: [{ _id: 'LDAP_Enable' }] })
.expect(400);
});
it('should reject an empty integer value without changing the setting', async () => {
const settingId = 'UserData_ProcessingFrequency';
const originalValue = await getSettingValueById(settingId);
await request
.post(api('settings'))
.set(credentials)
.send({ settings: [{ _id: settingId, value: null }] })
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
});
expect(await getSettingValueById(settingId)).to.equal(originalValue);
});
it('should successfully update multiple settings in a single request', async () => {
await request
.post(api('settings'))
.set(credentials)
.send({
settings: [
{ _id: 'LDAP_Enable', value: false },
{ _id: 'Accounts_Default_User_Preferences_masterVolume', value: 50 },
],
})
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
});
await request
.get(api('settings/LDAP_Enable'))
.set(credentials)
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('value', false);
});
await request
.get(api('settings/Accounts_Default_User_Preferences_masterVolume'))
.set(credentials)
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('value', 50);
});
});
it('should accept an optional editor for a setting', async () => {
await request
.post(api('settings'))
.set(credentials)
.send({ settings: [{ _id: 'Livechat_title_color', value: '#000000', editor: 'color' }] })
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
});
await request
.get(api('settings/Livechat_title_color'))
.set(credentials)
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('value', '#000000');
});
});
it('should return bad request when editor is an empty string', async () => {
await request
.post(api('settings'))
.set(credentials)
.send({ settings: [{ _id: 'Livechat_title_color', value: '#C1272D', editor: '' }] })
.expect(400);
});
it('should fail when the user does not have edit-privileged-setting permission', async () => {
await updatePermission('edit-privileged-setting', []);
await request
.post(api('settings'))
.set(credentials)
.send({ settings: [{ _id: 'LDAP_Enable', value: false }] })
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body.error).to.match(/error-action-not-allowed/);
});
});
});
});
describe('[/settings/:_id]', () => {
before(async () => {
await updatePermission('view-privileged-setting', ['admin']);
await updatePermission('edit-privileged-setting', ['admin']);
});
after(async () => {
await updatePermission('view-privileged-setting', ['admin']);
await updatePermission('edit-privileged-setting', ['admin']);
});
it('should successfully return one setting (GET)', async () => {
return request
.get(api('settings/Site_Url'))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('_id', 'Site_Url');
expect(res.body).to.have.property('value');
});
});
it('should fail returning a setting if user does NOT have the view-privileged-setting permission (GET)', async () => {
await updatePermission('view-privileged-setting', []);
return request
.get(api('settings/Site_Url'))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(403)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]');
});
});
it('should successfully set the value of a setting (POST)', async () => {
return request
.post(api('settings/LDAP_Enable'))
.set(credentials)
.send({
value: false,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
});
});
it('should fail updating the value of a setting less than its minValue (POST)', async () => {
return request
.post(api('settings/Accounts_Default_User_Preferences_masterVolume'))
.set(credentials)
.send({
value: '-1',
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property(
'error',
'Value for setting Accounts_Default_User_Preferences_masterVolume must be greater than or equal to 0 [error-invalid-setting-value]',
);
});
});
it('should fail updating the value of a setting greater than its maxValue (POST)', async () => {
return request
.post(api('settings/Accounts_Default_User_Preferences_masterVolume'))
.set(credentials)
.send({
value: '101',
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property(
'error',
'Value for setting Accounts_Default_User_Preferences_masterVolume must be less than or equal to 100 [error-invalid-setting-value]',
);
});
});
it('should fail updating the value of a setting if user does NOT have the edit-privileged-setting permission (POST)', async () => {
await updatePermission('edit-privileged-setting', []);
return request
.post(api('settings/LDAP_Enable'))
.set(credentials)
.send({
value: false,
})
.expect('Content-Type', 'application/json')
.expect(403)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]');
});
});
});
describe('[Room_Toolbox_Layout]', () => {
const validLayout = [
{ roomType: ['c', 'p'], maxVisibleNormal: 3, items: [{ id: 'members-list', featured: true, order: 1 }] },
{ roomType: ['d'], maxVisibleNormal: 1 },
];
const saveLayout = (value: string) => request.post(api('settings/Room_Toolbox_Layout')).set(credentials).send({ value });
after(() => updateSetting('Room_Toolbox_Layout', ''));
it('should reject a value that is not valid JSON', async () => {
await saveLayout('not json {')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error', 'Room_Toolbox_Layout_Invalid');
expect(res.body).to.have.property('errorType', 'error-setting-validation-failed');
});
});
it('should reject an entry declaring an unsupported room type', async () => {
await saveLayout(JSON.stringify([{ roomType: ['l'] }]))
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error', 'Room_Toolbox_Layout_Invalid');
expect(res.body).to.have.property('errorType', 'error-setting-validation-failed');
});
});
it('should reject entries claiming the same room type', async () => {
await saveLayout(JSON.stringify([{ roomType: ['c', 'p'] }, { roomType: ['c'] }]))
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error', 'Room_Toolbox_Layout_Invalid');
expect(res.body).to.have.property('errorType', 'error-setting-validation-failed');
});
});
it('should reject an entry repeating a room type', async () => {
await saveLayout(JSON.stringify([{ roomType: ['c', 'c'] }]))
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error', 'Room_Toolbox_Layout_Invalid');
expect(res.body).to.have.property('errorType', 'error-setting-validation-failed');
});
});
it('should reject an entry carrying an unknown key', async () => {
await saveLayout(JSON.stringify([{ roomType: ['c'], somethingElse: true }]))
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error', 'Room_Toolbox_Layout_Invalid');
expect(res.body).to.have.property('errorType', 'error-setting-validation-failed');
});
});
it('should accept a document matching the schema', async () => {
await saveLayout(JSON.stringify(validLayout))
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
});
});
it('should accept an empty value, leaving the setting unconfigured', async () => {
await saveLayout('')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
});
});
});
describe('[/service.configurations]', () => {
it('should return service configurations', async () => {
const res = await request.get(api('service.configurations')).set(credentials).expect('Content-Type', 'application/json').expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('configurations');
});
describe('With OAuth enabled', () => {
before(() => updateSetting('Accounts_OAuth_Google', true));
after(() => updateSetting('Accounts_OAuth_Google', false));
it('should include the OAuth service in the response', (done) => {
// wait 3 seconds before getting the service list so the server has had time to update it
setTimeout(() => {
void request
.get(api('service.configurations'))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('configurations');
expect((res.body.configurations as LoginServiceConfiguration[]).find(({ service }) => service === 'google')).to.exist;
})
.end(done);
}, 3000);
});
});
describe('With OAuth disabled', () => {
before(() => updateSetting('Accounts_OAuth_Google', false));
after(() => updateSetting('Accounts_OAuth_Google', false));
it('should not include the OAuth service in the response', (done) => {
// wait 3 seconds before getting the service list so the server has had time to update it
setTimeout(() => {
void request
.get(api('service.configurations'))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('configurations');
expect((res.body.configurations as LoginServiceConfiguration[]).find(({ service }) => service === 'google')).to.not.exist;
})
.end(done);
}, 3000);
});
});
});
describe('/settings.oauth', () => {
it('should have return list of available oauth services when user is not logged', async () => {
const res = await request.get(api('settings.oauth')).expect('Content-Type', 'application/json').expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('services').and.to.be.an('array');
});
it('should have return list of available oauth services when user is logged', async () => {
const res = await request.get(api('settings.oauth')).set(credentials).expect('Content-Type', 'application/json').expect(200);
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('services').and.to.be.an('array');
});
});
(IS_EE ? describe : describe.skip)('/audit.settings', () => {
const formatDate = (date: Date) => date.toISOString();
let startDate: Date;
let endDate: Date;
before(async () => {
// Changing these settings to true and then false ensures that we have audited events
// So we can test filtering reliably
startDate = new Date();
await Promise.all([updateSetting('Accounts_AllowAnonymousRead', true), updateSetting('Accounts_AllowDeleteOwnAccount', true)]);
await Promise.all([updateSetting('Accounts_AllowAnonymousRead', false), updateSetting('Accounts_AllowDeleteOwnAccount', false)]);
endDate = new Date();
});
it('should return list of settings changed (no filters)', async () => {
await request
.get(api('audit.settings'))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('events').and.to.be.an('array');
expect(res.body.events.length).to.be.greaterThanOrEqual(4);
});
});
it('should return list of settings between date ranges', async () => {
await request
.get(api('audit.settings'))
.query({ start: formatDate(startDate), end: formatDate(endDate) })
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('events').and.to.be.an('array');
expect(res.body.events.length).to.be.greaterThanOrEqual(4);
(res.body.events as IServerEvents['settings.changed'][]).forEach((event) => {
expect(new Date(event.ts).getTime()).to.be.greaterThanOrEqual(startDate.getTime());
expect(new Date(event.ts).getTime()).to.be.lessThanOrEqual(endDate.getTime());
});
});
});
it('should throw error when sending invalid dates', async () => {
const startDate = new Date();
const endDate = 'abcdef';
await request
.get(api('audit.settings'))
.query({ start: formatDate(startDate), end: endDate })
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error', 'The "end" query parameter must be a valid date.');
});
});
it('should return list of settings changed filtered by an actor', async () => {
await request
.get(api('audit.settings'))
.query({ actor: { type: 'user' }, count: 10 })
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('events').and.to.be.an('array');
expect(res.body.events.length).to.be.greaterThanOrEqual(1);
(res.body.events as IServerEvents['settings.changed'][]).forEach((sEvent) => expect(sEvent.actor.type).to.be.equal('user'));
});
});
it('should return list of changes of an specific setting', async () => {
await request
.get(api('audit.settings'))
.query({ settingId: 'Site_Url' })
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('events').and.to.be.an('array');
(res.body.events as IServerEvents['settings.changed'][]).forEach((event) => {
expect(event.data[0].key).to.be.equal('id');
expect(event.data[0].value).to.be.equal('Site_Url');
});
});
});
it('should return list of changes filtered by an actor between date ranges', async () => {
await request
.get(api('audit.settings'))
.query({ actor: { type: 'user' }, start: formatDate(startDate), end: formatDate(endDate) })
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('events').and.to.be.an('array');
expect(res.body.events.length).to.be.greaterThanOrEqual(4);
(res.body.events as IServerEvents['settings.changed'][]).forEach((event) => {
expect(event.actor.type).to.be.equal('user');
expect(new Date(event.ts).getTime()).to.be.greaterThanOrEqual(startDate.getTime());
expect(new Date(event.ts).getTime()).to.be.lessThanOrEqual(endDate.getTime());
});
});
});
describe('Masking sensitive settings', () => {
let originalSmtpPassword: string | undefined;
let originalSmtpUsername: string | undefined;
let maskingStart: Date;
let maskingEnd: Date;
const testPassword = 'testpassword123';
const testUsername = 'testuser@example.com';
before(async () => {
maskingStart = new Date();
originalSmtpPassword = (await getSettingValueById('SMTP_Password')) as string | undefined;
originalSmtpUsername = (await getSettingValueById('SMTP_Username')) as string | undefined;
await updateSetting('SMTP_Password', testPassword);
await updateSetting('SMTP_Username', testUsername);
maskingEnd = new Date();
});
after(async () => {
await updateSetting('SMTP_Password', originalSmtpPassword || '');
await updateSetting('SMTP_Username', originalSmtpUsername || '');
});
it('should mask sensitive settings in audit logs', async () => {
await request
.get(api('audit.settings'))
.query({ settingId: 'SMTP_Password', start: formatDate(maskingStart), end: formatDate(maskingEnd) })
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('events').and.to.be.an('array');
expect(res.body.events.length).to.be.greaterThanOrEqual(1);
const passwordEvents = res.body.events as IServerEvents['settings.changed'][];
const passwordEvent = passwordEvents.find((event) => {
const settingId = event.data.find((item) => item.key === 'id')?.value;
return settingId === 'SMTP_Password';
});
expect(passwordEvent).to.exist;
if (passwordEvent) {
const previous = passwordEvent.data.find((item) => item.key === 'previous')?.value;
const current = passwordEvent.data.find((item) => item.key === 'current')?.value;
if (previous && typeof previous === 'string' && previous.length > 0) {
expect(previous).to.include('*');
expect(previous).to.not.equal(testPassword);
if (previous.length > 8) {
expect(previous.substring(0, 3)).to.equal(originalSmtpPassword?.substring(0, 3));
expect(previous.substring(3)).to.match(/^\*+$/);
}
}
if (current && typeof current === 'string' && current.length > 0) {
expect(current).to.include('*');
expect(current).to.not.equal(testPassword);
if (testPassword.length > 8) {
expect(current.substring(0, 3)).to.equal(testPassword.substring(0, 3));
expect(current.substring(3)).to.match(/^\*+$/);
} else {
expect(current).to.match(/^\*+$/);
}
}
}
});
await request
.get(api('audit.settings'))
.query({ settingId: 'SMTP_Username', start: formatDate(maskingStart), end: formatDate(maskingEnd) })
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('events').and.to.be.an('array');
expect(res.body.events.length).to.be.greaterThanOrEqual(1);
const usernameEvents = res.body.events as IServerEvents['settings.changed'][];
const usernameEvent = usernameEvents.find((event) => {
const settingId = event.data.find((item) => item.key === 'id')?.value;
return settingId === 'SMTP_Username';
});
expect(usernameEvent).to.exist;
if (usernameEvent) {
const previous = usernameEvent.data.find((item) => item.key === 'previous')?.value;
const current = usernameEvent.data.find((item) => item.key === 'current')?.value;
if (previous && typeof previous === 'string' && previous.length > 0) {
expect(previous).to.include('*');
expect(previous).to.not.equal(originalSmtpUsername);
}
if (current && typeof current === 'string' && current.length > 0) {
expect(current).to.include('*');
expect(current).to.not.equal(testUsername);
if (testUsername.length > 8) {
expect(current.substring(0, 3)).to.equal(testUsername.substring(0, 3));
expect(current.substring(3)).to.match(/^\*+$/);
} else {
expect(current).to.match(/^\*+$/);
}
}
}
});
});
});
describe('Sorting', () => {
const isSortedAscending = (events: IServerEvents['settings.changed'][]) =>
events.every((event, i) => i === 0 || new Date(event.ts).getTime() >= new Date(events[i - 1].ts).getTime());
const isSortedDescending = (events: IServerEvents['settings.changed'][]) =>
events.every((event, i) => i === 0 || new Date(event.ts).getTime() <= new Date(events[i - 1].ts).getTime());
it('should sort by timestamp ascending', async () => {
const response = await request
.get(api('audit.settings'))
.query({ sort: JSON.stringify({ ts: 1 }), start: formatDate(startDate), end: formatDate(endDate) })
.set(credentials)
.expect(200);
expect(response.body).to.have.property('success', true);
expect(response.body.events).to.have.length.greaterThanOrEqual(2);
expect(isSortedAscending(response.body.events)).to.be.true;
});
it('should sort by timestamp descending', async () => {
const response = await request
.get(api('audit.settings'))
.query({ sort: JSON.stringify({ ts: -1 }), start: formatDate(startDate), end: formatDate(endDate) })
.set(credentials)
.expect(200);
expect(response.body).to.have.property('success', true);
expect(response.body.events).to.have.length.greaterThanOrEqual(2);
expect(isSortedDescending(response.body.events)).to.be.true;
});
it('should sort by timestamp descending by default', async () => {
const response = await request.get(api('audit.settings')).set(credentials).expect(200);
expect(response.body).to.have.property('success', true);
expect(response.body.events).to.have.length.greaterThanOrEqual(2);
expect(isSortedDescending(response.body.events)).to.be.true;
});
});
});
describe('Custom OAuth admin endpoints', () => {
const oauthName = 'apitest';
const enableSettingId = 'Accounts_OAuth_Custom-Apitest';
before(() => updatePermission('add-oauth-service', ['admin']));
// guarantee the permission is restored even if a permission-denied assertion fails mid-test,
// so a failure cannot cascade into the following cases
afterEach(() => updatePermission('add-oauth-service', ['admin']));
after(async () => {
await updatePermission('add-oauth-service', ['admin']);
// best-effort cleanup in case a test left the custom service behind
await request.post(api('settings.removeCustomOAuth')).set(credentials).send({ name: oauthName });
});
describe('[/settings.addCustomOAuth]', () => {
it('should fail when unauthenticated', () => request.post(api('settings.addCustomOAuth')).send({ name: oauthName }).expect(401));
it('should fail when the "name" param is not provided', () =>
request.post(api('settings.addCustomOAuth')).set(credentials).send({}).expect(400));
it('should fail when the user does not have the add-oauth-service permission', async () => {
await updatePermission('add-oauth-service', []);
await request
.post(api('settings.addCustomOAuth'))
.set(credentials)
.send({ name: oauthName })
.expect((res) => {
expect(res.status).to.equal(403);
expect(res.body).to.have.property('success', false);
});
});
it('should add a custom oauth service', async () => {
await request
.post(api('settings.addCustomOAuth'))
.set(credentials)
.send({ name: oauthName })
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
});
const value = await getSettingValueById(enableSettingId);
expect(value).to.be.a('boolean');
});
});
describe('[/settings.refreshOAuthServices]', () => {
it('should fail when unauthenticated', () => request.post(api('settings.refreshOAuthServices')).expect(401));
it('should fail when the user does not have the add-oauth-service permission', async () => {
await updatePermission('add-oauth-service', []);
await request
.post(api('settings.refreshOAuthServices'))
.set(credentials)
.expect((res) => {
expect(res.status).to.equal(403);
expect(res.body).to.have.property('success', false);
});
});
it('should refresh the oauth services', () =>
request
.post(api('settings.refreshOAuthServices'))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
}));
});
describe('[/settings.removeCustomOAuth]', () => {
it('should fail when unauthenticated', () => request.post(api('settings.removeCustomOAuth')).send({ name: oauthName }).expect(401));
it('should fail when the "name" param is not provided', () =>
request.post(api('settings.removeCustomOAuth')).set(credentials).send({}).expect(400));
it('should fail when the "name" normalizes to an empty string', () =>
request
.post(api('settings.removeCustomOAuth'))
.set(credentials)
.send({ name: '!!!' })
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
}));
it('should fail when the user does not have the add-oauth-service permission', async () => {
await updatePermission('add-oauth-service', []);
await request
.post(api('settings.removeCustomOAuth'))
.set(credentials)
.send({ name: oauthName })
.expect((res) => {
expect(res.status).to.equal(403);
expect(res.body).to.have.property('success', false);
});
});
it('should remove a custom oauth service', async () => {
await request
.post(api('settings.removeCustomOAuth'))
.set(credentials)
.send({ name: oauthName })
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
});
// the provider settings should be gone: settings/:_id answers 400 for an unknown id
await request.get(`/api/v1/settings/${enableSettingId}`).set(credentials).expect(400);
});
});
});
});