Merge branch 'main' into joins

This commit is contained in:
rijkvanzanten
2020-10-26 16:57:23 +01:00
82 changed files with 1174 additions and 296 deletions

2
api/package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "directus",
"version": "9.0.0-rc.1",
"version": "9.0.0-rc.2",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -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",

View File

@@ -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(),

View File

@@ -42,6 +42,8 @@ columns:
column: id
modified_on:
type: timestamp
nullable: false
default: '$now'
charset:
type: string
length: 50

View File

@@ -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

View File

@@ -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();

View File

@@ -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);
}
}

View File

@@ -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 &&

View File

@@ -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) {

View File

@@ -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';

View File

@@ -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;

View File

@@ -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'

View File

@@ -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;

View File

@@ -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;
};