Deprovisioning WIP

This commit is contained in:
Rory Powell 2021-09-28 09:48:00 +01:00
parent 968bd9893b
commit 8642868c5e
9 changed files with 91 additions and 13 deletions

View File

@ -73,6 +73,54 @@ exports.tryAddTenant = async (tenantId, userId, email) => {
await Promise.all(promises) await Promise.all(promises)
} }
const DocumentTypes = {
USER: "us",
}
const UNICODE_MAX = "\ufff0"
/**
* Gets parameters for retrieving users.
* Duplicate of "../db/utils" due to circular dependency
*/
const getGlobalUserParams = (globalId, otherProps = {}) => {
if (!globalId) {
globalId = ""
}
return {
...otherProps,
startkey: `${DocumentTypes.USER}${SEPARATOR}${globalId}`,
endkey: `${DocumentTypes.USER}${SEPARATOR}${globalId}${UNICODE_MAX}`,
}
}
exports.deleteTenant = async tenantId => {
const globalDb = exports.getGlobalDB()
let promises = []
// remove the tenant entry from global info
const infoDb = getDB(PLATFORM_INFO_DB)
let tenants = await infoDb.get(TENANT_DOC)
tenants.tenantIds = tenants.tenantIds.filter(id => id !== tenantId)
promises.push(infoDb.put(tenants))
// remove the users
const allUsers = await globalDb.allDocs(
getGlobalUserParams(null, {
include_docs: true,
})
)
allUsers.rows.map(row => {
promises.push(infoDb.remove(row.id, row.value.rev))
promises.push(infoDb.remove(row.doc.email, row.value.rev))
})
// remove the global db
promises.push(globalDb.destroy())
await Promise.all(promises)
// TODO: Delete all apps
}
exports.getGlobalDB = (tenantId = null) => { exports.getGlobalDB = (tenantId = null) => {
// tenant ID can be set externally, for example user API where // tenant ID can be set externally, for example user API where
// new tenants are being created, this may be the case // new tenants are being created, this may be the case

View File

@ -546,7 +546,7 @@ module External {
}, },
meta: { meta: {
table, table,
} },
} }
// can't really use response right now // can't really use response right now
const response = await makeExternalQuery(appId, json) const response = await makeExternalQuery(appId, json)

View File

