Merge branch 'master' into feature/role-multi-inheritance
This commit is contained in:
commit
aaf5debc52
|
@ -54,30 +54,46 @@ function getPackageJsonFields(): {
|
||||||
VERSION: string
|
VERSION: string
|
||||||
SERVICE_NAME: string
|
SERVICE_NAME: string
|
||||||
} {
|
} {
|
||||||
function findFileInAncestors(
|
function getParentFile(file: string) {
|
||||||
fileName: string,
|
function findFileInAncestors(
|
||||||
currentDir: string
|
fileName: string,
|
||||||
): string | null {
|
currentDir: string
|
||||||
const filePath = `${currentDir}/${fileName}`
|
): string | null {
|
||||||
if (existsSync(filePath)) {
|
const filePath = `${currentDir}/${fileName}`
|
||||||
return filePath
|
if (existsSync(filePath)) {
|
||||||
|
return filePath
|
||||||
|
}
|
||||||
|
|
||||||
|
const parentDir = `${currentDir}/..`
|
||||||
|
if (parentDir === currentDir) {
|
||||||
|
// reached root directory
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return findFileInAncestors(fileName, parentDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
const parentDir = `${currentDir}/..`
|
const packageJsonFile = findFileInAncestors(file, process.cwd())
|
||||||
if (parentDir === currentDir) {
|
const content = readFileSync(packageJsonFile!, "utf-8")
|
||||||
// reached root directory
|
const parsedContent = JSON.parse(content)
|
||||||
return null
|
return parsedContent
|
||||||
}
|
}
|
||||||
|
|
||||||
return findFileInAncestors(fileName, parentDir)
|
let localVersion: string | undefined
|
||||||
|
if (isDev() && !isTest()) {
|
||||||
|
try {
|
||||||
|
const lerna = getParentFile("lerna.json")
|
||||||
|
localVersion = lerna.version
|
||||||
|
} catch {
|
||||||
|
//
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const packageJsonFile = findFileInAncestors("package.json", process.cwd())
|
const parsedContent = getParentFile("package.json")
|
||||||
const content = readFileSync(packageJsonFile!, "utf-8")
|
|
||||||
const parsedContent = JSON.parse(content)
|
|
||||||
return {
|
return {
|
||||||
VERSION: process.env.BUDIBASE_VERSION || parsedContent.version,
|
VERSION:
|
||||||
|
localVersion || process.env.BUDIBASE_VERSION || parsedContent.version,
|
||||||
SERVICE_NAME: parsedContent.name,
|
SERVICE_NAME: parsedContent.name,
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import semver from "semver"
|
||||||
import { BuiltinPermissionID, PermissionLevel } from "./permissions"
|
import { BuiltinPermissionID, PermissionLevel } from "./permissions"
|
||||||
import {
|
import {
|
||||||
prefixRoleID,
|
prefixRoleID,
|
||||||
|
@ -7,7 +8,13 @@ import {
|
||||||
doWithDB,
|
doWithDB,
|
||||||
} from "../db"
|
} from "../db"
|
||||||
import { getAppDB } from "../context"
|
import { getAppDB } from "../context"
|
||||||
import { Screen, Role as RoleDoc, RoleUIMetadata } from "@budibase/types"
|
import {
|
||||||
|
Screen,
|
||||||
|
Role as RoleDoc,
|
||||||
|
RoleUIMetadata,
|
||||||
|
Database,
|
||||||
|
App,
|
||||||
|
} from "@budibase/types"
|
||||||
import cloneDeep from "lodash/fp/cloneDeep"
|
import cloneDeep from "lodash/fp/cloneDeep"
|
||||||
import { RoleColor, helpers } from "@budibase/shared-core"
|
import { RoleColor, helpers } from "@budibase/shared-core"
|
||||||
import { uniqBy } from "lodash"
|
import { uniqBy } from "lodash"
|
||||||
|
@ -24,14 +31,6 @@ const BUILTIN_IDS = {
|
||||||
BUILDER: "BUILDER",
|
BUILDER: "BUILDER",
|
||||||
}
|
}
|
||||||
|
|
||||||
// exclude internal roles like builder
|
|
||||||
const EXTERNAL_BUILTIN_ROLE_IDS = [
|
|
||||||
BUILTIN_IDS.ADMIN,
|
|
||||||
BUILTIN_IDS.POWER,
|
|
||||||
BUILTIN_IDS.BASIC,
|
|
||||||
BUILTIN_IDS.PUBLIC,
|
|
||||||
]
|
|
||||||
|
|
||||||
export const RoleIDVersion = {
|
export const RoleIDVersion = {
|
||||||
// original version, with a UUID based ID
|
// original version, with a UUID based ID
|
||||||
UUID: undefined,
|
UUID: undefined,
|
||||||
|
@ -432,7 +431,7 @@ export async function getAllRoles(appId?: string): Promise<RoleDoc[]> {
|
||||||
}
|
}
|
||||||
return internal(appDB)
|
return internal(appDB)
|
||||||
}
|
}
|
||||||
async function internal(db: any) {
|
async function internal(db: Database | undefined) {
|
||||||
let roles: RoleDoc[] = []
|
let roles: RoleDoc[] = []
|
||||||
if (db) {
|
if (db) {
|
||||||
const body = await db.allDocs(
|
const body = await db.allDocs(
|
||||||
|
@ -447,8 +446,26 @@ export async function getAllRoles(appId?: string): Promise<RoleDoc[]> {
|
||||||
}
|
}
|
||||||
const builtinRoles = getBuiltinRoles()
|
const builtinRoles = getBuiltinRoles()
|
||||||
|
|
||||||
|
// exclude internal roles like builder
|
||||||
|
let externalBuiltinRoles = []
|
||||||
|
|
||||||
|
if (!db || (await shouldIncludePowerRole(db))) {
|
||||||
|
externalBuiltinRoles = [
|
||||||
|
BUILTIN_IDS.ADMIN,
|
||||||
|
BUILTIN_IDS.POWER,
|
||||||
|
BUILTIN_IDS.BASIC,
|
||||||
|
BUILTIN_IDS.PUBLIC,
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
externalBuiltinRoles = [
|
||||||
|
BUILTIN_IDS.ADMIN,
|
||||||
|
BUILTIN_IDS.BASIC,
|
||||||
|
BUILTIN_IDS.PUBLIC,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
// need to combine builtin with any DB record of them (for sake of permissions)
|
// need to combine builtin with any DB record of them (for sake of permissions)
|
||||||
for (let builtinRoleId of EXTERNAL_BUILTIN_ROLE_IDS) {
|
for (let builtinRoleId of externalBuiltinRoles) {
|
||||||
const builtinRole = builtinRoles[builtinRoleId]
|
const builtinRole = builtinRoles[builtinRoleId]
|
||||||
const dbBuiltin = roles.filter(
|
const dbBuiltin = roles.filter(
|
||||||
dbRole =>
|
dbRole =>
|
||||||
|
@ -479,6 +496,18 @@ export async function getAllRoles(appId?: string): Promise<RoleDoc[]> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function shouldIncludePowerRole(db: Database) {
|
||||||
|
const app = await db.tryGet<App>(DocumentType.APP_METADATA)
|
||||||
|
const creationVersion = app?.creationVersion
|
||||||
|
if (!creationVersion || !semver.valid(creationVersion)) {
|
||||||
|
// Old apps don't have creationVersion, so we should include it for backward compatibility
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const isGreaterThan3x = semver.gte(creationVersion, "3.0.0")
|
||||||
|
return !isGreaterThan3x
|
||||||
|
}
|
||||||
|
|
||||||
export class AccessController {
|
export class AccessController {
|
||||||
userHierarchies: { [key: string]: string[] }
|
userHierarchies: { [key: string]: string[] }
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|
|
@ -208,9 +208,8 @@ export async function fetchAppDefinition(
|
||||||
export async function fetchAppPackage(
|
export async function fetchAppPackage(
|
||||||
ctx: UserCtx<void, FetchAppPackageResponse>
|
ctx: UserCtx<void, FetchAppPackageResponse>
|
||||||
) {
|
) {
|
||||||
const db = context.getAppDB()
|
|
||||||
const appId = context.getAppId()
|
const appId = context.getAppId()
|
||||||
let application = await db.get<App>(DocumentType.APP_METADATA)
|
const application = await sdk.applications.metadata.get()
|
||||||
const layouts = await getLayouts()
|
const layouts = await getLayouts()
|
||||||
let screens = await getScreens()
|
let screens = await getScreens()
|
||||||
const license = await licensing.cache.getCachedLicense()
|
const license = await licensing.cache.getCachedLicense()
|
||||||
|
@ -272,6 +271,7 @@ async function performAppCreate(ctx: UserCtx<CreateAppRequest, App>) {
|
||||||
path: ctx.request.body.file?.path,
|
path: ctx.request.body.file?.path,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const tenantId = tenancy.isMultiTenant() ? tenancy.getTenantId() : null
|
const tenantId = tenancy.isMultiTenant() ? tenancy.getTenantId() : null
|
||||||
const appId = generateDevAppID(generateAppID(tenantId))
|
const appId = generateDevAppID(generateAppID(tenantId))
|
||||||
|
|
||||||
|
@ -279,7 +279,7 @@ async function performAppCreate(ctx: UserCtx<CreateAppRequest, App>) {
|
||||||
const instance = await createInstance(appId, instanceConfig)
|
const instance = await createInstance(appId, instanceConfig)
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
|
|
||||||
let newApplication: App = {
|
const newApplication: App = {
|
||||||
_id: DocumentType.APP_METADATA,
|
_id: DocumentType.APP_METADATA,
|
||||||
_rev: undefined,
|
_rev: undefined,
|
||||||
appId,
|
appId,
|
||||||
|
@ -310,12 +310,18 @@ async function performAppCreate(ctx: UserCtx<CreateAppRequest, App>) {
|
||||||
disableUserMetadata: true,
|
disableUserMetadata: true,
|
||||||
skeletonLoader: true,
|
skeletonLoader: true,
|
||||||
},
|
},
|
||||||
|
creationVersion: undefined,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isImport = !!instanceConfig.file
|
||||||
|
if (!isImport) {
|
||||||
|
newApplication.creationVersion = envCore.VERSION
|
||||||
|
}
|
||||||
|
|
||||||
|
const existing = await sdk.applications.metadata.tryGet()
|
||||||
// If we used a template or imported an app there will be an existing doc.
|
// If we used a template or imported an app there will be an existing doc.
|
||||||
// Fetch and migrate some metadata from the existing app.
|
// Fetch and migrate some metadata from the existing app.
|
||||||
try {
|
if (existing) {
|
||||||
const existing: App = await db.get(DocumentType.APP_METADATA)
|
|
||||||
const keys: (keyof App)[] = [
|
const keys: (keyof App)[] = [
|
||||||
"_rev",
|
"_rev",
|
||||||
"navigation",
|
"navigation",
|
||||||
|
@ -323,6 +329,7 @@ async function performAppCreate(ctx: UserCtx<CreateAppRequest, App>) {
|
||||||
"customTheme",
|
"customTheme",
|
||||||
"icon",
|
"icon",
|
||||||
"snippets",
|
"snippets",
|
||||||
|
"creationVersion",
|
||||||
]
|
]
|
||||||
keys.forEach(key => {
|
keys.forEach(key => {
|
||||||
if (existing[key]) {
|
if (existing[key]) {
|
||||||
|
@ -340,14 +347,10 @@ async function performAppCreate(ctx: UserCtx<CreateAppRequest, App>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Migrate navigation settings and screens if required
|
// Migrate navigation settings and screens if required
|
||||||
if (existing) {
|
const navigation = await migrateAppNavigation()
|
||||||
const navigation = await migrateAppNavigation()
|
if (navigation) {
|
||||||
if (navigation) {
|
newApplication.navigation = navigation
|
||||||
newApplication.navigation = navigation
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
|
||||||
// Nothing to do
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await db.put(newApplication, { force: true })
|
const response = await db.put(newApplication, { force: true })
|
||||||
|
@ -489,8 +492,7 @@ export async function update(
|
||||||
|
|
||||||
export async function updateClient(ctx: UserCtx) {
|
export async function updateClient(ctx: UserCtx) {
|
||||||
// Get current app version
|
// Get current app version
|
||||||
const db = context.getAppDB()
|
const application = await sdk.applications.metadata.get()
|
||||||
const application = await db.get<App>(DocumentType.APP_METADATA)
|
|
||||||
const currentVersion = application.version
|
const currentVersion = application.version
|
||||||
|
|
||||||
let manifest
|
let manifest
|
||||||
|
@ -518,8 +520,7 @@ export async function updateClient(ctx: UserCtx) {
|
||||||
|
|
||||||
export async function revertClient(ctx: UserCtx) {
|
export async function revertClient(ctx: UserCtx) {
|
||||||
// Check app can be reverted
|
// Check app can be reverted
|
||||||
const db = context.getAppDB()
|
const application = await sdk.applications.metadata.get()
|
||||||
const application = await db.get<App>(DocumentType.APP_METADATA)
|
|
||||||
if (!application.revertableVersion) {
|
if (!application.revertableVersion) {
|
||||||
ctx.throw(400, "There is no version to revert to")
|
ctx.throw(400, "There is no version to revert to")
|
||||||
}
|
}
|
||||||
|
@ -577,7 +578,7 @@ async function destroyApp(ctx: UserCtx) {
|
||||||
|
|
||||||
const db = dbCore.getDB(devAppId)
|
const db = dbCore.getDB(devAppId)
|
||||||
// standard app deletion flow
|
// standard app deletion flow
|
||||||
const app = await db.get<App>(DocumentType.APP_METADATA)
|
const app = await sdk.applications.metadata.get()
|
||||||
const result = await db.destroy()
|
const result = await db.destroy()
|
||||||
await quotas.removeApp()
|
await quotas.removeApp()
|
||||||
await events.app.deleted(app)
|
await events.app.deleted(app)
|
||||||
|
@ -728,7 +729,7 @@ export async function updateAppPackage(
|
||||||
) {
|
) {
|
||||||
return context.doInAppContext(appId, async () => {
|
return context.doInAppContext(appId, async () => {
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
const application = await db.get<App>(DocumentType.APP_METADATA)
|
const application = await sdk.applications.metadata.get()
|
||||||
|
|
||||||
const newAppPackage: App = { ...application, ...appPackage }
|
const newAppPackage: App = { ...application, ...appPackage }
|
||||||
if (appPackage._rev !== application._rev) {
|
if (appPackage._rev !== application._rev) {
|
||||||
|
@ -754,7 +755,7 @@ export async function setRevertableVersion(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
const app = await db.get<App>(DocumentType.APP_METADATA)
|
const app = await sdk.applications.metadata.get()
|
||||||
app.revertableVersion = ctx.request.body.revertableVersion
|
app.revertableVersion = ctx.request.body.revertableVersion
|
||||||
await db.put(app)
|
await db.put(app)
|
||||||
|
|
||||||
|
@ -763,7 +764,7 @@ export async function setRevertableVersion(
|
||||||
|
|
||||||
async function migrateAppNavigation() {
|
async function migrateAppNavigation() {
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
const existing: App = await db.get(DocumentType.APP_METADATA)
|
const existing = await sdk.applications.metadata.get()
|
||||||
const layouts: Layout[] = await getLayouts()
|
const layouts: Layout[] = await getLayouts()
|
||||||
const screens: Screen[] = await getScreens()
|
const screens: Screen[] = await getScreens()
|
||||||
|
|
||||||
|
|
|
@ -2,10 +2,12 @@ import * as sync from "./sync"
|
||||||
import * as utils from "./utils"
|
import * as utils from "./utils"
|
||||||
import * as applications from "./applications"
|
import * as applications from "./applications"
|
||||||
import * as imports from "./import"
|
import * as imports from "./import"
|
||||||
|
import * as metadata from "./metadata"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
...sync,
|
...sync,
|
||||||
...utils,
|
...utils,
|
||||||
...applications,
|
...applications,
|
||||||
...imports,
|
...imports,
|
||||||
|
metadata,
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { context, DocumentType } from "@budibase/backend-core"
|
||||||
|
import { App } from "@budibase/types"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated the plan is to get everything using `tryGet` instead, then rename
|
||||||
|
* `tryGet` to `get`.
|
||||||
|
*/
|
||||||
|
export async function get() {
|
||||||
|
const db = context.getAppDB()
|
||||||
|
const application = await db.get<App>(DocumentType.APP_METADATA)
|
||||||
|
return application
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function tryGet() {
|
||||||
|
const db = context.getAppDB()
|
||||||
|
const application = await db.tryGet<App>(DocumentType.APP_METADATA)
|
||||||
|
return application
|
||||||
|
}
|
|
@ -27,6 +27,7 @@ export interface App extends Document {
|
||||||
usedPlugins?: Plugin[]
|
usedPlugins?: Plugin[]
|
||||||
upgradableVersion?: string
|
upgradableVersion?: string
|
||||||
snippets?: Snippet[]
|
snippets?: Snippet[]
|
||||||
|
creationVersion?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AppInstance {
|
export interface AppInstance {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { structures, TestConfiguration } from "../../../../tests"
|
import { structures, TestConfiguration } from "../../../../tests"
|
||||||
import { context, db, permissions, roles } from "@budibase/backend-core"
|
import { context, db, permissions, roles } from "@budibase/backend-core"
|
||||||
import { Database } from "@budibase/types"
|
import { App, Database } from "@budibase/types"
|
||||||
|
|
||||||
jest.mock("@budibase/backend-core", () => {
|
jest.mock("@budibase/backend-core", () => {
|
||||||
const core = jest.requireActual("@budibase/backend-core")
|
const core = jest.requireActual("@budibase/backend-core")
|
||||||
|
@ -30,6 +30,14 @@ async function addAppMetadata() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function updateAppMetadata(update: Partial<Omit<App, "_id" | "_rev">>) {
|
||||||
|
const app = await appDb.get("app_metadata")
|
||||||
|
await appDb.put({
|
||||||
|
...app,
|
||||||
|
...update,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
describe("/api/global/roles", () => {
|
describe("/api/global/roles", () => {
|
||||||
const config = new TestConfiguration()
|
const config = new TestConfiguration()
|
||||||
|
|
||||||
|
@ -69,6 +77,53 @@ describe("/api/global/roles", () => {
|
||||||
expect(res.body[appId].roles.length).toEqual(5)
|
expect(res.body[appId].roles.length).toEqual(5)
|
||||||
expect(res.body[appId].roles.map((r: any) => r._id)).toContain(ROLE_NAME)
|
expect(res.body[appId].roles.map((r: any) => r._id)).toContain(ROLE_NAME)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it.each(["3.0.0", "3.0.1", "3.1.0", "3.0.0+2146.b125a7c"])(
|
||||||
|
"exclude POWER roles after v3 (%s)",
|
||||||
|
async creationVersion => {
|
||||||
|
await updateAppMetadata({ creationVersion })
|
||||||
|
const res = await config.api.roles.get()
|
||||||
|
expect(res.body).toBeDefined()
|
||||||
|
expect(res.body[appId].roles.map((r: any) => r._id)).toEqual([
|
||||||
|
ROLE_NAME,
|
||||||
|
roles.BUILTIN_ROLE_IDS.ADMIN,
|
||||||
|
roles.BUILTIN_ROLE_IDS.BASIC,
|
||||||
|
roles.BUILTIN_ROLE_IDS.PUBLIC,
|
||||||
|
])
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
it.each(["2.9.0", "1.0.0", "0.0.0", "2.32.17+2146.b125a7c"])(
|
||||||
|
"include POWER roles before v3 (%s)",
|
||||||
|
async creationVersion => {
|
||||||
|
await updateAppMetadata({ creationVersion })
|
||||||
|
const res = await config.api.roles.get()
|
||||||
|
expect(res.body).toBeDefined()
|
||||||
|
expect(res.body[appId].roles.map((r: any) => r._id)).toEqual([
|
||||||
|
ROLE_NAME,
|
||||||
|
roles.BUILTIN_ROLE_IDS.ADMIN,
|
||||||
|
roles.BUILTIN_ROLE_IDS.POWER,
|
||||||
|
roles.BUILTIN_ROLE_IDS.BASIC,
|
||||||
|
roles.BUILTIN_ROLE_IDS.PUBLIC,
|
||||||
|
])
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
it.each(["invalid", ""])(
|
||||||
|
"include POWER roles when the version is corrupted (%s)",
|
||||||
|
async creationVersion => {
|
||||||
|
await updateAppMetadata({ creationVersion })
|
||||||
|
const res = await config.api.roles.get()
|
||||||
|
|
||||||
|
expect(res.body[appId].roles.map((r: any) => r._id)).toEqual([
|
||||||
|
ROLE_NAME,
|
||||||
|
roles.BUILTIN_ROLE_IDS.ADMIN,
|
||||||
|
roles.BUILTIN_ROLE_IDS.POWER,
|
||||||
|
roles.BUILTIN_ROLE_IDS.BASIC,
|
||||||
|
roles.BUILTIN_ROLE_IDS.PUBLIC,
|
||||||
|
])
|
||||||
|
}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("GET api/global/roles/:appId", () => {
|
describe("GET api/global/roles/:appId", () => {
|
||||||
|
|
Loading…
Reference in New Issue