mirror of
https://github.com/nocodb/nocodb.git
synced 2026-04-25 00:25:27 +00:00
feat: allow bypass on v2 bases only
Signed-off-by: mertmit <mertmit99@gmail.com>
This commit is contained in:
@@ -5,7 +5,13 @@ import { v7 as uuidv7 } from 'uuid';
|
||||
import type { Condition, Knex } from '~/db/CustomKnex';
|
||||
import XcMigrationSourcev3 from '~/meta/migrations/XcMigrationSourcev3';
|
||||
import { NcConfig } from '~/utils/nc-config';
|
||||
import { MetaTable, RootScopes, RootScopeTables } from '~/utils/globals';
|
||||
import {
|
||||
BaseRelatedMetaTables,
|
||||
BaseVersion,
|
||||
MetaTable,
|
||||
RootScopes,
|
||||
RootScopeTables,
|
||||
} from '~/utils/globals';
|
||||
import { NcError } from '~/helpers/catchError';
|
||||
import { isWorker } from '~/utils';
|
||||
|
||||
@@ -485,7 +491,15 @@ export class MetaService extends MetaServiceCE {
|
||||
}
|
||||
|
||||
if (workspace_id === RootScopes.BYPASS && base_id === RootScopes.BYPASS) {
|
||||
// bypass
|
||||
// bypass is only allowed for v2 bases, so lets join the base table to ensure the base is v2
|
||||
if (BaseRelatedMetaTables.includes(target as MetaTable)) {
|
||||
query.whereExists(function () {
|
||||
this.select(1)
|
||||
.from(`${MetaTable.PROJECT} as p`)
|
||||
.whereRaw('p.id = base_id')
|
||||
.andWhere('p.version', BaseVersion.V2);
|
||||
});
|
||||
}
|
||||
} else if (workspace_id === base_id) {
|
||||
if (!Object.values(RootScopes).includes(workspace_id as RootScopes)) {
|
||||
NcError.metaError({
|
||||
|
||||
@@ -84,6 +84,7 @@ const up = async (knex: Knex) => {
|
||||
[MetaTable.SYNC_SOURCE]: ['base_id', 'id'],
|
||||
[MetaTable.VIEWS]: ['base_id', 'id'],
|
||||
[MetaTable.WIDGETS]: ['base_id', 'id'],
|
||||
[MetaTable.MODEL_STAT]: ['fk_workspace_id', 'base_id', 'fk_model_id'],
|
||||
};
|
||||
|
||||
const customPkTitles = {
|
||||
@@ -255,6 +256,7 @@ const down = async (knex: Knex) => {
|
||||
[MetaTable.SYNC_SOURCE]: ['id'],
|
||||
[MetaTable.VIEWS]: ['id'],
|
||||
[MetaTable.WIDGETS]: ['id'],
|
||||
[MetaTable.MODEL_STAT]: ['fk_workspace_id', 'fk_model_id'],
|
||||
};
|
||||
|
||||
const customPkTitles = {
|
||||
|
||||
@@ -116,6 +116,355 @@ const getApiVersionFromUrl = (url: string) => {
|
||||
export class ExtractIdsMiddleware implements NestMiddleware, CanActivate {
|
||||
async use(req, res, next): Promise<any> {
|
||||
const { params } = req;
|
||||
|
||||
req.ncApiVersion = getApiVersionFromUrl(req.route.path);
|
||||
req.ncSocketId = req.headers['xc-socket-id'];
|
||||
|
||||
const bypassContext = {
|
||||
workspace_id: RootScopes.BYPASS,
|
||||
base_id: RootScopes.BYPASS,
|
||||
api_version: req.ncApiVersion,
|
||||
socket_id: req.ncSocketId,
|
||||
};
|
||||
|
||||
// this is a special route for ws operations we pass 'nc' as base id
|
||||
const isInternalApi = !!req.path?.startsWith('/api/v2/internal');
|
||||
const isInternalWorkspaceScope = isInternalApi && params.baseId === 'nc';
|
||||
|
||||
const baseId = params.baseId || params.baseName;
|
||||
|
||||
if (!isInternalWorkspaceScope && baseId) {
|
||||
const base = await Base.get(bypassContext, baseId);
|
||||
if (!base) {
|
||||
NcError.get(bypassContext).baseNotFound(baseId);
|
||||
}
|
||||
|
||||
const context = {
|
||||
workspace_id: base.fk_workspace_id,
|
||||
base_id: base.id,
|
||||
api_version: req.ncApiVersion,
|
||||
socket_id: req.ncSocketId,
|
||||
};
|
||||
|
||||
req.ncBaseId = base.id;
|
||||
req.ncWorkspaceId = base.fk_workspace_id;
|
||||
|
||||
const workspace = await Workspace.get(req.ncWorkspaceId);
|
||||
if (!workspace) {
|
||||
NcError.workspaceNotFound(req.ncWorkspaceId);
|
||||
}
|
||||
|
||||
req.ncOrgId = workspace.fk_org_id;
|
||||
|
||||
req.context = {
|
||||
org_id: req.ncOrgId,
|
||||
workspace_id: req.ncWorkspaceId,
|
||||
base_id: req.ncBaseId,
|
||||
api_version: req.ncApiVersion,
|
||||
socket_id: req.ncSocketId,
|
||||
nc_site_url: req.ncSiteUrl,
|
||||
};
|
||||
|
||||
if (params.mcpTokenId) {
|
||||
const mcpToken = await MCPToken.get(context, params.mcpTokenId);
|
||||
if (!mcpToken) {
|
||||
NcError.get(context).genericNotFound('MCPToken', params.mcpTokenId);
|
||||
}
|
||||
} else if (params.integrationId) {
|
||||
const integration = await Integration.get(
|
||||
context,
|
||||
params.integrationId,
|
||||
);
|
||||
if (!integration) {
|
||||
NcError.get(context).integrationNotFound(params.integrationId);
|
||||
}
|
||||
} else if (params.tableId || params.modelId || params.tableName) {
|
||||
const model = await Model.get(
|
||||
context,
|
||||
params.tableId || params.modelId || params.tableName,
|
||||
);
|
||||
|
||||
if (!model) {
|
||||
NcError.get(context).tableNotFound(
|
||||
params.tableId || params.modelId || params.tableName,
|
||||
);
|
||||
}
|
||||
|
||||
req.ncSourceId = model.source_id;
|
||||
} else if (params.viewId || params.viewName) {
|
||||
const view =
|
||||
(await View.get(context, params.viewId || params.viewName)) ||
|
||||
(await Model.get(context, params.viewId || params.viewName));
|
||||
|
||||
if (!view) {
|
||||
NcError.get(context).viewNotFound(params.viewId || params.viewName);
|
||||
}
|
||||
|
||||
req.ncSourceId = view.source_id;
|
||||
} else if (
|
||||
params.formViewId ||
|
||||
params.gridViewId ||
|
||||
params.kanbanViewId ||
|
||||
params.galleryViewId ||
|
||||
params.calendarViewId
|
||||
) {
|
||||
const view = await View.get(
|
||||
context,
|
||||
params.formViewId ||
|
||||
params.gridViewId ||
|
||||
params.kanbanViewId ||
|
||||
params.galleryViewId ||
|
||||
params.calendarViewId,
|
||||
);
|
||||
|
||||
if (!view) {
|
||||
NcError.get(context).viewNotFound(
|
||||
params.formViewId ||
|
||||
params.gridViewId ||
|
||||
params.kanbanViewId ||
|
||||
params.galleryViewId ||
|
||||
params.calendarViewId,
|
||||
);
|
||||
}
|
||||
|
||||
req.ncSourceId = view.source_id;
|
||||
} else if (params.publicDataUuid) {
|
||||
const view = await View.getByUUID(context, req.params.publicDataUuid);
|
||||
|
||||
if (!view) {
|
||||
NcError.get(context).viewNotFound(req.params.publicDataUuid);
|
||||
}
|
||||
|
||||
req.ncSourceId = view?.source_id;
|
||||
} else if (params.sharedViewUuid) {
|
||||
const view = await View.getByUUID(context, req.params.sharedViewUuid);
|
||||
|
||||
if (!view) {
|
||||
NcError.get(context).viewNotFound(req.params.sharedViewUuid);
|
||||
}
|
||||
|
||||
req.ncSourceId = view.source_id;
|
||||
} else if (params.sharedBaseUuid) {
|
||||
const base = await Base.getByUuid(context, req.params.sharedBaseUuid);
|
||||
|
||||
if (!base) {
|
||||
NcError.get(context).baseNotFound(req.params.sharedBaseUuid);
|
||||
}
|
||||
} else if (params.sharedDashboardUuid) {
|
||||
const dashboard = await Dashboard.getByUUID(
|
||||
context,
|
||||
req.params.sharedDashboardUuid,
|
||||
);
|
||||
|
||||
if (!dashboard) {
|
||||
NcError.dashboardNotFound(req.params.sharedDashboardUuid);
|
||||
}
|
||||
} else if (params.hookId) {
|
||||
const hook = await Hook.get(context, params.hookId);
|
||||
|
||||
if (!hook) {
|
||||
NcError.get(context).genericNotFound('Webhook', params.hookId);
|
||||
}
|
||||
|
||||
req.ncSourceId = hook.source_id;
|
||||
} else if (params.rowColorConditionId) {
|
||||
const rowColorCondition = await RowColorCondition.getById(
|
||||
context,
|
||||
params.rowColorConditionId,
|
||||
);
|
||||
|
||||
if (!rowColorCondition) {
|
||||
NcError.get(context).genericNotFound(
|
||||
'Row color condition',
|
||||
params.rowColorConditionId,
|
||||
);
|
||||
}
|
||||
|
||||
const view = await View.get(context, rowColorCondition.fk_view_id);
|
||||
|
||||
if (!view) {
|
||||
NcError.get(context).viewNotFound(rowColorCondition.fk_view_id);
|
||||
}
|
||||
|
||||
req.ncSourceId = view.source_id;
|
||||
} else if (params.gridViewColumnId) {
|
||||
const gridViewColumn = await GridViewColumn.get(
|
||||
context,
|
||||
params.gridViewColumnId,
|
||||
);
|
||||
|
||||
if (!gridViewColumn) {
|
||||
NcError.get(context).fieldNotFound(params.gridViewColumnId);
|
||||
}
|
||||
|
||||
req.ncSourceId = gridViewColumn?.source_id;
|
||||
} else if (params.formViewColumnId) {
|
||||
const formViewColumn = await FormViewColumn.get(
|
||||
context,
|
||||
params.formViewColumnId,
|
||||
);
|
||||
|
||||
if (!formViewColumn) {
|
||||
NcError.get(context).fieldNotFound(params.formViewColumnId);
|
||||
}
|
||||
|
||||
req.ncSourceId = formViewColumn.source_id;
|
||||
} else if (params.galleryViewColumnId) {
|
||||
const galleryViewColumn = await GalleryViewColumn.get(
|
||||
context,
|
||||
params.galleryViewColumnId,
|
||||
);
|
||||
|
||||
if (!galleryViewColumn) {
|
||||
NcError.get(context).fieldNotFound(params.galleryViewColumnId);
|
||||
}
|
||||
|
||||
req.ncSourceId = galleryViewColumn.source_id;
|
||||
} else if (params.columnId) {
|
||||
const column = await Column.get(context, { colId: params.columnId });
|
||||
|
||||
if (!column) {
|
||||
NcError.get(context).fieldNotFound(params.columnId);
|
||||
}
|
||||
|
||||
req.ncSourceId = column.source_id;
|
||||
} else if (params.filterId) {
|
||||
const filter = await Filter.get(context, params.filterId);
|
||||
|
||||
if (!filter) {
|
||||
NcError.genericNotFound('Filter', params.filterId);
|
||||
}
|
||||
|
||||
req.ncSourceId = filter.source_id;
|
||||
} else if (params.filterParentId) {
|
||||
const filter = await Filter.get(context, params.filterParentId);
|
||||
|
||||
if (!filter) {
|
||||
NcError.genericNotFound('Filter', params.filterParentId);
|
||||
}
|
||||
|
||||
req.ncSourceId = filter.source_id;
|
||||
} else if (params.widgetId) {
|
||||
const widget = await Widget.get(context, params.widgetId);
|
||||
|
||||
if (!widget) {
|
||||
NcError.genericNotFound('Widget', params.widgetId);
|
||||
}
|
||||
} else if (params.sortId) {
|
||||
const sort = await Sort.get(context, params.sortId);
|
||||
|
||||
if (!sort) {
|
||||
NcError.genericNotFound('Sort', params.sortId);
|
||||
}
|
||||
|
||||
req.ncSourceId = sort.source_id;
|
||||
} else if (params.syncId) {
|
||||
const syncSource = await SyncSource.get(context, req.params.syncId);
|
||||
|
||||
if (!syncSource) {
|
||||
NcError.genericNotFound('Sync Source', req.params.syncId);
|
||||
}
|
||||
|
||||
req.ncSourceId = syncSource.source_id;
|
||||
} else if (params.extensionId) {
|
||||
const extension = await Extension.get(context, req.params.extensionId);
|
||||
|
||||
if (!extension) {
|
||||
NcError.genericNotFound('Extension', req.params.extensionId);
|
||||
}
|
||||
}
|
||||
/*
|
||||
TODO: migrate after comments api
|
||||
// extract fk_model_id from query params only if it's audit post or comments post, get, patch, delete endpoint
|
||||
else if (
|
||||
['/api/v1/db/meta/comments', '/api/v2/meta/comments'].some(
|
||||
(auditInsertOrUpdatePath) =>
|
||||
req.route.path === auditInsertOrUpdatePath,
|
||||
) &&
|
||||
req.method === 'POST' &&
|
||||
req.body?.fk_model_id
|
||||
) {
|
||||
const model = await Model.getByIdOrName(context, {
|
||||
id: req.body.fk_model_id,
|
||||
});
|
||||
|
||||
if (!model) {
|
||||
NcError.get(context).tableNotFound(req.body.fk_model_id);
|
||||
}
|
||||
|
||||
req.ncBaseId = model.base_id;
|
||||
req.ncSourceId = model.source_id;
|
||||
} else if (
|
||||
[
|
||||
'/api/v2/meta/comments/count',
|
||||
'/api/v1/db/meta/comments/count',
|
||||
'/api/v2/meta/comments',
|
||||
'/api/v1/db/meta/comments',
|
||||
].some((auditReadPath) => req.route.path === auditReadPath) &&
|
||||
req.method === 'GET' &&
|
||||
req.query.fk_model_id
|
||||
) {
|
||||
const model = await Model.getByIdOrName(context, {
|
||||
id: req.query?.fk_model_id,
|
||||
});
|
||||
|
||||
if (!model) {
|
||||
NcError.get(context).tableNotFound(req.query?.fk_model_id);
|
||||
}
|
||||
|
||||
req.ncBaseId = model.base_id;
|
||||
req.ncSourceId = model.source_id;
|
||||
} else if (
|
||||
[
|
||||
'/api/v1/db/meta/comment/:commentId',
|
||||
'/api/v2/meta/comment/:commentId',
|
||||
'/api/v1/db/meta/comment/:commentId/resolve',
|
||||
'/api/v2/meta/comment/:commentId/resolve',
|
||||
].some((auditPatchPath) => req.route.path === auditPatchPath) &&
|
||||
(req.method === 'PATCH' ||
|
||||
req.method === 'DELETE' ||
|
||||
req.method === 'POST') &&
|
||||
req.params.commentId
|
||||
) {
|
||||
const audit = await Comment.get(context, params.commentId);
|
||||
|
||||
if (!audit) {
|
||||
NcError.genericNotFound('Comment', params.commentId);
|
||||
}
|
||||
|
||||
req.ncBaseId = audit.base_id;
|
||||
req.ncSourceId = audit.source_id;
|
||||
}
|
||||
*/
|
||||
} else {
|
||||
await this.legacyExtractIds(req);
|
||||
}
|
||||
|
||||
await this.additionalValidation({ req, res, next });
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
await this.use(
|
||||
context.switchToHttp().getRequest(),
|
||||
context.switchToHttp().getResponse(),
|
||||
() => {},
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
// additional validation logic which can be overridden
|
||||
protected async additionalValidation(_param: {
|
||||
next: any;
|
||||
res: any;
|
||||
req: any;
|
||||
}) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
protected async legacyExtractIds(req) {
|
||||
const { params } = req;
|
||||
let view;
|
||||
|
||||
const context = {
|
||||
@@ -626,28 +975,6 @@ export class ExtractIdsMiddleware implements NestMiddleware, CanActivate {
|
||||
if (req.ncBaseId && !isInternalWorkspaceScope) {
|
||||
req.permissions = await Permission.list(req.context, req.ncBaseId);
|
||||
}
|
||||
|
||||
await this.additionalValidation({ req, res, next });
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
await this.use(
|
||||
context.switchToHttp().getRequest(),
|
||||
context.switchToHttp().getResponse(),
|
||||
() => {},
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
// additional validation logic which can be overridden
|
||||
protected async additionalValidation(_param: {
|
||||
next: any;
|
||||
res: any;
|
||||
req: any;
|
||||
}) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -93,6 +93,56 @@ export enum MetaTable {
|
||||
WIDGETS = 'nc_widgets_v2',
|
||||
}
|
||||
|
||||
export const BaseRelatedMetaTables = [
|
||||
MetaTable.CALENDAR_VIEW_COLUMNS,
|
||||
MetaTable.CALENDAR_VIEW_RANGE,
|
||||
MetaTable.CALENDAR_VIEW,
|
||||
MetaTable.COL_BARCODE,
|
||||
MetaTable.COL_BUTTON,
|
||||
MetaTable.COL_FORMULA,
|
||||
MetaTable.COL_LONG_TEXT,
|
||||
MetaTable.COL_LOOKUP,
|
||||
MetaTable.COL_QRCODE,
|
||||
MetaTable.COL_RELATIONS,
|
||||
MetaTable.COL_ROLLUP,
|
||||
MetaTable.COL_SELECT_OPTIONS,
|
||||
MetaTable.COLUMNS,
|
||||
MetaTable.COMMENTS_REACTIONS,
|
||||
MetaTable.COMMENTS,
|
||||
MetaTable.CUSTOM_URLS,
|
||||
MetaTable.DASHBOARDS,
|
||||
MetaTable.MODEL_ROLE_VISIBILITY,
|
||||
MetaTable.EXTENSIONS,
|
||||
MetaTable.FILTER_EXP,
|
||||
MetaTable.FORM_VIEW_COLUMNS,
|
||||
MetaTable.FORM_VIEW,
|
||||
MetaTable.GALLERY_VIEW_COLUMNS,
|
||||
MetaTable.GALLERY_VIEW,
|
||||
MetaTable.GRID_VIEW_COLUMNS,
|
||||
MetaTable.GRID_VIEW,
|
||||
MetaTable.HOOK_LOGS,
|
||||
MetaTable.HOOKS,
|
||||
MetaTable.KANBAN_VIEW_COLUMNS,
|
||||
MetaTable.KANBAN_VIEW,
|
||||
MetaTable.MAP_VIEW_COLUMNS,
|
||||
MetaTable.MAP_VIEW,
|
||||
MetaTable.MCP_TOKENS,
|
||||
MetaTable.MODELS,
|
||||
MetaTable.PERMISSIONS,
|
||||
MetaTable.PERMISSION_SUBJECTS,
|
||||
MetaTable.ROW_COLOR_CONDITIONS,
|
||||
MetaTable.SCRIPTS,
|
||||
MetaTable.SORT,
|
||||
MetaTable.SOURCES,
|
||||
MetaTable.SYNC_CONFIGS,
|
||||
MetaTable.SYNC_LOGS,
|
||||
MetaTable.SYNC_MAPPINGS,
|
||||
MetaTable.SYNC_SOURCE,
|
||||
MetaTable.VIEWS,
|
||||
MetaTable.WIDGETS,
|
||||
MetaTable.MODEL_STAT,
|
||||
];
|
||||
|
||||
export const orderedMetaTables = [
|
||||
MetaTable.MODEL_ROLE_VISIBILITY,
|
||||
MetaTable.PLUGIN,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Logger } from '@nestjs/common';
|
||||
import type { BaseType, BoolType, MetaType } from 'nocodb-sdk';
|
||||
import type { DB_TYPES } from '~/utils/globals';
|
||||
import type { BaseVersion, DB_TYPES } from '~/utils/globals';
|
||||
import type { NcContext } from '~/interface/config';
|
||||
import {
|
||||
BaseUser,
|
||||
@@ -45,6 +45,7 @@ export default class Base implements BaseType {
|
||||
public linked_db_projects?: Base[];
|
||||
public default_role?: 'no-access';
|
||||
public is_snapshot?: boolean;
|
||||
public version?: BaseVersion;
|
||||
|
||||
// shared base props
|
||||
uuid?: string;
|
||||
|
||||
@@ -73,8 +73,59 @@ export enum MetaTable {
|
||||
PERMISSION_SUBJECTS = 'nc_permission_subjects',
|
||||
DASHBOARDS = 'nc_dashboards_v2',
|
||||
WIDGETS = 'nc_widgets_v2',
|
||||
MODEL_STAT = 'nc_model_stat',
|
||||
}
|
||||
|
||||
export const BaseRelatedMetaTables = [
|
||||
MetaTable.CALENDAR_VIEW_COLUMNS,
|
||||
MetaTable.CALENDAR_VIEW_RANGE,
|
||||
MetaTable.CALENDAR_VIEW,
|
||||
MetaTable.COL_BARCODE,
|
||||
MetaTable.COL_BUTTON,
|
||||
MetaTable.COL_FORMULA,
|
||||
MetaTable.COL_LONG_TEXT,
|
||||
MetaTable.COL_LOOKUP,
|
||||
MetaTable.COL_QRCODE,
|
||||
MetaTable.COL_RELATIONS,
|
||||
MetaTable.COL_ROLLUP,
|
||||
MetaTable.COL_SELECT_OPTIONS,
|
||||
MetaTable.COLUMNS,
|
||||
MetaTable.COMMENTS_REACTIONS,
|
||||
MetaTable.COMMENTS,
|
||||
MetaTable.CUSTOM_URLS,
|
||||
MetaTable.DASHBOARDS,
|
||||
MetaTable.MODEL_ROLE_VISIBILITY,
|
||||
MetaTable.EXTENSIONS,
|
||||
MetaTable.FILTER_EXP,
|
||||
MetaTable.FORM_VIEW_COLUMNS,
|
||||
MetaTable.FORM_VIEW,
|
||||
MetaTable.GALLERY_VIEW_COLUMNS,
|
||||
MetaTable.GALLERY_VIEW,
|
||||
MetaTable.GRID_VIEW_COLUMNS,
|
||||
MetaTable.GRID_VIEW,
|
||||
MetaTable.HOOK_LOGS,
|
||||
MetaTable.HOOKS,
|
||||
MetaTable.KANBAN_VIEW_COLUMNS,
|
||||
MetaTable.KANBAN_VIEW,
|
||||
MetaTable.MAP_VIEW_COLUMNS,
|
||||
MetaTable.MAP_VIEW,
|
||||
MetaTable.MCP_TOKENS,
|
||||
MetaTable.MODELS,
|
||||
MetaTable.PERMISSIONS,
|
||||
MetaTable.PERMISSION_SUBJECTS,
|
||||
MetaTable.ROW_COLOR_CONDITIONS,
|
||||
MetaTable.SCRIPTS,
|
||||
MetaTable.SORT,
|
||||
MetaTable.SOURCES,
|
||||
MetaTable.SYNC_CONFIGS,
|
||||
MetaTable.SYNC_LOGS,
|
||||
MetaTable.SYNC_MAPPINGS,
|
||||
MetaTable.SYNC_SOURCE,
|
||||
MetaTable.VIEWS,
|
||||
MetaTable.WIDGETS,
|
||||
MetaTable.MODEL_STAT,
|
||||
];
|
||||
|
||||
export enum MetaTableOldV2 {
|
||||
PROJECT = 'nc_projects_v2',
|
||||
PROJECT_USERS = 'nc_project_users_v2',
|
||||
|
||||
Reference in New Issue
Block a user