diff --git a/packages/server/src/api/controllers/user.js b/packages/server/src/api/controllers/user.js index fc207c479f..b44983cf9e 100644 --- a/packages/server/src/api/controllers/user.js +++ b/packages/server/src/api/controllers/user.js @@ -5,9 +5,9 @@ const { } = require("../../db/utils") const { InternalTables } = require("../../db/utils") const { - getGlobalUsers, addAppRoleToUser, } = require("../../utilities/workerRequests") +const { getGlobalUsers, getGlobalUser } = require("../../utilities/global") const { getFullUser } = require("../../utilities/users") function removeGlobalProps(user) { @@ -20,7 +20,7 @@ function removeGlobalProps(user) { exports.fetchMetadata = async function (ctx) { const database = new CouchDB(ctx.appId) - const global = await getGlobalUsers(ctx, ctx.appId) + const global = await getGlobalUsers(ctx.appId) const metadata = ( await database.allDocs( getUserMetadataParams(null, { diff --git a/packages/server/src/api/routes/tests/routing.spec.js b/packages/server/src/api/routes/tests/routing.spec.js index 38cd62ae76..96ec492dbc 100644 --- a/packages/server/src/api/routes/tests/routing.spec.js +++ b/packages/server/src/api/routes/tests/routing.spec.js @@ -26,11 +26,6 @@ describe("/routing", () => { describe("fetch", () => { it("returns the correct routing for basic user", async () => { - workerRequests.getGlobalUsers.mockImplementationOnce((ctx, appId) => { - return { - roleId: BUILTIN_ROLE_IDS.BASIC, - } - }) const res = await request .get(`/api/routing/client`) .set(await config.roleHeaders({ @@ -52,13 +47,6 @@ describe("/routing", () => { }) it("returns the correct routing for power user", async () => { - workerRequests.getGlobalUsers.mockImplementationOnce((ctx, appId) => { - return { - roles: { - [appId]: BUILTIN_ROLE_IDS.POWER, - } - } - }) const res = await request .get(`/api/routing/client`) .set(await config.roleHeaders({ diff --git a/packages/server/src/api/routes/tests/user.spec.js b/packages/server/src/api/routes/tests/user.spec.js index 30aa711524..492ebfff5b 100644 --- a/packages/server/src/api/routes/tests/user.spec.js +++ b/packages/server/src/api/routes/tests/user.spec.js @@ -1,7 +1,6 @@ const { BUILTIN_ROLE_IDS } = require("@budibase/auth/roles") const { checkPermissionsEndpoint } = require("./utilities/TestFunctions") const setup = require("./utilities") -const workerRequests = require("../../../utilities/workerRequests") jest.mock("../../../utilities/workerRequests", () => ({ getGlobalUsers: jest.fn(() => { @@ -25,30 +24,18 @@ describe("/users", () => { }) describe("fetch", () => { - beforeEach(() => { - workerRequests.getGlobalUsers.mockImplementationOnce(() => ([ - { - _id: "us_uuid1", - }, - { - _id: "us_uuid2", - } - ] - )) - }) - it("returns a list of users from an instance db", async () => { - await config.createUser("brenda@brenda.com", "brendas_password") - await config.createUser("pam@pam.com", "pam_password") + await config.createUser("uuidx") + await config.createUser("uuidy") const res = await request .get(`/api/users/metadata`) .set(config.defaultHeaders()) .expect("Content-Type", /json/) .expect(200) - expect(res.body.length).toBe(2) - expect(res.body.find(u => u._id === `ro_ta_users_us_uuid1`)).toBeDefined() - expect(res.body.find(u => u._id === `ro_ta_users_us_uuid2`)).toBeDefined() + expect(res.body.length).toBe(3) + expect(res.body.find(u => u._id === `ro_ta_users_us_uuidx`)).toBeDefined() + expect(res.body.find(u => u._id === `ro_ta_users_us_uuidy`)).toBeDefined() }) it("should apply authorization to endpoint", async () => { @@ -65,9 +52,6 @@ describe("/users", () => { }) describe("update", () => { - beforeEach(() => { - }) - it("should be able to update the user", async () => { const user = await config.createUser() user.roleId = BUILTIN_ROLE_IDS.BASIC @@ -94,14 +78,6 @@ describe("/users", () => { }) describe("find", () => { - beforeEach(() => { - jest.resetAllMocks() - workerRequests.getGlobalUsers.mockImplementationOnce(() => ({ - _id: "us_uuid1", - roleId: BUILTIN_ROLE_IDS.POWER, - })) - }) - it("should be able to find the user", async () => { const user = await config.createUser() const res = await request @@ -110,7 +86,7 @@ describe("/users", () => { .expect(200) .expect("Content-Type", /json/) expect(res.body._id).toEqual(user._id) - expect(res.body.roleId).toEqual(BUILTIN_ROLE_IDS.POWER) + expect(res.body.roleId).toEqual(BUILTIN_ROLE_IDS.ADMIN) expect(res.body.tableId).toBeDefined() }) }) diff --git a/packages/server/src/db/linkedRows/index.js b/packages/server/src/db/linkedRows/index.js index 5af10e41d7..37d76b5c1f 100644 --- a/packages/server/src/db/linkedRows/index.js +++ b/packages/server/src/db/linkedRows/index.js @@ -11,7 +11,12 @@ const { const { flatten } = require("lodash") const CouchDB = require("../../db") const { FieldTypes } = require("../../constants") -const { getMultiIDParams } = require("../../db/utils") +const { + getMultiIDParams, + USER_METDATA_PREFIX, +} = require("../../db/utils") +const { partition } = require("lodash") +const { getGlobalUsers } = require("../../utilities/global") /** * This functionality makes sure that when rows with links are created, updated or deleted they are processed @@ -57,6 +62,30 @@ async function getLinksForRows(appId, rows) { ) } +async function getFullLinkedDocs(appId, links) { + // create DBs + const db = new CouchDB(appId) + const linkedRowIds = links.map(link => link.id) + let linked = (await db.allDocs(getMultiIDParams(linkedRowIds))).rows.map( + row => row.doc + ) + // need to handle users as specific cases + let [users, other] = partition(linked, linkRow => linkRow._id.startsWith(USER_METDATA_PREFIX)) + const globalUsers = await getGlobalUsers(appId, users) + users = users.map(user => { + const globalUser = globalUsers.find(globalUser => globalUser && user._id.includes(globalUser._id)) + return { + ...globalUser, + // doing user second overwrites the id and rev (always metadata) + ...user, + } + }) + return [ + ...other, + ...users, + ] +} + /** * Update link documents for a row or table - this is to be called by the API controller when a change is occurring. * @param {string} eventType states what type of change which is occurring, means this can be expanded upon in the @@ -154,14 +183,13 @@ exports.attachFullLinkedDocs = async (appId, table, rows) => { if (linkedTableIds.length === 0) { return rows } + // create DBs const db = new CouchDB(appId) + // get all the links const links = (await getLinksForRows(appId, rows)).filter(link => rows.some(row => row._id === link.thisId) ) - const linkedRowIds = links.map(link => link.id) - const linked = (await db.allDocs(getMultiIDParams(linkedRowIds))).rows.map( - row => row.doc - ) + let linked = await getFullLinkedDocs(appId, links) const linkedTables = [] for (let row of rows) { for (let link of links.filter(link => link.thisId === row._id)) { diff --git a/packages/server/src/db/utils.js b/packages/server/src/db/utils.js index 29617a7dff..270cc9b312 100644 --- a/packages/server/src/db/utils.js +++ b/packages/server/src/db/utils.js @@ -6,17 +6,11 @@ const { APP_DEV_PREFIX, APP_PREFIX, SEPARATOR, + StaticDatabases, } = require("@budibase/auth/db") const UNICODE_MAX = "\ufff0" -const StaticDatabases = { - BUILDER: { - name: "builder-db", - baseDoc: "builder-doc", - }, -} - const AppStatus = { DEV: "development", ALL: "all", @@ -54,9 +48,17 @@ const SearchIndexes = { ROWS: "rows", } +exports.StaticDatabases = { + BUILDER: { + name: "builder-db", + baseDoc: "builder-doc", + }, + ...StaticDatabases, +} + exports.APP_PREFIX = APP_PREFIX exports.APP_DEV_PREFIX = APP_DEV_PREFIX -exports.StaticDatabases = StaticDatabases +exports.USER_METDATA_PREFIX = `${DocumentTypes.ROW}${SEPARATOR}${InternalTables.USER_METADATA}${SEPARATOR}` exports.ViewNames = ViewNames exports.InternalTables = InternalTables exports.DocumentTypes = DocumentTypes diff --git a/packages/server/src/tests/utilities/TestConfiguration.js b/packages/server/src/tests/utilities/TestConfiguration.js index 4ba5abbb59..7e56b66dc9 100644 --- a/packages/server/src/tests/utilities/TestConfiguration.js +++ b/packages/server/src/tests/utilities/TestConfiguration.js @@ -306,8 +306,8 @@ class TestConfiguration { return await this._req(config, null, controllers.layout.save) } - async createUser() { - const globalId = `us_${Math.random()}` + async createUser(id = null) { + const globalId = !id ? `us_${Math.random()}` : `us_${id}` const resp = await this.globalUser(globalId) return { ...resp, diff --git a/packages/server/src/utilities/global.js b/packages/server/src/utilities/global.js new file mode 100644 index 0000000000..47482f3abd --- /dev/null +++ b/packages/server/src/utilities/global.js @@ -0,0 +1,58 @@ +const CouchDB = require("../db") +const { + getMultiIDParams, + getGlobalIDFromUserMetadataID, + StaticDatabases, +} = require("../db/utils") +const { BUILTIN_ROLE_IDS } = require("@budibase/auth/roles") +const { getDeployedAppID } = require("@budibase/auth/db") +const { getGlobalUserParams } = require("@budibase/auth/db") + +exports.updateAppRole = (appId, user) => { + if (!user.roles) { + return user + } + if (user.builder && user.builder.global) { + user.roleId = BUILTIN_ROLE_IDS.ADMIN + } else { + // always use the deployed app + user.roleId = user.roles[getDeployedAppID(appId)] + if (!user.roleId) { + user.roleId = BUILTIN_ROLE_IDS.PUBLIC + } + } + delete user.roles + return user +} + +exports.getGlobalUser = async (appId, userId) => { + const db = CouchDB(StaticDatabases.GLOBAL.name) + let user = await db.get(getGlobalIDFromUserMetadataID(userId)) + if (user) { + delete user.password + } + return exports.updateAppRole(appId, user) +} + +exports.getGlobalUsers = async (appId = null, users = null) => { + const db = CouchDB(StaticDatabases.GLOBAL.name) + let globalUsers + if (users) { + const globalIds = users.map(user => getGlobalIDFromUserMetadataID(user._id)) + globalUsers = (await db.allDocs(getMultiIDParams(globalIds))).rows.map( + row => row.doc + ) + } else { + globalUsers = (await db.allDocs(getGlobalUserParams(null,{ + include_docs: true, + }))).rows.map(row => row.doc) + } + globalUsers = globalUsers.filter(user => user != null).map(user => { + delete user.password + return user + }) + if (!appId) { + return globalUsers + } + return globalUsers.map(user => exports.updateAppRole(appId, user)) +} \ No newline at end of file diff --git a/packages/server/src/utilities/users.js b/packages/server/src/utilities/users.js index eb52f4b867..6144397bf1 100644 --- a/packages/server/src/utilities/users.js +++ b/packages/server/src/utilities/users.js @@ -1,13 +1,9 @@ const CouchDB = require("../db") -const { getGlobalIDFromUserMetadataID, InternalTables } = require("../db/utils") -const { getGlobalUsers } = require("../utilities/workerRequests") +const { InternalTables } = require("../db/utils") +const { getGlobalUser } = require("../utilities/global") exports.getFullUser = async (ctx, userId) => { - const global = await getGlobalUsers( - ctx, - ctx.appId, - getGlobalIDFromUserMetadataID(userId) - ) + const global = await getGlobalUser(ctx.appId, userId) let metadata try { // this will throw an error if the db doesn't exist, or there is no appId diff --git a/packages/server/src/utilities/workerRequests.js b/packages/server/src/utilities/workerRequests.js index 59ab2c296c..cb06b5b8d4 100644 --- a/packages/server/src/utilities/workerRequests.js +++ b/packages/server/src/utilities/workerRequests.js @@ -1,26 +1,8 @@ const fetch = require("node-fetch") const env = require("../environment") const { checkSlashesInUrl } = require("./index") -const { BUILTIN_ROLE_IDS } = require("@budibase/auth/roles") const { getDeployedAppID } = require("@budibase/auth/db") -const { getGlobalIDFromUserMetadataID } = require("../db/utils") - -function getAppRole(appId, user) { - if (!user.roles) { - return user - } - if (user.builder && user.builder.global) { - user.roleId = BUILTIN_ROLE_IDS.ADMIN - } else { - // always use the deployed app - user.roleId = user.roles[getDeployedAppID(appId)] - if (!user.roleId) { - user.roleId = BUILTIN_ROLE_IDS.PUBLIC - } - } - delete user.roles - return user -} +const { updateAppRole, getGlobalUser } = require("./global") function request(ctx, request, noApiKey) { if (!request.headers) { @@ -90,27 +72,6 @@ exports.getDeployedApps = async ctx => { } } -exports.getGlobalUsers = async (ctx, appId = null, globalId = null) => { - const endpoint = globalId - ? `/api/admin/users/${globalId}` - : `/api/admin/users` - const reqCfg = { method: "GET" } - const response = await fetch( - checkSlashesInUrl(env.WORKER_URL + endpoint), - request(ctx, reqCfg) - ) - let users = await response.json() - if (!appId) { - return users - } - if (Array.isArray(users)) { - users = users.map(user => getAppRole(appId, user)) - } else { - users = getAppRole(appId, users) - } - return users -} - exports.getGlobalSelf = async (ctx, appId = null) => { const endpoint = `/api/admin/users/self` const response = await fetch( @@ -123,7 +84,7 @@ exports.getGlobalSelf = async (ctx, appId = null) => { } let json = await response.json() if (appId) { - json = getAppRole(appId, json) + json = updateAppRole(appId, json) } return json } @@ -136,8 +97,7 @@ exports.addAppRoleToUser = async (ctx, appId, roleId, userId = null) => { user = await exports.getGlobalSelf(ctx) endpoint = `/api/admin/users/self` } else { - userId = getGlobalIDFromUserMetadataID(userId) - user = await exports.getGlobalUsers(ctx, appId, userId) + user = await getGlobalUser(appId, userId) body._id = userId endpoint = `/api/admin/users` }