@ -1,4 +1,4 @@
import {Table} from "./common"; import { Table } from "./common"
export enum Operation { export enum Operation {
CREATE = "CREATE", CREATE = "CREATE",
@ -139,7 +139,7 @@ export interface QueryJson {
paginate?: PaginationJson paginate?: PaginationJson
body?: object body?: object
meta?: { meta?: {
table?: Table, table?: Table
} }
extra?: { extra?: {
idFilter?: SearchFilters idFilter?: SearchFilters

View File

@ -148,7 +148,7 @@ function buildRead(knex: Knex, json: QueryJson, limit: number): KnexQuery {
if (!resource) { if (!resource) {
resource = { fields: [] } resource = { fields: [] }
} }
let selectStatement: string|string[] = "*" let selectStatement: string | string[] = "*"
// handle select // handle select
if (resource.fields && resource.fields.length > 0) { if (resource.fields && resource.fields.length > 0) {
// select the resources as the format "table.columnName" - this is what is provided // select the resources as the format "table.columnName" - this is what is provided

View File

@ -12,7 +12,11 @@ import { getSqlQuery } from "./utils"
module MySQLModule { module MySQLModule {
const mysql = require("mysql") const mysql = require("mysql")
const Sql = require("./base/sql") const Sql = require("./base/sql")
const { buildExternalTableId, convertType, copyExistingPropsOver } = require("./utils") const {
buildExternalTableId,
convertType,
copyExistingPropsOver,
} = require("./utils")
const { FieldTypes } = require("../constants") const { FieldTypes } = require("../constants")
interface MySQLConfig { interface MySQLConfig {
@ -104,7 +108,7 @@ module MySQLModule {
client: any, client: any,
query: SqlQuery, query: SqlQuery,
connect: boolean = true connect: boolean = true
): Promise<any[]|any> { ): Promise<any[] | any> {
// Node MySQL is callback based, so we must wrap our call in a promise // Node MySQL is callback based, so we must wrap our call in a promise
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (connect) { if (connect) {
@ -248,9 +252,9 @@ module MySQLModule {
json.extra = { json.extra = {
idFilter: { idFilter: {
equal: { equal: {
[primaryKey]: results.insertId [primaryKey]: results.insertId,
}, },
} },
} }
return json return json
} }

View File

@ -12,7 +12,11 @@ module PostgresModule {
const { Pool } = require("pg") const { Pool } = require("pg")
const Sql = require("./base/sql") const Sql = require("./base/sql")
const { FieldTypes } = require("../constants") const { FieldTypes } = require("../constants")
const { buildExternalTableId, convertType, copyExistingPropsOver } = require("./utils") const {
buildExternalTableId,
convertType,
copyExistingPropsOver,
} = require("./utils")
const { escapeDangerousCharacters } = require("../utilities") const { escapeDangerousCharacters } = require("../utilities")
const JSON_REGEX = /'{.*}'::json/s const JSON_REGEX = /'{.*}'::json/s
@ -193,10 +197,16 @@ module PostgresModule {
} }
const type: string = convertType(column.data_type, TYPE_MAP) const type: string = convertType(column.data_type, TYPE_MAP)
const identity = !!(column.identity_generation || column.identity_start || column.identity_increment) const identity = !!(
const hasDefault = typeof column.column_default === "string" && column.identity_generation ||
column.identity_start ||
column.identity_increment
)
const hasDefault =
typeof column.column_default === "string" &&
column.column_default.startsWith("nextval") column.column_default.startsWith("nextval")
const isGenerated = column.is_generated && column.is_generated !== "NEVER" const isGenerated =
column.is_generated && column.is_generated !== "NEVER"
const isAuto: boolean = hasDefault || identity || isGenerated const isAuto: boolean = hasDefault || identity || isGenerated
tables[tableName].schema[columnName] = { tables[tableName].schema[columnName] = {
autocolumn: isAuto, autocolumn: isAuto,

View File

@ -84,7 +84,11 @@ export function isIsoDateString(str: string) {
} }
// add the existing relationships from the entities if they exist, to prevent them from being overridden // add the existing relationships from the entities if they exist, to prevent them from being overridden
export function copyExistingPropsOver(tableName: string, tables: { [key: string]: any }, entities: { [key: string]: any }) { export function copyExistingPropsOver(
tableName: string,
tables: { [key: string]: any },
entities: { [key: string]: any }
) {
if (entities && entities[tableName]) { if (entities && entities[tableName]) {
if (entities[tableName].primaryDisplay) { if (entities[tableName].primaryDisplay) {
tables[tableName].primaryDisplay = entities[tableName].primaryDisplay tables[tableName].primaryDisplay = entities[tableName].primaryDisplay

View File

@ -1,5 +1,6 @@
const CouchDB = require("../../../db") const CouchDB = require("../../../db")
const { StaticDatabases } = require("@budibase/auth/db") const { StaticDatabases } = require("@budibase/auth/db")
const { deleteTenant, getTenantId } = require("@budibase/auth/tenancy")
exports.exists = async ctx => { exports.exists = async ctx => {
const tenantId = ctx.request.params const tenantId = ctx.request.params
@ -31,3 +32,13 @@ exports.fetch = async ctx => {
} }
ctx.body = tenants ctx.body = tenants
} }
exports.delete = async ctx => {
const tenantId = getTenantId()
if (ctx.params.tenantId !== tenantId) {
ctx.throw(403, "Unauthorized")
}
await deleteTenant(tenantId)
}

View File

@ -7,5 +7,6 @@ const router = Router()
router router
.get("/api/system/tenants/:tenantId/exists", controller.exists) .get("/api/system/tenants/:tenantId/exists", controller.exists)
.get("/api/system/tenants", adminOnly, controller.fetch) .get("/api/system/tenants", adminOnly, controller.fetch)
.delete("/api/system/tenants/:tenantId", adminOnly, controller.delete)
module.exports = router module.exports = router