security: Enable authorize plugins to grant modify-only access
parent
6ed11b7605
commit
02757079c0
|
@ -253,8 +253,8 @@ following are true:
|
||||||
For pre-authentication invocations of your authorize function, you can pass the
|
For pre-authentication invocations of your authorize function, you can pass the
|
||||||
following values to the provided callback:
|
following values to the provided callback:
|
||||||
|
|
||||||
* `[true]` or `['create']` will immediately grant access without requiring the
|
* `[true]`, `['create']`, or `['modify']` will immediately grant access without
|
||||||
user to authenticate.
|
requiring the user to authenticate.
|
||||||
* `[false]` will trigger authentication unless authentication is not required.
|
* `[false]` will trigger authentication unless authentication is not required.
|
||||||
* `[]` or `undefined` will defer the decision to the next authorization plugin
|
* `[]` or `undefined` will defer the decision to the next authorization plugin
|
||||||
(if any, otherwise it is the same as calling with `[false]`).
|
(if any, otherwise it is the same as calling with `[false]`).
|
||||||
|
@ -267,7 +267,11 @@ public.
|
||||||
For post-authentication invocations of your authorize function, you can pass the
|
For post-authentication invocations of your authorize function, you can pass the
|
||||||
following values to the provided callback:
|
following values to the provided callback:
|
||||||
|
|
||||||
* `[true]` or `['create']` will grant access.
|
* `[true]` or `['create']` will grant access to modify or create the pad if the
|
||||||
|
request is for a pad, otherwise access is simply granted. (Access will be
|
||||||
|
downgraded to modify-only if `settings.editOnly` is true.)
|
||||||
|
* `['modify']` will grant access to modify but not create the pad if the
|
||||||
|
request is for a pad, otherwise access is simply granted.
|
||||||
* `[false]` will deny access.
|
* `[false]` will deny access.
|
||||||
* `[]` or `undefined` will defer the authorization decision to the next
|
* `[]` or `undefined` will defer the authorization decision to the next
|
||||||
authorization plugin (if any, otherwise deny).
|
authorization plugin (if any, otherwise deny).
|
||||||
|
|
|
@ -58,6 +58,8 @@ exports.checkAccess = async function(padID, sessionCookie, token, password, user
|
||||||
return DENY;
|
return DENY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let canCreate = !settings.editOnly;
|
||||||
|
|
||||||
if (settings.requireAuthentication) {
|
if (settings.requireAuthentication) {
|
||||||
// Make sure the user has authenticated if authentication is required. The caller should have
|
// Make sure the user has authenticated if authentication is required. The caller should have
|
||||||
// already performed this check, but it is repeated here just in case.
|
// already performed this check, but it is repeated here just in case.
|
||||||
|
@ -73,6 +75,7 @@ exports.checkAccess = async function(padID, sessionCookie, token, password, user
|
||||||
authLogger.debug('access denied: unauthorized');
|
authLogger.debug('access denied: unauthorized');
|
||||||
return DENY;
|
return DENY;
|
||||||
}
|
}
|
||||||
|
if (level !== 'create') canCreate = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// allow plugins to deny access
|
// allow plugins to deny access
|
||||||
|
@ -88,7 +91,7 @@ exports.checkAccess = async function(padID, sessionCookie, token, password, user
|
||||||
const p_padExists = padManager.doesPadExist(padID);
|
const p_padExists = padManager.doesPadExist(padID);
|
||||||
|
|
||||||
const padExists = await p_padExists;
|
const padExists = await p_padExists;
|
||||||
if (!padExists && settings.editOnly) {
|
if (!padExists && !canCreate) {
|
||||||
authLogger.debug('access denied: user attempted to create a pad, which is prohibited');
|
authLogger.debug('access denied: user attempted to create a pad, which is prohibited');
|
||||||
return DENY;
|
return DENY;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ exports.normalizeAuthzLevel = (level) => {
|
||||||
switch (level) {
|
switch (level) {
|
||||||
case true:
|
case true:
|
||||||
return 'create';
|
return 'create';
|
||||||
|
case 'modify':
|
||||||
case 'create':
|
case 'create':
|
||||||
return level;
|
return level;
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -125,6 +125,7 @@ describe('socket.io access checks', function() {
|
||||||
beforeEach(async function() {
|
beforeEach(async function() {
|
||||||
Object.assign(settingsBackup, settings);
|
Object.assign(settingsBackup, settings);
|
||||||
assert(socket == null);
|
assert(socket == null);
|
||||||
|
settings.editOnly = false;
|
||||||
settings.requireAuthentication = false;
|
settings.requireAuthentication = false;
|
||||||
settings.requireAuthorization = false;
|
settings.requireAuthorization = false;
|
||||||
settings.users = {
|
settings.users = {
|
||||||
|
@ -224,4 +225,57 @@ describe('socket.io access checks', function() {
|
||||||
const message = await handshake(socket, 'other-pad');
|
const message = await handshake(socket, 'other-pad');
|
||||||
assert.equal(message.accessStatus, 'deny');
|
assert.equal(message.accessStatus, 'deny');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Authorization levels via authorize hook
|
||||||
|
it("level='create' -> can create", async () => {
|
||||||
|
authorize = () => 'create';
|
||||||
|
settings.requireAuthentication = true;
|
||||||
|
settings.requireAuthorization = true;
|
||||||
|
const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
|
||||||
|
socket = await connect(res);
|
||||||
|
const clientVars = await handshake(socket, 'pad');
|
||||||
|
assert.equal(clientVars.type, 'CLIENT_VARS');
|
||||||
|
assert.equal(clientVars.data.readonly, false);
|
||||||
|
});
|
||||||
|
it('level=true -> can create', async () => {
|
||||||
|
authorize = () => true;
|
||||||
|
settings.requireAuthentication = true;
|
||||||
|
settings.requireAuthorization = true;
|
||||||
|
const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
|
||||||
|
socket = await connect(res);
|
||||||
|
const clientVars = await handshake(socket, 'pad');
|
||||||
|
assert.equal(clientVars.type, 'CLIENT_VARS');
|
||||||
|
assert.equal(clientVars.data.readonly, false);
|
||||||
|
});
|
||||||
|
it("level='modify' -> can modify", async () => {
|
||||||
|
const pad = await padManager.getPad('pad'); // Create the pad.
|
||||||
|
authorize = () => 'modify';
|
||||||
|
settings.requireAuthentication = true;
|
||||||
|
settings.requireAuthorization = true;
|
||||||
|
const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
|
||||||
|
socket = await connect(res);
|
||||||
|
const clientVars = await handshake(socket, 'pad');
|
||||||
|
assert.equal(clientVars.type, 'CLIENT_VARS');
|
||||||
|
assert.equal(clientVars.data.readonly, false);
|
||||||
|
});
|
||||||
|
it("level='create' settings.editOnly=true -> unable to create", async () => {
|
||||||
|
authorize = () => 'create';
|
||||||
|
settings.requireAuthentication = true;
|
||||||
|
settings.requireAuthorization = true;
|
||||||
|
settings.editOnly = true;
|
||||||
|
const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
|
||||||
|
socket = await connect(res);
|
||||||
|
const message = await handshake(socket, 'pad');
|
||||||
|
assert.equal(message.accessStatus, 'deny');
|
||||||
|
});
|
||||||
|
it("level='modify' settings.editOnly=false -> unable to create", async () => {
|
||||||
|
authorize = () => 'modify';
|
||||||
|
settings.requireAuthentication = true;
|
||||||
|
settings.requireAuthorization = true;
|
||||||
|
settings.editOnly = false;
|
||||||
|
const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
|
||||||
|
socket = await connect(res);
|
||||||
|
const message = await handshake(socket, 'pad');
|
||||||
|
assert.equal(message.accessStatus, 'deny');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue