Merge pull request #3145 from Budibase/prevent-dev-app-access
Prevent non builder from accessing dev apps
This commit is contained in:
commit
54ddfb32e0
|
@ -4,39 +4,27 @@
|
|||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Launch Program",
|
||||
"program": "${workspaceFolder}/app.js",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Debug External",
|
||||
"program": "${workspaceFolder}/packages/cli/bin/budi",
|
||||
"args": [],
|
||||
"cwd":"C:/code/my-apps",
|
||||
"console": "externalTerminal"
|
||||
},
|
||||
{
|
||||
"name": "Budibase Server",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"runtimeArgs": ["--nolazy", "-r", "ts-node/register/transpile-only"],
|
||||
"args": ["${workspaceFolder}/packages/server/src/index.ts"],
|
||||
"runtimeArgs": [
|
||||
"--nolazy",
|
||||
"-r",
|
||||
"ts-node/register/transpile-only"
|
||||
],
|
||||
"args": [
|
||||
"${workspaceFolder}/packages/server/src/index.ts"
|
||||
],
|
||||
"cwd": "${workspaceFolder}/packages/server"
|
||||
},
|
||||
{
|
||||
},
|
||||
{
|
||||
"name": "Budibase Worker",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/packages/worker/src/index.js",
|
||||
"cwd": "${workspaceFolder}/packages/worker"
|
||||
}
|
||||
}
|
||||
],
|
||||
"compounds": [
|
||||
{
|
||||
|
|
|
@ -8,3 +8,5 @@ env._set("CLIENT_ID", "test-client-id")
|
|||
env._set("BUDIBASE_DIR", tmpdir("budibase-unittests"))
|
||||
env._set("LOG_LEVEL", "silent")
|
||||
env._set("PORT", 0)
|
||||
|
||||
global.console.log = jest.fn() // console.log are ignored in tests
|
||||
|
|
|
@ -13,8 +13,7 @@ describe("/authenticate", () => {
|
|||
|
||||
describe("fetch self", () => {
|
||||
it("should be able to fetch self", async () => {
|
||||
await config.createUser("test@test.com", "p4ssw0rd")
|
||||
const headers = await config.login("test@test.com", "p4ssw0rd", { userId: "us_uuid1" })
|
||||
const headers = await config.login()
|
||||
const res = await request
|
||||
.get(`/api/self`)
|
||||
.set(headers)
|
||||
|
|
|
@ -89,6 +89,9 @@ describe("/permission", () => {
|
|||
|
||||
describe("check public user allowed", () => {
|
||||
it("should be able to read the row", async () => {
|
||||
// replicate changes before checking permissions
|
||||
await config.deploy()
|
||||
|
||||
const res = await request
|
||||
.get(`/api/${table._id}/rows`)
|
||||
.set(config.publicHeaders())
|
||||
|
|
|
@ -94,9 +94,9 @@ describe("/queries", () => {
|
|||
const query = await config.createQuery()
|
||||
const res = await request
|
||||
.get(`/api/queries/${query._id}`)
|
||||
.set(await config.roleHeaders({}))
|
||||
.expect("Content-Type", /json/)
|
||||
.set(await config.defaultHeaders())
|
||||
.expect(200)
|
||||
.expect("Content-Type", /json/)
|
||||
expect(res.body.fields).toBeUndefined()
|
||||
expect(res.body.parameters).toBeUndefined()
|
||||
expect(res.body.schema).toBeUndefined()
|
||||
|
|
|
@ -21,16 +21,31 @@ describe("/routing", () => {
|
|||
screen2.routing.roleId = BUILTIN_ROLE_IDS.POWER
|
||||
screen2.routing.route = route
|
||||
screen2 = await config.createScreen(screen2)
|
||||
await config.deploy()
|
||||
})
|
||||
|
||||
describe("fetch", () => {
|
||||
it("prevents a public user from accessing development app", async () => {
|
||||
await request
|
||||
.get(`/api/routing/client`)
|
||||
.set(config.publicHeaders({ prodApp: false }))
|
||||
.expect(302)
|
||||
})
|
||||
|
||||
it("prevents a non builder from accessing development app", async () => {
|
||||
await request
|
||||
.get(`/api/routing/client`)
|
||||
.set(await config.roleHeaders({
|
||||
roleId: BUILTIN_ROLE_IDS.BASIC,
|
||||
prodApp: false
|
||||
}))
|
||||
.expect(302)
|
||||
})
|
||||
it("returns the correct routing for basic user", async () => {
|
||||
const res = await request
|
||||
.get(`/api/routing/client`)
|
||||
.set(await config.roleHeaders({
|
||||
email: "basic@test.com",
|
||||
roleId: BUILTIN_ROLE_IDS.BASIC,
|
||||
builder: false
|
||||
roleId: BUILTIN_ROLE_IDS.BASIC
|
||||
}))
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
|
@ -49,9 +64,7 @@ describe("/routing", () => {
|
|||
const res = await request
|
||||
.get(`/api/routing/client`)
|
||||
.set(await config.roleHeaders({
|
||||
email: "basic@test.com",
|
||||
roleId: BUILTIN_ROLE_IDS.POWER,
|
||||
builder: false,
|
||||
roleId: BUILTIN_ROLE_IDS.POWER
|
||||
}))
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
|
@ -71,9 +84,12 @@ describe("/routing", () => {
|
|||
it("should fetch all routes for builder", async () => {
|
||||
const res = await request
|
||||
.get(`/api/routing`)
|
||||
.set(config.defaultHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.set(await config.roleHeaders({
|
||||
roleId: BUILTIN_ROLE_IDS.POWER,
|
||||
builder: true,
|
||||
}))
|
||||
.expect(200)
|
||||
.expect("Content-Type", /json/)
|
||||
expect(res.body.routes).toBeDefined()
|
||||
expect(res.body.routes[route].subpaths[route]).toBeDefined()
|
||||
const subpath = res.body.routes[route].subpaths[route]
|
||||
|
|
|
@ -38,7 +38,7 @@ describe("/users", () => {
|
|||
})
|
||||
|
||||
it("should apply authorization to endpoint", async () => {
|
||||
await config.createUser("brenda@brenda.com", "brendas_password")
|
||||
await config.createUser()
|
||||
await checkPermissionsEndpoint({
|
||||
config,
|
||||
request,
|
||||
|
|
|
@ -50,9 +50,10 @@ exports.createRequest = (request, method, url, body) => {
|
|||
}
|
||||
|
||||
exports.checkBuilderEndpoint = async ({ config, method, url, body }) => {
|
||||
const headers = await config.login("test@test.com", "test", {
|
||||
const headers = await config.login({
|
||||
userId: "us_fail",
|
||||
builder: false,
|
||||
prodApp: true,
|
||||
})
|
||||
await exports
|
||||
.createRequest(config.request, method, url, body)
|
||||
|
@ -68,11 +69,9 @@ exports.checkPermissionsEndpoint = async ({
|
|||
passRole,
|
||||
failRole,
|
||||
}) => {
|
||||
const password = "PASSWORD"
|
||||
let user = await config.createUser("pass@budibase.com", password, passRole)
|
||||
const passHeader = await config.login("pass@budibase.com", password, {
|
||||
const passHeader = await config.login({
|
||||
roleId: passRole,
|
||||
userId: user.globalId,
|
||||
prodApp: true,
|
||||
})
|
||||
|
||||
await exports
|
||||
|
@ -82,13 +81,12 @@ exports.checkPermissionsEndpoint = async ({
|
|||
|
||||
let failHeader
|
||||
if (failRole === BUILTIN_ROLE_IDS.PUBLIC) {
|
||||
failHeader = config.publicHeaders()
|
||||
failHeader = config.publicHeaders({ prodApp: true })
|
||||
} else {
|
||||
user = await config.createUser("fail@budibase.com", password, failRole)
|
||||
failHeader = await config.login("fail@budibase.com", password, {
|
||||
failHeader = await config.login({
|
||||
roleId: failRole,
|
||||
userId: user.globalId,
|
||||
builder: false,
|
||||
prodApp: true,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -112,8 +112,11 @@ describe("/webhooks", () => {
|
|||
|
||||
describe("trigger", () => {
|
||||
it("should allow triggering from public", async () => {
|
||||
// replicate changes before checking webhook
|
||||
await config.deploy()
|
||||
|
||||
const res = await request
|
||||
.post(`/api/webhooks/trigger/${config.getAppId()}/${webhook._id}`)
|
||||
.post(`/api/webhooks/trigger/${config.prodAppId}/${webhook._id}`)
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
expect(res.body.message).toBeDefined()
|
||||
|
|
|
@ -3,7 +3,7 @@ const { getAppId, setCookie, getCookie, clearCookie } =
|
|||
const { Cookies } = require("@budibase/auth").constants
|
||||
const { getRole } = require("@budibase/auth/roles")
|
||||
const { BUILTIN_ROLE_IDS } = require("@budibase/auth/roles")
|
||||
const { generateUserMetadataID } = require("../db/utils")
|
||||
const { generateUserMetadataID, isDevAppID } = require("../db/utils")
|
||||
const { dbExists } = require("@budibase/auth/db")
|
||||
const { isUserInAppTenant } = require("@budibase/auth/tenancy")
|
||||
const { getCachedSelf } = require("../utilities/global")
|
||||
|
@ -35,6 +35,15 @@ module.exports = async (ctx, next) => {
|
|||
requestAppId = requestAppId || appId
|
||||
}
|
||||
|
||||
// deny access to application preview
|
||||
if (
|
||||
isDevAppID(requestAppId) &&
|
||||
(!ctx.user || !ctx.user.builder || !ctx.user.builder.global)
|
||||
) {
|
||||
clearCookie(ctx, Cookies.CurrentApp)
|
||||
return ctx.redirect("/")
|
||||
}
|
||||
|
||||
let appId,
|
||||
roleId = BUILTIN_ROLE_IDS.PUBLIC
|
||||
if (!ctx.user) {
|
||||
|
|
|
@ -26,7 +26,6 @@ auth.init(CouchDB)
|
|||
|
||||
const GLOBAL_USER_ID = "us_uuid1"
|
||||
const EMAIL = "babs@babs.com"
|
||||
const PASSWORD = "babs_password"
|
||||
|
||||
class TestConfiguration {
|
||||
constructor(openServer = true) {
|
||||
|
@ -67,13 +66,18 @@ class TestConfiguration {
|
|||
return request.body
|
||||
}
|
||||
|
||||
async globalUser(id = GLOBAL_USER_ID, builder = true, roles) {
|
||||
async globalUser({
|
||||
id = GLOBAL_USER_ID,
|
||||
builder = true,
|
||||
email = EMAIL,
|
||||
roles,
|
||||
} = {}) {
|
||||
const db = getGlobalDB(TENANT_ID)
|
||||
let existing
|
||||
try {
|
||||
existing = await db.get(id)
|
||||
} catch (err) {
|
||||
existing = {}
|
||||
existing = { email }
|
||||
}
|
||||
const user = {
|
||||
_id: id,
|
||||
|
@ -84,6 +88,8 @@ class TestConfiguration {
|
|||
await createASession(id, { sessionId: "sessionid", tenantId: TENANT_ID })
|
||||
if (builder) {
|
||||
user.builder = { global: true }
|
||||
} else {
|
||||
user.builder = { global: false }
|
||||
}
|
||||
const resp = await db.put(user)
|
||||
return {
|
||||
|
@ -132,12 +138,14 @@ class TestConfiguration {
|
|||
return headers
|
||||
}
|
||||
|
||||
publicHeaders() {
|
||||
publicHeaders({ prodApp = true } = {}) {
|
||||
const appId = prodApp ? this.prodAppId : this.appId
|
||||
|
||||
const headers = {
|
||||
Accept: "application/json",
|
||||
}
|
||||
if (this.appId) {
|
||||
headers[Headers.APP_ID] = this.appId
|
||||
if (appId) {
|
||||
headers[Headers.APP_ID] = appId
|
||||
}
|
||||
return headers
|
||||
}
|
||||
|
@ -146,17 +154,37 @@ class TestConfiguration {
|
|||
email = EMAIL,
|
||||
roleId = BUILTIN_ROLE_IDS.ADMIN,
|
||||
builder = false,
|
||||
}) {
|
||||
return this.login(email, PASSWORD, { roleId, builder })
|
||||
prodApp = true,
|
||||
} = {}) {
|
||||
return this.login({ email, roleId, builder, prodApp })
|
||||
}
|
||||
|
||||
async createApp(appName) {
|
||||
// create dev app
|
||||
this.app = await this._req({ name: appName }, null, controllers.app.create)
|
||||
this.appId = this.app.appId
|
||||
|
||||
// create production app
|
||||
this.prodApp = await this.deploy()
|
||||
this.prodAppId = this.prodApp.appId
|
||||
|
||||
this.allApps.push(this.prodApp)
|
||||
this.allApps.push(this.app)
|
||||
|
||||
return this.app
|
||||
}
|
||||
|
||||
async deploy() {
|
||||
const deployment = await this._req(null, null, controllers.deploy.deployApp)
|
||||
const prodAppId = deployment.appId.replace("_dev", "")
|
||||
const appPackage = await this._req(
|
||||
null,
|
||||
{ appId: prodAppId },
|
||||
controllers.app.fetchAppPackage
|
||||
)
|
||||
return appPackage.application
|
||||
}
|
||||
|
||||
async updateTable(config = null) {
|
||||
config = config || basicTable()
|
||||
this.table = await this._req(config, null, controllers.table.save)
|
||||
|
@ -313,9 +341,9 @@ class TestConfiguration {
|
|||
return await this._req(config, null, controllers.layout.save)
|
||||
}
|
||||
|
||||
async createUser(id = null) {
|
||||
async createUser(id = null, email = EMAIL) {
|
||||
const globalId = !id ? `us_${Math.random()}` : `us_${id}`
|
||||
const resp = await this.globalUser(globalId)
|
||||
const resp = await this.globalUser({ id: globalId, email })
|
||||
await userCache.invalidateUser(globalId)
|
||||
return {
|
||||
...resp,
|
||||
|
@ -323,21 +351,21 @@ class TestConfiguration {
|
|||
}
|
||||
}
|
||||
|
||||
async login(email, password, { roleId, userId, builder } = {}) {
|
||||
async login({ roleId, userId, builder, prodApp = false } = {}) {
|
||||
const appId = prodApp ? this.prodAppId : this.appId
|
||||
|
||||
userId = !userId ? `us_uuid1` : userId
|
||||
if (!this.request) {
|
||||
throw "Server has not been opened, cannot login."
|
||||
}
|
||||
// make sure the user exists in the global DB
|
||||
if (roleId !== BUILTIN_ROLE_IDS.PUBLIC) {
|
||||
const appId = `app${this.getAppId().split("app_dev")[1]}`
|
||||
await this.globalUser(userId, builder, {
|
||||
[appId]: roleId,
|
||||
await this.globalUser({
|
||||
userId,
|
||||
builder,
|
||||
roles: { [this.prodAppId]: roleId },
|
||||
})
|
||||
}
|
||||
if (!email || !password) {
|
||||
await this.createUser()
|
||||
}
|
||||
await createASession(userId, {
|
||||
sessionId: "sessionid",
|
||||
tenantId: TENANT_ID,
|
||||
|
@ -350,7 +378,7 @@ class TestConfiguration {
|
|||
}
|
||||
const app = {
|
||||
roleId: roleId,
|
||||
appId: this.appId,
|
||||
appId,
|
||||
}
|
||||
const authToken = jwt.sign(auth, env.JWT_SECRET)
|
||||
const appToken = jwt.sign(app, env.JWT_SECRET)
|
||||
|
@ -363,7 +391,7 @@ class TestConfiguration {
|
|||
`${Cookies.Auth}=${authToken}`,
|
||||
`${Cookies.CurrentApp}=${appToken}`,
|
||||
],
|
||||
[Headers.APP_ID]: this.appId,
|
||||
[Headers.APP_ID]: appId,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,4 +12,5 @@ module.exports = {
|
|||
screen: require("../../api/controllers/screen"),
|
||||
webhook: require("../../api/controllers/webhook"),
|
||||
layout: require("../../api/controllers/layout"),
|
||||
deploy: require("../../api/controllers/deploy"),
|
||||
}
|
||||
|
|
|
@ -82,6 +82,13 @@ class InMemoryQueue {
|
|||
// TODO: implement for testing
|
||||
console.log(cronJobId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Implemented for tests
|
||||
*/
|
||||
getRepeatableJobs() {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = InMemoryQueue
|
||||
|
|
|
@ -24,7 +24,7 @@ describe("/api/global/auth", () => {
|
|||
// initially configure settings
|
||||
await config.saveSmtpConfig()
|
||||
await config.saveSettingsConfig()
|
||||
await config.createUser("test@test.com")
|
||||
await config.createUser()
|
||||
const res = await request
|
||||
.post(`/api/global/auth/${TENANT_ID}/reset`)
|
||||
.send({
|
||||
|
|
Loading…
Reference in New Issue