From 89fa4f1a7a8ddaecad3659f6eaa37416a4452744 Mon Sep 17 00:00:00 2001 From: Tobias Bocanegra Date: Wed, 15 Nov 2023 03:11:05 -0800 Subject: [PATCH] feat: implement partition specific auth (#456) fixes #274 --- src/steps/authenticate.js | 39 +++++++++++++++++++++-------- src/steps/utils.js | 7 ++++++ src/utils/json-filter.js | 3 ++- test/steps/authenticate.test.js | 44 ++++++++++++++++++++++++++++++++- 4 files changed, 81 insertions(+), 12 deletions(-) diff --git a/src/steps/authenticate.js b/src/steps/authenticate.js index 654b64f0..94d3bec9 100644 --- a/src/steps/authenticate.js +++ b/src/steps/authenticate.js @@ -10,6 +10,7 @@ * governing permissions and limitations under the License. */ import { getAuthInfo, makeAuthError } from '../utils/auth.js'; +import { toArray } from './utils.js'; /** * Checks if the given email is allowed. @@ -27,6 +28,26 @@ export function isAllowed(email = '', allows = []) { return allows.findIndex((a) => a === email || a === wild) >= 0; } +/** + * Returns the normalized access configuration for the current partition. + * @param state + * @return {{}} + */ +export function getAccessConfig(state) { + const { access } = state.config; + if (!access) { + return { + allow: [], + apiKeyId: [], + }; + } + const { partition } = state; + return { + allow: toArray(access[partition]?.allow ?? access.allow), + apiKeyId: toArray(access[partition]?.apiKeyId ?? access.apiKeyId), + }; +} + /** * Handles authentication * @type PipelineStep @@ -43,8 +64,11 @@ export async function authenticate(state, req, res) { return; } + // get partition relative auth info + const access = getAccessConfig(state); + // if not protected, do nothing - if (!state.config?.access?.allow) { + if (!access.allow.length) { return; } @@ -77,20 +101,15 @@ export async function authenticate(state, req, res) { // validate jti if (jti) { - const ids = Array.isArray(state.config.access.apiKeyId) - ? state.config.access.apiKeyId - : [state.config.access.apiKeyId]; - if (ids.indexOf(jti) < 0) { - state.log.warn(`[auth] invalid jti ${jti}: does not match configured id ${state.config.access.apiKeyId}`); + if (access.apiKeyId.indexOf(jti) < 0) { + state.log.warn(`[auth] invalid jti ${jti}: does not match configured id ${access.apiKeyId}`); makeAuthError(state, req, res, 'invalid-jti'); } } // check profile is allowed - const { allow } = state.config.access; - const allows = Array.isArray(allow) ? allow : [allow]; - if (!isAllowed(email, allows)) { - state.log.warn(`[auth] profile not allowed for ${allows}`); + if (!isAllowed(email, access.allow)) { + state.log.warn(`[auth] profile not allowed for ${access.allow}`); makeAuthError(state, req, res, 'forbidden', 403); } } diff --git a/src/steps/utils.js b/src/steps/utils.js index ade8e374..15e8ab3b 100644 --- a/src/steps/utils.js +++ b/src/steps/utils.js @@ -216,3 +216,10 @@ export function rewriteUrl(state, url) { return url; } + +export function toArray(v) { + if (!v) { + return []; + } + return Array.isArray(v) ? v : [v]; +} diff --git a/src/utils/json-filter.js b/src/utils/json-filter.js index 6fba68ee..f07ceb48 100644 --- a/src/utils/json-filter.js +++ b/src/utils/json-filter.js @@ -10,6 +10,7 @@ * governing permissions and limitations under the License. */ import { PipelineStatusError } from '../PipelineStatusError.js'; +import { toArray } from '../steps/utils.js'; const TYPE_KEY = ':type'; @@ -78,7 +79,7 @@ export default function jsonFilter(state, res, query) { } state.timer?.update('json-filter'); - const requestedSheets = Array.isArray(sheet) ? sheet : [sheet]; + const requestedSheets = toArray(sheet); if (requestedSheets.length === 0 && 'default' in json) { requestedSheets.push('default'); } diff --git a/test/steps/authenticate.test.js b/test/steps/authenticate.test.js index ca133766..2aaf44f3 100644 --- a/test/steps/authenticate.test.js +++ b/test/steps/authenticate.test.js @@ -13,7 +13,7 @@ import assert from 'assert'; import esmock from 'esmock'; import { - authenticate, + authenticate, getAccessConfig, isAllowed, isOwnerRepoAllowed, requireProject, @@ -383,3 +383,45 @@ describe('Authenticate Test', () => { assert.strictEqual(res.status, 200); }); }); + +describe('Access config tests', () => { + it('returns empty access config', () => { + const state = new PipelineState({}); + assert.deepStrictEqual(getAccessConfig(state), { + allow: [], + apiKeyId: [], + }); + }); + + it('returns default access config', () => { + const state = new PipelineState({}); + state.config = { + access: { + allow: '*@adobe.com', + }, + }; + assert.deepStrictEqual(getAccessConfig(state), { + allow: ['*@adobe.com'], + apiKeyId: [], + }); + }); + + it('can partially overwrite access config', () => { + const state = new PipelineState({ + partition: 'live', + }); + state.config = { + access: { + allow: '*@adobe.com', + apiKeyId: '1234', + live: { + allow: ['foo@adobe.com', 'bar@adobe.com'], + }, + }, + }; + assert.deepStrictEqual(getAccessConfig(state), { + allow: ['foo@adobe.com', 'bar@adobe.com'], + apiKeyId: ['1234'], + }); + }); +});