From 2555d711b23d5045e2096eba6042b6f26b3aae8a Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Thu, 22 Apr 2021 13:46:54 +0100 Subject: [PATCH] scoped configuration management --- packages/auth/src/db/utils.js | 49 ++++++++++++++++++- packages/auth/src/index.js | 2 + .../auth/src/middleware/passport/google.js | 6 +-- .../src/api/controllers/admin/configs.js | 48 ++---------------- .../src/api/controllers/admin/templates.js | 2 +- packages/worker/src/api/controllers/auth.js | 15 ++++-- 6 files changed, 67 insertions(+), 55 deletions(-) diff --git a/packages/auth/src/db/utils.js b/packages/auth/src/db/utils.js index 408daf7dd4..2f34bc0c51 100644 --- a/packages/auth/src/db/utils.js +++ b/packages/auth/src/db/utils.js @@ -98,7 +98,7 @@ exports.getTemplateParams = (ownerId, templateId, otherProps = {}) => { * Generates a new configuration ID. * @returns {string} The new configuration ID which the config doc can be stored under. */ -exports.generateConfigID = ({ type, group, user }) => { +const generateConfigID = ({ type, group, user }) => { const scope = [type, group, user].filter(Boolean).join(SEPARATOR) return `${DocumentTypes.CONFIG}${SEPARATOR}${scope}` @@ -107,7 +107,7 @@ exports.generateConfigID = ({ type, group, user }) => { /** * Gets parameters for retrieving configurations. */ -exports.getConfigParams = ({ type, group, user }, otherProps = {}) => { +const getConfigParams = ({ type, group, user }, otherProps = {}) => { const scope = [type, group, user].filter(Boolean).join(SEPARATOR) return { @@ -116,3 +116,48 @@ exports.getConfigParams = ({ type, group, user }, otherProps = {}) => { endkey: `${DocumentTypes.CONFIG}${SEPARATOR}${scope}${UNICODE_MAX}`, } } + +/** + * Returns the most granular configuration document from the DB based on the type, group and userID passed. + * @param {*} db - db instance to quer + * @param {Object} scopes - the type, group and userID scopes of the configuration. + * @returns The most granular configuration document based on the scope. + */ +const determineScopedConfig = async function(db, { type, user, group }) { + const response = await db.allDocs( + getConfigParams( + { type, user, group }, + { + include_docs: true, + } + ) + ) + const configs = response.rows.map(row => row.doc) + + // Find the config with the most granular scope based on context + const scopedConfig = configs.find(config => { + // Config is specific to a user and a group + if (config._id.includes(generateConfigID({ type, user, group }))) { + return config + } + + // Config is specific to a user + if (config._id.includes(generateConfigID({ type, user }))) { + return config + } + + // Config is specific to a group only + if (config._id.includes(generateConfigID({ type, group }))) { + return config + } + + // Config specific to a config type only + return config + }) + + return scopedConfig +} + +exports.generateConfigID = generateConfigID +exports.getConfigParams = getConfigParams +exports.determineScopedConfig = determineScopedConfig diff --git a/packages/auth/src/index.js b/packages/auth/src/index.js index bdc9e16609..4149314b4d 100644 --- a/packages/auth/src/index.js +++ b/packages/auth/src/index.js @@ -22,6 +22,7 @@ const { getEmailFromUserID, generateConfigID, getConfigParams, + determineScopedConfig, } = require("./db/utils") // Strategies @@ -71,6 +72,7 @@ module.exports = { getEmailFromUserID, generateConfigID, getConfigParams, + determineScopedConfig, hash, compare, getAppId, diff --git a/packages/auth/src/middleware/passport/google.js b/packages/auth/src/middleware/passport/google.js index ea49b2c35c..968dfa3e93 100644 --- a/packages/auth/src/middleware/passport/google.js +++ b/packages/auth/src/middleware/passport/google.js @@ -51,12 +51,8 @@ async function authenticate(token, tokenSecret, profile, done) { * from couchDB rather than environment variables, using this factory is necessary for dynamically configuring passport. * @returns Dynamically configured Passport Google Strategy */ -exports.strategyFactory = async function(scope) { +exports.strategyFactory = async function(config) { try { - const db = database.getDB(StaticDatabases.GLOBAL.name) - - const config = await db.get(scope) - const { clientID, clientSecret, callbackURL } = config if (!clientID || !clientSecret || !callbackURL) { diff --git a/packages/worker/src/api/controllers/admin/configs.js b/packages/worker/src/api/controllers/admin/configs.js index 5e39ebefc0..08c2b6df7d 100644 --- a/packages/worker/src/api/controllers/admin/configs.js +++ b/packages/worker/src/api/controllers/admin/configs.js @@ -1,5 +1,5 @@ const CouchDB = require("../../../db") -const { StaticDatabases } = require("@budibase/auth") +const { StaticDatabases, determineScopedConfig } = require("@budibase/auth") const { generateConfigID, getConfigParams } = require("@budibase/auth") const GLOBAL_DB = StaticDatabases.GLOBAL.name @@ -59,49 +59,11 @@ exports.find = async function(ctx) { } try { - const response = await db.allDocs( - getConfigParams( - { - type: ctx.params.type, - user: userId, - group, - }, - { - include_docs: true, - } - ) - ) - const configs = response.rows.map(row => row.doc) - // Find the config with the most granular scope based on context - const scopedConfig = configs.find(config => { - // Config is specific to a user and a group - if ( - config._id.includes( - generateConfigID({ type: ctx.params.type, user: userId, group }) - ) - ) { - return config - } - - // Config is specific to a user - if ( - config._id.includes( - generateConfigID({ type: ctx.params.type, user: userId }) - ) - ) { - return config - } - - // Config is specific to a group only - if ( - config._id.includes(generateConfigID({ type: ctx.params.type, group })) - ) { - return config - } - - // Config specific to a config type only - return config + const scopedConfig = await determineScopedConfig(db, { + type: ctx.params.type, + user: userId, + group, }) if (scopedConfig) { diff --git a/packages/worker/src/api/controllers/admin/templates.js b/packages/worker/src/api/controllers/admin/templates.js index 2b4f6f8284..d91323e0a1 100644 --- a/packages/worker/src/api/controllers/admin/templates.js +++ b/packages/worker/src/api/controllers/admin/templates.js @@ -74,6 +74,6 @@ exports.find = async ctx => { exports.destroy = async ctx => { // TODO - const db = new CouchDB(GLOBAL_DB) + // const db = new CouchDB(GLOBAL_DB) ctx.body = {} } diff --git a/packages/worker/src/api/controllers/auth.js b/packages/worker/src/api/controllers/auth.js index f9e5e212de..a58a7abdab 100644 --- a/packages/worker/src/api/controllers/auth.js +++ b/packages/worker/src/api/controllers/auth.js @@ -1,10 +1,14 @@ +const { determineScopedConfig } = require("@budibase/auth") const authPkg = require("@budibase/auth") const { google } = require("@budibase/auth/src/middleware") const { Configs } = require("../../constants") +const CouchDB = require("../../db") const { clearCookie } = authPkg.utils const { Cookies } = authPkg const { passport } = authPkg.auth +const GLOBAL_DB = authPkg.StaticDatabases.GLOBAL.name + exports.authenticate = async (ctx, next) => { return passport.authenticate("local", async (err, user) => { if (err) { @@ -41,11 +45,12 @@ exports.logout = async ctx => { * On a successful login, you will be redirected to the googleAuth callback route. */ exports.googlePreAuth = async (ctx, next) => { - const strategy = await google.strategyFactory({ + const db = new CouchDB(GLOBAL_DB) + const config = await determineScopedConfig(db, { type: Configs.GOOGLE, - user: ctx.user._id, group: ctx.query.group, }) + const strategy = await google.strategyFactory(config) return passport.authenticate(strategy, { scope: ["profile", "email"], @@ -53,11 +58,13 @@ exports.googlePreAuth = async (ctx, next) => { } exports.googleAuth = async (ctx, next) => { - const strategy = await google.strategyFactory({ + const db = new CouchDB(GLOBAL_DB) + + const config = await determineScopedConfig(db, { type: Configs.GOOGLE, - user: ctx.user._id, group: ctx.query.group, }) + const strategy = await google.strategyFactory(config) return passport.authenticate( strategy,