Re-writing how global users are handled in server, specifically how they are retrieved, so that for relationships it can handle the global user.

This commit is contained in:
mike12345567 2021-06-08 16:06:30 +01:00
parent bb20722b48
commit 33184be064
9 changed files with 117 additions and 109 deletions

View File

@ -5,9 +5,9 @@ const {
} = require("../../db/utils") } = require("../../db/utils")
const { InternalTables } = require("../../db/utils") const { InternalTables } = require("../../db/utils")
const { const {
getGlobalUsers,
addAppRoleToUser, addAppRoleToUser,
} = require("../../utilities/workerRequests") } = require("../../utilities/workerRequests")
const { getGlobalUsers, getGlobalUser } = require("../../utilities/global")
const { getFullUser } = require("../../utilities/users") const { getFullUser } = require("../../utilities/users")
function removeGlobalProps(user) { function removeGlobalProps(user) {
@ -20,7 +20,7 @@ function removeGlobalProps(user) {
exports.fetchMetadata = async function (ctx) { exports.fetchMetadata = async function (ctx) {
const database = new CouchDB(ctx.appId) const database = new CouchDB(ctx.appId)
const global = await getGlobalUsers(ctx, ctx.appId) const global = await getGlobalUsers(ctx.appId)
const metadata = ( const metadata = (
await database.allDocs( await database.allDocs(
getUserMetadataParams(null, { getUserMetadataParams(null, {

View File

@ -26,11 +26,6 @@ describe("/routing", () => {
describe("fetch", () => { describe("fetch", () => {
it("returns the correct routing for basic user", async () => { it("returns the correct routing for basic user", async () => {
workerRequests.getGlobalUsers.mockImplementationOnce((ctx, appId) => {
return {
roleId: BUILTIN_ROLE_IDS.BASIC,
}
})
const res = await request const res = await request
.get(`/api/routing/client`) .get(`/api/routing/client`)
.set(await config.roleHeaders({ .set(await config.roleHeaders({
@ -52,13 +47,6 @@ describe("/routing", () => {
}) })
it("returns the correct routing for power user", async () => { 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 const res = await request
.get(`/api/routing/client`) .get(`/api/routing/client`)
.set(await config.roleHeaders({ .set(await config.roleHeaders({

View File

@ -1,7 +1,6 @@
const { BUILTIN_ROLE_IDS } = require("@budibase/auth/roles") const { BUILTIN_ROLE_IDS } = require("@budibase/auth/roles")
const { checkPermissionsEndpoint } = require("./utilities/TestFunctions") const { checkPermissionsEndpoint } = require("./utilities/TestFunctions")
const setup = require("./utilities") const setup = require("./utilities")
const workerRequests = require("../../../utilities/workerRequests")
jest.mock("../../../utilities/workerRequests", () => ({ jest.mock("../../../utilities/workerRequests", () => ({
getGlobalUsers: jest.fn(() => { getGlobalUsers: jest.fn(() => {
@ -25,30 +24,18 @@ describe("/users", () => {
}) })
describe("fetch", () => { describe("fetch", () => {
beforeEach(() => {
workerRequests.getGlobalUsers.mockImplementationOnce(() => ([
{
_id: "us_uuid1",
},
{
_id: "us_uuid2",
}
]
))
})
it("returns a list of users from an instance db", async () => { it("returns a list of users from an instance db", async () => {
await config.createUser("brenda@brenda.com", "brendas_password") await config.createUser("uuidx")
await config.createUser("pam@pam.com", "pam_password") await config.createUser("uuidy")
const res = await request const res = await request
.get(`/api/users/metadata`) .get(`/api/users/metadata`)
.set(config.defaultHeaders()) .set(config.defaultHeaders())
.expect("Content-Type", /json/) .expect("Content-Type", /json/)
.expect(200) .expect(200)
expect(res.body.length).toBe(2) expect(res.body.length).toBe(3)
expect(res.body.find(u => u._id === `ro_ta_users_us_uuid1`)).toBeDefined() expect(res.body.find(u => u._id === `ro_ta_users_us_uuidx`)).toBeDefined()
expect(res.body.find(u => u._id === `ro_ta_users_us_uuid2`)).toBeDefined() expect(res.body.find(u => u._id === `ro_ta_users_us_uuidy`)).toBeDefined()
}) })
it("should apply authorization to endpoint", async () => { it("should apply authorization to endpoint", async () => {
@ -65,9 +52,6 @@ describe("/users", () => {
}) })
describe("update", () => { describe("update", () => {
beforeEach(() => {
})
it("should be able to update the user", async () => { it("should be able to update the user", async () => {
const user = await config.createUser() const user = await config.createUser()
user.roleId = BUILTIN_ROLE_IDS.BASIC user.roleId = BUILTIN_ROLE_IDS.BASIC
@ -94,14 +78,6 @@ describe("/users", () => {
}) })
describe("find", () => { 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 () => { it("should be able to find the user", async () => {
const user = await config.createUser() const user = await config.createUser()
const res = await request const res = await request
@ -110,7 +86,7 @@ describe("/users", () => {
.expect(200) .expect(200)
.expect("Content-Type", /json/) .expect("Content-Type", /json/)
expect(res.body._id).toEqual(user._id) 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() expect(res.body.tableId).toBeDefined()
}) })
}) })

View File

@ -11,7 +11,12 @@ const {
const { flatten } = require("lodash") const { flatten } = require("lodash")
const CouchDB = require("../../db") const CouchDB = require("../../db")
const { FieldTypes } = require("../../constants") 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 * 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. * 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 * @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) { if (linkedTableIds.length === 0) {
return rows return rows
} }
// create DBs
const db = new CouchDB(appId) const db = new CouchDB(appId)
// get all the links
const links = (await getLinksForRows(appId, rows)).filter(link => const links = (await getLinksForRows(appId, rows)).filter(link =>
rows.some(row => row._id === link.thisId) rows.some(row => row._id === link.thisId)
) )
const linkedRowIds = links.map(link => link.id) let linked = await getFullLinkedDocs(appId, links)
const linked = (await db.allDocs(getMultiIDParams(linkedRowIds))).rows.map(
row => row.doc
)
const linkedTables = [] const linkedTables = []
for (let row of rows) { for (let row of rows) {
for (let link of links.filter(link => link.thisId === row._id)) { for (let link of links.filter(link => link.thisId === row._id)) {

View File

@ -6,17 +6,11 @@ const {
APP_DEV_PREFIX, APP_DEV_PREFIX,
APP_PREFIX, APP_PREFIX,
SEPARATOR, SEPARATOR,
StaticDatabases,
} = require("@budibase/auth/db") } = require("@budibase/auth/db")
const UNICODE_MAX = "\ufff0" const UNICODE_MAX = "\ufff0"
const StaticDatabases = {
BUILDER: {
name: "builder-db",
baseDoc: "builder-doc",
},
}
const AppStatus = { const AppStatus = {
DEV: "development", DEV: "development",
ALL: "all", ALL: "all",
@ -54,9 +48,17 @@ const SearchIndexes = {
ROWS: "rows", ROWS: "rows",
} }
exports.StaticDatabases = {
BUILDER: {
name: "builder-db",
baseDoc: "builder-doc",
},
...StaticDatabases,
}
exports.APP_PREFIX = APP_PREFIX exports.APP_PREFIX = APP_PREFIX
exports.APP_DEV_PREFIX = APP_DEV_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.ViewNames = ViewNames
exports.InternalTables = InternalTables exports.InternalTables = InternalTables
exports.DocumentTypes = DocumentTypes exports.DocumentTypes = DocumentTypes

View File

@ -306,8 +306,8 @@ class TestConfiguration {
return await this._req(config, null, controllers.layout.save) return await this._req(config, null, controllers.layout.save)
} }
async createUser() { async createUser(id = null) {
const globalId = `us_${Math.random()}` const globalId = !id ? `us_${Math.random()}` : `us_${id}`
const resp = await this.globalUser(globalId) const resp = await this.globalUser(globalId)
return { return {
...resp, ...resp,

View File

@ -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))
}

View File

@ -1,13 +1,9 @@
const CouchDB = require("../db") const CouchDB = require("../db")
const { getGlobalIDFromUserMetadataID, InternalTables } = require("../db/utils") const { InternalTables } = require("../db/utils")
const { getGlobalUsers } = require("../utilities/workerRequests") const { getGlobalUser } = require("../utilities/global")
exports.getFullUser = async (ctx, userId) => { exports.getFullUser = async (ctx, userId) => {
const global = await getGlobalUsers( const global = await getGlobalUser(ctx.appId, userId)
ctx,
ctx.appId,
getGlobalIDFromUserMetadataID(userId)
)
let metadata let metadata
try { try {
// this will throw an error if the db doesn't exist, or there is no appId // this will throw an error if the db doesn't exist, or there is no appId

View File

@ -1,26 +1,8 @@
const fetch = require("node-fetch") const fetch = require("node-fetch")
const env = require("../environment") const env = require("../environment")
const { checkSlashesInUrl } = require("./index") const { checkSlashesInUrl } = require("./index")
const { BUILTIN_ROLE_IDS } = require("@budibase/auth/roles")
const { getDeployedAppID } = require("@budibase/auth/db") const { getDeployedAppID } = require("@budibase/auth/db")
const { getGlobalIDFromUserMetadataID } = require("../db/utils") const { updateAppRole, getGlobalUser } = require("./global")
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
}
function request(ctx, request, noApiKey) { function request(ctx, request, noApiKey) {
if (!request.headers) { 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) => { exports.getGlobalSelf = async (ctx, appId = null) => {
const endpoint = `/api/admin/users/self` const endpoint = `/api/admin/users/self`
const response = await fetch( const response = await fetch(
@ -123,7 +84,7 @@ exports.getGlobalSelf = async (ctx, appId = null) => {
} }
let json = await response.json() let json = await response.json()
if (appId) { if (appId) {
json = getAppRole(appId, json) json = updateAppRole(appId, json)
} }
return json return json
} }
@ -136,8 +97,7 @@ exports.addAppRoleToUser = async (ctx, appId, roleId, userId = null) => {
user = await exports.getGlobalSelf(ctx) user = await exports.getGlobalSelf(ctx)
endpoint = `/api/admin/users/self` endpoint = `/api/admin/users/self`
} else { } else {
userId = getGlobalIDFromUserMetadataID(userId) user = await getGlobalUser(appId, userId)
user = await exports.getGlobalUsers(ctx, appId, userId)
body._id = userId body._id = userId
endpoint = `/api/admin/users` endpoint = `/api/admin/users`
} }