123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545 |
- "use strict";
- Object.defineProperty(exports, "__esModule", {
- value: true
- });
- exports.VolatileClassesSchemas = exports.SchemaController = void 0;
- exports.buildMergedSchemaObject = buildMergedSchemaObject;
- exports.classNameIsValid = classNameIsValid;
- exports.defaultColumns = exports.default = exports.convertSchemaToAdapterSchema = void 0;
- exports.fieldNameIsValid = fieldNameIsValid;
- exports.invalidClassNameMessage = invalidClassNameMessage;
- exports.systemClasses = exports.requiredColumns = exports.load = void 0;
- var _StorageAdapter = require("../Adapters/Storage/StorageAdapter");
- var _SchemaCache = _interopRequireDefault(require("../Adapters/Cache/SchemaCache"));
- var _DatabaseController = _interopRequireDefault(require("./DatabaseController"));
- var _Config = _interopRequireDefault(require("../Config"));
- var _deepcopy = _interopRequireDefault(require("deepcopy"));
- function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
- function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
- function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
- function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
- function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
- function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
- function _objectDestructuringEmpty(t) { if (null == t) throw new TypeError("Cannot destructure " + t); }
- function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
- const Parse = require('parse/node').Parse;
- const defaultColumns = exports.defaultColumns = Object.freeze({
-
- _Default: {
- objectId: {
- type: 'String'
- },
- createdAt: {
- type: 'Date'
- },
- updatedAt: {
- type: 'Date'
- },
- ACL: {
- type: 'ACL'
- }
- },
-
- _User: {
- username: {
- type: 'String'
- },
- password: {
- type: 'String'
- },
- email: {
- type: 'String'
- },
- emailVerified: {
- type: 'Boolean'
- },
- authData: {
- type: 'Object'
- }
- },
-
- _Installation: {
- installationId: {
- type: 'String'
- },
- deviceToken: {
- type: 'String'
- },
- channels: {
- type: 'Array'
- },
- deviceType: {
- type: 'String'
- },
- pushType: {
- type: 'String'
- },
- GCMSenderId: {
- type: 'String'
- },
- timeZone: {
- type: 'String'
- },
- localeIdentifier: {
- type: 'String'
- },
- badge: {
- type: 'Number'
- },
- appVersion: {
- type: 'String'
- },
- appName: {
- type: 'String'
- },
- appIdentifier: {
- type: 'String'
- },
- parseVersion: {
- type: 'String'
- }
- },
-
- _Role: {
- name: {
- type: 'String'
- },
- users: {
- type: 'Relation',
- targetClass: '_User'
- },
- roles: {
- type: 'Relation',
- targetClass: '_Role'
- }
- },
-
- _Session: {
- user: {
- type: 'Pointer',
- targetClass: '_User'
- },
- installationId: {
- type: 'String'
- },
- sessionToken: {
- type: 'String'
- },
- expiresAt: {
- type: 'Date'
- },
- createdWith: {
- type: 'Object'
- }
- },
- _Product: {
- productIdentifier: {
- type: 'String'
- },
- download: {
- type: 'File'
- },
- downloadName: {
- type: 'String'
- },
- icon: {
- type: 'File'
- },
- order: {
- type: 'Number'
- },
- title: {
- type: 'String'
- },
- subtitle: {
- type: 'String'
- }
- },
- _PushStatus: {
- pushTime: {
- type: 'String'
- },
- source: {
- type: 'String'
- },
-
- query: {
- type: 'String'
- },
-
- payload: {
- type: 'String'
- },
-
- title: {
- type: 'String'
- },
- expiry: {
- type: 'Number'
- },
- expiration_interval: {
- type: 'Number'
- },
- status: {
- type: 'String'
- },
- numSent: {
- type: 'Number'
- },
- numFailed: {
- type: 'Number'
- },
- pushHash: {
- type: 'String'
- },
- errorMessage: {
- type: 'Object'
- },
- sentPerType: {
- type: 'Object'
- },
- failedPerType: {
- type: 'Object'
- },
- sentPerUTCOffset: {
- type: 'Object'
- },
- failedPerUTCOffset: {
- type: 'Object'
- },
- count: {
- type: 'Number'
- }
- },
- _JobStatus: {
- jobName: {
- type: 'String'
- },
- source: {
- type: 'String'
- },
- status: {
- type: 'String'
- },
- message: {
- type: 'String'
- },
- params: {
- type: 'Object'
- },
-
- finishedAt: {
- type: 'Date'
- }
- },
- _JobSchedule: {
- jobName: {
- type: 'String'
- },
- description: {
- type: 'String'
- },
- params: {
- type: 'String'
- },
- startAfter: {
- type: 'String'
- },
- daysOfWeek: {
- type: 'Array'
- },
- timeOfDay: {
- type: 'String'
- },
- lastRun: {
- type: 'Number'
- },
- repeatMinutes: {
- type: 'Number'
- }
- },
- _Hooks: {
- functionName: {
- type: 'String'
- },
- className: {
- type: 'String'
- },
- triggerName: {
- type: 'String'
- },
- url: {
- type: 'String'
- }
- },
- _GlobalConfig: {
- objectId: {
- type: 'String'
- },
- params: {
- type: 'Object'
- },
- masterKeyOnly: {
- type: 'Object'
- }
- },
- _GraphQLConfig: {
- objectId: {
- type: 'String'
- },
- config: {
- type: 'Object'
- }
- },
- _Audience: {
- objectId: {
- type: 'String'
- },
- name: {
- type: 'String'
- },
- query: {
- type: 'String'
- },
-
- lastUsed: {
- type: 'Date'
- },
- timesUsed: {
- type: 'Number'
- }
- },
- _Idempotency: {
- reqId: {
- type: 'String'
- },
- expire: {
- type: 'Date'
- }
- }
- });
- const requiredColumns = exports.requiredColumns = Object.freeze({
- read: {
- _User: ['username']
- },
- write: {
- _Product: ['productIdentifier', 'icon', 'order', 'title', 'subtitle'],
- _Role: ['name', 'ACL']
- }
- });
- const invalidColumns = ['length'];
- const systemClasses = exports.systemClasses = Object.freeze(['_User', '_Installation', '_Role', '_Session', '_Product', '_PushStatus', '_JobStatus', '_JobSchedule', '_Audience', '_Idempotency']);
- const volatileClasses = Object.freeze(['_JobStatus', '_PushStatus', '_Hooks', '_GlobalConfig', '_GraphQLConfig', '_JobSchedule', '_Audience', '_Idempotency']);
- const roleRegex = /^role:.*/;
- const protectedFieldsPointerRegex = /^userField:.*/;
- const publicRegex = /^\*$/;
- const authenticatedRegex = /^authenticated$/;
- const requiresAuthenticationRegex = /^requiresAuthentication$/;
- const clpPointerRegex = /^pointerFields$/;
- const protectedFieldsRegex = Object.freeze([protectedFieldsPointerRegex, publicRegex, authenticatedRegex, roleRegex]);
- const clpFieldsRegex = Object.freeze([clpPointerRegex, publicRegex, requiresAuthenticationRegex, roleRegex]);
- function validatePermissionKey(key, userIdRegExp) {
- let matchesSome = false;
- for (const regEx of clpFieldsRegex) {
- if (key.match(regEx) !== null) {
- matchesSome = true;
- break;
- }
- }
-
- const valid = matchesSome || key.match(userIdRegExp) !== null;
- if (!valid) {
- throw new Parse.Error(Parse.Error.INVALID_JSON, `'${key}' is not a valid key for class level permissions`);
- }
- }
- function validateProtectedFieldsKey(key, userIdRegExp) {
- let matchesSome = false;
- for (const regEx of protectedFieldsRegex) {
- if (key.match(regEx) !== null) {
- matchesSome = true;
- break;
- }
- }
-
- const valid = matchesSome || key.match(userIdRegExp) !== null;
- if (!valid) {
- throw new Parse.Error(Parse.Error.INVALID_JSON, `'${key}' is not a valid key for class level permissions`);
- }
- }
- const CLPValidKeys = Object.freeze(['find', 'count', 'get', 'create', 'update', 'delete', 'addField', 'readUserFields', 'writeUserFields', 'protectedFields']);
- function validateCLP(perms, fields, userIdRegExp) {
- if (!perms) {
- return;
- }
- for (const operationKey in perms) {
- if (CLPValidKeys.indexOf(operationKey) == -1) {
- throw new Parse.Error(Parse.Error.INVALID_JSON, `${operationKey} is not a valid operation for class level permissions`);
- }
- const operation = perms[operationKey];
-
-
- validateCLPjson(operation, operationKey);
- if (operationKey === 'readUserFields' || operationKey === 'writeUserFields') {
-
-
- for (const fieldName of operation) {
- validatePointerPermission(fieldName, fields, operationKey);
- }
-
-
- continue;
- }
-
- if (operationKey === 'protectedFields') {
- for (const entity in operation) {
-
- validateProtectedFieldsKey(entity, userIdRegExp);
- const protectedFields = operation[entity];
- if (!Array.isArray(protectedFields)) {
- throw new Parse.Error(Parse.Error.INVALID_JSON, `'${protectedFields}' is not a valid value for protectedFields[${entity}] - expected an array.`);
- }
-
- for (const field of protectedFields) {
-
- if (defaultColumns._Default[field]) {
- throw new Parse.Error(Parse.Error.INVALID_JSON, `Default field '${field}' can not be protected`);
- }
-
- if (!Object.prototype.hasOwnProperty.call(fields, field)) {
- throw new Parse.Error(Parse.Error.INVALID_JSON, `Field '${field}' in protectedFields:${entity} does not exist`);
- }
- }
- }
-
- continue;
- }
-
-
-
-
-
-
-
- for (const entity in operation) {
-
- validatePermissionKey(entity, userIdRegExp);
-
-
- if (entity === 'pointerFields') {
- const pointerFields = operation[entity];
- if (Array.isArray(pointerFields)) {
- for (const pointerField of pointerFields) {
- validatePointerPermission(pointerField, fields, operation);
- }
- } else {
- throw new Parse.Error(Parse.Error.INVALID_JSON, `'${pointerFields}' is not a valid value for ${operationKey}[${entity}] - expected an array.`);
- }
-
- continue;
- }
-
- const permit = operation[entity];
- if (permit !== true) {
- throw new Parse.Error(Parse.Error.INVALID_JSON, `'${permit}' is not a valid value for class level permissions ${operationKey}:${entity}:${permit}`);
- }
- }
- }
- }
- function validateCLPjson(operation, operationKey) {
- if (operationKey === 'readUserFields' || operationKey === 'writeUserFields') {
- if (!Array.isArray(operation)) {
- throw new Parse.Error(Parse.Error.INVALID_JSON, `'${operation}' is not a valid value for class level permissions ${operationKey} - must be an array`);
- }
- } else {
- if (typeof operation === 'object' && operation !== null) {
-
- return;
- } else {
- throw new Parse.Error(Parse.Error.INVALID_JSON, `'${operation}' is not a valid value for class level permissions ${operationKey} - must be an object`);
- }
- }
- }
- function validatePointerPermission(fieldName, fields, operation) {
-
-
-
-
-
-
-
- if (!(fields[fieldName] && (fields[fieldName].type == 'Pointer' && fields[fieldName].targetClass == '_User' || fields[fieldName].type == 'Array'))) {
- throw new Parse.Error(Parse.Error.INVALID_JSON, `'${fieldName}' is not a valid column for class level pointer permissions ${operation}`);
- }
- }
- const joinClassRegex = /^_Join:[A-Za-z0-9_]+:[A-Za-z0-9_]+/;
- const classAndFieldRegex = /^[A-Za-z][A-Za-z0-9_]*$/;
- function classNameIsValid(className) {
-
- return (
-
- systemClasses.indexOf(className) > -1 ||
-
- joinClassRegex.test(className) ||
-
- fieldNameIsValid(className, className)
- );
- }
- function fieldNameIsValid(fieldName, className) {
- if (className && className !== '_Hooks') {
- if (fieldName === 'className') {
- return false;
- }
- }
- return classAndFieldRegex.test(fieldName) && !invalidColumns.includes(fieldName);
- }
- function fieldNameIsValidForClass(fieldName, className) {
- if (!fieldNameIsValid(fieldName, className)) {
- return false;
- }
- if (defaultColumns._Default[fieldName]) {
- return false;
- }
- if (defaultColumns[className] && defaultColumns[className][fieldName]) {
- return false;
- }
- return true;
- }
- function invalidClassNameMessage(className) {
- return 'Invalid classname: ' + className + ', classnames can only have alphanumeric characters and _, and must start with an alpha character ';
- }
- const invalidJsonError = new Parse.Error(Parse.Error.INVALID_JSON, 'invalid JSON');
- const validNonRelationOrPointerTypes = ['Number', 'String', 'Boolean', 'Date', 'Object', 'Array', 'GeoPoint', 'File', 'Bytes', 'Polygon'];
- const fieldTypeIsInvalid = ({
- type,
- targetClass
- }) => {
- if (['Pointer', 'Relation'].indexOf(type) >= 0) {
- if (!targetClass) {
- return new Parse.Error(135, `type ${type} needs a class name`);
- } else if (typeof targetClass !== 'string') {
- return invalidJsonError;
- } else if (!classNameIsValid(targetClass)) {
- return new Parse.Error(Parse.Error.INVALID_CLASS_NAME, invalidClassNameMessage(targetClass));
- } else {
- return undefined;
- }
- }
- if (typeof type !== 'string') {
- return invalidJsonError;
- }
- if (validNonRelationOrPointerTypes.indexOf(type) < 0) {
- return new Parse.Error(Parse.Error.INCORRECT_TYPE, `invalid field type: ${type}`);
- }
- return undefined;
- };
- const convertSchemaToAdapterSchema = schema => {
- schema = injectDefaultSchema(schema);
- delete schema.fields.ACL;
- schema.fields._rperm = {
- type: 'Array'
- };
- schema.fields._wperm = {
- type: 'Array'
- };
- if (schema.className === '_User') {
- delete schema.fields.password;
- schema.fields._hashed_password = {
- type: 'String'
- };
- }
- return schema;
- };
- exports.convertSchemaToAdapterSchema = convertSchemaToAdapterSchema;
- const convertAdapterSchemaToParseSchema = _ref => {
- let schema = _extends({}, (_objectDestructuringEmpty(_ref), _ref));
- delete schema.fields._rperm;
- delete schema.fields._wperm;
- schema.fields.ACL = {
- type: 'ACL'
- };
- if (schema.className === '_User') {
- delete schema.fields.authData;
- delete schema.fields._hashed_password;
- schema.fields.password = {
- type: 'String'
- };
- }
- if (schema.indexes && Object.keys(schema.indexes).length === 0) {
- delete schema.indexes;
- }
- return schema;
- };
- class SchemaData {
- constructor(allSchemas = [], protectedFields = {}) {
- this.__data = {};
- this.__protectedFields = protectedFields;
- allSchemas.forEach(schema => {
- if (volatileClasses.includes(schema.className)) {
- return;
- }
- Object.defineProperty(this, schema.className, {
- get: () => {
- if (!this.__data[schema.className]) {
- const data = {};
- data.fields = injectDefaultSchema(schema).fields;
- data.classLevelPermissions = (0, _deepcopy.default)(schema.classLevelPermissions);
- data.indexes = schema.indexes;
- const classProtectedFields = this.__protectedFields[schema.className];
- if (classProtectedFields) {
- for (const key in classProtectedFields) {
- const unq = new Set([...(data.classLevelPermissions.protectedFields[key] || []), ...classProtectedFields[key]]);
- data.classLevelPermissions.protectedFields[key] = Array.from(unq);
- }
- }
- this.__data[schema.className] = data;
- }
- return this.__data[schema.className];
- }
- });
- });
-
- volatileClasses.forEach(className => {
- Object.defineProperty(this, className, {
- get: () => {
- if (!this.__data[className]) {
- const schema = injectDefaultSchema({
- className,
- fields: {},
- classLevelPermissions: {}
- });
- const data = {};
- data.fields = schema.fields;
- data.classLevelPermissions = schema.classLevelPermissions;
- data.indexes = schema.indexes;
- this.__data[className] = data;
- }
- return this.__data[className];
- }
- });
- });
- }
- }
- const injectDefaultSchema = ({
- className,
- fields,
- classLevelPermissions,
- indexes
- }) => {
- const defaultSchema = {
- className,
- fields: _objectSpread(_objectSpread(_objectSpread({}, defaultColumns._Default), defaultColumns[className] || {}), fields),
- classLevelPermissions
- };
- if (indexes && Object.keys(indexes).length !== 0) {
- defaultSchema.indexes = indexes;
- }
- return defaultSchema;
- };
- const _HooksSchema = {
- className: '_Hooks',
- fields: defaultColumns._Hooks
- };
- const _GlobalConfigSchema = {
- className: '_GlobalConfig',
- fields: defaultColumns._GlobalConfig
- };
- const _GraphQLConfigSchema = {
- className: '_GraphQLConfig',
- fields: defaultColumns._GraphQLConfig
- };
- const _PushStatusSchema = convertSchemaToAdapterSchema(injectDefaultSchema({
- className: '_PushStatus',
- fields: {},
- classLevelPermissions: {}
- }));
- const _JobStatusSchema = convertSchemaToAdapterSchema(injectDefaultSchema({
- className: '_JobStatus',
- fields: {},
- classLevelPermissions: {}
- }));
- const _JobScheduleSchema = convertSchemaToAdapterSchema(injectDefaultSchema({
- className: '_JobSchedule',
- fields: {},
- classLevelPermissions: {}
- }));
- const _AudienceSchema = convertSchemaToAdapterSchema(injectDefaultSchema({
- className: '_Audience',
- fields: defaultColumns._Audience,
- classLevelPermissions: {}
- }));
- const _IdempotencySchema = convertSchemaToAdapterSchema(injectDefaultSchema({
- className: '_Idempotency',
- fields: defaultColumns._Idempotency,
- classLevelPermissions: {}
- }));
- const VolatileClassesSchemas = exports.VolatileClassesSchemas = [_HooksSchema, _JobStatusSchema, _JobScheduleSchema, _PushStatusSchema, _GlobalConfigSchema, _GraphQLConfigSchema, _AudienceSchema, _IdempotencySchema];
- const dbTypeMatchesObjectType = (dbType, objectType) => {
- if (dbType.type !== objectType.type) return false;
- if (dbType.targetClass !== objectType.targetClass) return false;
- if (dbType === objectType.type) return true;
- if (dbType.type === objectType.type) return true;
- return false;
- };
- const typeToString = type => {
- if (typeof type === 'string') {
- return type;
- }
- if (type.targetClass) {
- return `${type.type}<${type.targetClass}>`;
- }
- return `${type.type}`;
- };
- const ttl = {
- date: Date.now(),
- duration: undefined
- };
- class SchemaController {
- constructor(databaseAdapter) {
- this._dbAdapter = databaseAdapter;
- const config = _Config.default.get(Parse.applicationId);
- this.schemaData = new SchemaData(_SchemaCache.default.all(), this.protectedFields);
- this.protectedFields = config.protectedFields;
- const customIds = config.allowCustomObjectId;
- const customIdRegEx = /^.{1,}$/u;
- const autoIdRegEx = /^[a-zA-Z0-9]{1,}$/;
- this.userIdRegEx = customIds ? customIdRegEx : autoIdRegEx;
- this._dbAdapter.watch(() => {
- this.reloadData({
- clearCache: true
- });
- });
- }
- async reloadDataIfNeeded() {
- if (this._dbAdapter.enableSchemaHooks) {
- return;
- }
- const {
- date,
- duration
- } = ttl || {};
- if (!duration) {
- return;
- }
- const now = Date.now();
- if (now - date > duration) {
- ttl.date = now;
- await this.reloadData({
- clearCache: true
- });
- }
- }
- reloadData(options = {
- clearCache: false
- }) {
- if (this.reloadDataPromise && !options.clearCache) {
- return this.reloadDataPromise;
- }
- this.reloadDataPromise = this.getAllClasses(options).then(allSchemas => {
- this.schemaData = new SchemaData(allSchemas, this.protectedFields);
- delete this.reloadDataPromise;
- }, err => {
- this.schemaData = new SchemaData();
- delete this.reloadDataPromise;
- throw err;
- }).then(() => {});
- return this.reloadDataPromise;
- }
- async getAllClasses(options = {
- clearCache: false
- }) {
- if (options.clearCache) {
- return this.setAllClasses();
- }
- await this.reloadDataIfNeeded();
- const cached = _SchemaCache.default.all();
- if (cached && cached.length) {
- return Promise.resolve(cached);
- }
- return this.setAllClasses();
- }
- setAllClasses() {
- return this._dbAdapter.getAllClasses().then(allSchemas => allSchemas.map(injectDefaultSchema)).then(allSchemas => {
- _SchemaCache.default.put(allSchemas);
- return allSchemas;
- });
- }
- getOneSchema(className, allowVolatileClasses = false, options = {
- clearCache: false
- }) {
- if (options.clearCache) {
- _SchemaCache.default.clear();
- }
- if (allowVolatileClasses && volatileClasses.indexOf(className) > -1) {
- const data = this.schemaData[className];
- return Promise.resolve({
- className,
- fields: data.fields,
- classLevelPermissions: data.classLevelPermissions,
- indexes: data.indexes
- });
- }
- const cached = _SchemaCache.default.get(className);
- if (cached && !options.clearCache) {
- return Promise.resolve(cached);
- }
- return this.setAllClasses().then(allSchemas => {
- const oneSchema = allSchemas.find(schema => schema.className === className);
- if (!oneSchema) {
- return Promise.reject(undefined);
- }
- return oneSchema;
- });
- }
-
-
-
-
-
-
-
- async addClassIfNotExists(className, fields = {}, classLevelPermissions, indexes = {}) {
- var validationError = this.validateNewClass(className, fields, classLevelPermissions);
- if (validationError) {
- if (validationError instanceof Parse.Error) {
- return Promise.reject(validationError);
- } else if (validationError.code && validationError.error) {
- return Promise.reject(new Parse.Error(validationError.code, validationError.error));
- }
- return Promise.reject(validationError);
- }
- try {
- const adapterSchema = await this._dbAdapter.createClass(className, convertSchemaToAdapterSchema({
- fields,
- classLevelPermissions,
- indexes,
- className
- }));
-
- await this.reloadData({
- clearCache: true
- });
- const parseSchema = convertAdapterSchemaToParseSchema(adapterSchema);
- return parseSchema;
- } catch (error) {
- if (error && error.code === Parse.Error.DUPLICATE_VALUE) {
- throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, `Class ${className} already exists.`);
- } else {
- throw error;
- }
- }
- }
- updateClass(className, submittedFields, classLevelPermissions, indexes, database) {
- return this.getOneSchema(className).then(schema => {
- const existingFields = schema.fields;
- Object.keys(submittedFields).forEach(name => {
- const field = submittedFields[name];
- if (existingFields[name] && existingFields[name].type !== field.type && field.__op !== 'Delete') {
- throw new Parse.Error(255, `Field ${name} exists, cannot update.`);
- }
- if (!existingFields[name] && field.__op === 'Delete') {
- throw new Parse.Error(255, `Field ${name} does not exist, cannot delete.`);
- }
- });
- delete existingFields._rperm;
- delete existingFields._wperm;
- const newSchema = buildMergedSchemaObject(existingFields, submittedFields);
- const defaultFields = defaultColumns[className] || defaultColumns._Default;
- const fullNewSchema = Object.assign({}, newSchema, defaultFields);
- const validationError = this.validateSchemaData(className, newSchema, classLevelPermissions, Object.keys(existingFields));
- if (validationError) {
- throw new Parse.Error(validationError.code, validationError.error);
- }
-
-
- const deletedFields = [];
- const insertedFields = [];
- Object.keys(submittedFields).forEach(fieldName => {
- if (submittedFields[fieldName].__op === 'Delete') {
- deletedFields.push(fieldName);
- } else {
- insertedFields.push(fieldName);
- }
- });
- let deletePromise = Promise.resolve();
- if (deletedFields.length > 0) {
- deletePromise = this.deleteFields(deletedFields, className, database);
- }
- let enforceFields = [];
- return deletePromise
- .then(() => this.reloadData({
- clearCache: true
- }))
- .then(() => {
- const promises = insertedFields.map(fieldName => {
- const type = submittedFields[fieldName];
- return this.enforceFieldExists(className, fieldName, type);
- });
- return Promise.all(promises);
- }).then(results => {
- enforceFields = results.filter(result => !!result);
- return this.setPermissions(className, classLevelPermissions, newSchema);
- }).then(() => this._dbAdapter.setIndexesWithSchemaFormat(className, indexes, schema.indexes, fullNewSchema)).then(() => this.reloadData({
- clearCache: true
- }))
-
- .then(() => {
- this.ensureFields(enforceFields);
- const schema = this.schemaData[className];
- const reloadedSchema = {
- className: className,
- fields: schema.fields,
- classLevelPermissions: schema.classLevelPermissions
- };
- if (schema.indexes && Object.keys(schema.indexes).length !== 0) {
- reloadedSchema.indexes = schema.indexes;
- }
- return reloadedSchema;
- });
- }).catch(error => {
- if (error === undefined) {
- throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, `Class ${className} does not exist.`);
- } else {
- throw error;
- }
- });
- }
-
-
- enforceClassExists(className) {
- if (this.schemaData[className]) {
- return Promise.resolve(this);
- }
-
- return (
-
- this.addClassIfNotExists(className).catch(() => {
-
-
-
-
- return this.reloadData({
- clearCache: true
- });
- }).then(() => {
-
- if (this.schemaData[className]) {
- return this;
- } else {
- throw new Parse.Error(Parse.Error.INVALID_JSON, `Failed to add ${className}`);
- }
- }).catch(() => {
-
- throw new Parse.Error(Parse.Error.INVALID_JSON, 'schema class name does not revalidate');
- })
- );
- }
- validateNewClass(className, fields = {}, classLevelPermissions) {
- if (this.schemaData[className]) {
- throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, `Class ${className} already exists.`);
- }
- if (!classNameIsValid(className)) {
- return {
- code: Parse.Error.INVALID_CLASS_NAME,
- error: invalidClassNameMessage(className)
- };
- }
- return this.validateSchemaData(className, fields, classLevelPermissions, []);
- }
- validateSchemaData(className, fields, classLevelPermissions, existingFieldNames) {
- for (const fieldName in fields) {
- if (existingFieldNames.indexOf(fieldName) < 0) {
- if (!fieldNameIsValid(fieldName, className)) {
- return {
- code: Parse.Error.INVALID_KEY_NAME,
- error: 'invalid field name: ' + fieldName
- };
- }
- if (!fieldNameIsValidForClass(fieldName, className)) {
- return {
- code: 136,
- error: 'field ' + fieldName + ' cannot be added'
- };
- }
- const fieldType = fields[fieldName];
- const error = fieldTypeIsInvalid(fieldType);
- if (error) return {
- code: error.code,
- error: error.message
- };
- if (fieldType.defaultValue !== undefined) {
- let defaultValueType = getType(fieldType.defaultValue);
- if (typeof defaultValueType === 'string') {
- defaultValueType = {
- type: defaultValueType
- };
- } else if (typeof defaultValueType === 'object' && fieldType.type === 'Relation') {
- return {
- code: Parse.Error.INCORRECT_TYPE,
- error: `The 'default value' option is not applicable for ${typeToString(fieldType)}`
- };
- }
- if (!dbTypeMatchesObjectType(fieldType, defaultValueType)) {
- return {
- code: Parse.Error.INCORRECT_TYPE,
- error: `schema mismatch for ${className}.${fieldName} default value; expected ${typeToString(fieldType)} but got ${typeToString(defaultValueType)}`
- };
- }
- } else if (fieldType.required) {
- if (typeof fieldType === 'object' && fieldType.type === 'Relation') {
- return {
- code: Parse.Error.INCORRECT_TYPE,
- error: `The 'required' option is not applicable for ${typeToString(fieldType)}`
- };
- }
- }
- }
- }
- for (const fieldName in defaultColumns[className]) {
- fields[fieldName] = defaultColumns[className][fieldName];
- }
- const geoPoints = Object.keys(fields).filter(key => fields[key] && fields[key].type === 'GeoPoint');
- if (geoPoints.length > 1) {
- return {
- code: Parse.Error.INCORRECT_TYPE,
- error: 'currently, only one GeoPoint field may exist in an object. Adding ' + geoPoints[1] + ' when ' + geoPoints[0] + ' already exists.'
- };
- }
- validateCLP(classLevelPermissions, fields, this.userIdRegEx);
- }
-
- async setPermissions(className, perms, newSchema) {
- if (typeof perms === 'undefined') {
- return Promise.resolve();
- }
- validateCLP(perms, newSchema, this.userIdRegEx);
- await this._dbAdapter.setClassLevelPermissions(className, perms);
- const cached = _SchemaCache.default.get(className);
- if (cached) {
- cached.classLevelPermissions = perms;
- }
- }
-
-
-
-
- enforceFieldExists(className, fieldName, type, isValidation, maintenance) {
- if (fieldName.indexOf('.') > 0) {
-
-
-
- const [x, y] = fieldName.split('.');
- fieldName = x;
- const isArrayIndex = Array.from(y).every(c => c >= '0' && c <= '9');
- if (isArrayIndex && !['sentPerUTCOffset', 'failedPerUTCOffset'].includes(fieldName)) {
- type = 'Array';
- } else {
- type = 'Object';
- }
- }
- let fieldNameToValidate = `${fieldName}`;
- if (maintenance && fieldNameToValidate.charAt(0) === '_') {
- fieldNameToValidate = fieldNameToValidate.substring(1);
- }
- if (!fieldNameIsValid(fieldNameToValidate, className)) {
- throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, `Invalid field name: ${fieldName}.`);
- }
-
- if (!type) {
- return undefined;
- }
- const expectedType = this.getExpectedType(className, fieldName);
- if (typeof type === 'string') {
- type = {
- type
- };
- }
- if (type.defaultValue !== undefined) {
- let defaultValueType = getType(type.defaultValue);
- if (typeof defaultValueType === 'string') {
- defaultValueType = {
- type: defaultValueType
- };
- }
- if (!dbTypeMatchesObjectType(type, defaultValueType)) {
- throw new Parse.Error(Parse.Error.INCORRECT_TYPE, `schema mismatch for ${className}.${fieldName} default value; expected ${typeToString(type)} but got ${typeToString(defaultValueType)}`);
- }
- }
- if (expectedType) {
- if (!dbTypeMatchesObjectType(expectedType, type)) {
- throw new Parse.Error(Parse.Error.INCORRECT_TYPE, `schema mismatch for ${className}.${fieldName}; expected ${typeToString(expectedType)} but got ${typeToString(type)}`);
- }
-
-
- if (isValidation || JSON.stringify(expectedType) === JSON.stringify(type)) {
- return undefined;
- }
-
-
- return this._dbAdapter.updateFieldOptions(className, fieldName, type);
- }
- return this._dbAdapter.addFieldIfNotExists(className, fieldName, type).catch(error => {
- if (error.code == Parse.Error.INCORRECT_TYPE) {
-
- throw error;
- }
-
-
-
- return Promise.resolve();
- }).then(() => {
- return {
- className,
- fieldName,
- type
- };
- });
- }
- ensureFields(fields) {
- for (let i = 0; i < fields.length; i += 1) {
- const {
- className,
- fieldName
- } = fields[i];
- let {
- type
- } = fields[i];
- const expectedType = this.getExpectedType(className, fieldName);
- if (typeof type === 'string') {
- type = {
- type: type
- };
- }
- if (!expectedType || !dbTypeMatchesObjectType(expectedType, type)) {
- throw new Parse.Error(Parse.Error.INVALID_JSON, `Could not add field ${fieldName}`);
- }
- }
- }
-
- deleteField(fieldName, className, database) {
- return this.deleteFields([fieldName], className, database);
- }
-
-
-
-
-
-
-
- deleteFields(fieldNames, className, database) {
- if (!classNameIsValid(className)) {
- throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, invalidClassNameMessage(className));
- }
- fieldNames.forEach(fieldName => {
- if (!fieldNameIsValid(fieldName, className)) {
- throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, `invalid field name: ${fieldName}`);
- }
-
- if (!fieldNameIsValidForClass(fieldName, className)) {
- throw new Parse.Error(136, `field ${fieldName} cannot be changed`);
- }
- });
- return this.getOneSchema(className, false, {
- clearCache: true
- }).catch(error => {
- if (error === undefined) {
- throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, `Class ${className} does not exist.`);
- } else {
- throw error;
- }
- }).then(schema => {
- fieldNames.forEach(fieldName => {
- if (!schema.fields[fieldName]) {
- throw new Parse.Error(255, `Field ${fieldName} does not exist, cannot delete.`);
- }
- });
- const schemaFields = _objectSpread({}, schema.fields);
- return database.adapter.deleteFields(className, schema, fieldNames).then(() => {
- return Promise.all(fieldNames.map(fieldName => {
- const field = schemaFields[fieldName];
- if (field && field.type === 'Relation') {
-
- return database.adapter.deleteClass(`_Join:${fieldName}:${className}`);
- }
- return Promise.resolve();
- }));
- });
- }).then(() => {
- _SchemaCache.default.clear();
- });
- }
-
-
-
- async validateObject(className, object, query, maintenance) {
- let geocount = 0;
- const schema = await this.enforceClassExists(className);
- const promises = [];
- for (const fieldName in object) {
- if (object[fieldName] && getType(object[fieldName]) === 'GeoPoint') {
- geocount++;
- }
- if (geocount > 1) {
- return Promise.reject(new Parse.Error(Parse.Error.INCORRECT_TYPE, 'there can only be one geopoint field in a class'));
- }
- }
- for (const fieldName in object) {
- if (object[fieldName] === undefined) {
- continue;
- }
- const expected = getType(object[fieldName]);
- if (!expected) {
- continue;
- }
- if (fieldName === 'ACL') {
-
- continue;
- }
- promises.push(schema.enforceFieldExists(className, fieldName, expected, true, maintenance));
- }
- const results = await Promise.all(promises);
- const enforceFields = results.filter(result => !!result);
- if (enforceFields.length !== 0) {
-
- await this.reloadData({
- clearCache: true
- });
- }
- this.ensureFields(enforceFields);
- const promise = Promise.resolve(schema);
- return thenValidateRequiredColumns(promise, className, object, query);
- }
-
- validateRequiredColumns(className, object, query) {
- const columns = requiredColumns.write[className];
- if (!columns || columns.length == 0) {
- return Promise.resolve(this);
- }
- const missingColumns = columns.filter(function (column) {
- if (query && query.objectId) {
- if (object[column] && typeof object[column] === 'object') {
-
- return object[column].__op == 'Delete';
- }
-
- return false;
- }
- return !object[column];
- });
- if (missingColumns.length > 0) {
- throw new Parse.Error(Parse.Error.INCORRECT_TYPE, missingColumns[0] + ' is required.');
- }
- return Promise.resolve(this);
- }
- testPermissionsForClassName(className, aclGroup, operation) {
- return SchemaController.testPermissions(this.getClassLevelPermissions(className), aclGroup, operation);
- }
-
- static testPermissions(classPermissions, aclGroup, operation) {
- if (!classPermissions || !classPermissions[operation]) {
- return true;
- }
- const perms = classPermissions[operation];
- if (perms['*']) {
- return true;
- }
-
- if (aclGroup.some(acl => {
- return perms[acl] === true;
- })) {
- return true;
- }
- return false;
- }
-
- static validatePermission(classPermissions, className, aclGroup, operation, action) {
- if (SchemaController.testPermissions(classPermissions, aclGroup, operation)) {
- return Promise.resolve();
- }
- if (!classPermissions || !classPermissions[operation]) {
- return true;
- }
- const perms = classPermissions[operation];
-
-
- if (perms['requiresAuthentication']) {
-
- if (!aclGroup || aclGroup.length == 0) {
- throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Permission denied, user needs to be authenticated.');
- } else if (aclGroup.indexOf('*') > -1 && aclGroup.length == 1) {
- throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Permission denied, user needs to be authenticated.');
- }
-
-
- return Promise.resolve();
- }
-
-
- const permissionField = ['get', 'find', 'count'].indexOf(operation) > -1 ? 'readUserFields' : 'writeUserFields';
-
- if (permissionField == 'writeUserFields' && operation == 'create') {
- throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, `Permission denied for action ${operation} on class ${className}.`);
- }
-
- if (Array.isArray(classPermissions[permissionField]) && classPermissions[permissionField].length > 0) {
- return Promise.resolve();
- }
- const pointerFields = classPermissions[operation].pointerFields;
- if (Array.isArray(pointerFields) && pointerFields.length > 0) {
-
- if (operation !== 'addField' || action === 'update') {
-
- return Promise.resolve();
- }
- }
- throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, `Permission denied for action ${operation} on class ${className}.`);
- }
-
- validatePermission(className, aclGroup, operation, action) {
- return SchemaController.validatePermission(this.getClassLevelPermissions(className), className, aclGroup, operation, action);
- }
- getClassLevelPermissions(className) {
- return this.schemaData[className] && this.schemaData[className].classLevelPermissions;
- }
-
-
- getExpectedType(className, fieldName) {
- if (this.schemaData[className]) {
- const expectedType = this.schemaData[className].fields[fieldName];
- return expectedType === 'map' ? 'Object' : expectedType;
- }
- return undefined;
- }
-
- hasClass(className) {
- if (this.schemaData[className]) {
- return Promise.resolve(true);
- }
- return this.reloadData().then(() => !!this.schemaData[className]);
- }
- }
- exports.SchemaController = exports.default = SchemaController;
- const load = (dbAdapter, options) => {
- const schema = new SchemaController(dbAdapter);
- ttl.duration = dbAdapter.schemaCacheTtl;
- return schema.reloadData(options).then(() => schema);
- };
- exports.load = load;
- function buildMergedSchemaObject(existingFields, putRequest) {
- const newSchema = {};
-
- const sysSchemaField = Object.keys(defaultColumns).indexOf(existingFields._id) === -1 ? [] : Object.keys(defaultColumns[existingFields._id]);
- for (const oldField in existingFields) {
- if (oldField !== '_id' && oldField !== 'ACL' && oldField !== 'updatedAt' && oldField !== 'createdAt' && oldField !== 'objectId') {
- if (sysSchemaField.length > 0 && sysSchemaField.indexOf(oldField) !== -1) {
- continue;
- }
- const fieldIsDeleted = putRequest[oldField] && putRequest[oldField].__op === 'Delete';
- if (!fieldIsDeleted) {
- newSchema[oldField] = existingFields[oldField];
- }
- }
- }
- for (const newField in putRequest) {
- if (newField !== 'objectId' && putRequest[newField].__op !== 'Delete') {
- if (sysSchemaField.length > 0 && sysSchemaField.indexOf(newField) !== -1) {
- continue;
- }
- newSchema[newField] = putRequest[newField];
- }
- }
- return newSchema;
- }
- function thenValidateRequiredColumns(schemaPromise, className, object, query) {
- return schemaPromise.then(schema => {
- return schema.validateRequiredColumns(className, object, query);
- });
- }
- function getType(obj) {
- const type = typeof obj;
- switch (type) {
- case 'boolean':
- return 'Boolean';
- case 'string':
- return 'String';
- case 'number':
- return 'Number';
- case 'map':
- case 'object':
- if (!obj) {
- return undefined;
- }
- return getObjectType(obj);
- case 'function':
- case 'symbol':
- case 'undefined':
- default:
- throw 'bad obj: ' + obj;
- }
- }
- function getObjectType(obj) {
- if (obj instanceof Array) {
- return 'Array';
- }
- if (obj.__type) {
- switch (obj.__type) {
- case 'Pointer':
- if (obj.className) {
- return {
- type: 'Pointer',
- targetClass: obj.className
- };
- }
- break;
- case 'Relation':
- if (obj.className) {
- return {
- type: 'Relation',
- targetClass: obj.className
- };
- }
- break;
- case 'File':
- if (obj.name) {
- return 'File';
- }
- break;
- case 'Date':
- if (obj.iso) {
- return 'Date';
- }
- break;
- case 'GeoPoint':
- if (obj.latitude != null && obj.longitude != null) {
- return 'GeoPoint';
- }
- break;
- case 'Bytes':
- if (obj.base64) {
- return 'Bytes';
- }
- break;
- case 'Polygon':
- if (obj.coordinates) {
- return 'Polygon';
- }
- break;
- }
- throw new Parse.Error(Parse.Error.INCORRECT_TYPE, 'This is not a valid ' + obj.__type);
- }
- if (obj['$ne']) {
- return getObjectType(obj['$ne']);
- }
- if (obj.__op) {
- switch (obj.__op) {
- case 'Increment':
- return 'Number';
- case 'Delete':
- return null;
- case 'Add':
- case 'AddUnique':
- case 'Remove':
- return 'Array';
- case 'AddRelation':
- case 'RemoveRelation':
- return {
- type: 'Relation',
- targetClass: obj.objects[0].className
- };
- case 'Batch':
- return getObjectType(obj.ops[0]);
- default:
- throw 'unexpected op: ' + obj.__op;
- }
- }
- return 'Object';
- }
|