Tests complete + backwards compatibility for deployment
This commit is contained in:
parent
715d42d3e6
commit
0a4b1eb552
|
@ -98,10 +98,6 @@ spec:
|
|||
value: http://worker-service:{{ .Values.services.worker.port }}
|
||||
- name: PLATFORM_URL
|
||||
value: {{ .Values.globals.platformUrl | quote }}
|
||||
- name: USE_QUOTAS
|
||||
value: {{ .Values.globals.useQuotas | quote }}
|
||||
- name: EXCLUDE_QUOTAS_TENANTS
|
||||
value: {{ .Values.globals.excludeQuotasTenants | quote }}
|
||||
- name: ACCOUNT_PORTAL_URL
|
||||
value: {{ .Values.globals.accountPortalUrl | quote }}
|
||||
- name: ACCOUNT_PORTAL_API_KEY
|
||||
|
|
|
@ -93,15 +93,13 @@ globals:
|
|||
logLevel: info
|
||||
selfHosted: "1" # set to 0 for budibase cloud environment, set to 1 for self-hosted setup
|
||||
multiTenancy: "0" # set to 0 to disable multiple orgs, set to 1 to enable multiple orgs
|
||||
useQuotas: "0"
|
||||
excludeQuotasTenants: "" # comma seperated list of tenants to exclude from quotas
|
||||
accountPortalUrl: ""
|
||||
accountPortalApiKey: ""
|
||||
cookieDomain: ""
|
||||
platformUrl: ""
|
||||
httpMigrations: "0"
|
||||
google:
|
||||
clientId: ""
|
||||
clientId: ""
|
||||
secret: ""
|
||||
|
||||
createSecrets: true # creates an internal API key, JWT secrets and redis password for you
|
||||
|
|
|
@ -56,7 +56,8 @@ exports.createApiKeyView = async () => {
|
|||
await db.put(designDoc)
|
||||
}
|
||||
|
||||
exports.createUserBuildersView = async db => {
|
||||
exports.createUserBuildersView = async () => {
|
||||
const db = getGlobalDB()
|
||||
let designDoc
|
||||
try {
|
||||
designDoc = await db.get("_design/database")
|
||||
|
@ -82,6 +83,7 @@ exports.queryGlobalView = async (viewName, params, db = null) => {
|
|||
const CreateFuncByName = {
|
||||
[ViewNames.USER_BY_EMAIL]: exports.createUserEmailView,
|
||||
[ViewNames.BY_API_KEY]: exports.createApiKeyView,
|
||||
[ViewNames.USER_BY_BUILDERS]: exports.createUserBuildersView,
|
||||
}
|
||||
// can pass DB in if working with something specific
|
||||
if (!db) {
|
||||
|
|
|
@ -28,8 +28,7 @@ module.exports = {
|
|||
SELF_HOSTED: !!parseInt(process.env.SELF_HOSTED),
|
||||
COOKIE_DOMAIN: process.env.COOKIE_DOMAIN,
|
||||
PLATFORM_URL: process.env.PLATFORM_URL,
|
||||
USE_QUOTAS: process.env.USE_QUOTAS,
|
||||
EXCLUDE_QUOTAS_TENANTS: process.env.EXCLUDE_QUOTAS_TENANTS,
|
||||
TENANT_FEATURE_FLAGS: process.env.TENANT_FEATURE_FLAGS,
|
||||
isTest,
|
||||
_set(key, value) {
|
||||
process.env[key] = value
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
const env = require("../environment")
|
||||
const tenancy = require("../tenancy")
|
||||
|
||||
/**
|
||||
* Read the TENANT_FEATURE_FLAGS env var and return an array of features flags for each tenant.
|
||||
* The env var is formatted as:
|
||||
* tenant1:feature1:feature2,tenant2:feature1
|
||||
*/
|
||||
const getFeatureFlags = () => {
|
||||
if (!env.TENANT_FEATURE_FLAGS) {
|
||||
return
|
||||
}
|
||||
|
||||
const tenantFeatureFlags = {}
|
||||
|
||||
env.TENANT_FEATURE_FLAGS.split(",").forEach(tenantToFeatures => {
|
||||
const [tenantId, ...features] = tenantToFeatures.split(":")
|
||||
|
||||
features.forEach(feature => {
|
||||
if (!tenantFeatureFlags[tenantId]) {
|
||||
tenantFeatureFlags[tenantId] = []
|
||||
}
|
||||
tenantFeatureFlags[tenantId].push(feature)
|
||||
})
|
||||
})
|
||||
|
||||
return tenantFeatureFlags
|
||||
}
|
||||
|
||||
const TENANT_FEATURE_FLAGS = getFeatureFlags()
|
||||
|
||||
exports.isEnabled = featureFlag => {
|
||||
const tenantId = tenancy.getTenantId()
|
||||
|
||||
return (
|
||||
TENANT_FEATURE_FLAGS &&
|
||||
TENANT_FEATURE_FLAGS[tenantId] &&
|
||||
TENANT_FEATURE_FLAGS[tenantId].includes(featureFlag)
|
||||
)
|
||||
}
|
||||
|
||||
exports.getTenantFeatureFlags = tenantId => {
|
||||
if (TENANT_FEATURE_FLAGS && TENANT_FEATURE_FLAGS[tenantId]) {
|
||||
return TENANT_FEATURE_FLAGS[tenantId]
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
exports.FeatureFlag = {
|
||||
LICENSING: "LICENSING",
|
||||
}
|
|
@ -19,4 +19,5 @@ module.exports = {
|
|||
env: require("./environment"),
|
||||
accounts: require("./cloud/accounts"),
|
||||
tenancy: require("./tenancy"),
|
||||
featureFlags: require("./featureFlags"),
|
||||
}
|
||||
|
|
|
@ -55,4 +55,4 @@ exports.strategyFactory = async function (config, callbackUrl, saveUserFn) {
|
|||
}
|
||||
}
|
||||
// expose for testing
|
||||
exports.authenticate = buildVerifyFn
|
||||
exports.buildVerifyFn = buildVerifyFn
|
||||
|
|
|
@ -129,4 +129,4 @@ exports.strategyFactory = async function (config, callbackUrl, saveUserFn) {
|
|||
}
|
||||
|
||||
// expose for testing
|
||||
exports.authenticate = buildVerifyFn
|
||||
exports.buildVerifyFn = buildVerifyFn
|
||||
|
|
|
@ -58,8 +58,10 @@ describe("google", () => {
|
|||
|
||||
it("delegates authentication to third party common", async () => {
|
||||
const google = require("../google")
|
||||
const mockSaveUserFn = jest.fn()
|
||||
const authenticate = await google.buildVerifyFn(mockSaveUserFn)
|
||||
|
||||
await google.authenticate(
|
||||
await authenticate(
|
||||
data.accessToken,
|
||||
data.refreshToken,
|
||||
profile,
|
||||
|
@ -69,7 +71,8 @@ describe("google", () => {
|
|||
expect(authenticateThirdParty).toHaveBeenCalledWith(
|
||||
user,
|
||||
true,
|
||||
mockDone)
|
||||
mockDone,
|
||||
mockSaveUserFn)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -83,8 +83,10 @@ describe("oidc", () => {
|
|||
|
||||
async function doAuthenticate() {
|
||||
const oidc = require("../oidc")
|
||||
const mockSaveUserFn = jest.fn()
|
||||
const authenticate = await oidc.buildVerifyFn(mockSaveUserFn)
|
||||
|
||||
await oidc.authenticate(
|
||||
await authenticate(
|
||||
issuer,
|
||||
sub,
|
||||
profile,
|
||||
|
|
|
@ -147,7 +147,9 @@ exports.getGlobalUserByEmail = async email => {
|
|||
}
|
||||
|
||||
exports.getBuildersCount = async () => {
|
||||
const builders = await queryGlobalView(ViewNames.BUILDERS)
|
||||
const builders = await queryGlobalView(ViewNames.USER_BY_BUILDERS, {
|
||||
include_docs: false,
|
||||
})
|
||||
return builders.total_rows
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -9,7 +9,7 @@
|
|||
"url": "https://github.com/Budibase/budibase.git"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "rimraf dist/ && tsc && mv dist/src/* dist/ && rmdir dist/src/ && yarn postbuild",
|
||||
"build": "rimraf dist/ && tsc -p tsconfig.build.json && mv dist/src/* dist/ && rmdir dist/src/ && yarn postbuild",
|
||||
"postbuild": "copyfiles -u 1 src/**/*.svelte dist/ && copyfiles -u 1 src/**/*.hbs dist/ && copyfiles -u 1 src/**/*.json dist/",
|
||||
"test": "jest --coverage --maxWorkers=2",
|
||||
"test:watch": "jest --watch",
|
||||
|
@ -81,6 +81,7 @@
|
|||
"@koa/router": "8.0.0",
|
||||
"@sendgrid/mail": "7.1.1",
|
||||
"@sentry/node": "6.17.7",
|
||||
"@types/koa__router": "^8.0.11",
|
||||
"airtable": "0.10.1",
|
||||
"arangojs": "7.2.0",
|
||||
"aws-sdk": "^2.767.0",
|
||||
|
@ -146,7 +147,7 @@
|
|||
"@types/apidoc": "^0.50.0",
|
||||
"@types/bull": "^3.15.1",
|
||||
"@types/google-spreadsheet": "^3.1.5",
|
||||
"@types/jest": "^26.0.23",
|
||||
"@types/jest": "^27.4.1",
|
||||
"@types/koa": "^2.13.3",
|
||||
"@types/koa-router": "^7.4.2",
|
||||
"@types/lodash": "^4.14.179",
|
||||
|
|
|
@ -389,7 +389,7 @@ const destroyApp = async (ctx: any) => {
|
|||
const db = getAppDB()
|
||||
|
||||
const result = await db.destroy()
|
||||
if (ctx.query.unpublish) {
|
||||
if (ctx.query?.unpublish) {
|
||||
await quotas.removePublishedApp()
|
||||
} else {
|
||||
await quotas.removeApp()
|
||||
|
@ -408,7 +408,7 @@ const destroyApp = async (ctx: any) => {
|
|||
}
|
||||
|
||||
const preDestroyApp = async (ctx: any) => {
|
||||
const rows = await getUniqueRows([ctx.appId])
|
||||
const rows = await getUniqueRows([ctx.params.appId])
|
||||
ctx.rowCount = rows.length
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
const Router = require("@koa/router")
|
||||
import Router from "@koa/router"
|
||||
import * as controller from "../controllers/application"
|
||||
import authorized from "../../middleware/authorized"
|
||||
const { BUILDER } = require("@budibase/backend-core/permissions")
|
||||
import { BUILDER } from "@budibase/backend-core/permissions"
|
||||
|
||||
const router = Router()
|
||||
const router = new Router()
|
||||
|
||||
router
|
||||
.post("/api/applications/:appId/sync", authorized(BUILDER), controller.sync)
|
||||
|
|
|
@ -127,4 +127,4 @@ applyRoutes(queryEndpoints, PermissionTypes.QUERY, "queryId")
|
|||
// needs to be applied last for routing purposes, don't override other endpoints
|
||||
applyRoutes(rowEndpoints, PermissionTypes.TABLE, "tableId", "rowId")
|
||||
|
||||
module.exports = publicRouter
|
||||
export default publicRouter
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
const Router = require("@koa/router")
|
||||
import Router from "@koa/router"
|
||||
import * as rowController from "../controllers/row"
|
||||
import authorized from "../../middleware/authorized"
|
||||
import { paramResource, paramSubResource } from "../../middleware/resourceId"
|
||||
|
@ -8,7 +8,7 @@ const {
|
|||
} = require("@budibase/backend-core/permissions")
|
||||
const { internalSearchValidator } = require("./utils/validators")
|
||||
|
||||
const router = Router()
|
||||
const router = new Router()
|
||||
|
||||
router
|
||||
/**
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
const Router = require("@koa/router")
|
||||
import Router from "@koa/router"
|
||||
import * as controller from "../controllers/static"
|
||||
import { budibaseTempDir } from "../../utilities/budibaseDir"
|
||||
import authorized from "../../middleware/authorized"
|
||||
|
@ -10,10 +10,10 @@ import {
|
|||
import * as env from "../../environment"
|
||||
import { paramResource } from "../../middleware/resourceId"
|
||||
|
||||
const router = Router()
|
||||
const router = new Router()
|
||||
|
||||
/* istanbul ignore next */
|
||||
router.param("file", async (file, ctx, next) => {
|
||||
router.param("file", async (file: any, ctx: any, next: any) => {
|
||||
ctx.file = file && file.includes(".") ? file : "index.html"
|
||||
if (!ctx.file.startsWith("budibase-client")) {
|
||||
return next()
|
|
@ -1,31 +1,38 @@
|
|||
const rowController = require("../../../controllers/row")
|
||||
const appController = require("../../../controllers/application")
|
||||
const { AppStatus } = require("../../../../db/utils")
|
||||
const { BUILTIN_ROLE_IDS } = require("@budibase/backend-core/roles")
|
||||
const { TENANT_ID } = require("../../../../tests/utilities/structures")
|
||||
const { getAppDB, doInAppContext } = require("@budibase/backend-core/context")
|
||||
const env = require("../../../../environment")
|
||||
import * as rowController from "../../../controllers/row"
|
||||
import * as appController from "../../../controllers/application"
|
||||
import { AppStatus } from "../../../../db/utils"
|
||||
import { BUILTIN_ROLE_IDS } from "@budibase/backend-core/roles"
|
||||
import { TENANT_ID } from "../../../../tests/utilities/structures"
|
||||
import { getAppDB, doInAppContext } from "@budibase/backend-core/context"
|
||||
import * as env from "../../../../environment"
|
||||
|
||||
function Request(appId, params) {
|
||||
this.appId = appId
|
||||
this.params = params
|
||||
this.request = {}
|
||||
class Request {
|
||||
appId: any
|
||||
params: any
|
||||
request: any
|
||||
body: any
|
||||
|
||||
constructor(appId: any, params: any) {
|
||||
this.appId = appId
|
||||
this.params = params
|
||||
this.request = {}
|
||||
}
|
||||
}
|
||||
|
||||
function runRequest(appId, controlFunc, request) {
|
||||
function runRequest(appId: any, controlFunc: any, request?: any) {
|
||||
return doInAppContext(appId, async () => {
|
||||
return controlFunc(request)
|
||||
})
|
||||
}
|
||||
|
||||
exports.getAllTableRows = async config => {
|
||||
export const getAllTableRows = async (config: any) => {
|
||||
const req = new Request(config.appId, { tableId: config.table._id })
|
||||
await runRequest(config.appId, rowController.fetch, req)
|
||||
return req.body
|
||||
}
|
||||
|
||||
exports.clearAllApps = async (tenantId = TENANT_ID) => {
|
||||
const req = { query: { status: AppStatus.DEV }, user: { tenantId } }
|
||||
export const clearAllApps = async (tenantId = TENANT_ID) => {
|
||||
const req: any = { query: { status: AppStatus.DEV }, user: { tenantId } }
|
||||
await appController.fetch(req)
|
||||
const apps = req.body
|
||||
if (!apps || apps.length <= 0) {
|
||||
|
@ -34,11 +41,11 @@ exports.clearAllApps = async (tenantId = TENANT_ID) => {
|
|||
for (let app of apps) {
|
||||
const { appId } = app
|
||||
const req = new Request(null, { appId })
|
||||
await runRequest(appId, appController.delete, req)
|
||||
await runRequest(appId, appController.destroy, req)
|
||||
}
|
||||
}
|
||||
|
||||
exports.clearAllAutomations = async config => {
|
||||
export const clearAllAutomations = async (config: any) => {
|
||||
const automations = await config.getAllAutomations()
|
||||
for (let auto of automations) {
|
||||
await doInAppContext(config.appId, async () => {
|
||||
|
@ -47,7 +54,12 @@ exports.clearAllAutomations = async config => {
|
|||
}
|
||||
}
|
||||
|
||||
exports.createRequest = (request, method, url, body) => {
|
||||
export const createRequest = (
|
||||
request: any,
|
||||
method: any,
|
||||
url: any,
|
||||
body: any
|
||||
) => {
|
||||
let req
|
||||
|
||||
if (method === "POST") req = request.post(url).send(body)
|
||||
|
@ -59,7 +71,12 @@ exports.createRequest = (request, method, url, body) => {
|
|||
return req
|
||||
}
|
||||
|
||||
exports.checkBuilderEndpoint = async ({ config, method, url, body }) => {
|
||||
export const checkBuilderEndpoint = async ({
|
||||
config,
|
||||
method,
|
||||
url,
|
||||
body,
|
||||
}: any) => {
|
||||
const headers = await config.login({
|
||||
userId: "us_fail",
|
||||
builder: false,
|
||||
|
@ -71,14 +88,14 @@ exports.checkBuilderEndpoint = async ({ config, method, url, body }) => {
|
|||
.expect(403)
|
||||
}
|
||||
|
||||
exports.checkPermissionsEndpoint = async ({
|
||||
export const checkPermissionsEndpoint = async ({
|
||||
config,
|
||||
method,
|
||||
url,
|
||||
body,
|
||||
passRole,
|
||||
failRole,
|
||||
}) => {
|
||||
}: any) => {
|
||||
const passHeader = await config.login({
|
||||
roleId: passRole,
|
||||
prodApp: true,
|
||||
|
@ -106,11 +123,11 @@ exports.checkPermissionsEndpoint = async ({
|
|||
.expect(403)
|
||||
}
|
||||
|
||||
exports.getDB = () => {
|
||||
export const getDB = () => {
|
||||
return getAppDB()
|
||||
}
|
||||
|
||||
exports.testAutomation = async (config, automation) => {
|
||||
export const testAutomation = async (config: any, automation: any) => {
|
||||
return runRequest(automation.appId, async () => {
|
||||
return await config.request
|
||||
.post(`/api/automations/${automation._id}/test`)
|
||||
|
@ -126,7 +143,7 @@ exports.testAutomation = async (config, automation) => {
|
|||
})
|
||||
}
|
||||
|
||||
exports.runInProd = async func => {
|
||||
export const runInProd = async (func: any) => {
|
||||
const nodeEnv = env.NODE_ENV
|
||||
const workerId = env.JEST_WORKER_ID
|
||||
env._set("NODE_ENV", "PRODUCTION")
|
|
@ -1,4 +1,3 @@
|
|||
import { quotas } from "@budibase/pro"
|
||||
import { save } from "../../api/controllers/row"
|
||||
import { cleanUpRow, getError } from "../automationUtils"
|
||||
import { buildCtx } from "./utils"
|
||||
|
@ -78,7 +77,7 @@ export async function run({ inputs, appId, emitter }: any) {
|
|||
|
||||
try {
|
||||
inputs.row = await cleanUpRow(inputs.row.tableId, inputs.row)
|
||||
await quotas.addRow(() => save(ctx))
|
||||
await save(ctx)
|
||||
return {
|
||||
row: inputs.row,
|
||||
response: ctx.body,
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { destroy } from "../../api/controllers/row"
|
||||
import { buildCtx } from "./utils"
|
||||
import { getError } from "../automationUtils"
|
||||
import { quotas } from "@budibase/pro"
|
||||
|
||||
export const definition = {
|
||||
description: "Delete a row from your database",
|
||||
|
@ -74,7 +73,6 @@ export async function run({ inputs, appId, emitter }: any) {
|
|||
|
||||
try {
|
||||
await destroy(ctx)
|
||||
await quotas.removeRow()
|
||||
return {
|
||||
response: ctx.body,
|
||||
row: ctx.row,
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
jest.mock("../../utilities/usageQuota")
|
||||
jest.mock("../../threads/automation")
|
||||
jest.mock("../../utilities/redis", () => ({
|
||||
init: jest.fn(),
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
jest.mock("../../utilities/usageQuota")
|
||||
|
||||
const usageQuota = require("../../utilities/usageQuota")
|
||||
const setup = require("./utilities")
|
||||
import * as setup from "./utilities"
|
||||
|
||||
describe("test the create row action", () => {
|
||||
let table, row
|
||||
let table: any
|
||||
let row: any
|
||||
let config = setup.getConfig()
|
||||
|
||||
beforeEach(async () => {
|
||||
|
@ -36,20 +34,11 @@ describe("test the create row action", () => {
|
|||
row: {
|
||||
tableId: "invalid",
|
||||
invalid: "invalid",
|
||||
}
|
||||
},
|
||||
})
|
||||
expect(res.success).toEqual(false)
|
||||
})
|
||||
|
||||
it("check usage quota attempts", async () => {
|
||||
await setup.runInProd(async () => {
|
||||
await setup.runStep(setup.actions.CREATE_ROW.stepId, {
|
||||
row
|
||||
})
|
||||
expect(usageQuota.update).toHaveBeenCalledWith("rows", 1)
|
||||
})
|
||||
})
|
||||
|
||||
it("should check invalid inputs return an error", async () => {
|
||||
const res = await setup.runStep(setup.actions.CREATE_ROW.stepId, {})
|
||||
expect(res.success).toEqual(false)
|
|
@ -1,10 +1,9 @@
|
|||
jest.mock("../../utilities/usageQuota")
|
||||
|
||||
const usageQuota = require("../../utilities/usageQuota")
|
||||
const setup = require("./utilities")
|
||||
|
||||
describe("test the delete row action", () => {
|
||||
let table, row, inputs
|
||||
let table: any
|
||||
let row: any
|
||||
let inputs: any
|
||||
let config = setup.getConfig()
|
||||
|
||||
beforeEach(async () => {
|
||||
|
@ -37,7 +36,6 @@ describe("test the delete row action", () => {
|
|||
it("check usage quota attempts", async () => {
|
||||
await setup.runInProd(async () => {
|
||||
await setup.runStep(setup.actions.DELETE_ROW.stepId, inputs)
|
||||
expect(usageQuota.update).toHaveBeenCalledWith("rows", -1)
|
||||
})
|
||||
})
|
||||
|
|
@ -18,7 +18,6 @@ exports.afterAll = () => {
|
|||
|
||||
exports.runInProd = async fn => {
|
||||
env._set("NODE_ENV", "production")
|
||||
env._set("USE_QUOTAS", 1)
|
||||
let error
|
||||
try {
|
||||
await fn()
|
||||
|
@ -26,7 +25,6 @@ exports.runInProd = async fn => {
|
|||
error = err
|
||||
}
|
||||
env._set("NODE_ENV", "jest")
|
||||
env._set("USE_QUOTAS", null)
|
||||
if (error) {
|
||||
throw error
|
||||
}
|
||||
|
|
|
@ -38,8 +38,6 @@ module.exports = {
|
|||
ENABLE_ANALYTICS: process.env.ENABLE_ANALYTICS,
|
||||
MINIO_ACCESS_KEY: process.env.MINIO_ACCESS_KEY,
|
||||
MINIO_SECRET_KEY: process.env.MINIO_SECRET_KEY,
|
||||
USE_QUOTAS: process.env.USE_QUOTAS,
|
||||
EXCLUDE_QUOTAS_TENANTS: process.env.EXCLUDE_QUOTAS_TENANTS,
|
||||
REDIS_URL: process.env.REDIS_URL,
|
||||
REDIS_PASSWORD: process.env.REDIS_PASSWORD,
|
||||
INTERNAL_API_KEY: process.env.INTERNAL_API_KEY,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
const {
|
||||
import {
|
||||
getUserRoleHierarchy,
|
||||
getRequiredResourceRole,
|
||||
BUILTIN_ROLE_IDS,
|
||||
} = require("@budibase/backend-core/roles")
|
||||
} from "@budibase/backend-core/roles"
|
||||
const {
|
||||
PermissionTypes,
|
||||
doesHaveBasePermission,
|
||||
|
@ -12,7 +12,7 @@ const { isWebhookEndpoint } = require("./utils")
|
|||
const { buildCsrfMiddleware } = require("@budibase/backend-core/auth")
|
||||
const { getAppId } = require("@budibase/backend-core/context")
|
||||
|
||||
function hasResource(ctx) {
|
||||
function hasResource(ctx: any) {
|
||||
return ctx.resourceId != null
|
||||
}
|
||||
|
||||
|
@ -24,7 +24,12 @@ const csrf = buildCsrfMiddleware()
|
|||
* - Builders can access all resources.
|
||||
* - Otherwise the user must have the required role.
|
||||
*/
|
||||
const checkAuthorized = async (ctx, resourceRoles, permType, permLevel) => {
|
||||
const checkAuthorized = async (
|
||||
ctx: any,
|
||||
resourceRoles: any,
|
||||
permType: any,
|
||||
permLevel: any
|
||||
) => {
|
||||
// check if this is a builder api and the user is not a builder
|
||||
const isBuilder = ctx.user && ctx.user.builder && ctx.user.builder.global
|
||||
const isBuilderApi = permType === PermissionTypes.BUILDER
|
||||
|
@ -39,10 +44,10 @@ const checkAuthorized = async (ctx, resourceRoles, permType, permLevel) => {
|
|||
}
|
||||
|
||||
const checkAuthorizedResource = async (
|
||||
ctx,
|
||||
resourceRoles,
|
||||
permType,
|
||||
permLevel
|
||||
ctx: any,
|
||||
resourceRoles: any,
|
||||
permType: any,
|
||||
permLevel: any
|
||||
) => {
|
||||
// get the user's roles
|
||||
const roleId = ctx.roleId || BUILTIN_ROLE_IDS.PUBLIC
|
||||
|
@ -53,7 +58,9 @@ const checkAuthorizedResource = async (
|
|||
// check if the user has the required role
|
||||
if (resourceRoles.length > 0) {
|
||||
// deny access if the user doesn't have the required resource role
|
||||
const found = userRoles.find(role => resourceRoles.indexOf(role._id) !== -1)
|
||||
const found = userRoles.find(
|
||||
(role: any) => resourceRoles.indexOf(role._id) !== -1
|
||||
)
|
||||
if (!found) {
|
||||
ctx.throw(403, permError)
|
||||
}
|
||||
|
@ -63,9 +70,8 @@ const checkAuthorizedResource = async (
|
|||
}
|
||||
}
|
||||
|
||||
module.exports =
|
||||
(permType, permLevel = null) =>
|
||||
async (ctx, next) => {
|
||||
export = (permType: any, permLevel: any = null) =>
|
||||
async (ctx: any, next: any) => {
|
||||
// webhooks don't need authentication, each webhook unique
|
||||
// also internal requests (between services) don't need authorized
|
||||
if (isWebhookEndpoint(ctx) || ctx.internal) {
|
||||
|
@ -81,7 +87,7 @@ module.exports =
|
|||
await builderMiddleware(ctx, permType)
|
||||
|
||||
// get the resource roles
|
||||
let resourceRoles = []
|
||||
let resourceRoles: any = []
|
||||
const appId = getAppId()
|
||||
if (appId && hasResource(ctx)) {
|
||||
resourceRoles = await getRequiredResourceRole(permLevel, ctx)
|
|
@ -1,134 +0,0 @@
|
|||
jest.mock("../../db")
|
||||
jest.mock("../../utilities/usageQuota")
|
||||
jest.mock("@budibase/backend-core/tenancy", () => ({
|
||||
getTenantId: () => "testing123"
|
||||
}))
|
||||
|
||||
const usageQuotaMiddleware = require("../usageQuota")
|
||||
const usageQuota = require("../../utilities/usageQuota")
|
||||
const CouchDB = require("../../db")
|
||||
const env = require("../../environment")
|
||||
|
||||
class TestConfiguration {
|
||||
constructor() {
|
||||
this.throw = jest.fn()
|
||||
this.next = jest.fn()
|
||||
this.middleware = usageQuotaMiddleware
|
||||
this.ctx = {
|
||||
throw: this.throw,
|
||||
next: this.next,
|
||||
appId: "test",
|
||||
request: {
|
||||
body: {}
|
||||
},
|
||||
req: {
|
||||
method: "POST",
|
||||
url: "/applications"
|
||||
}
|
||||
}
|
||||
usageQuota.useQuotas = () => true
|
||||
}
|
||||
|
||||
executeMiddleware() {
|
||||
return this.middleware(this.ctx, this.next)
|
||||
}
|
||||
|
||||
setProd(bool) {
|
||||
if (bool) {
|
||||
env.isDev = () => false
|
||||
env.isProd = () => true
|
||||
this.ctx.user = { tenantId: "test" }
|
||||
} else {
|
||||
env.isDev = () => true
|
||||
env.isProd = () => false
|
||||
}
|
||||
}
|
||||
|
||||
setMethod(method) {
|
||||
this.ctx.req.method = method
|
||||
}
|
||||
|
||||
setUrl(url) {
|
||||
this.ctx.req.url = url
|
||||
}
|
||||
|
||||
setBody(body) {
|
||||
this.ctx.request.body = body
|
||||
}
|
||||
|
||||
setFiles(files) {
|
||||
this.ctx.request.files = { file: files }
|
||||
}
|
||||
}
|
||||
|
||||
describe("usageQuota middleware", () => {
|
||||
let config
|
||||
|
||||
beforeEach(() => {
|
||||
config = new TestConfiguration()
|
||||
})
|
||||
|
||||
it("skips the middleware if there is no usage property or method", async () => {
|
||||
await config.executeMiddleware()
|
||||
expect(config.next).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("passes through to next middleware if document already exists", async () => {
|
||||
config.setProd(true)
|
||||
config.setBody({
|
||||
_id: "test",
|
||||
_rev: "test",
|
||||
})
|
||||
|
||||
CouchDB.mockImplementationOnce(() => ({
|
||||
get: async () => true
|
||||
}))
|
||||
|
||||
await config.executeMiddleware()
|
||||
|
||||
expect(config.next).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("throws if request has _id, but the document no longer exists", async () => {
|
||||
config.setBody({
|
||||
_id: "123",
|
||||
_rev: "test",
|
||||
})
|
||||
config.setProd(true)
|
||||
|
||||
CouchDB.mockImplementationOnce(() => ({
|
||||
get: async () => {
|
||||
throw new Error()
|
||||
}
|
||||
}))
|
||||
|
||||
await config.executeMiddleware()
|
||||
expect(config.throw).toHaveBeenCalledWith(404, `${config.ctx.request.body._id} does not exist`)
|
||||
})
|
||||
|
||||
it("calculates and persists the correct usage quota for the relevant action", async () => {
|
||||
config.setUrl("/rows")
|
||||
|
||||
await config.executeMiddleware()
|
||||
|
||||
expect(usageQuota.update).toHaveBeenCalledWith("rows", 1)
|
||||
expect(config.next).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
// it("calculates the correct file size from a file upload call and adds it to quota", async () => {
|
||||
// config.setUrl("/upload")
|
||||
// config.setProd(true)
|
||||
// config.setFiles([
|
||||
// {
|
||||
// size: 100
|
||||
// },
|
||||
// {
|
||||
// size: 10000
|
||||
// },
|
||||
// ])
|
||||
// await config.executeMiddleware()
|
||||
|
||||
// expect(usageQuota.update).toHaveBeenCalledWith("storage", 10100)
|
||||
// expect(config.next).toHaveBeenCalled()
|
||||
// })
|
||||
})
|
|
@ -1,4 +1,3 @@
|
|||
const env = require("../../../environment")
|
||||
const TestConfig = require("../../../tests/utilities/TestConfiguration")
|
||||
|
||||
const syncApps = jest.fn()
|
||||
|
@ -14,7 +13,6 @@ describe("run", () => {
|
|||
|
||||
beforeEach(async () => {
|
||||
await config.init()
|
||||
env._set("USE_QUOTAS", 1)
|
||||
})
|
||||
|
||||
afterAll(config.end)
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
const { getGlobalDB } = require("@budibase/backend-core/tenancy")
|
||||
const TestConfig = require("../../../../tests/utilities/TestConfiguration")
|
||||
const { getUsageQuotaDoc, update, Properties } = require("../../../../utilities/usageQuota")
|
||||
const syncApps = require("../syncApps")
|
||||
const env = require("../../../../environment")
|
||||
|
||||
describe("syncApps", () => {
|
||||
let config = new TestConfig(false)
|
||||
|
||||
beforeEach(async () => {
|
||||
await config.init()
|
||||
env._set("USE_QUOTAS", 1)
|
||||
})
|
||||
|
||||
afterAll(config.end)
|
||||
|
||||
it("runs successfully", async () => {
|
||||
// create the usage quota doc and mock usages
|
||||
const db = getGlobalDB()
|
||||
await getUsageQuotaDoc(db)
|
||||
await update(Properties.APPS, 3)
|
||||
|
||||
let usageDoc = await getUsageQuotaDoc(db)
|
||||
expect(usageDoc.usageQuota.apps).toEqual(3)
|
||||
|
||||
// create an extra app to test the migration
|
||||
await config.createApp("quota-test")
|
||||
|
||||
// migrate
|
||||
await syncApps.run()
|
||||
|
||||
// assert the migration worked
|
||||
usageDoc = await getUsageQuotaDoc(db)
|
||||
expect(usageDoc.usageQuota.apps).toEqual(2)
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
import TestConfig from "../../../../tests/utilities/TestConfiguration"
|
||||
import * as syncApps from "../syncApps"
|
||||
import { quotas, QuotaUsageType, StaticQuotaName } from "@budibase/pro"
|
||||
|
||||
describe("syncApps", () => {
|
||||
let config = new TestConfig(false)
|
||||
|
||||
beforeEach(async () => {
|
||||
await config.init()
|
||||
})
|
||||
|
||||
afterAll(config.end)
|
||||
|
||||
it("runs successfully", async () => {
|
||||
// create the usage quota doc and mock usages
|
||||
await quotas.getQuotaUsage()
|
||||
await quotas.setUsage(3, StaticQuotaName.APPS, QuotaUsageType.STATIC)
|
||||
|
||||
let usageDoc = await quotas.getQuotaUsage()
|
||||
expect(usageDoc.usageQuota.apps).toEqual(3)
|
||||
|
||||
// create an extra app to test the migration
|
||||
await config.createApp("quota-test")
|
||||
|
||||
// migrate
|
||||
await syncApps.run()
|
||||
|
||||
// assert the migration worked
|
||||
usageDoc = await quotas.getQuotaUsage()
|
||||
expect(usageDoc.usageQuota.apps).toEqual(2)
|
||||
})
|
||||
})
|
|
@ -1,43 +0,0 @@
|
|||
const { getGlobalDB } = require("@budibase/backend-core/tenancy")
|
||||
const TestConfig = require("../../../../tests/utilities/TestConfiguration")
|
||||
const { getUsageQuotaDoc, update, Properties } = require("../../../../utilities/usageQuota")
|
||||
const syncRows = require("../syncRows")
|
||||
const env = require("../../../../environment")
|
||||
|
||||
describe("syncRows", () => {
|
||||
let config = new TestConfig(false)
|
||||
|
||||
beforeEach(async () => {
|
||||
await config.init()
|
||||
env._set("USE_QUOTAS", 1)
|
||||
})
|
||||
|
||||
afterAll(config.end)
|
||||
|
||||
it("runs successfully", async () => {
|
||||
// create the usage quota doc and mock usages
|
||||
const db = getGlobalDB()
|
||||
await getUsageQuotaDoc(db)
|
||||
await update(Properties.ROW, 300)
|
||||
|
||||
let usageDoc = await getUsageQuotaDoc(db)
|
||||
expect(usageDoc.usageQuota.rows).toEqual(300)
|
||||
|
||||
// app 1
|
||||
await config.createTable()
|
||||
await config.createRow()
|
||||
// app 2
|
||||
await config.createApp("second-app")
|
||||
await config.createTable()
|
||||
await config.createRow()
|
||||
await config.createRow()
|
||||
|
||||
// migrate
|
||||
await syncRows.run()
|
||||
|
||||
// assert the migration worked
|
||||
usageDoc = await getUsageQuotaDoc(db)
|
||||
expect(usageDoc.usageQuota.rows).toEqual(3)
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
import TestConfig from "../../../../tests/utilities/TestConfiguration"
|
||||
import * as syncRows from "../syncRows"
|
||||
import { quotas, QuotaUsageType, StaticQuotaName } from "@budibase/pro"
|
||||
|
||||
describe("syncRows", () => {
|
||||
let config = new TestConfig(false)
|
||||
|
||||
beforeEach(async () => {
|
||||
await config.init()
|
||||
})
|
||||
|
||||
afterAll(config.end)
|
||||
|
||||
it("runs successfully", async () => {
|
||||
// create the usage quota doc and mock usages
|
||||
await quotas.getQuotaUsage()
|
||||
await quotas.setUsage(300, StaticQuotaName.ROWS, QuotaUsageType.STATIC)
|
||||
|
||||
let usageDoc = await quotas.getQuotaUsage()
|
||||
expect(usageDoc.usageQuota.rows).toEqual(300)
|
||||
|
||||
// app 1
|
||||
await config.createTable()
|
||||
await config.createRow()
|
||||
// app 2
|
||||
await config.createApp("second-app")
|
||||
await config.createTable()
|
||||
await config.createRow()
|
||||
await config.createRow()
|
||||
|
||||
// migrate
|
||||
await syncRows.run()
|
||||
|
||||
// assert the migration worked
|
||||
usageDoc = await quotas.getQuotaUsage()
|
||||
expect(usageDoc.usageQuota.rows).toEqual(3)
|
||||
})
|
||||
})
|
|
@ -3,3 +3,5 @@ declare module "@budibase/backend-core/tenancy"
|
|||
declare module "@budibase/backend-core/db"
|
||||
declare module "@budibase/backend-core/context"
|
||||
declare module "@budibase/backend-core/cache"
|
||||
declare module "@budibase/backend-core/permissions"
|
||||
declare module "@budibase/backend-core/roles"
|
||||
|
|
|
@ -1,72 +0,0 @@
|
|||
const getTenantId = jest.fn()
|
||||
jest.mock("@budibase/backend-core/tenancy", () => ({
|
||||
getTenantId
|
||||
}))
|
||||
const usageQuota = require("../../usageQuota")
|
||||
const env = require("../../../environment")
|
||||
|
||||
class TestConfiguration {
|
||||
constructor() {
|
||||
this.enableQuotas()
|
||||
}
|
||||
|
||||
enableQuotas = () => {
|
||||
env.USE_QUOTAS = 1
|
||||
}
|
||||
|
||||
disableQuotas = () => {
|
||||
env.USE_QUOTAS = null
|
||||
}
|
||||
|
||||
setTenantId = (tenantId) => {
|
||||
getTenantId.mockReturnValue(tenantId)
|
||||
}
|
||||
|
||||
setExcludedTenants = (tenants) => {
|
||||
env.EXCLUDE_QUOTAS_TENANTS = tenants
|
||||
}
|
||||
|
||||
reset = () => {
|
||||
this.disableQuotas()
|
||||
this.setExcludedTenants(null)
|
||||
}
|
||||
}
|
||||
|
||||
describe("usageQuota", () => {
|
||||
let config
|
||||
|
||||
beforeEach(() => {
|
||||
config = new TestConfiguration()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
config.reset()
|
||||
})
|
||||
|
||||
describe("useQuotas", () => {
|
||||
it("works when no settings have been provided", () => {
|
||||
config.reset()
|
||||
expect(usageQuota.useQuotas()).toBe(false)
|
||||
})
|
||||
it("honours USE_QUOTAS setting", () => {
|
||||
config.disableQuotas()
|
||||
expect(usageQuota.useQuotas()).toBe(false)
|
||||
|
||||
config.enableQuotas()
|
||||
expect(usageQuota.useQuotas()).toBe(true)
|
||||
})
|
||||
it("honours EXCLUDE_QUOTAS_TENANTS setting", () => {
|
||||
config.setTenantId("test")
|
||||
|
||||
// tenantId is in the list
|
||||
config.setExcludedTenants("test, test2, test2")
|
||||
expect(usageQuota.useQuotas()).toBe(false)
|
||||
config.setExcludedTenants("test,test2,test2")
|
||||
expect(usageQuota.useQuotas()).toBe(false)
|
||||
|
||||
// tenantId is not in the list
|
||||
config.setTenantId("other")
|
||||
expect(usageQuota.useQuotas()).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
// Used for building with tsc
|
||||
"extends": "./tsconfig.json",
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"**/*.json",
|
||||
"**/*.spec.js",
|
||||
"**/*.spec.ts"
|
||||
]
|
||||
}
|
|
@ -19,7 +19,7 @@
|
|||
"exclude": [
|
||||
"node_modules",
|
||||
"**/*.json",
|
||||
"**/*.spec.ts",
|
||||
"**/*.spec.js"
|
||||
"**/*.spec.js",
|
||||
// "**/*.spec.ts" // don't exclude spec.ts files for editor support
|
||||
]
|
||||
}
|
||||
|
|
|
@ -2531,13 +2531,13 @@
|
|||
dependencies:
|
||||
"@types/istanbul-lib-report" "*"
|
||||
|
||||
"@types/jest@^26.0.23":
|
||||
version "26.0.24"
|
||||
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.24.tgz#943d11976b16739185913a1936e0de0c4a7d595a"
|
||||
integrity sha512-E/X5Vib8BWqZNRlDxj9vYXhsDwPYbPINqKF9BsnSoon4RQ0D9moEuLD8txgyypFLH7J4+Lho9Nr/c8H0Fi+17w==
|
||||
"@types/jest@^27.4.1":
|
||||
version "27.4.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-27.4.1.tgz#185cbe2926eaaf9662d340cc02e548ce9e11ab6d"
|
||||
integrity sha512-23iPJADSmicDVrWk+HT58LMJtzLAnB2AgIzplQuq/bSrGaxCrlvRFjGbXmamnnk/mAmCdLStiGqggu28ocUyiw==
|
||||
dependencies:
|
||||
jest-diff "^26.0.0"
|
||||
pretty-format "^26.0.0"
|
||||
jest-matcher-utils "^27.0.0"
|
||||
pretty-format "^27.0.0"
|
||||
|
||||
"@types/json-schema@*", "@types/json-schema@^7.0.6", "@types/json-schema@^7.0.8":
|
||||
version "7.0.9"
|
||||
|
@ -2577,6 +2577,13 @@
|
|||
"@types/koa-compose" "*"
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/koa__router@^8.0.11":
|
||||
version "8.0.11"
|
||||
resolved "https://registry.yarnpkg.com/@types/koa__router/-/koa__router-8.0.11.tgz#d7b37e6db934fc072ea1baa2ab92bc8ac4564f3e"
|
||||
integrity sha512-WXgKWpBsbS14kzmzD9LeFapOIa678h7zvUHxDwXwSx4ETKXhXLVUAToX6jZ/U7EihM7qwyD9W/BZvB0MRu7MTQ==
|
||||
dependencies:
|
||||
"@types/koa" "*"
|
||||
|
||||
"@types/lodash@^4.14.179":
|
||||
version "4.14.180"
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.180.tgz#4ab7c9ddfc92ec4a887886483bc14c79fb380670"
|
||||
|
@ -7375,7 +7382,7 @@ jest-diff@^24.9.0:
|
|||
jest-get-type "^24.9.0"
|
||||
pretty-format "^24.9.0"
|
||||
|
||||
jest-diff@^26.0.0, jest-diff@^26.6.2:
|
||||
jest-diff@^26.6.2:
|
||||
version "26.6.2"
|
||||
resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-26.6.2.tgz#1aa7468b52c3a68d7d5c5fdcdfcd5e49bd164394"
|
||||
integrity sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==
|
||||
|
@ -7614,7 +7621,7 @@ jest-matcher-utils@^26.6.2:
|
|||
jest-get-type "^26.3.0"
|
||||
pretty-format "^26.6.2"
|
||||
|
||||
jest-matcher-utils@^27.5.1:
|
||||
jest-matcher-utils@^27.0.0, jest-matcher-utils@^27.5.1:
|
||||
version "27.5.1"
|
||||
resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz#9c0cdbda8245bc22d2331729d1091308b40cf8ab"
|
||||
integrity sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==
|
||||
|
@ -10413,7 +10420,7 @@ pretty-format@^24.9.0:
|
|||
ansi-styles "^3.2.0"
|
||||
react-is "^16.8.4"
|
||||
|
||||
pretty-format@^26.0.0, pretty-format@^26.6.2:
|
||||
pretty-format@^26.6.2:
|
||||
version "26.6.2"
|
||||
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93"
|
||||
integrity sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==
|
||||
|
@ -10423,7 +10430,7 @@ pretty-format@^26.0.0, pretty-format@^26.6.2:
|
|||
ansi-styles "^4.0.0"
|
||||
react-is "^17.0.1"
|
||||
|
||||
pretty-format@^27.5.1:
|
||||
pretty-format@^27.0.0, pretty-format@^27.5.1:
|
||||
version "27.5.1"
|
||||
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e"
|
||||
integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==
|
||||
|
|
|
@ -75,7 +75,8 @@ describe("/api/global/auth", () => {
|
|||
afterEach(() => {
|
||||
expect(strategyFactory).toBeCalledWith(
|
||||
chosenConfig,
|
||||
`http://localhost:10000/api/global/auth/${TENANT_ID}/oidc/callback`
|
||||
`http://localhost:10000/api/global/auth/${TENANT_ID}/oidc/callback`,
|
||||
expect.any(Function)
|
||||
)
|
||||
})
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue