Merge remote-tracking branch 'upstream/master'
This commit is contained in:
@@ -4,6 +4,16 @@
|
||||
|
||||
import cache from './cache';
|
||||
|
||||
function removeTrailingSlash(str) {
|
||||
if (!str) {
|
||||
return str;
|
||||
}
|
||||
if (str.endsWith("/")) {
|
||||
str = str.substr(0, str.length-1);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
export class Config {
|
||||
constructor(applicationId: string, mount: string) {
|
||||
let DatabaseAdapter = require('./DatabaseAdapter');
|
||||
@@ -24,7 +34,7 @@ export class Config {
|
||||
this.database = DatabaseAdapter.getDatabaseConnection(applicationId, cacheInfo.collectionPrefix);
|
||||
|
||||
this.serverURL = cacheInfo.serverURL;
|
||||
this.publicServerURL = cacheInfo.publicServerURL;
|
||||
this.publicServerURL = removeTrailingSlash(cacheInfo.publicServerURL);
|
||||
this.verifyUserEmails = cacheInfo.verifyUserEmails;
|
||||
this.appName = cacheInfo.appName;
|
||||
|
||||
@@ -35,7 +45,7 @@ export class Config {
|
||||
this.userController = cacheInfo.userController;
|
||||
this.authDataManager = cacheInfo.authDataManager;
|
||||
this.customPages = cacheInfo.customPages || {};
|
||||
this.mount = mount;
|
||||
this.mount = removeTrailingSlash(mount);
|
||||
this.liveQueryController = cacheInfo.liveQueryController;
|
||||
}
|
||||
|
||||
@@ -43,6 +53,11 @@ export class Config {
|
||||
this.validateEmailConfiguration({verifyUserEmails: options.verifyUserEmails,
|
||||
appName: options.appName,
|
||||
publicServerURL: options.publicServerURL})
|
||||
if (options.publicServerURL) {
|
||||
if (!options.publicServerURL.startsWith("http://") && !options.publicServerURL.startsWith("https://")) {
|
||||
throw "publicServerURL should be a valid HTTPS URL starting with https://"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static validateEmailConfiguration({verifyUserEmails, appName, publicServerURL}) {
|
||||
@@ -56,6 +71,18 @@ export class Config {
|
||||
}
|
||||
}
|
||||
|
||||
get mount() {
|
||||
var mount = this._mount;
|
||||
if (this.publicServerURL) {
|
||||
mount = this.publicServerURL;
|
||||
}
|
||||
return mount;
|
||||
}
|
||||
|
||||
set mount(newValue) {
|
||||
this._mount = newValue;
|
||||
}
|
||||
|
||||
get invalidLinkURL() {
|
||||
return this.customPages.invalidLink || `${this.publicServerURL}/apps/invalid_link.html`;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// A database adapter that works with data exported from the hosted
|
||||
// Parse database.
|
||||
|
||||
import intersect from 'intersect';
|
||||
|
||||
var mongodb = require('mongodb');
|
||||
var Parse = require('parse/node').Parse;
|
||||
|
||||
@@ -103,9 +105,14 @@ DatabaseController.prototype.redirectClassNameForKey = function(className, key)
|
||||
// batch request, that could confuse other users of the schema.
|
||||
DatabaseController.prototype.validateObject = function(className, object, query, options) {
|
||||
let schema;
|
||||
let isMaster = !('acl' in options);
|
||||
var aclGroup = options.acl || [];
|
||||
return this.loadSchema().then(s => {
|
||||
schema = s;
|
||||
return this.canAddField(schema, className, object, options.acl || []);
|
||||
if (isMaster) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return this.canAddField(schema, className, object, aclGroup);
|
||||
}).then(() => {
|
||||
return schema.validateObject(className, object, query);
|
||||
});
|
||||
@@ -487,18 +494,28 @@ DatabaseController.prototype.reduceRelationKeys = function(className, query) {
|
||||
}
|
||||
};
|
||||
|
||||
DatabaseController.prototype.addInObjectIdsIds = function(ids, query) {
|
||||
if (typeof query.objectId == 'string') {
|
||||
// Add equality op as we are sure
|
||||
// we had a constraint on that one
|
||||
query.objectId = {'$eq': query.objectId};
|
||||
DatabaseController.prototype.addInObjectIdsIds = function(ids = null, query) {
|
||||
let idsFromString = typeof query.objectId === 'string' ? [query.objectId] : null;
|
||||
let idsFromEq = query.objectId && query.objectId['$eq'] ? [query.objectId['$eq']] : null;
|
||||
let idsFromIn = query.objectId && query.objectId['$in'] ? query.objectId['$in'] : null;
|
||||
|
||||
let allIds = [idsFromString, idsFromEq, idsFromIn, ids].filter(list => list !== null);
|
||||
let totalLength = allIds.reduce((memo, list) => memo + list.length, 0);
|
||||
|
||||
let idsIntersection = [];
|
||||
if (totalLength > 125) {
|
||||
idsIntersection = intersect.big(allIds);
|
||||
} else {
|
||||
idsIntersection = intersect(allIds);
|
||||
}
|
||||
query.objectId = query.objectId || {};
|
||||
let queryIn = [].concat(query.objectId['$in'] || [], ids || []);
|
||||
// make a set and spread to remove duplicates
|
||||
// replace the $in operator as other constraints
|
||||
// may be set
|
||||
query.objectId['$in'] = [...new Set(queryIn)];
|
||||
|
||||
// Need to make sure we don't clobber existing $lt or other constraints on objectId.
|
||||
// Clobbering $eq, $in and shorthand $eq (query.objectId === 'string') constraints
|
||||
// is expected though.
|
||||
if (!('objectId' in query) || typeof query.objectId === 'string') {
|
||||
query.objectId = {};
|
||||
}
|
||||
query.objectId['$in'] = idsIntersection;
|
||||
|
||||
return query;
|
||||
}
|
||||
@@ -518,7 +535,7 @@ DatabaseController.prototype.addInObjectIdsIds = function(ids, query) {
|
||||
// anything about users, ideally. Then, improve the format of the ACL
|
||||
// arg to work like the others.
|
||||
DatabaseController.prototype.find = function(className, query, options = {}) {
|
||||
var mongoOptions = {};
|
||||
let mongoOptions = {};
|
||||
if (options.skip) {
|
||||
mongoOptions.skip = options.skip;
|
||||
}
|
||||
@@ -526,45 +543,39 @@ DatabaseController.prototype.find = function(className, query, options = {}) {
|
||||
mongoOptions.limit = options.limit;
|
||||
}
|
||||
|
||||
var isMaster = !('acl' in options);
|
||||
var aclGroup = options.acl || [];
|
||||
var acceptor = function(schema) {
|
||||
return schema.hasKeys(className, keysForQuery(query));
|
||||
};
|
||||
var schema;
|
||||
return this.loadSchema(acceptor).then((s) => {
|
||||
let isMaster = !('acl' in options);
|
||||
let aclGroup = options.acl || [];
|
||||
let acceptor = schema => schema.hasKeys(className, keysForQuery(query))
|
||||
let schema = null;
|
||||
return this.loadSchema(acceptor).then(s => {
|
||||
schema = s;
|
||||
if (options.sort) {
|
||||
mongoOptions.sort = {};
|
||||
for (var key in options.sort) {
|
||||
var mongoKey = transform.transformKey(schema, className, key);
|
||||
for (let key in options.sort) {
|
||||
let mongoKey = transform.transformKey(schema, className, key);
|
||||
mongoOptions.sort[mongoKey] = options.sort[key];
|
||||
}
|
||||
}
|
||||
|
||||
if (!isMaster) {
|
||||
var op = 'find';
|
||||
var k = Object.keys(query);
|
||||
if (k.length == 1 && typeof query.objectId == 'string') {
|
||||
op = 'get';
|
||||
}
|
||||
let op = typeof query.objectId == 'string' && Object.keys(query).length === 1 ?
|
||||
'get' :
|
||||
'find';
|
||||
return schema.validatePermission(className, aclGroup, op);
|
||||
}
|
||||
return Promise.resolve();
|
||||
}).then(() => {
|
||||
return this.reduceRelationKeys(className, query);
|
||||
}).then(() => {
|
||||
return this.reduceInRelation(className, query, schema);
|
||||
}).then(() => {
|
||||
return this.adaptiveCollection(className);
|
||||
}).then(collection => {
|
||||
var mongoWhere = transform.transformWhere(schema, className, query);
|
||||
})
|
||||
.then(() => this.reduceRelationKeys(className, query))
|
||||
.then(() => this.reduceInRelation(className, query, schema))
|
||||
.then(() => this.adaptiveCollection(className))
|
||||
.then(collection => {
|
||||
let mongoWhere = transform.transformWhere(schema, className, query);
|
||||
if (!isMaster) {
|
||||
var orParts = [
|
||||
let orParts = [
|
||||
{"_rperm" : { "$exists": false }},
|
||||
{"_rperm" : { "$in" : ["*"]}}
|
||||
];
|
||||
for (var acl of aclGroup) {
|
||||
for (let acl of aclGroup) {
|
||||
orParts.push({"_rperm" : { "$in" : [acl]}});
|
||||
}
|
||||
mongoWhere = {'$and': [mongoWhere, {'$or': orParts}]};
|
||||
|
||||
@@ -206,7 +206,14 @@ function matchesKeyConstraints(object, key, constraints) {
|
||||
}
|
||||
break;
|
||||
case '$exists':
|
||||
if (typeof object[key] === 'undefined') {
|
||||
let propertyExists = typeof object[key] !== 'undefined';
|
||||
let existenceIsRequired = constraints['$exists'];
|
||||
if (typeof constraints['$exists'] !== 'boolean') {
|
||||
// The SDK will never submit a non-boolean for $exists, but if someone
|
||||
// tries to submit a non-boolean for $exits outside the SDKs, just ignore it.
|
||||
break;
|
||||
}
|
||||
if ((!propertyExists && existenceIsRequired) || (propertyExists && !existenceIsRequired)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -449,39 +449,42 @@ function includePath(config, auth, response, path) {
|
||||
if (pointers.length == 0) {
|
||||
return response;
|
||||
}
|
||||
let pointersHash = {};
|
||||
var className = null;
|
||||
var objectIds = {};
|
||||
for (var pointer of pointers) {
|
||||
if (className === null) {
|
||||
className = pointer.className;
|
||||
} else {
|
||||
if (className != pointer.className) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_JSON,
|
||||
'inconsistent type data for include');
|
||||
}
|
||||
let className = pointer.className;
|
||||
// only include the good pointers
|
||||
if (className) {
|
||||
pointersHash[className] = pointersHash[className] || [];
|
||||
pointersHash[className].push(pointer.objectId);
|
||||
}
|
||||
objectIds[pointer.objectId] = true;
|
||||
}
|
||||
if (!className) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_JSON,
|
||||
'bad pointers');
|
||||
}
|
||||
|
||||
let queryPromises = Object.keys(pointersHash).map((className) => {
|
||||
var where = {'objectId': {'$in': pointersHash[className]}};
|
||||
var query = new RestQuery(config, auth, className, where);
|
||||
return query.execute().then((results) => {
|
||||
results.className = className;
|
||||
return Promise.resolve(results);
|
||||
})
|
||||
})
|
||||
|
||||
// Get the objects for all these object ids
|
||||
var where = {'objectId': {'$in': Object.keys(objectIds)}};
|
||||
var query = new RestQuery(config, auth, className, where);
|
||||
return query.execute().then((includeResponse) => {
|
||||
var replace = {};
|
||||
for (var obj of includeResponse.results) {
|
||||
obj.__type = 'Object';
|
||||
obj.className = className;
|
||||
return Promise.all(queryPromises).then((responses) => {
|
||||
var replace = responses.reduce((replace, includeResponse) => {
|
||||
for (var obj of includeResponse.results) {
|
||||
obj.__type = 'Object';
|
||||
obj.className = includeResponse.className;
|
||||
|
||||
if(className == "_User"){
|
||||
delete obj.sessionToken;
|
||||
if(className == "_User"){
|
||||
delete obj.sessionToken;
|
||||
}
|
||||
replace[obj.objectId] = obj;
|
||||
}
|
||||
return replace;
|
||||
}, {})
|
||||
|
||||
replace[obj.objectId] = obj;
|
||||
}
|
||||
var resp = {
|
||||
results: replacePointers(response.results, path, replace)
|
||||
};
|
||||
@@ -534,7 +537,8 @@ function findPointers(object, path) {
|
||||
// pointers inflated.
|
||||
function replacePointers(object, path, replace) {
|
||||
if (object instanceof Array) {
|
||||
return object.map((obj) => replacePointers(obj, path, replace));
|
||||
return object.map((obj) => replacePointers(obj, path, replace))
|
||||
.filter((obj) => obj != null && obj != undefined);
|
||||
}
|
||||
|
||||
if (typeof object !== 'object') {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import PromiseRouter from '../PromiseRouter';
|
||||
import PromiseRouter from '../PromiseRouter';
|
||||
import * as middleware from "../middlewares";
|
||||
import { Parse } from "parse/node";
|
||||
import { Parse } from "parse/node";
|
||||
|
||||
export class PushRouter extends PromiseRouter {
|
||||
|
||||
@@ -46,8 +46,7 @@ export class PushRouter extends PromiseRouter {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
|
||||
'Channels and query should be set at least one.');
|
||||
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED, 'Sending a push requires either "channels" or a "where" query.');
|
||||
}
|
||||
return where;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
export default {
|
||||
"appId": {
|
||||
"appId": {
|
||||
env: "PARSE_SERVER_APPLICATION_ID",
|
||||
help: "Your Parse Application ID",
|
||||
required: true
|
||||
},
|
||||
"masterKey": {
|
||||
"masterKey": {
|
||||
env: "PARSE_SERVER_MASTER_KEY",
|
||||
help: "Your Parse Master Key",
|
||||
required: true
|
||||
@@ -21,53 +21,63 @@ export default {
|
||||
return opt;
|
||||
}
|
||||
},
|
||||
"databaseURI": {
|
||||
"databaseURI": {
|
||||
env: "PARSE_SERVER_DATABASE_URI",
|
||||
help: "The full URI to your mongodb database"
|
||||
},
|
||||
"serverURL": {
|
||||
"serverURL": {
|
||||
env: "PARSE_SERVER_URL",
|
||||
help: "URL to your parse server with http:// or https://.",
|
||||
},
|
||||
"clientKey": {
|
||||
"publicServerURL": {
|
||||
env: "PARSE_PUBLIC_SERVER_URL",
|
||||
help: "Public URL to your parse server with http:// or https://.",
|
||||
},
|
||||
"clientKey": {
|
||||
env: "PARSE_SERVER_CLIENT_KEY",
|
||||
help: "Key for iOS, MacOS, tvOS clients"
|
||||
},
|
||||
"javascriptKey": {
|
||||
"javascriptKey": {
|
||||
env: "PARSE_SERVER_JAVASCRIPT_KEY",
|
||||
help: "Key for the Javascript SDK"
|
||||
},
|
||||
"restAPIKey": {
|
||||
},
|
||||
"restAPIKey": {
|
||||
env: "PARSE_SERVER_REST_API_KEY",
|
||||
help: "Key for REST calls"
|
||||
},
|
||||
"dotNetKey": {
|
||||
},
|
||||
"dotNetKey": {
|
||||
env: "PARSE_SERVER_DOT_NET_KEY",
|
||||
help: "Key for Unity and .Net SDK"
|
||||
},
|
||||
"cloud": {
|
||||
},
|
||||
"cloud": {
|
||||
env: "PARSE_SERVER_CLOUD_CODE_MAIN",
|
||||
help: "Full path to your cloud code main.js"
|
||||
},
|
||||
},
|
||||
"push": {
|
||||
env: "PARSE_SERVER_PUSH",
|
||||
help: "Configuration for push, as stringified JSON. See https://github.com/ParsePlatform/parse-server/wiki/Push",
|
||||
action: function(opt) {
|
||||
if (typeof opt == 'object') {
|
||||
return opt;
|
||||
}
|
||||
return JSON.parse(opt)
|
||||
}
|
||||
},
|
||||
"oauth": {
|
||||
"oauth": {
|
||||
env: "PARSE_SERVER_OAUTH_PROVIDERS",
|
||||
help: "Configuration for your oAuth providers, as stringified JSON. See https://github.com/ParsePlatform/parse-server/wiki/Parse-Server-Guide#oauth",
|
||||
action: function(opt) {
|
||||
if (typeof opt == 'object') {
|
||||
return opt;
|
||||
}
|
||||
return JSON.parse(opt)
|
||||
}
|
||||
},
|
||||
"fileKey": {
|
||||
"fileKey": {
|
||||
env: "PARSE_SERVER_FILE_KEY",
|
||||
help: "Key for your files",
|
||||
},
|
||||
"facebookAppIds": {
|
||||
},
|
||||
"facebookAppIds": {
|
||||
env: "PARSE_SERVER_FACEBOOK_APP_IDS",
|
||||
help: "Comma separated list for your facebook app Ids",
|
||||
type: "list",
|
||||
@@ -81,7 +91,7 @@ export default {
|
||||
action: function(opt) {
|
||||
if (opt == "true" || opt == "1") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
},
|
||||
@@ -95,22 +105,69 @@ export default {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
"mountPath": {
|
||||
"mountPath": {
|
||||
env: "PARSE_SERVER_MOUNT_PATH",
|
||||
help: "Mount path for the server, defaults to /parse",
|
||||
default: "/parse"
|
||||
},
|
||||
"databaseAdapter": {
|
||||
env: "PARSE_SERVER_DATABASE_ADAPTER",
|
||||
help: "Adapter module for the database sub-system"
|
||||
},
|
||||
"filesAdapter": {
|
||||
env: "PARSE_SERVER_FILES_ADAPTER",
|
||||
help: "Adapter module for the files sub-system"
|
||||
help: "Adapter module for the files sub-system",
|
||||
action: function action(opt) {
|
||||
if (typeof opt == 'object') {
|
||||
return opt;
|
||||
}
|
||||
try {
|
||||
return JSON.parse(opt);
|
||||
} catch(e) {}
|
||||
return opt;
|
||||
}
|
||||
},
|
||||
"emailAdapter": {
|
||||
env: "PARSE_SERVER_EMAIL_ADAPTER",
|
||||
help: "Adapter module for the email sending",
|
||||
action: function action(opt) {
|
||||
if (typeof opt == 'object') {
|
||||
return opt;
|
||||
}
|
||||
try {
|
||||
return JSON.parse(opt);
|
||||
} catch(e) {}
|
||||
return opt;
|
||||
}
|
||||
},
|
||||
"loggerAdapter": {
|
||||
env: "PARSE_SERVER_LOGGER_ADAPTER",
|
||||
help: "Adapter module for the logging sub-system"
|
||||
help: "Adapter module for the logging sub-system",
|
||||
action: function action(opt) {
|
||||
if (typeof opt == 'object') {
|
||||
return opt;
|
||||
}
|
||||
try {
|
||||
return JSON.parse(opt);
|
||||
} catch(e) {}
|
||||
return opt;
|
||||
}
|
||||
},
|
||||
"liveQuery": {
|
||||
env: "PARSE_SERVER_LIVE_QUERY_OPTIONS",
|
||||
help: "liveQuery options",
|
||||
action: function action(opt) {
|
||||
if (typeof opt == 'object') {
|
||||
return opt;
|
||||
}
|
||||
return JSON.parse(opt);
|
||||
}
|
||||
},
|
||||
"customPages": {
|
||||
env: "PARSE_SERVER_CUSTOM_PAGES",
|
||||
help: "custom pages for pasword validation and reset",
|
||||
action: function action(opt) {
|
||||
if (typeof opt == 'object') {
|
||||
return opt;
|
||||
}
|
||||
return JSON.parse(opt);
|
||||
}
|
||||
},
|
||||
"maxUploadSize": {
|
||||
env: "PARSE_SERVER_MAX_UPLOAD_SIZE",
|
||||
|
||||
@@ -187,13 +187,12 @@ export function transformKeyValue(schema, className, restKey, restValue, options
|
||||
// Returns the mongo form of the query.
|
||||
// Throws a Parse.Error if the input query is invalid.
|
||||
function transformWhere(schema, className, restWhere) {
|
||||
var mongoWhere = {};
|
||||
let mongoWhere = {};
|
||||
if (restWhere['ACL']) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_QUERY,
|
||||
'Cannot query on ACL.');
|
||||
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Cannot query on ACL.');
|
||||
}
|
||||
for (var restKey in restWhere) {
|
||||
var out = transformKeyValue(schema, className, restKey, restWhere[restKey],
|
||||
for (let restKey in restWhere) {
|
||||
let out = transformKeyValue(schema, className, restKey, restWhere[restKey],
|
||||
{query: true, validate: true});
|
||||
mongoWhere[out.key] = out.value;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user