Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Jeremy
2016-04-01 11:33:11 -04:00
15 changed files with 582 additions and 162 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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