mirror of
https://github.com/directus/directus.git
synced 2026-04-25 03:00:53 -04:00
Merge branch 'main' into joins
This commit is contained in:
2
api/package-lock.json
generated
2
api/package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "directus",
|
||||
"version": "9.0.0-rc.1",
|
||||
"version": "9.0.0-rc.2",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "directus",
|
||||
"version": "9.0.0-rc.1",
|
||||
"version": "9.0.0-rc.2",
|
||||
"license": "GPL-3.0-only",
|
||||
"homepage": "https://github.com/directus/next#readme",
|
||||
"description": "Directus is a real-time API and App dashboard for managing SQL database content.",
|
||||
@@ -106,6 +106,7 @@
|
||||
"liquidjs": "^9.14.1",
|
||||
"lodash": "^4.17.19",
|
||||
"macos-release": "^2.4.1",
|
||||
"mime-types": "^2.1.27",
|
||||
"ms": "^2.1.2",
|
||||
"nanoid": "^3.1.12",
|
||||
"node-machine-id": "^1.1.12",
|
||||
|
||||
@@ -60,7 +60,6 @@ const newFieldSchema = Joi.object({
|
||||
field: Joi.string().required(),
|
||||
type: Joi.string().valid(...types, null),
|
||||
schema: Joi.object({
|
||||
comment: Joi.string().allow(null),
|
||||
default_value: Joi.any(),
|
||||
max_length: [Joi.number(), Joi.string(), Joi.valid(null)],
|
||||
is_nullable: Joi.bool(),
|
||||
|
||||
@@ -42,6 +42,8 @@ columns:
|
||||
column: id
|
||||
modified_on:
|
||||
type: timestamp
|
||||
nullable: false
|
||||
default: '$now'
|
||||
charset:
|
||||
type: string
|
||||
length: 50
|
||||
|
||||
@@ -135,15 +135,18 @@ fields:
|
||||
- field: key
|
||||
name: Key
|
||||
type: string
|
||||
schema:
|
||||
is_nullable: false
|
||||
meta:
|
||||
interface: slug
|
||||
options:
|
||||
onlyOnCreate: false
|
||||
required: true
|
||||
width: half
|
||||
- field: fit
|
||||
name: Fit
|
||||
type: string
|
||||
schema:
|
||||
is_nullable: false
|
||||
meta:
|
||||
interface: dropdown
|
||||
options:
|
||||
@@ -152,34 +155,35 @@ fields:
|
||||
text: Contain (preserve aspect ratio)
|
||||
- value: cover
|
||||
text: Cover (forces exact size)
|
||||
required: true
|
||||
width: half
|
||||
- field: width
|
||||
name: Width
|
||||
type: integer
|
||||
schema:
|
||||
is_nullable: false
|
||||
meta:
|
||||
interface: numeric
|
||||
required: true
|
||||
width: half
|
||||
- field: height
|
||||
name: Height
|
||||
type: integer
|
||||
schema:
|
||||
is_nullable: false
|
||||
meta:
|
||||
interface: numeric
|
||||
required: true
|
||||
width: half
|
||||
- field: quality
|
||||
type: integer
|
||||
name: Quality
|
||||
schema:
|
||||
default_value: 80
|
||||
is_nullable: false
|
||||
meta:
|
||||
interface: slider
|
||||
options:
|
||||
max: 100
|
||||
min: 0
|
||||
step: 1
|
||||
required: true
|
||||
width: full
|
||||
template: '{{key}}'
|
||||
special: json
|
||||
|
||||
@@ -99,8 +99,9 @@ async function createTables(database: Knex) {
|
||||
if (columnInfo.default !== undefined) {
|
||||
let defaultValue = columnInfo.default;
|
||||
|
||||
if (isObject(defaultValue) || Array.isArray(defaultValue))
|
||||
if (isObject(defaultValue) || Array.isArray(defaultValue)) {
|
||||
defaultValue = JSON.stringify(defaultValue);
|
||||
}
|
||||
|
||||
if (defaultValue === '$now') {
|
||||
defaultValue = database!.fn.now();
|
||||
|
||||
@@ -72,7 +72,7 @@ function registerHooks(hooks: string[]) {
|
||||
registerHook(hook);
|
||||
} catch (error) {
|
||||
logger.warn(`Couldn't register hook "${hook}"`);
|
||||
logger.info(error);
|
||||
logger.warn(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ function registerEndpoints(endpoints: string[], router: Router) {
|
||||
registerEndpoint(endpoint);
|
||||
} catch (error) {
|
||||
logger.warn(`Couldn't register endpoint "${endpoint}"`);
|
||||
logger.info(error);
|
||||
logger.warn(error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,22 +17,25 @@ import { ForbiddenException, FailedValidationException } from '../exceptions';
|
||||
import { uniq, merge, flatten } from 'lodash';
|
||||
import generateJoi from '../utils/generate-joi';
|
||||
import { ItemsService } from './items';
|
||||
import { PayloadService } from './payload';
|
||||
import { parseFilter } from '../utils/parse-filter';
|
||||
import { toArray } from '../utils/to-array';
|
||||
|
||||
export class AuthorizationService {
|
||||
knex: Knex;
|
||||
accountability: Accountability | null;
|
||||
payloadService: PayloadService;
|
||||
|
||||
constructor(options?: AbstractServiceOptions) {
|
||||
this.knex = options?.knex || database;
|
||||
this.accountability = options?.accountability || null;
|
||||
this.payloadService = new PayloadService('directus_permissions', { knex: this.knex });
|
||||
}
|
||||
|
||||
async processAST(ast: AST, action: PermissionsAction = 'read'): Promise<AST> {
|
||||
const collectionsRequested = getCollectionsFromAST(ast);
|
||||
|
||||
const permissionsForCollections = await this.knex
|
||||
let permissionsForCollections = await this.knex
|
||||
.select<Permission[]>('*')
|
||||
.from('directus_permissions')
|
||||
.where({ action, role: this.accountability?.role })
|
||||
@@ -41,6 +44,11 @@ export class AuthorizationService {
|
||||
collectionsRequested.map(({ collection }) => collection)
|
||||
);
|
||||
|
||||
permissionsForCollections = (await this.payloadService.processValues(
|
||||
'read',
|
||||
permissionsForCollections
|
||||
)) as Permission[];
|
||||
|
||||
// If the permissions don't match the collections, you don't have permission to read all of them
|
||||
const uniqueCollectionsRequestedCount = uniq(
|
||||
collectionsRequested.map(({ collection }) => collection)
|
||||
@@ -111,7 +119,7 @@ export class AuthorizationService {
|
||||
(permission) => permission.collection === collection
|
||||
)!;
|
||||
|
||||
const allowedFields = permissions.fields?.split(',') || [];
|
||||
const allowedFields = permissions.fields || [];
|
||||
|
||||
for (const childNode of ast.children) {
|
||||
if (childNode.type !== 'field') {
|
||||
@@ -213,21 +221,26 @@ export class AuthorizationService {
|
||||
permissions: {},
|
||||
validation: {},
|
||||
limit: null,
|
||||
fields: '*',
|
||||
fields: ['*'],
|
||||
presets: {},
|
||||
};
|
||||
} else {
|
||||
permission = await this.knex
|
||||
.select<Permission>('*')
|
||||
.select('*')
|
||||
.from('directus_permissions')
|
||||
.where({ action, collection, role: this.accountability?.role || null })
|
||||
.first();
|
||||
|
||||
permission = (await this.payloadService.processValues(
|
||||
'read',
|
||||
permission as Item
|
||||
)) as Permission;
|
||||
|
||||
// Check if you have permission to access the fields you're trying to acces
|
||||
|
||||
if (!permission) throw new ForbiddenException();
|
||||
|
||||
const allowedFields = permission.fields?.split(',') || [];
|
||||
const allowedFields = permission.fields || [];
|
||||
|
||||
if (allowedFields.includes('*') === false) {
|
||||
for (const payload of payloads) {
|
||||
@@ -260,13 +273,16 @@ export class AuthorizationService {
|
||||
.from('directus_fields')
|
||||
.where({ collection, field: column.name })
|
||||
.first();
|
||||
|
||||
const specials = (field?.special || '').split(',');
|
||||
|
||||
const hasGenerateSpecial = [
|
||||
'uuid',
|
||||
'date-created',
|
||||
'role-created',
|
||||
'user-created',
|
||||
].some((name) => specials.includes(name));
|
||||
|
||||
const isRequired =
|
||||
column.is_nullable === false &&
|
||||
column.has_auto_increment === false &&
|
||||
|
||||
@@ -260,7 +260,13 @@ export class FieldsService {
|
||||
}
|
||||
|
||||
if (field.schema.default_value) {
|
||||
column.defaultTo(field.schema.default_value);
|
||||
const defaultValue = field.schema.default_value.toLowerCase();
|
||||
|
||||
if (defaultValue === 'now()') {
|
||||
column.defaultTo(this.knex.fn.now());
|
||||
} else {
|
||||
column.defaultTo(field.schema.default_value);
|
||||
}
|
||||
}
|
||||
|
||||
if (field.schema.is_nullable !== undefined && field.schema.is_nullable === false) {
|
||||
|
||||
@@ -4,12 +4,13 @@ import sharp from 'sharp';
|
||||
import { parse as parseICC } from 'icc';
|
||||
import parseEXIF from 'exif-reader';
|
||||
import parseIPTC from '../utils/parse-iptc';
|
||||
import path from 'path';
|
||||
import { AbstractServiceOptions, File, PrimaryKey } from '../types';
|
||||
import { clone } from 'lodash';
|
||||
import cache from '../cache';
|
||||
import { ForbiddenException } from '../exceptions';
|
||||
import { toArray } from '../utils/to-array';
|
||||
import { extension } from 'mime-types';
|
||||
import path from 'path';
|
||||
|
||||
export class FilesService extends ItemsService {
|
||||
constructor(options?: AbstractServiceOptions) {
|
||||
@@ -37,7 +38,10 @@ export class FilesService extends ItemsService {
|
||||
primaryKey = await this.create(payload);
|
||||
}
|
||||
|
||||
payload.filename_disk = primaryKey + path.extname(payload.filename_download);
|
||||
const fileExtension =
|
||||
(payload.type && extension(payload.type)) || path.extname(payload.filename_download);
|
||||
|
||||
payload.filename_disk = primaryKey + fileExtension;
|
||||
|
||||
if (!payload.type) {
|
||||
payload.type = 'application/octet-stream';
|
||||
|
||||
@@ -18,7 +18,6 @@ import {
|
||||
GraphQLInputObjectType,
|
||||
ObjectFieldNode,
|
||||
GraphQLID,
|
||||
ValueNode,
|
||||
FieldNode,
|
||||
GraphQLFieldConfigMap,
|
||||
GraphQLInt,
|
||||
@@ -26,11 +25,9 @@ import {
|
||||
StringValueNode,
|
||||
BooleanValueNode,
|
||||
ArgumentNode,
|
||||
GraphQLScalarType,
|
||||
GraphQLBoolean,
|
||||
ObjectValueNode,
|
||||
GraphQLUnionType,
|
||||
GraphQLUnionTypeConfig,
|
||||
} from 'graphql';
|
||||
import { getGraphQLType } from '../utils/get-graphql-type';
|
||||
import { RelationsService } from './relations';
|
||||
@@ -65,7 +62,7 @@ export class GraphQLService {
|
||||
this.knex = options?.knex || database;
|
||||
this.fieldsService = new FieldsService(options);
|
||||
this.collectionsService = new CollectionsService(options);
|
||||
this.relationsService = new RelationsService({ knex: this.knex });
|
||||
this.relationsService = new RelationsService(options);
|
||||
}
|
||||
|
||||
args = {
|
||||
@@ -138,6 +135,7 @@ export class GraphQLService {
|
||||
const relatedIsSystem = relationForField.one_collection!.startsWith(
|
||||
'directus_'
|
||||
);
|
||||
|
||||
const relatedType = relatedIsSystem
|
||||
? schema[relationForField.one_collection!.substring(9)].type
|
||||
: schema.items[relationForField.one_collection!].type;
|
||||
|
||||
@@ -25,7 +25,9 @@ export class RelationsService extends ItemsService {
|
||||
| ParsedRelation
|
||||
| ParsedRelation[]
|
||||
| null;
|
||||
|
||||
const filteredResults = await this.filterForbidden(results);
|
||||
|
||||
return filteredResults;
|
||||
}
|
||||
|
||||
@@ -58,6 +60,7 @@ export class RelationsService extends ItemsService {
|
||||
this.accountability?.role || null,
|
||||
'read'
|
||||
);
|
||||
|
||||
const allowedFields = await this.permissionsService.getAllowedFields(
|
||||
this.accountability?.role || null,
|
||||
'read'
|
||||
|
||||
@@ -26,7 +26,6 @@ export type FieldMeta = {
|
||||
interface: string | null;
|
||||
options: Record<string, any> | null;
|
||||
locked: boolean;
|
||||
required: boolean;
|
||||
readonly: boolean;
|
||||
hidden: boolean;
|
||||
sort: number | null;
|
||||
|
||||
@@ -9,5 +9,5 @@ export type Permission = {
|
||||
validation: Record<string, any>;
|
||||
limit: number | null;
|
||||
presets: Record<string, any> | null;
|
||||
fields: string | null;
|
||||
fields: string[] | null;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user