allow option to make pad names case-insensitive (#5501) by @DanielHabenicht

* New option to make pad names case-insensitive

fixes #3844

* fix helper.gotoTimeslider()

* fix helper.aNewPad() return value

* Update src/node/utils/Settings.js

Co-authored-by: Richard Hansen <rhansen@rhansen.org>

* remove timeout

* rename enforceLowerCasePadIds to lowerCasePadIds

* use before and after hooks

* update with socket specific test

* enforce sanitizing padID for websocket connections

- only enforce for newly created pads, to combat case-sensitive pad name hijacking

* Added updated package.json file.

---------

Co-authored-by: Richard Hansen <rhansen@rhansen.org>
Co-authored-by: SamTV12345 <40429738+samtv12345@users.noreply.github.com>
This commit is contained in:
DanielHabenicht 2023-07-03 20:52:49 +02:00 committed by GitHub
parent 22704f7dff
commit 675c0130b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 950 additions and 858 deletions

View File

@ -634,5 +634,10 @@
"customLocaleStrings": {},
/* Disable Admin UI tests */
"enableAdminUITests": false
"enableAdminUITests": false,
/*
* Enable/Disable case-insensitive pad names.
*/
"lowerCasePadIds": "${LOWER_CASE_PAD_IDS:false}"
}

View File

@ -635,5 +635,10 @@
"customLocaleStrings": {},
/* Disable Admin UI tests */
"enableAdminUITests": false
"enableAdminUITests": false,
/*
* Enable/Disable case-insensitive pad names.
*/
"lowerCasePadIds": false
}

View File

@ -22,6 +22,7 @@
const CustomError = require('../utils/customError');
const Pad = require('../db/Pad');
const db = require('./DB');
const settings = require('../utils/Settings');
/**
* A cache of all loaded Pads.
@ -170,6 +171,8 @@ exports.sanitizePadId = async (padId) => {
padId = padId.replace(from, to);
}
if (settings.lowerCasePadIds) padId = padId.toLowerCase();
// we're out of possible transformations, so just return it
return padId;
};

View File

@ -236,6 +236,11 @@ exports.handleMessage = async (socket, message) => {
padID: message.padId,
token: message.token,
};
// Pad does not exist, so we need to sanitize the id
if (!(await padManager.doesPadExist(thisSession.auth.padID))) {
thisSession.auth.padID = await padManager.sanitizePadId(thisSession.auth.padID);
}
const padIds = await readOnlyManager.getIds(thisSession.auth.padID);
thisSession.padId = padIds.padId;
thisSession.readOnlyPadId = padIds.readOnlyPadId;

View File

@ -430,6 +430,11 @@ exports.importMaxFileSize = 50 * 1024 * 1024;
*/
exports.enableAdminUITests = false;
/*
* Enable auto conversion of pad Ids to lowercase.
* e.g. /p/EtHeRpAd to /p/etherpad
*/
exports.lowerCasePadIds = false;
// checks if abiword is avaiable
exports.abiwordAvailable = () => {

1682
src/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,90 @@
'use strict';
const assert = require('assert').strict;
const common = require('../common');
const padManager = require('../../../node/db/PadManager');
const settings = require('../../../node/utils/Settings');
describe(__filename, function () {
let agent;
const cleanUpPads = async () => {
const {padIDs} = await padManager.listAllPads();
await Promise.all(padIDs.map(async (padId) => {
if (await padManager.doesPadExist(padId)) {
const pad = await padManager.getPad(padId);
await pad.remove();
}
}));
};
let backup;
before(async function () {
backup = settings.lowerCasePadIds;
agent = await common.init();
});
beforeEach(async function () {
await cleanUpPads();
});
afterEach(async function () {
await cleanUpPads();
});
after(async function () {
settings.lowerCasePadIds = backup;
});
describe('not activated', function () {
beforeEach(async function () {
settings.lowerCasePadIds = false;
});
it('do nothing', async function () {
await agent.get('/p/UPPERCASEpad')
.expect(200);
});
});
describe('activated', function () {
beforeEach(async function () {
settings.lowerCasePadIds = true;
});
it('lowercase pad ids', async function () {
await agent.get('/p/UPPERCASEpad')
.expect(302)
.expect('location', 'uppercasepad');
});
it('keeps old pads accessible', async function () {
Object.assign(settings, {
lowerCasePadIds: false,
});
await padManager.getPad('ALREADYexistingPad', 'oldpad');
await padManager.getPad('alreadyexistingpad', 'newpad');
Object.assign(settings, {
lowerCasePadIds: true,
});
const oldPad = await agent.get('/p/ALREADYexistingPad').expect(200);
const oldPadSocket = await common.connect(oldPad);
const oldPadHandshake = await common.handshake(oldPadSocket, 'ALREADYexistingPad');
assert.equal(oldPadHandshake.data.padId, 'ALREADYexistingPad');
assert.equal(oldPadHandshake.data.collab_client_vars.initialAttributedText.text, 'oldpad\n');
const newPad = await agent.get('/p/alreadyexistingpad').expect(200);
const newPadSocket = await common.connect(newPad);
const newPadHandshake = await common.handshake(newPadSocket, 'alreadyexistingpad');
assert.equal(newPadHandshake.data.padId, 'alreadyexistingpad');
assert.equal(newPadHandshake.data.collab_client_vars.initialAttributedText.text, 'newpad\n');
});
it('disallow creation of different case pad-name via socket connection', async function () {
await padManager.getPad('maliciousattempt', 'attempt');
const newPad = await agent.get('/p/maliciousattempt').expect(200);
const newPadSocket = await common.connect(newPad);
const newPadHandshake = await common.handshake(newPadSocket, 'MaliciousAttempt');
assert.equal(newPadHandshake.data.collab_client_vars.initialAttributedText.text, 'attempt\n');
});
});
});

View File

@ -181,7 +181,9 @@ const helper = {};
helper.padOuter$.fx.off = true;
helper.padInner$.fx.off = true;
return opts.id;
// Don't return opts.id -- the server might have redirected the browser to a transformed version
// of the requested pad ID.
return helper.padChrome$.window.clientVars.padId;
};
helper.newAdmin = async (page) => {

View File

@ -175,9 +175,8 @@ helper.disableStickyChatviaIcon = async () => {
*/
helper.gotoTimeslider = async (revision) => {
revision = Number.isInteger(revision) ? `#${revision}` : '';
const iframe = $('#iframe-container iframe');
iframe.attr('src', `${iframe.attr('src')}/timeslider${revision}`);
helper.padChrome$.window.location.href =
`${helper.padChrome$.window.location.pathname}/timeslider${revision}`;
await helper.waitForPromise(() => helper.timesliderTimerTime() &&
!Number.isNaN(new Date(helper.timesliderTimerTime()).getTime()), 10000);
};