Merge branch 'develop' into merge-master-develop

This commit is contained in:
Rory Powell 2023-03-31 12:39:22 +01:00
commit e9322be28f
14 changed files with 212 additions and 119 deletions

View File

@ -199,7 +199,6 @@ export async function platformLogout(opts: PlatformLogoutOpts) {
} else {
// clear cookies
clearCookie(ctx, Cookie.Auth)
clearCookie(ctx, Cookie.CurrentApp)
}
const sessionIds = sessions.map(({ sessionId }) => sessionId)

View File

@ -4,7 +4,6 @@ export enum UserStatus {
}
export enum Cookie {
CurrentApp = "budibase:currentapp",
Auth = "budibase:auth",
Init = "budibase:init",
ACCOUNT_RETURN_URL = "budibase:account:returnurl",

View File

@ -8,4 +8,5 @@ export * as plugins from "./plugins"
export * as sso from "./sso"
export * as tenant from "./tenants"
export * as users from "./users"
export * as userGroups from "./userGroups"
export { generator } from "./generator"

View File

@ -0,0 +1,10 @@
import { UserGroup } from "@budibase/types"
import { generator } from "./generator"
export function userGroup(): UserGroup {
return {
name: generator.word(),
icon: generator.word(),
color: generator.word(),
}
}

View File

@ -136,6 +136,7 @@
const onUpdateColumns = () => {
selectedRows = []
fetch.refresh()
tables.fetchTable(id)
}
// Fetch data whenever rows are modified. Unfortunately we have to lose

View File

@ -79,7 +79,7 @@
}
// Validate tenant if in a multi-tenant env
if (useAccountPortal && multiTenancyEnabled) {
if (multiTenancyEnabled) {
await validateTenantId()
}
} catch (error) {

View File

@ -22,6 +22,18 @@ export function createTablesStore() {
}))
}
const fetchTable = async tableId => {
const table = await API.fetchTableDefinition(tableId)
store.update(state => {
const indexToUpdate = state.list.findIndex(t => t._id === table._id)
state.list[indexToUpdate] = table
return {
...state,
}
})
}
const select = tableId => {
store.update(state => ({
...state,
@ -126,6 +138,7 @@ export function createTablesStore() {
return {
subscribe: derivedStore.subscribe,
fetch,
fetchTable,
init: fetch,
select,
save,

View File

@ -2,7 +2,6 @@ import {
utils,
constants,
roles,
db as dbCore,
tenancy,
context,
} from "@budibase/backend-core"
@ -15,29 +14,10 @@ import { UserCtx } from "@budibase/types"
export default async (ctx: UserCtx, next: any) => {
// try to get the appID from the request
let requestAppId = await utils.getAppIdFromCtx(ctx)
// get app cookie if it exists
let appCookie: { appId?: string } | undefined
try {
appCookie = utils.getCookie(ctx, constants.Cookie.CurrentApp)
} catch (err) {
utils.clearCookie(ctx, constants.Cookie.CurrentApp)
}
if (!appCookie && !requestAppId) {
if (!requestAppId) {
return next()
}
// check the app exists referenced in cookie
if (appCookie) {
const appId = appCookie.appId
const exists = await dbCore.dbExists(appId)
if (!exists) {
utils.clearCookie(ctx, constants.Cookie.CurrentApp)
return next()
}
// if the request app ID wasn't set, update it with the cookie
requestAppId = requestAppId || appId
}
// deny access to application preview
if (!env.isTest()) {
if (
@ -45,7 +25,6 @@ export default async (ctx: UserCtx, next: any) => {
!isWebhookEndpoint(ctx) &&
(!ctx.user || !ctx.user.builder || !ctx.user.builder.global)
) {
utils.clearCookie(ctx, constants.Cookie.CurrentApp)
return ctx.redirect("/")
}
}
@ -127,14 +106,6 @@ export default async (ctx: UserCtx, next: any) => {
role: await roles.getRole(roleId),
}
}
if (
(requestAppId !== appId ||
appCookie == null ||
appCookie.appId !== requestAppId) &&
!skipCookie
) {
utils.setCookie(ctx, { appId }, constants.Cookie.CurrentApp)
}
return next()
})

View File

@ -158,27 +158,22 @@ describe("Current app middleware", () => {
})
describe("check functionality when logged in", () => {
async function checkExpected(setCookie) {
async function checkExpected() {
config.setUser()
await config.executeMiddleware()
let { utils } = require("@budibase/backend-core")
if (setCookie) {
expect(utils.setCookie).toHaveBeenCalled()
} else {
expect(utils.setCookie).not.toHaveBeenCalled()
}
expect(config.ctx.roleId).toEqual("PUBLIC")
expect(config.ctx.user.role._id).toEqual("PUBLIC")
expect(config.ctx.appId).toEqual("app_test")
expect(config.next).toHaveBeenCalled()
}
it("should be able to setup an app token when cookie not setup", async () => {
it("should be able to setup an app token on a first call", async () => {
mockAuthWithCookie()
await checkExpected(true)
await checkExpected()
})
it("should perform correct when no cookie exists", async () => {
it("should perform correct on a first call", async () => {
mockReset()
jest.mock("@budibase/backend-core", () => {
const core = jest.requireActual("@budibase/backend-core")
@ -206,38 +201,7 @@ describe("Current app middleware", () => {
},
}
})
await checkExpected(true)
})
it("lastly check what occurs when cookie doesn't need updated", async () => {
mockReset()
jest.mock("@budibase/backend-core", () => {
const core = jest.requireActual("@budibase/backend-core")
return {
...core,
db: {
...core.db,
dbExists: () => true,
},
utils: {
getAppIdFromCtx: () => {
return "app_test"
},
setCookie: jest.fn(),
getCookie: () => ({ appId: "app_test", roleId: "PUBLIC" }),
},
cache: {
user: {
getUser: async id => {
return {
_id: "us_uuid1",
}
},
},
},
}
})
await checkExpected(false)
await checkExpected()
})
})
})

View File

@ -0,0 +1,159 @@
import { db, roles } from "@budibase/backend-core"
import { structures } from "@budibase/backend-core/tests"
import { sdk as proSdk } from "@budibase/pro"
import TestConfiguration from "../../../tests/utilities/TestConfiguration"
import { rawUserMetadata, syncGlobalUsers } from "../utils"
describe("syncGlobalUsers", () => {
const config = new TestConfiguration()
beforeEach(async () => {
await config.init()
})
afterAll(config.end)
it("the default user is synced", async () => {
await config.doInContext(config.appId, async () => {
await syncGlobalUsers()
const metadata = await rawUserMetadata()
expect(metadata).toHaveLength(1)
expect(metadata).toEqual([
expect.objectContaining({
_id: db.generateUserMetadataID(config.user._id),
}),
])
})
})
it("admin and builders users are synced", async () => {
const user1 = await config.createUser({ admin: true })
const user2 = await config.createUser({ admin: false, builder: true })
await config.doInContext(config.appId, async () => {
expect(await rawUserMetadata()).toHaveLength(1)
await syncGlobalUsers()
const metadata = await rawUserMetadata()
expect(metadata).toHaveLength(3)
expect(metadata).toContainEqual(
expect.objectContaining({
_id: db.generateUserMetadataID(user1._id),
})
)
expect(metadata).toContainEqual(
expect.objectContaining({
_id: db.generateUserMetadataID(user2._id),
})
)
})
})
it("app users are not synced if not specified", async () => {
const user = await config.createUser({ admin: false, builder: false })
await config.doInContext(config.appId, async () => {
await syncGlobalUsers()
const metadata = await rawUserMetadata()
expect(metadata).toHaveLength(1)
expect(metadata).not.toContainEqual(
expect.objectContaining({
_id: db.generateUserMetadataID(user._id),
})
)
})
})
it("app users are added when group is assigned to app", async () => {
await config.doInTenant(async () => {
const group = await proSdk.groups.save(structures.userGroups.userGroup())
const user1 = await config.createUser({ admin: false, builder: false })
const user2 = await config.createUser({ admin: false, builder: false })
await proSdk.groups.addUsers(group.id, [user1._id, user2._id])
await config.doInContext(config.appId, async () => {
await syncGlobalUsers()
expect(await rawUserMetadata()).toHaveLength(1)
await proSdk.groups.updateGroupApps(group.id, {
appsToAdd: [
{ appId: config.prodAppId!, roleId: roles.BUILTIN_ROLE_IDS.BASIC },
],
})
await syncGlobalUsers()
const metadata = await rawUserMetadata()
expect(metadata).toHaveLength(3)
expect(metadata).toContainEqual(
expect.objectContaining({
_id: db.generateUserMetadataID(user1._id),
})
)
expect(metadata).toContainEqual(
expect.objectContaining({
_id: db.generateUserMetadataID(user2._id),
})
)
})
})
})
it("app users are removed when app is removed from user group", async () => {
await config.doInTenant(async () => {
const group = await proSdk.groups.save(structures.userGroups.userGroup())
const user1 = await config.createUser({ admin: false, builder: false })
const user2 = await config.createUser({ admin: false, builder: false })
await proSdk.groups.updateGroupApps(group.id, {
appsToAdd: [
{ appId: config.prodAppId!, roleId: roles.BUILTIN_ROLE_IDS.BASIC },
],
})
await proSdk.groups.addUsers(group.id, [user1._id, user2._id])
await config.doInContext(config.appId, async () => {
await syncGlobalUsers()
expect(await rawUserMetadata()).toHaveLength(3)
await proSdk.groups.updateGroupApps(group.id, {
appsToRemove: [{ appId: config.prodAppId! }],
})
await syncGlobalUsers()
const metadata = await rawUserMetadata()
expect(metadata).toHaveLength(1)
})
})
})
it("app users are removed when app is removed from user group", async () => {
await config.doInTenant(async () => {
const group = await proSdk.groups.save(structures.userGroups.userGroup())
const user1 = await config.createUser({ admin: false, builder: false })
const user2 = await config.createUser({ admin: false, builder: false })
await proSdk.groups.updateGroupApps(group.id, {
appsToAdd: [
{ appId: config.prodAppId!, roleId: roles.BUILTIN_ROLE_IDS.BASIC },
],
})
await proSdk.groups.addUsers(group.id, [user1._id, user2._id])
await config.doInContext(config.appId, async () => {
await syncGlobalUsers()
expect(await rawUserMetadata()).toHaveLength(3)
await proSdk.groups.removeUsers(group.id, [user1._id])
await syncGlobalUsers()
const metadata = await rawUserMetadata()
expect(metadata).toHaveLength(2)
expect(metadata).not.toContainEqual(
expect.objectContaining({
_id: db.generateUserMetadataID(user1._id),
})
)
})
})
})
})

View File

@ -6,25 +6,33 @@ import {
InternalTables,
} from "../../db/utils"
import { isEqual } from "lodash"
import { ContextUser, UserMetadata } from "@budibase/types"
export function combineMetadataAndUser(user: any, metadata: any) {
export function combineMetadataAndUser(
user: ContextUser,
metadata: UserMetadata | UserMetadata[]
) {
const metadataId = generateUserMetadataID(user._id!)
const found = Array.isArray(metadata)
? metadata.find(doc => doc._id === metadataId)
: metadata
// skip users with no access
if (
user.roleId == null ||
user.roleId === rolesCore.BUILTIN_ROLE_IDS.PUBLIC
) {
// If it exists and it should not, we must remove it
if (found?._id) {
return { ...found, _deleted: true }
}
return null
}
delete user._rev
const metadataId = generateUserMetadataID(user._id)
const newDoc = {
...user,
_id: metadataId,
tableId: InternalTables.USER_METADATA,
}
const found = Array.isArray(metadata)
? metadata.find(doc => doc._id === metadataId)
: metadata
// copy rev over for the purposes of equality check
if (found) {
newDoc._rev = found._rev
@ -58,7 +66,7 @@ export async function syncGlobalUsers() {
])
const toWrite = []
for (let user of users) {
const combined = await combineMetadataAndUser(user, metadata)
const combined = combineMetadataAndUser(user, metadata)
if (combined) {
toWrite.push(combined)
}

View File

@ -47,6 +47,7 @@ import {
SourceName,
Table,
SearchFilters,
UserRoles,
} from "@budibase/types"
type DefaultUserValues = {
@ -277,7 +278,7 @@ class TestConfiguration {
email?: string
builder?: boolean
admin?: boolean
roles?: any
roles?: UserRoles
} = {}
) {
let { id, firstName, lastName, email, builder, admin, roles } = user
@ -330,21 +331,13 @@ class TestConfiguration {
sessionId: "sessionid",
tenantId: this.getTenantId(),
}
const app = {
roleId: roleId,
appId,
}
const authToken = auth.jwt.sign(authObj, coreEnv.JWT_SECRET)
const appToken = auth.jwt.sign(app, coreEnv.JWT_SECRET)
// returning necessary request headers
await cache.user.invalidateUser(userId)
return {
Accept: "application/json",
Cookie: [
`${constants.Cookie.Auth}=${authToken}`,
`${constants.Cookie.CurrentApp}=${appToken}`,
],
Cookie: [`${constants.Cookie.Auth}=${authToken}`],
[constants.Header.APP_ID]: appId,
}
})
@ -359,18 +352,11 @@ class TestConfiguration {
sessionId: "sessionid",
tenantId,
}
const app = {
roleId: roles.BUILTIN_ROLE_IDS.ADMIN,
appId: this.appId,
}
const authToken = auth.jwt.sign(authObj, coreEnv.JWT_SECRET)
const appToken = auth.jwt.sign(app, coreEnv.JWT_SECRET)
const headers: any = {
Accept: "application/json",
Cookie: [
`${constants.Cookie.Auth}=${authToken}`,
`${constants.Cookie.CurrentApp}=${appToken}`,
],
Cookie: [`${constants.Cookie.Auth}=${authToken}`],
[constants.Header.CSRF_TOKEN]: this.defaultUserValues.csrfToken,
Host: this.tenantHost(),
...extras,

View File

@ -50,11 +50,6 @@ async function passportCallback(
setCookie(ctx, token, Cookie.Auth, { sign: false })
// set the token in a header as well for APIs
ctx.set(Header.TOKEN, token)
// get rid of any app cookies on login
// have to check test because this breaks cypress
if (!env.isTest()) {
clearCookie(ctx, Cookie.CurrentApp)
}
}
export const login = async (ctx: Ctx<LoginRequest>, next: any) => {

View File

@ -2,7 +2,6 @@ import * as userSdk from "../../../sdk/users"
import {
featureFlags,
tenancy,
constants,
db as dbCore,
utils,
encryption,
@ -11,7 +10,7 @@ import {
import env from "../../../environment"
import { groups } from "@budibase/pro"
import { UpdateSelfRequest, UpdateSelfResponse, UserCtx } from "@budibase/types"
const { getCookie, clearCookie, newid } = utils
const { newid } = utils
function newTestApiKey() {
return env.ENCRYPTED_TEST_PUBLIC_API_KEY
@ -71,16 +70,6 @@ export async function fetchAPIKey(ctx: any) {
ctx.body = cleanupDevInfo(devInfo)
}
const checkCurrentApp = (ctx: any) => {
const appCookie = getCookie(ctx, constants.Cookie.CurrentApp)
if (appCookie && !tenancy.isUserInAppTenant(appCookie.appId)) {
// there is a currentapp cookie from another tenant
// remove the cookie as this is incompatible with the builder
// due to builder and admin permissions being removed
clearCookie(ctx, constants.Cookie.CurrentApp)
}
}
/**
* Add the attributes that are session based to the current user.
*/
@ -101,8 +90,6 @@ export async function getSelf(ctx: any) {
id: userId,
}
checkCurrentApp(ctx)
// get the main body of the user
const user = await userSdk.getUser(userId)
ctx.body = await groups.enrichUserRolesFromGroups(user)