Merge branch 'master' of github.com:ParsePlatform/parse-server into new-quickstart
This commit is contained in:
@@ -1 +1,3 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
require("../lib/cli/parse-server");
|
require("../lib/cli/parse-server");
|
||||||
|
|||||||
@@ -60,6 +60,7 @@
|
|||||||
"build": "./node_modules/.bin/babel src/ -d lib/",
|
"build": "./node_modules/.bin/babel src/ -d lib/",
|
||||||
"pretest": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=3.0.8} ./node_modules/.bin/mongodb-runner start",
|
"pretest": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=3.0.8} ./node_modules/.bin/mongodb-runner start",
|
||||||
"test": "cross-env NODE_ENV=test TESTING=1 ./node_modules/.bin/babel-node $COVERAGE_OPTION ./node_modules/jasmine/bin/jasmine.js",
|
"test": "cross-env NODE_ENV=test TESTING=1 ./node_modules/.bin/babel-node $COVERAGE_OPTION ./node_modules/jasmine/bin/jasmine.js",
|
||||||
|
"test:win": "npm run pretest && cross-env NODE_ENV=test TESTING=1 ./node_modules/.bin/babel-node ./node_modules/babel-istanbul/lib/cli.js cover -x **/spec/** ./node_modules/jasmine/bin/jasmine.js && npm run posttest",
|
||||||
"posttest": "./node_modules/.bin/mongodb-runner stop",
|
"posttest": "./node_modules/.bin/mongodb-runner stop",
|
||||||
"coverage": "cross-env COVERAGE_OPTION='./node_modules/babel-istanbul/lib/cli.js cover -x **/spec/**' npm test",
|
"coverage": "cross-env COVERAGE_OPTION='./node_modules/babel-istanbul/lib/cli.js cover -x **/spec/**' npm test",
|
||||||
"start": "node ./bin/parse-server",
|
"start": "node ./bin/parse-server",
|
||||||
|
|||||||
23
spec/DatabaseAdapter.spec.js
Normal file
23
spec/DatabaseAdapter.spec.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
let DatabaseAdapter = require('../src/DatabaseAdapter');
|
||||||
|
|
||||||
|
describe('DatabaseAdapter', () => {
|
||||||
|
it('options and URI are available to adapter', done => {
|
||||||
|
DatabaseAdapter.setAppDatabaseURI('optionsTest', 'mongodb://localhost:27017/optionsTest');
|
||||||
|
DatabaseAdapter.setAppDatabaseOptions('optionsTest', {foo: "bar"});
|
||||||
|
let optionsTestDatabaseConnection = DatabaseAdapter.getDatabaseConnection('optionsTest');
|
||||||
|
|
||||||
|
expect(optionsTestDatabaseConnection instanceof Object).toBe(true);
|
||||||
|
expect(optionsTestDatabaseConnection.adapter._options instanceof Object).toBe(true);
|
||||||
|
expect(optionsTestDatabaseConnection.adapter._options.foo).toBe("bar");
|
||||||
|
|
||||||
|
DatabaseAdapter.setAppDatabaseURI('noOptionsTest', 'mongodb://localhost:27017/noOptionsTest');
|
||||||
|
let noOptionsTestDatabaseConnection = DatabaseAdapter.getDatabaseConnection('noOptionsTest');
|
||||||
|
|
||||||
|
expect(noOptionsTestDatabaseConnection instanceof Object).toBe(true);
|
||||||
|
expect(noOptionsTestDatabaseConnection.adapter._options instanceof Object).toBe(false);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -35,8 +35,13 @@ describe('info logs', () => {
|
|||||||
size: 1,
|
size: 1,
|
||||||
level: 'info'
|
level: 'info'
|
||||||
}, (results) => {
|
}, (results) => {
|
||||||
expect(results[0].message).toEqual('testing info logs');
|
if(results.length == 0) {
|
||||||
done();
|
fail('The adapter should return non-empty results');
|
||||||
|
done();
|
||||||
|
} else {
|
||||||
|
expect(results[0].message).toEqual('testing info logs');
|
||||||
|
done();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -56,8 +61,14 @@ describe('error logs', () => {
|
|||||||
size: 1,
|
size: 1,
|
||||||
level: 'error'
|
level: 'error'
|
||||||
}, (results) => {
|
}, (results) => {
|
||||||
expect(results[0].message).toEqual('testing error logs');
|
if(results.length == 0) {
|
||||||
done();
|
fail('The adapter should return non-empty results');
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
expect(results[0].message).toEqual('testing error logs');
|
||||||
|
done();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
var OAuth = require("../src/oauth/OAuth1Client");
|
var OAuth = require("../src/authDataManager/OAuth1Client");
|
||||||
var request = require('request');
|
var request = require('request');
|
||||||
|
|
||||||
describe('OAuth', function() {
|
describe('OAuth', function() {
|
||||||
@@ -138,7 +138,7 @@ describe('OAuth', function() {
|
|||||||
|
|
||||||
["facebook", "github", "instagram", "google", "linkedin", "meetup", "twitter"].map(function(providerName){
|
["facebook", "github", "instagram", "google", "linkedin", "meetup", "twitter"].map(function(providerName){
|
||||||
it("Should validate structure of "+providerName, (done) => {
|
it("Should validate structure of "+providerName, (done) => {
|
||||||
var provider = require("../src/oauth/"+providerName);
|
var provider = require("../src/authDataManager/"+providerName);
|
||||||
jequal(typeof provider.validateAuthData, "function");
|
jequal(typeof provider.validateAuthData, "function");
|
||||||
jequal(typeof provider.validateAppId, "function");
|
jequal(typeof provider.validateAppId, "function");
|
||||||
jequal(provider.validateAuthData({}, {}).constructor, Promise.prototype.constructor);
|
jequal(provider.validateAuthData({}, {}).constructor, Promise.prototype.constructor);
|
||||||
|
|||||||
@@ -643,6 +643,7 @@ describe('miscellaneous', function() {
|
|||||||
it('test afterSave get original object on update', function(done) {
|
it('test afterSave get original object on update', function(done) {
|
||||||
var triggerTime = 0;
|
var triggerTime = 0;
|
||||||
// Register a mock beforeSave hook
|
// Register a mock beforeSave hook
|
||||||
|
|
||||||
Parse.Cloud.afterSave('GameScore', function(req, res) {
|
Parse.Cloud.afterSave('GameScore', function(req, res) {
|
||||||
var object = req.object;
|
var object = req.object;
|
||||||
expect(object instanceof Parse.Object).toBeTruthy();
|
expect(object instanceof Parse.Object).toBeTruthy();
|
||||||
@@ -693,6 +694,56 @@ describe('miscellaneous', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('test afterSave get full original object even req auth can not query it', (done) => {
|
||||||
|
var triggerTime = 0;
|
||||||
|
// Register a mock beforeSave hook
|
||||||
|
Parse.Cloud.afterSave('GameScore', function(req, res) {
|
||||||
|
var object = req.object;
|
||||||
|
var originalObject = req.original;
|
||||||
|
if (triggerTime == 0) {
|
||||||
|
// Create
|
||||||
|
} else if (triggerTime == 1) {
|
||||||
|
// Update
|
||||||
|
expect(object.get('foo')).toEqual('baz');
|
||||||
|
// Make sure we get the full originalObject
|
||||||
|
expect(originalObject instanceof Parse.Object).toBeTruthy();
|
||||||
|
expect(originalObject.get('fooAgain')).toEqual('barAgain');
|
||||||
|
expect(originalObject.id).not.toBeUndefined();
|
||||||
|
expect(originalObject.createdAt).not.toBeUndefined();
|
||||||
|
expect(originalObject.updatedAt).not.toBeUndefined();
|
||||||
|
expect(originalObject.get('foo')).toEqual('bar');
|
||||||
|
} else {
|
||||||
|
res.error();
|
||||||
|
}
|
||||||
|
triggerTime++;
|
||||||
|
res.success();
|
||||||
|
});
|
||||||
|
|
||||||
|
var obj = new Parse.Object('GameScore');
|
||||||
|
obj.set('foo', 'bar');
|
||||||
|
obj.set('fooAgain', 'barAgain');
|
||||||
|
var acl = new Parse.ACL();
|
||||||
|
// Make sure our update request can not query the object
|
||||||
|
acl.setPublicReadAccess(false);
|
||||||
|
acl.setPublicWriteAccess(true);
|
||||||
|
obj.setACL(acl);
|
||||||
|
obj.save().then(function() {
|
||||||
|
// We only update foo
|
||||||
|
obj.set('foo', 'baz');
|
||||||
|
return obj.save();
|
||||||
|
}).then(function() {
|
||||||
|
// Make sure the checking has been triggered
|
||||||
|
expect(triggerTime).toBe(2);
|
||||||
|
// Clear mock afterSave
|
||||||
|
Parse.Cloud._removeHook("Triggers", "afterSave", "GameScore");
|
||||||
|
done();
|
||||||
|
}, function(error) {
|
||||||
|
console.error(error);
|
||||||
|
fail(error);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('afterSave flattens custom operations', done => {
|
it('afterSave flattens custom operations', done => {
|
||||||
var triggerTime = 0;
|
var triggerTime = 0;
|
||||||
// Register a mock beforeSave hook
|
// Register a mock beforeSave hook
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
var request = require('request');
|
var request = require('request');
|
||||||
var passwordCrypto = require('../src/password');
|
var passwordCrypto = require('../src/password');
|
||||||
|
var Config = require('../src/Config');
|
||||||
|
|
||||||
function verifyACL(user) {
|
function verifyACL(user) {
|
||||||
const ACL = user.getACL();
|
const ACL = user.getACL();
|
||||||
@@ -904,6 +905,50 @@ describe('Parse.User testing', () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var getMockMyOauthProvider = function() {
|
||||||
|
return {
|
||||||
|
authData: {
|
||||||
|
id: "12345",
|
||||||
|
access_token: "12345",
|
||||||
|
expiration_date: new Date().toJSON(),
|
||||||
|
},
|
||||||
|
shouldError: false,
|
||||||
|
loggedOut: false,
|
||||||
|
synchronizedUserId: null,
|
||||||
|
synchronizedAuthToken: null,
|
||||||
|
synchronizedExpiration: null,
|
||||||
|
|
||||||
|
authenticate: function(options) {
|
||||||
|
if (this.shouldError) {
|
||||||
|
options.error(this, "An error occurred");
|
||||||
|
} else if (this.shouldCancel) {
|
||||||
|
options.error(this, null);
|
||||||
|
} else {
|
||||||
|
options.success(this, this.authData);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
restoreAuthentication: function(authData) {
|
||||||
|
if (!authData) {
|
||||||
|
this.synchronizedUserId = null;
|
||||||
|
this.synchronizedAuthToken = null;
|
||||||
|
this.synchronizedExpiration = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
this.synchronizedUserId = authData.id;
|
||||||
|
this.synchronizedAuthToken = authData.access_token;
|
||||||
|
this.synchronizedExpiration = authData.expiration_date;
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
getAuthType: function() {
|
||||||
|
return "myoauth";
|
||||||
|
},
|
||||||
|
deauthenticate: function() {
|
||||||
|
this.loggedOut = true;
|
||||||
|
this.restoreAuthentication(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
var ExtendedUser = Parse.User.extend({
|
var ExtendedUser = Parse.User.extend({
|
||||||
extended: function() {
|
extended: function() {
|
||||||
@@ -1284,6 +1329,151 @@ describe('Parse.User testing', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("link multiple providers", (done) => {
|
||||||
|
var provider = getMockFacebookProvider();
|
||||||
|
var mockProvider = getMockMyOauthProvider();
|
||||||
|
Parse.User._registerAuthenticationProvider(provider);
|
||||||
|
Parse.User._logInWith("facebook", {
|
||||||
|
success: function(model) {
|
||||||
|
ok(model instanceof Parse.User, "Model should be a Parse.User");
|
||||||
|
strictEqual(Parse.User.current(), model);
|
||||||
|
ok(model.extended(), "Should have used the subclass.");
|
||||||
|
strictEqual(provider.authData.id, provider.synchronizedUserId);
|
||||||
|
strictEqual(provider.authData.access_token, provider.synchronizedAuthToken);
|
||||||
|
strictEqual(provider.authData.expiration_date, provider.synchronizedExpiration);
|
||||||
|
ok(model._isLinked("facebook"), "User should be linked to facebook");
|
||||||
|
Parse.User._registerAuthenticationProvider(mockProvider);
|
||||||
|
let objectId = model.id;
|
||||||
|
model._linkWith("myoauth", {
|
||||||
|
success: function(model) {
|
||||||
|
expect(model.id).toEqual(objectId);
|
||||||
|
ok(model._isLinked("facebook"), "User should be linked to facebook");
|
||||||
|
ok(model._isLinked("myoauth"), "User should be linked to myoauth");
|
||||||
|
done();
|
||||||
|
},
|
||||||
|
error: function(error) {
|
||||||
|
console.error(error);
|
||||||
|
fail('SHould not fail');
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
error: function(model, error) {
|
||||||
|
ok(false, "linking should have worked");
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("link multiple providers and update token", (done) => {
|
||||||
|
var provider = getMockFacebookProvider();
|
||||||
|
var mockProvider = getMockMyOauthProvider();
|
||||||
|
Parse.User._registerAuthenticationProvider(provider);
|
||||||
|
Parse.User._logInWith("facebook", {
|
||||||
|
success: function(model) {
|
||||||
|
ok(model instanceof Parse.User, "Model should be a Parse.User");
|
||||||
|
strictEqual(Parse.User.current(), model);
|
||||||
|
ok(model.extended(), "Should have used the subclass.");
|
||||||
|
strictEqual(provider.authData.id, provider.synchronizedUserId);
|
||||||
|
strictEqual(provider.authData.access_token, provider.synchronizedAuthToken);
|
||||||
|
strictEqual(provider.authData.expiration_date, provider.synchronizedExpiration);
|
||||||
|
ok(model._isLinked("facebook"), "User should be linked to facebook");
|
||||||
|
Parse.User._registerAuthenticationProvider(mockProvider);
|
||||||
|
let objectId = model.id;
|
||||||
|
model._linkWith("myoauth", {
|
||||||
|
success: function(model) {
|
||||||
|
expect(model.id).toEqual(objectId);
|
||||||
|
ok(model._isLinked("facebook"), "User should be linked to facebook");
|
||||||
|
ok(model._isLinked("myoauth"), "User should be linked to myoauth");
|
||||||
|
model._linkWith("facebook", {
|
||||||
|
success: () => {
|
||||||
|
ok(model._isLinked("facebook"), "User should be linked to facebook");
|
||||||
|
ok(model._isLinked("myoauth"), "User should be linked to myoauth");
|
||||||
|
done();
|
||||||
|
},
|
||||||
|
error: () => {
|
||||||
|
fail('should link again');
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
error: function(error) {
|
||||||
|
console.error(error);
|
||||||
|
fail('SHould not fail');
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
error: function(model, error) {
|
||||||
|
ok(false, "linking should have worked");
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fail linking with existing', (done) => {
|
||||||
|
var provider = getMockFacebookProvider();
|
||||||
|
Parse.User._registerAuthenticationProvider(provider);
|
||||||
|
Parse.User._logInWith("facebook", {
|
||||||
|
success: function(model) {
|
||||||
|
Parse.User.logOut().then(() => {
|
||||||
|
let user = new Parse.User();
|
||||||
|
user.setUsername('user');
|
||||||
|
user.setPassword('password');
|
||||||
|
return user.signUp().then(() => {
|
||||||
|
// try to link here
|
||||||
|
user._linkWith('facebook', {
|
||||||
|
success: () => {
|
||||||
|
fail('should not succeed');
|
||||||
|
done();
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have authData in beforeSave and afterSave', (done) => {
|
||||||
|
|
||||||
|
Parse.Cloud.beforeSave('_User', (request, response) => {
|
||||||
|
let authData = request.object.get('authData');
|
||||||
|
expect(authData).not.toBeUndefined();
|
||||||
|
if (authData) {
|
||||||
|
expect(authData.facebook.id).toEqual('8675309');
|
||||||
|
expect(authData.facebook.access_token).toEqual('jenny');
|
||||||
|
} else {
|
||||||
|
fail('authData should be set');
|
||||||
|
}
|
||||||
|
response.success();
|
||||||
|
});
|
||||||
|
|
||||||
|
Parse.Cloud.afterSave('_User', (request, response) => {
|
||||||
|
let authData = request.object.get('authData');
|
||||||
|
expect(authData).not.toBeUndefined();
|
||||||
|
if (authData) {
|
||||||
|
expect(authData.facebook.id).toEqual('8675309');
|
||||||
|
expect(authData.facebook.access_token).toEqual('jenny');
|
||||||
|
} else {
|
||||||
|
fail('authData should be set');
|
||||||
|
}
|
||||||
|
response.success();
|
||||||
|
});
|
||||||
|
|
||||||
|
var provider = getMockFacebookProvider();
|
||||||
|
Parse.User._registerAuthenticationProvider(provider);
|
||||||
|
Parse.User._logInWith("facebook", {
|
||||||
|
success: function(model) {
|
||||||
|
Parse.Cloud._removeHook('Triggers', 'beforeSave', Parse.User.className);
|
||||||
|
Parse.Cloud._removeHook('Triggers', 'afterSave', Parse.User.className);
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('set password then change password', (done) => {
|
it('set password then change password', (done) => {
|
||||||
Parse.User.signUp('bob', 'barker').then((bob) => {
|
Parse.User.signUp('bob', 'barker').then((bob) => {
|
||||||
@@ -1780,5 +1970,41 @@ describe('Parse.User testing', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Sometimes the authData still has null on that keys
|
||||||
|
// https://github.com/ParsePlatform/parse-server/issues/935
|
||||||
|
it('should cleanup null authData keys', (done) => {
|
||||||
|
let database = new Config(Parse.applicationId).database;
|
||||||
|
database.create('_User', {
|
||||||
|
username: 'user',
|
||||||
|
password: '$2a$10$8/wZJyEuiEaobBBqzTG.jeY.XSFJd0rzaN//ososvEI4yLqI.4aie',
|
||||||
|
_auth_data_facebook: null
|
||||||
|
}, {}).then(() => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
request.get({
|
||||||
|
url: 'http://localhost:8378/1/login?username=user&password=test',
|
||||||
|
headers: {
|
||||||
|
'X-Parse-Application-Id': 'test',
|
||||||
|
'X-Parse-Master-Key': 'test',
|
||||||
|
},
|
||||||
|
json: true
|
||||||
|
}, (err, res, body) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve(body);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}).then((user) => {
|
||||||
|
let authData = user.authData;
|
||||||
|
expect(user.username).toEqual('user');
|
||||||
|
expect(authData).toBeUndefined();
|
||||||
|
done();
|
||||||
|
}).catch((err) => {
|
||||||
|
fail('this should not fail');
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -148,7 +148,8 @@ describe('rest create', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('handles no anonymous users config', (done) => {
|
it('handles no anonymous users config', (done) => {
|
||||||
var NoAnnonConfig = Object.assign({}, config, {enableAnonymousUsers: false});
|
var NoAnnonConfig = Object.assign({}, config);
|
||||||
|
NoAnnonConfig.authDataManager.setEnableAnonymousUsers(false);
|
||||||
var data1 = {
|
var data1 = {
|
||||||
authData: {
|
authData: {
|
||||||
anonymous: {
|
anonymous: {
|
||||||
@@ -162,6 +163,7 @@ describe('rest create', () => {
|
|||||||
}, (err) => {
|
}, (err) => {
|
||||||
expect(err.code).toEqual(Parse.Error.UNSUPPORTED_SERVICE);
|
expect(err.code).toEqual(Parse.Error.UNSUPPORTED_SERVICE);
|
||||||
expect(err.message).toEqual('This authentication method is unsupported.');
|
expect(err.message).toEqual('This authentication method is unsupported.');
|
||||||
|
NoAnnonConfig.authDataManager.setEnableAnonymousUsers(true);
|
||||||
done();
|
done();
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ jasmine.DEFAULT_TIMEOUT_INTERVAL = 2000;
|
|||||||
var cache = require('../src/cache').default;
|
var cache = require('../src/cache').default;
|
||||||
var DatabaseAdapter = require('../src/DatabaseAdapter');
|
var DatabaseAdapter = require('../src/DatabaseAdapter');
|
||||||
var express = require('express');
|
var express = require('express');
|
||||||
var facebook = require('../src/oauth/facebook');
|
var facebook = require('../src/authDataManager/facebook');
|
||||||
var ParseServer = require('../src/index').ParseServer;
|
var ParseServer = require('../src/index').ParseServer;
|
||||||
|
var path = require('path');
|
||||||
|
|
||||||
var databaseURI = process.env.DATABASE_URI;
|
var databaseURI = process.env.DATABASE_URI;
|
||||||
var cloudMain = process.env.CLOUD_CODE_MAIN || '../spec/cloud/main.js';
|
var cloudMain = process.env.CLOUD_CODE_MAIN || '../spec/cloud/main.js';
|
||||||
@@ -26,7 +27,7 @@ var defaultConfiguration = {
|
|||||||
collectionPrefix: 'test_',
|
collectionPrefix: 'test_',
|
||||||
fileKey: 'test',
|
fileKey: 'test',
|
||||||
push: {
|
push: {
|
||||||
'ios': {
|
'ios': {
|
||||||
cert: 'prodCert.pem',
|
cert: 'prodCert.pem',
|
||||||
key: 'prodKey.pem',
|
key: 'prodKey.pem',
|
||||||
production: true,
|
production: true,
|
||||||
@@ -36,7 +37,7 @@ var defaultConfiguration = {
|
|||||||
oauth: { // Override the facebook provider
|
oauth: { // Override the facebook provider
|
||||||
facebook: mockFacebook(),
|
facebook: mockFacebook(),
|
||||||
myoauth: {
|
myoauth: {
|
||||||
module: "../spec/myoauth" // relative path as it's run from src
|
module: path.resolve(__dirname, "myoauth") // relative path as it's run from src
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -81,7 +82,7 @@ afterEach(function(done) {
|
|||||||
Parse.User.logOut().then(() => {
|
Parse.User.logOut().then(() => {
|
||||||
return clearData();
|
return clearData();
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
DatabaseAdapter.clearDatabaseURIs();
|
DatabaseAdapter.clearDatabaseSettings();
|
||||||
done();
|
done();
|
||||||
}, (error) => {
|
}, (error) => {
|
||||||
console.log('error in clearData', error);
|
console.log('error in clearData', error);
|
||||||
|
|||||||
@@ -23,6 +23,27 @@ var hasAllPODobject = () => {
|
|||||||
return obj;
|
return obj;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let defaultClassLevelPermissions = {
|
||||||
|
find: {
|
||||||
|
'*': true
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
'*': true
|
||||||
|
},
|
||||||
|
get: {
|
||||||
|
'*': true
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
'*': true
|
||||||
|
},
|
||||||
|
addField: {
|
||||||
|
'*': true
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
'*': true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var plainOldDataSchema = {
|
var plainOldDataSchema = {
|
||||||
className: 'HasAllPOD',
|
className: 'HasAllPOD',
|
||||||
fields: {
|
fields: {
|
||||||
@@ -40,7 +61,8 @@ var plainOldDataSchema = {
|
|||||||
aArray: {type: 'Array'},
|
aArray: {type: 'Array'},
|
||||||
aGeoPoint: {type: 'GeoPoint'},
|
aGeoPoint: {type: 'GeoPoint'},
|
||||||
aFile: {type: 'File'}
|
aFile: {type: 'File'}
|
||||||
}
|
},
|
||||||
|
classLevelPermissions: defaultClassLevelPermissions
|
||||||
};
|
};
|
||||||
|
|
||||||
var pointersAndRelationsSchema = {
|
var pointersAndRelationsSchema = {
|
||||||
@@ -61,6 +83,7 @@ var pointersAndRelationsSchema = {
|
|||||||
targetClass: 'HasAllPOD',
|
targetClass: 'HasAllPOD',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
classLevelPermissions: defaultClassLevelPermissions
|
||||||
}
|
}
|
||||||
|
|
||||||
var noAuthHeaders = {
|
var noAuthHeaders = {
|
||||||
@@ -296,7 +319,8 @@ describe('schemas', () => {
|
|||||||
objectId: {type: 'String'},
|
objectId: {type: 'String'},
|
||||||
foo: {type: 'Number'},
|
foo: {type: 'Number'},
|
||||||
ptr: {type: 'Pointer', targetClass: 'SomeClass'},
|
ptr: {type: 'Pointer', targetClass: 'SomeClass'},
|
||||||
}
|
},
|
||||||
|
classLevelPermissions: defaultClassLevelPermissions
|
||||||
});
|
});
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@@ -318,7 +342,8 @@ describe('schemas', () => {
|
|||||||
createdAt: {type: 'Date'},
|
createdAt: {type: 'Date'},
|
||||||
updatedAt: {type: 'Date'},
|
updatedAt: {type: 'Date'},
|
||||||
objectId: {type: 'String'},
|
objectId: {type: 'String'},
|
||||||
}
|
},
|
||||||
|
classLevelPermissions: defaultClassLevelPermissions
|
||||||
});
|
});
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@@ -490,7 +515,8 @@ describe('schemas', () => {
|
|||||||
"objectId": {"type": "String"},
|
"objectId": {"type": "String"},
|
||||||
"updatedAt": {"type": "Date"},
|
"updatedAt": {"type": "Date"},
|
||||||
"geo2": {"type": "GeoPoint"},
|
"geo2": {"type": "GeoPoint"},
|
||||||
}
|
},
|
||||||
|
classLevelPermissions: defaultClassLevelPermissions
|
||||||
})).toEqual(undefined);
|
})).toEqual(undefined);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@@ -539,6 +565,7 @@ describe('schemas', () => {
|
|||||||
"updatedAt": {"type": "Date"},
|
"updatedAt": {"type": "Date"},
|
||||||
"newField": {"type": "String"},
|
"newField": {"type": "String"},
|
||||||
},
|
},
|
||||||
|
classLevelPermissions: defaultClassLevelPermissions
|
||||||
})).toEqual(undefined);
|
})).toEqual(undefined);
|
||||||
request.get({
|
request.get({
|
||||||
url: 'http://localhost:8378/1/schemas/NewClass',
|
url: 'http://localhost:8378/1/schemas/NewClass',
|
||||||
@@ -553,7 +580,8 @@ describe('schemas', () => {
|
|||||||
updatedAt: {type: 'Date'},
|
updatedAt: {type: 'Date'},
|
||||||
objectId: {type: 'String'},
|
objectId: {type: 'String'},
|
||||||
newField: {type: 'String'},
|
newField: {type: 'String'},
|
||||||
}
|
},
|
||||||
|
classLevelPermissions: defaultClassLevelPermissions
|
||||||
});
|
});
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@@ -590,7 +618,8 @@ describe('schemas', () => {
|
|||||||
emailVerified: {type: 'Boolean'},
|
emailVerified: {type: 'Boolean'},
|
||||||
newField: {type: 'String'},
|
newField: {type: 'String'},
|
||||||
ACL: {type: 'ACL'}
|
ACL: {type: 'ACL'}
|
||||||
}
|
},
|
||||||
|
classLevelPermissions: defaultClassLevelPermissions
|
||||||
});
|
});
|
||||||
request.get({
|
request.get({
|
||||||
url: 'http://localhost:8378/1/schemas/_User',
|
url: 'http://localhost:8378/1/schemas/_User',
|
||||||
@@ -610,7 +639,8 @@ describe('schemas', () => {
|
|||||||
emailVerified: {type: 'Boolean'},
|
emailVerified: {type: 'Boolean'},
|
||||||
newField: {type: 'String'},
|
newField: {type: 'String'},
|
||||||
ACL: {type: 'ACL'}
|
ACL: {type: 'ACL'}
|
||||||
}
|
},
|
||||||
|
classLevelPermissions: defaultClassLevelPermissions
|
||||||
});
|
});
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@@ -656,7 +686,8 @@ describe('schemas', () => {
|
|||||||
aNewString: {type: 'String'},
|
aNewString: {type: 'String'},
|
||||||
aNewPointer: {type: 'Pointer', targetClass: 'HasAllPOD'},
|
aNewPointer: {type: 'Pointer', targetClass: 'HasAllPOD'},
|
||||||
aNewRelation: {type: 'Relation', targetClass: 'HasAllPOD'},
|
aNewRelation: {type: 'Relation', targetClass: 'HasAllPOD'},
|
||||||
}
|
},
|
||||||
|
classLevelPermissions: defaultClassLevelPermissions
|
||||||
});
|
});
|
||||||
var obj2 = new Parse.Object('HasAllPOD');
|
var obj2 = new Parse.Object('HasAllPOD');
|
||||||
obj2.set('aNewPointer', obj1);
|
obj2.set('aNewPointer', obj1);
|
||||||
@@ -872,4 +903,597 @@ describe('schemas', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should set/get schema permissions', done => {
|
||||||
|
request.post({
|
||||||
|
url: 'http://localhost:8378/1/schemas/AClass',
|
||||||
|
headers: masterKeyHeaders,
|
||||||
|
json: true,
|
||||||
|
body: {
|
||||||
|
classLevelPermissions: {
|
||||||
|
find: {
|
||||||
|
'*': true
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
'role:admin': true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, (error, response, body) => {
|
||||||
|
expect(error).toEqual(null);
|
||||||
|
request.get({
|
||||||
|
url: 'http://localhost:8378/1/schemas/AClass',
|
||||||
|
headers: masterKeyHeaders,
|
||||||
|
json: true,
|
||||||
|
}, (error, response, body) => {
|
||||||
|
expect(response.statusCode).toEqual(200);
|
||||||
|
expect(response.body.classLevelPermissions).toEqual({
|
||||||
|
find: {
|
||||||
|
'*': true
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
'role:admin': true
|
||||||
|
},
|
||||||
|
get: {
|
||||||
|
'*': true
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
'*': true
|
||||||
|
},
|
||||||
|
addField: {
|
||||||
|
'*': true
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
'*': true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fail setting schema permissions with invalid key', done => {
|
||||||
|
|
||||||
|
let object = new Parse.Object('AClass');
|
||||||
|
object.save().then(() => {
|
||||||
|
request.put({
|
||||||
|
url: 'http://localhost:8378/1/schemas/AClass',
|
||||||
|
headers: masterKeyHeaders,
|
||||||
|
json: true,
|
||||||
|
body: {
|
||||||
|
classLevelPermissions: {
|
||||||
|
find: {
|
||||||
|
'*': true
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
'role:admin': true
|
||||||
|
},
|
||||||
|
dummy: {
|
||||||
|
'some': true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, (error, response, body) => {
|
||||||
|
expect(error).toEqual(null);
|
||||||
|
expect(body.code).toEqual(107);
|
||||||
|
expect(body.error).toEqual('dummy is not a valid operation for class level permissions');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not be able to add a field', done => {
|
||||||
|
request.post({
|
||||||
|
url: 'http://localhost:8378/1/schemas/AClass',
|
||||||
|
headers: masterKeyHeaders,
|
||||||
|
json: true,
|
||||||
|
body: {
|
||||||
|
classLevelPermissions: {
|
||||||
|
find: {
|
||||||
|
'*': true
|
||||||
|
},
|
||||||
|
addField: {
|
||||||
|
'role:admin': true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, (error, response, body) => {
|
||||||
|
expect(error).toEqual(null);
|
||||||
|
let object = new Parse.Object('AClass');
|
||||||
|
object.set('hello', 'world');
|
||||||
|
return object.save().then(() => {
|
||||||
|
fail('should not be able to add a field');
|
||||||
|
done();
|
||||||
|
}, (err) => {
|
||||||
|
expect(err.message).toEqual('Permission denied for this action.');
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not be able to add a field', done => {
|
||||||
|
request.post({
|
||||||
|
url: 'http://localhost:8378/1/schemas/AClass',
|
||||||
|
headers: masterKeyHeaders,
|
||||||
|
json: true,
|
||||||
|
body: {
|
||||||
|
classLevelPermissions: {
|
||||||
|
find: {
|
||||||
|
'*': true
|
||||||
|
},
|
||||||
|
addField: {
|
||||||
|
'*': true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, (error, response, body) => {
|
||||||
|
expect(error).toEqual(null);
|
||||||
|
let object = new Parse.Object('AClass');
|
||||||
|
object.set('hello', 'world');
|
||||||
|
return object.save().then(() => {
|
||||||
|
done();
|
||||||
|
}, (err) => {
|
||||||
|
fail('should be able to add a field');
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw with invalid userId (>10 chars)', done => {
|
||||||
|
request.post({
|
||||||
|
url: 'http://localhost:8378/1/schemas/AClass',
|
||||||
|
headers: masterKeyHeaders,
|
||||||
|
json: true,
|
||||||
|
body: {
|
||||||
|
classLevelPermissions: {
|
||||||
|
find: {
|
||||||
|
'1234567890A': true
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, (error, response, body) => {
|
||||||
|
expect(body.error).toEqual("'1234567890A' is not a valid key for class level permissions");
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw with invalid userId (<10 chars)', done => {
|
||||||
|
request.post({
|
||||||
|
url: 'http://localhost:8378/1/schemas/AClass',
|
||||||
|
headers: masterKeyHeaders,
|
||||||
|
json: true,
|
||||||
|
body: {
|
||||||
|
classLevelPermissions: {
|
||||||
|
find: {
|
||||||
|
'a12345678': true
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, (error, response, body) => {
|
||||||
|
expect(body.error).toEqual("'a12345678' is not a valid key for class level permissions");
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw with invalid userId (invalid char)', done => {
|
||||||
|
request.post({
|
||||||
|
url: 'http://localhost:8378/1/schemas/AClass',
|
||||||
|
headers: masterKeyHeaders,
|
||||||
|
json: true,
|
||||||
|
body: {
|
||||||
|
classLevelPermissions: {
|
||||||
|
find: {
|
||||||
|
'12345_6789': true
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, (error, response, body) => {
|
||||||
|
expect(body.error).toEqual("'12345_6789' is not a valid key for class level permissions");
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw with invalid * (spaces)', done => {
|
||||||
|
request.post({
|
||||||
|
url: 'http://localhost:8378/1/schemas/AClass',
|
||||||
|
headers: masterKeyHeaders,
|
||||||
|
json: true,
|
||||||
|
body: {
|
||||||
|
classLevelPermissions: {
|
||||||
|
find: {
|
||||||
|
' *': true
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, (error, response, body) => {
|
||||||
|
expect(body.error).toEqual("' *' is not a valid key for class level permissions");
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw with invalid * (spaces)', done => {
|
||||||
|
request.post({
|
||||||
|
url: 'http://localhost:8378/1/schemas/AClass',
|
||||||
|
headers: masterKeyHeaders,
|
||||||
|
json: true,
|
||||||
|
body: {
|
||||||
|
classLevelPermissions: {
|
||||||
|
find: {
|
||||||
|
'* ': true
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, (error, response, body) => {
|
||||||
|
expect(body.error).toEqual("'* ' is not a valid key for class level permissions");
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw with invalid value', done => {
|
||||||
|
request.post({
|
||||||
|
url: 'http://localhost:8378/1/schemas/AClass',
|
||||||
|
headers: masterKeyHeaders,
|
||||||
|
json: true,
|
||||||
|
body: {
|
||||||
|
classLevelPermissions: {
|
||||||
|
find: {
|
||||||
|
'*': 1
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, (error, response, body) => {
|
||||||
|
expect(body.error).toEqual("'1' is not a valid value for class level permissions find:*:1");
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw with invalid value', done => {
|
||||||
|
request.post({
|
||||||
|
url: 'http://localhost:8378/1/schemas/AClass',
|
||||||
|
headers: masterKeyHeaders,
|
||||||
|
json: true,
|
||||||
|
body: {
|
||||||
|
classLevelPermissions: {
|
||||||
|
find: {
|
||||||
|
'*': ""
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, (error, response, body) => {
|
||||||
|
expect(body.error).toEqual("'' is not a valid value for class level permissions find:*:");
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
function setPermissionsOnClass(className, permissions, doPut) {
|
||||||
|
let op = request.post;
|
||||||
|
if (doPut)
|
||||||
|
{
|
||||||
|
op = request.put;
|
||||||
|
}
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
op({
|
||||||
|
url: 'http://localhost:8378/1/schemas/'+className,
|
||||||
|
headers: masterKeyHeaders,
|
||||||
|
json: true,
|
||||||
|
body: {
|
||||||
|
classLevelPermissions: permissions
|
||||||
|
}
|
||||||
|
}, (error, response, body) => {
|
||||||
|
if (error) {
|
||||||
|
return reject(error);
|
||||||
|
}
|
||||||
|
if (body.error) {
|
||||||
|
return reject(body);
|
||||||
|
}
|
||||||
|
return resolve(body);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
it('validate CLP 1', done => {
|
||||||
|
let user = new Parse.User();
|
||||||
|
user.setUsername('user');
|
||||||
|
user.setPassword('user');
|
||||||
|
|
||||||
|
let admin = new Parse.User();
|
||||||
|
admin.setUsername('admin');
|
||||||
|
admin.setPassword('admin');
|
||||||
|
|
||||||
|
let role = new Parse.Role('admin', new Parse.ACL());
|
||||||
|
|
||||||
|
setPermissionsOnClass('AClass', {
|
||||||
|
'find': {
|
||||||
|
'role:admin': true
|
||||||
|
}
|
||||||
|
}).then(() => {
|
||||||
|
return Parse.Object.saveAll([user, admin, role], {useMasterKey: true});
|
||||||
|
}).then(()=> {
|
||||||
|
role.relation('users').add(admin);
|
||||||
|
return role.save(null, {useMasterKey: true});
|
||||||
|
}).then(() => {
|
||||||
|
return Parse.User.logIn('user', 'user').then(() => {
|
||||||
|
let obj = new Parse.Object('AClass');
|
||||||
|
return obj.save();
|
||||||
|
})
|
||||||
|
}).then(() => {
|
||||||
|
let query = new Parse.Query('AClass');
|
||||||
|
return query.find().then((err) => {
|
||||||
|
fail('Use should hot be able to find!')
|
||||||
|
}, (err) => {
|
||||||
|
expect(err.message).toEqual('Permission denied for this action.');
|
||||||
|
return Promise.resolve();
|
||||||
|
})
|
||||||
|
}).then(() => {
|
||||||
|
return Parse.User.logIn('admin', 'admin');
|
||||||
|
}).then( () => {
|
||||||
|
let query = new Parse.Query('AClass');
|
||||||
|
return query.find();
|
||||||
|
}).then((results) => {
|
||||||
|
expect(results.length).toBe(1);
|
||||||
|
done();
|
||||||
|
}, () => {
|
||||||
|
fail("should not fail!");
|
||||||
|
done();
|
||||||
|
}).catch( (err) => {
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
it('validate CLP 2', done => {
|
||||||
|
let user = new Parse.User();
|
||||||
|
user.setUsername('user');
|
||||||
|
user.setPassword('user');
|
||||||
|
|
||||||
|
let admin = new Parse.User();
|
||||||
|
admin.setUsername('admin');
|
||||||
|
admin.setPassword('admin');
|
||||||
|
|
||||||
|
let role = new Parse.Role('admin', new Parse.ACL());
|
||||||
|
|
||||||
|
setPermissionsOnClass('AClass', {
|
||||||
|
'find': {
|
||||||
|
'role:admin': true
|
||||||
|
}
|
||||||
|
}).then(() => {
|
||||||
|
return Parse.Object.saveAll([user, admin, role], {useMasterKey: true});
|
||||||
|
}).then(()=> {
|
||||||
|
role.relation('users').add(admin);
|
||||||
|
return role.save(null, {useMasterKey: true});
|
||||||
|
}).then(() => {
|
||||||
|
return Parse.User.logIn('user', 'user').then(() => {
|
||||||
|
let obj = new Parse.Object('AClass');
|
||||||
|
return obj.save();
|
||||||
|
})
|
||||||
|
}).then(() => {
|
||||||
|
let query = new Parse.Query('AClass');
|
||||||
|
return query.find().then((err) => {
|
||||||
|
fail('User should not be able to find!')
|
||||||
|
}, (err) => {
|
||||||
|
expect(err.message).toEqual('Permission denied for this action.');
|
||||||
|
return Promise.resolve();
|
||||||
|
})
|
||||||
|
}).then(() => {
|
||||||
|
// let everyone see it now
|
||||||
|
return setPermissionsOnClass('AClass', {
|
||||||
|
'find': {
|
||||||
|
'role:admin': true,
|
||||||
|
'*': true
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
|
}).then(() => {
|
||||||
|
let query = new Parse.Query('AClass');
|
||||||
|
return query.find().then((result) => {
|
||||||
|
expect(result.length).toBe(1);
|
||||||
|
}, (err) => {
|
||||||
|
fail('User should be able to find!')
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
}).then(() => {
|
||||||
|
return Parse.User.logIn('admin', 'admin');
|
||||||
|
}).then( () => {
|
||||||
|
let query = new Parse.Query('AClass');
|
||||||
|
return query.find();
|
||||||
|
}).then((results) => {
|
||||||
|
expect(results.length).toBe(1);
|
||||||
|
done();
|
||||||
|
}, (err) => {
|
||||||
|
fail("should not fail!");
|
||||||
|
done();
|
||||||
|
}).catch( (err) => {
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
it('validate CLP 3', done => {
|
||||||
|
let user = new Parse.User();
|
||||||
|
user.setUsername('user');
|
||||||
|
user.setPassword('user');
|
||||||
|
|
||||||
|
let admin = new Parse.User();
|
||||||
|
admin.setUsername('admin');
|
||||||
|
admin.setPassword('admin');
|
||||||
|
|
||||||
|
let role = new Parse.Role('admin', new Parse.ACL());
|
||||||
|
|
||||||
|
setPermissionsOnClass('AClass', {
|
||||||
|
'find': {
|
||||||
|
'role:admin': true
|
||||||
|
}
|
||||||
|
}).then(() => {
|
||||||
|
return Parse.Object.saveAll([user, admin, role], {useMasterKey: true});
|
||||||
|
}).then(()=> {
|
||||||
|
role.relation('users').add(admin);
|
||||||
|
return role.save(null, {useMasterKey: true});
|
||||||
|
}).then(() => {
|
||||||
|
return Parse.User.logIn('user', 'user').then(() => {
|
||||||
|
let obj = new Parse.Object('AClass');
|
||||||
|
return obj.save();
|
||||||
|
})
|
||||||
|
}).then(() => {
|
||||||
|
let query = new Parse.Query('AClass');
|
||||||
|
return query.find().then((err) => {
|
||||||
|
fail('User should not be able to find!')
|
||||||
|
}, (err) => {
|
||||||
|
expect(err.message).toEqual('Permission denied for this action.');
|
||||||
|
return Promise.resolve();
|
||||||
|
})
|
||||||
|
}).then(() => {
|
||||||
|
// delete all CLP
|
||||||
|
return setPermissionsOnClass('AClass', null, true);
|
||||||
|
}).then(() => {
|
||||||
|
let query = new Parse.Query('AClass');
|
||||||
|
return query.find().then((result) => {
|
||||||
|
expect(result.length).toBe(1);
|
||||||
|
}, (err) => {
|
||||||
|
fail('User should be able to find!')
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
}).then(() => {
|
||||||
|
return Parse.User.logIn('admin', 'admin');
|
||||||
|
}).then( () => {
|
||||||
|
let query = new Parse.Query('AClass');
|
||||||
|
return query.find();
|
||||||
|
}).then((results) => {
|
||||||
|
expect(results.length).toBe(1);
|
||||||
|
done();
|
||||||
|
}, (err) => {
|
||||||
|
fail("should not fail!");
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('validate CLP 4', done => {
|
||||||
|
let user = new Parse.User();
|
||||||
|
user.setUsername('user');
|
||||||
|
user.setPassword('user');
|
||||||
|
|
||||||
|
let admin = new Parse.User();
|
||||||
|
admin.setUsername('admin');
|
||||||
|
admin.setPassword('admin');
|
||||||
|
|
||||||
|
let role = new Parse.Role('admin', new Parse.ACL());
|
||||||
|
|
||||||
|
setPermissionsOnClass('AClass', {
|
||||||
|
'find': {
|
||||||
|
'role:admin': true
|
||||||
|
}
|
||||||
|
}).then(() => {
|
||||||
|
return Parse.Object.saveAll([user, admin, role], {useMasterKey: true});
|
||||||
|
}).then(()=> {
|
||||||
|
role.relation('users').add(admin);
|
||||||
|
return role.save(null, {useMasterKey: true});
|
||||||
|
}).then(() => {
|
||||||
|
return Parse.User.logIn('user', 'user').then(() => {
|
||||||
|
let obj = new Parse.Object('AClass');
|
||||||
|
return obj.save();
|
||||||
|
})
|
||||||
|
}).then(() => {
|
||||||
|
let query = new Parse.Query('AClass');
|
||||||
|
return query.find().then((err) => {
|
||||||
|
fail('User should not be able to find!')
|
||||||
|
}, (err) => {
|
||||||
|
expect(err.message).toEqual('Permission denied for this action.');
|
||||||
|
return Promise.resolve();
|
||||||
|
})
|
||||||
|
}).then(() => {
|
||||||
|
// borked CLP should not affec security
|
||||||
|
return setPermissionsOnClass('AClass', {
|
||||||
|
'found': {
|
||||||
|
'role:admin': true
|
||||||
|
}
|
||||||
|
}, true).then(() => {
|
||||||
|
fail("Should not be able to save a borked CLP");
|
||||||
|
}, () => {
|
||||||
|
return Promise.resolve();
|
||||||
|
})
|
||||||
|
}).then(() => {
|
||||||
|
let query = new Parse.Query('AClass');
|
||||||
|
return query.find().then((result) => {
|
||||||
|
fail('User should not be able to find!')
|
||||||
|
}, (err) => {
|
||||||
|
expect(err.message).toEqual('Permission denied for this action.');
|
||||||
|
return Promise.resolve();
|
||||||
|
});
|
||||||
|
}).then(() => {
|
||||||
|
return Parse.User.logIn('admin', 'admin');
|
||||||
|
}).then( () => {
|
||||||
|
let query = new Parse.Query('AClass');
|
||||||
|
return query.find();
|
||||||
|
}).then((results) => {
|
||||||
|
expect(results.length).toBe(1);
|
||||||
|
done();
|
||||||
|
}, (err) => {
|
||||||
|
fail("should not fail!");
|
||||||
|
done();
|
||||||
|
}).catch( (err) => {
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
it('validate CLP 5', done => {
|
||||||
|
let user = new Parse.User();
|
||||||
|
user.setUsername('user');
|
||||||
|
user.setPassword('user');
|
||||||
|
|
||||||
|
let user2 = new Parse.User();
|
||||||
|
user2.setUsername('user2');
|
||||||
|
user2.setPassword('user2');
|
||||||
|
let admin = new Parse.User();
|
||||||
|
admin.setUsername('admin');
|
||||||
|
admin.setPassword('admin');
|
||||||
|
|
||||||
|
let role = new Parse.Role('admin', new Parse.ACL());
|
||||||
|
|
||||||
|
Promise.resolve().then(() => {
|
||||||
|
return Parse.Object.saveAll([user, user2, admin, role], {useMasterKey: true});
|
||||||
|
}).then(()=> {
|
||||||
|
role.relation('users').add(admin);
|
||||||
|
return role.save(null, {useMasterKey: true}).then(() => {
|
||||||
|
let perm = {
|
||||||
|
find: {}
|
||||||
|
};
|
||||||
|
// let the user find
|
||||||
|
perm['find'][user.id] = true;
|
||||||
|
return setPermissionsOnClass('AClass', perm);
|
||||||
|
})
|
||||||
|
}).then(() => {
|
||||||
|
return Parse.User.logIn('user', 'user').then(() => {
|
||||||
|
let obj = new Parse.Object('AClass');
|
||||||
|
return obj.save();
|
||||||
|
})
|
||||||
|
}).then(() => {
|
||||||
|
let query = new Parse.Query('AClass');
|
||||||
|
return query.find().then((res) => {
|
||||||
|
expect(res.length).toEqual(1);
|
||||||
|
}, (err) => {
|
||||||
|
fail('User should be able to find!')
|
||||||
|
return Promise.resolve();
|
||||||
|
})
|
||||||
|
}).then(() => {
|
||||||
|
return Parse.User.logIn('admin', 'admin');
|
||||||
|
}).then( () => {
|
||||||
|
let query = new Parse.Query('AClass');
|
||||||
|
return query.find();
|
||||||
|
}).then((results) => {
|
||||||
|
fail("should not be able to read!");
|
||||||
|
return Promise.resolve();
|
||||||
|
}, (err) => {
|
||||||
|
expect(err.message).toEqual('Permission denied for this action.');
|
||||||
|
return Promise.resolve();
|
||||||
|
}).then(() => {
|
||||||
|
return Parse.User.logIn('user2', 'user2');
|
||||||
|
}).then( () => {
|
||||||
|
let query = new Parse.Query('AClass');
|
||||||
|
return query.find();
|
||||||
|
}).then((results) => {
|
||||||
|
fail("should not be able to read!");
|
||||||
|
return Promise.resolve();
|
||||||
|
}, (err) => {
|
||||||
|
expect(err.message).toEqual('Permission denied for this action.');
|
||||||
|
return Promise.resolve();
|
||||||
|
}).then(() => {
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,12 +10,14 @@ const MongoSchemaCollectionName = '_SCHEMA';
|
|||||||
export class MongoStorageAdapter {
|
export class MongoStorageAdapter {
|
||||||
// Private
|
// Private
|
||||||
_uri: string;
|
_uri: string;
|
||||||
|
_options: Object;
|
||||||
// Public
|
// Public
|
||||||
connectionPromise;
|
connectionPromise;
|
||||||
database;
|
database;
|
||||||
|
|
||||||
constructor(uri: string) {
|
constructor(uri: string, options: Object) {
|
||||||
this._uri = uri;
|
this._uri = uri;
|
||||||
|
this._options = options;
|
||||||
}
|
}
|
||||||
|
|
||||||
connect() {
|
connect() {
|
||||||
@@ -23,7 +25,7 @@ export class MongoStorageAdapter {
|
|||||||
return this.connectionPromise;
|
return this.connectionPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.connectionPromise = MongoClient.connect(this._uri).then(database => {
|
this.connectionPromise = MongoClient.connect(this._uri, this._options).then(database => {
|
||||||
this.database = database;
|
this.database = database;
|
||||||
});
|
});
|
||||||
return this.connectionPromise;
|
return this.connectionPromise;
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ export class Config {
|
|||||||
this.restAPIKey = cacheInfo.restAPIKey;
|
this.restAPIKey = cacheInfo.restAPIKey;
|
||||||
this.fileKey = cacheInfo.fileKey;
|
this.fileKey = cacheInfo.fileKey;
|
||||||
this.facebookAppIds = cacheInfo.facebookAppIds;
|
this.facebookAppIds = cacheInfo.facebookAppIds;
|
||||||
this.enableAnonymousUsers = cacheInfo.enableAnonymousUsers;
|
|
||||||
this.allowClientClassCreation = cacheInfo.allowClientClassCreation;
|
this.allowClientClassCreation = cacheInfo.allowClientClassCreation;
|
||||||
this.database = DatabaseAdapter.getDatabaseConnection(applicationId, cacheInfo.collectionPrefix);
|
this.database = DatabaseAdapter.getDatabaseConnection(applicationId, cacheInfo.collectionPrefix);
|
||||||
|
|
||||||
@@ -34,7 +33,7 @@ export class Config {
|
|||||||
this.pushController = cacheInfo.pushController;
|
this.pushController = cacheInfo.pushController;
|
||||||
this.loggerController = cacheInfo.loggerController;
|
this.loggerController = cacheInfo.loggerController;
|
||||||
this.userController = cacheInfo.userController;
|
this.userController = cacheInfo.userController;
|
||||||
this.oauth = cacheInfo.oauth;
|
this.authDataManager = cacheInfo.authDataManager;
|
||||||
this.customPages = cacheInfo.customPages || {};
|
this.customPages = cacheInfo.customPages || {};
|
||||||
this.mount = mount;
|
this.mount = mount;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,8 +101,12 @@ DatabaseController.prototype.redirectClassNameForKey = function(className, key)
|
|||||||
// Returns a promise that resolves to the new schema.
|
// Returns a promise that resolves to the new schema.
|
||||||
// This does not update this.schema, because in a situation like a
|
// This does not update this.schema, because in a situation like a
|
||||||
// batch request, that could confuse other users of the schema.
|
// batch request, that could confuse other users of the schema.
|
||||||
DatabaseController.prototype.validateObject = function(className, object, query) {
|
DatabaseController.prototype.validateObject = function(className, object, query, options) {
|
||||||
return this.loadSchema().then((schema) => {
|
let schema;
|
||||||
|
return this.loadSchema().then(s => {
|
||||||
|
schema = s;
|
||||||
|
return this.canAddField(schema, className, object, options.acl || []);
|
||||||
|
}).then(() => {
|
||||||
return schema.validateObject(className, object, query);
|
return schema.validateObject(className, object, query);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -332,6 +336,22 @@ DatabaseController.prototype.create = function(className, object, options) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
DatabaseController.prototype.canAddField = function(schema, className, object, aclGroup) {
|
||||||
|
let classSchema = schema.data[className];
|
||||||
|
if (!classSchema) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
let fields = Object.keys(object);
|
||||||
|
let schemaFields = Object.keys(classSchema);
|
||||||
|
let newKeys = fields.filter((field) => {
|
||||||
|
return schemaFields.indexOf(field) < 0;
|
||||||
|
})
|
||||||
|
if (newKeys.length > 0) {
|
||||||
|
return schema.validatePermission(className, aclGroup, 'addField');
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
// Runs a mongo query on the database.
|
// Runs a mongo query on the database.
|
||||||
// This should only be used for testing - use 'find' for normal code
|
// This should only be used for testing - use 'find' for normal code
|
||||||
// to avoid Mongo-format dependencies.
|
// to avoid Mongo-format dependencies.
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ let adapter = MongoStorageAdapter;
|
|||||||
let dbConnections = {};
|
let dbConnections = {};
|
||||||
let databaseURI = DefaultDatabaseURI;
|
let databaseURI = DefaultDatabaseURI;
|
||||||
let appDatabaseURIs = {};
|
let appDatabaseURIs = {};
|
||||||
|
let appDatabaseOptions = {};
|
||||||
|
|
||||||
function setAdapter(databaseAdapter) {
|
function setAdapter(databaseAdapter) {
|
||||||
adapter = databaseAdapter;
|
adapter = databaseAdapter;
|
||||||
@@ -37,10 +38,15 @@ function setAppDatabaseURI(appId, uri) {
|
|||||||
appDatabaseURIs[appId] = uri;
|
appDatabaseURIs[appId] = uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setAppDatabaseOptions(appId: string, options: Object) {
|
||||||
|
appDatabaseOptions[appId] = options;
|
||||||
|
}
|
||||||
|
|
||||||
//Used by tests
|
//Used by tests
|
||||||
function clearDatabaseURIs() {
|
function clearDatabaseSettings() {
|
||||||
appDatabaseURIs = {};
|
appDatabaseURIs = {};
|
||||||
dbConnections = {};
|
dbConnections = {};
|
||||||
|
appDatabaseOptions = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDatabaseConnection(appId: string, collectionPrefix: string) {
|
function getDatabaseConnection(appId: string, collectionPrefix: string) {
|
||||||
@@ -50,7 +56,7 @@ function getDatabaseConnection(appId: string, collectionPrefix: string) {
|
|||||||
|
|
||||||
var dbURI = (appDatabaseURIs[appId] ? appDatabaseURIs[appId] : databaseURI);
|
var dbURI = (appDatabaseURIs[appId] ? appDatabaseURIs[appId] : databaseURI);
|
||||||
|
|
||||||
let storageAdapter = new adapter(dbURI);
|
let storageAdapter = new adapter(dbURI, appDatabaseOptions[appId]);
|
||||||
dbConnections[appId] = new DatabaseController(storageAdapter, {
|
dbConnections[appId] = new DatabaseController(storageAdapter, {
|
||||||
collectionPrefix: collectionPrefix
|
collectionPrefix: collectionPrefix
|
||||||
});
|
});
|
||||||
@@ -62,7 +68,8 @@ module.exports = {
|
|||||||
getDatabaseConnection: getDatabaseConnection,
|
getDatabaseConnection: getDatabaseConnection,
|
||||||
setAdapter: setAdapter,
|
setAdapter: setAdapter,
|
||||||
setDatabaseURI: setDatabaseURI,
|
setDatabaseURI: setDatabaseURI,
|
||||||
|
setAppDatabaseOptions: setAppDatabaseOptions,
|
||||||
setAppDatabaseURI: setAppDatabaseURI,
|
setAppDatabaseURI: setAppDatabaseURI,
|
||||||
clearDatabaseURIs: clearDatabaseURIs,
|
clearDatabaseSettings: clearDatabaseSettings,
|
||||||
defaultDatabaseURI: databaseURI
|
defaultDatabaseURI: databaseURI
|
||||||
};
|
};
|
||||||
|
|||||||
233
src/RestWrite.js
233
src/RestWrite.js
@@ -9,7 +9,6 @@ var Auth = require('./Auth');
|
|||||||
var Config = require('./Config');
|
var Config = require('./Config');
|
||||||
var cryptoUtils = require('./cryptoUtils');
|
var cryptoUtils = require('./cryptoUtils');
|
||||||
var passwordCrypto = require('./password');
|
var passwordCrypto = require('./password');
|
||||||
var oauth = require("./oauth");
|
|
||||||
var Parse = require('parse/node');
|
var Parse = require('parse/node');
|
||||||
var triggers = require('./triggers');
|
var triggers = require('./triggers');
|
||||||
|
|
||||||
@@ -33,7 +32,7 @@ function RestWrite(config, auth, className, query, data, originalData) {
|
|||||||
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'objectId ' +
|
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'objectId ' +
|
||||||
'is an invalid field name.');
|
'is an invalid field name.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// When the operation is complete, this.response may have several
|
// When the operation is complete, this.response may have several
|
||||||
// fields.
|
// fields.
|
||||||
// response: the actual data to be returned
|
// response: the actual data to be returned
|
||||||
@@ -128,7 +127,7 @@ RestWrite.prototype.validateClientClassCreation = function() {
|
|||||||
|
|
||||||
// Validates this operation against the schema.
|
// Validates this operation against the schema.
|
||||||
RestWrite.prototype.validateSchema = function() {
|
RestWrite.prototype.validateSchema = function() {
|
||||||
return this.config.database.validateObject(this.className, this.data, this.query);
|
return this.config.database.validateObject(this.className, this.data, this.query, this.runOptions);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Runs any beforeSave triggers against this operation.
|
// Runs any beforeSave triggers against this operation.
|
||||||
@@ -211,170 +210,96 @@ RestWrite.prototype.validateAuthData = function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var authData = this.data.authData;
|
var authData = this.data.authData;
|
||||||
var anonData = this.data.authData.anonymous;
|
|
||||||
|
|
||||||
if (this.config.enableAnonymousUsers === true && (anonData === null ||
|
|
||||||
(anonData && anonData.id))) {
|
|
||||||
return this.handleAnonymousAuthData();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Not anon, try other providers
|
|
||||||
var providers = Object.keys(authData);
|
var providers = Object.keys(authData);
|
||||||
if (!anonData && providers.length == 1) {
|
if (providers.length > 0) {
|
||||||
var provider = providers[0];
|
let canHandleAuthData = providers.reduce((canHandle, provider) => {
|
||||||
var providerAuthData = authData[provider];
|
var providerAuthData = authData[provider];
|
||||||
var hasToken = (providerAuthData && providerAuthData.id);
|
var hasToken = (providerAuthData && providerAuthData.id);
|
||||||
if (providerAuthData === null || hasToken) {
|
return canHandle && (hasToken || providerAuthData == null);
|
||||||
return this.handleOAuthAuthData(provider);
|
}, true);
|
||||||
|
if (canHandleAuthData) {
|
||||||
|
return this.handleAuthData(authData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new Parse.Error(Parse.Error.UNSUPPORTED_SERVICE,
|
throw new Parse.Error(Parse.Error.UNSUPPORTED_SERVICE,
|
||||||
'This authentication method is unsupported.');
|
'This authentication method is unsupported.');
|
||||||
};
|
};
|
||||||
|
|
||||||
RestWrite.prototype.handleAnonymousAuthData = function() {
|
RestWrite.prototype.handleAuthDataValidation = function(authData) {
|
||||||
var anonData = this.data.authData.anonymous;
|
let validations = Object.keys(authData).map((provider) => {
|
||||||
if (anonData === null && this.query) {
|
if (authData[provider] === null) {
|
||||||
// We are unlinking the user from the anonymous provider
|
|
||||||
this.data._auth_data_anonymous = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if this user already exists
|
|
||||||
return this.config.database.find(
|
|
||||||
this.className,
|
|
||||||
{'authData.anonymous.id': anonData.id}, {})
|
|
||||||
.then((results) => {
|
|
||||||
if (results.length > 0) {
|
|
||||||
if (!this.query) {
|
|
||||||
// We're signing up, but this user already exists. Short-circuit
|
|
||||||
delete results[0].password;
|
|
||||||
this.response = {
|
|
||||||
response: results[0],
|
|
||||||
location: this.location()
|
|
||||||
};
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this is a PUT for the same user, allow the linking
|
|
||||||
if (results[0].objectId === this.query.objectId) {
|
|
||||||
// Delete the rest format key before saving
|
|
||||||
delete this.data.authData;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We're trying to create a duplicate account. Forbid it
|
|
||||||
throw new Parse.Error(Parse.Error.ACCOUNT_ALREADY_LINKED,
|
|
||||||
'this auth is already used');
|
|
||||||
}
|
|
||||||
|
|
||||||
// This anonymous user does not already exist, so transform it
|
|
||||||
// to a saveable format
|
|
||||||
this.data._auth_data_anonymous = anonData;
|
|
||||||
|
|
||||||
// Delete the rest format key before saving
|
|
||||||
delete this.data.authData;
|
|
||||||
})
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
RestWrite.prototype.handleOAuthAuthData = function(provider) {
|
|
||||||
var authData = this.data.authData[provider];
|
|
||||||
|
|
||||||
if (authData === null && this.query) {
|
|
||||||
// We are unlinking from the provider.
|
|
||||||
this.data["_auth_data_" + provider ] = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var appIds;
|
|
||||||
var oauthOptions = this.config.oauth[provider];
|
|
||||||
if (oauthOptions) {
|
|
||||||
appIds = oauthOptions.appIds;
|
|
||||||
} else if (provider == "facebook") {
|
|
||||||
appIds = this.config.facebookAppIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
var validateAuthData;
|
|
||||||
var validateAppId;
|
|
||||||
|
|
||||||
|
|
||||||
if (oauth[provider]) {
|
|
||||||
validateAuthData = oauth[provider].validateAuthData;
|
|
||||||
validateAppId = oauth[provider].validateAppId;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try the configuration methods
|
|
||||||
if (oauthOptions) {
|
|
||||||
if (oauthOptions.module) {
|
|
||||||
validateAuthData = require(oauthOptions.module).validateAuthData;
|
|
||||||
validateAppId = require(oauthOptions.module).validateAppId;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (oauthOptions.validateAuthData) {
|
|
||||||
validateAuthData = oauthOptions.validateAuthData;
|
|
||||||
}
|
|
||||||
if (oauthOptions.validateAppId) {
|
|
||||||
validateAppId = oauthOptions.validateAppId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// try the custom provider first, fallback on the oauth implementation
|
|
||||||
|
|
||||||
if (!validateAuthData || !validateAppId) {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
return validateAuthData(authData, oauthOptions)
|
|
||||||
.then(() => {
|
|
||||||
if (appIds && typeof validateAppId === "function") {
|
|
||||||
return validateAppId(appIds, authData, oauthOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
// No validation required by the developer
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
let validateAuthData = this.config.authDataManager.getValidatorForProvider(provider);
|
||||||
|
if (!validateAuthData) {
|
||||||
|
throw new Parse.Error(Parse.Error.UNSUPPORTED_SERVICE,
|
||||||
|
'This authentication method is unsupported.');
|
||||||
|
};
|
||||||
|
return validateAuthData(authData[provider]);
|
||||||
|
});
|
||||||
|
return Promise.all(validations);
|
||||||
|
}
|
||||||
|
|
||||||
}).then(() => {
|
RestWrite.prototype.findUsersWithAuthData = function(authData) {
|
||||||
// Check if this user already exists
|
let providers = Object.keys(authData);
|
||||||
// TODO: does this handle re-linking correctly?
|
let query = providers.reduce((memo, provider) => {
|
||||||
var query = {};
|
if (!authData[provider]) {
|
||||||
query['authData.' + provider + '.id'] = authData.id;
|
return memo;
|
||||||
return this.config.database.find(
|
}
|
||||||
|
let queryKey = `authData.${provider}.id`;
|
||||||
|
let query = {};
|
||||||
|
query[queryKey] = authData[provider].id;
|
||||||
|
memo.push(query);
|
||||||
|
return memo;
|
||||||
|
}, []).filter((q) => {
|
||||||
|
return typeof q !== undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
let findPromise = Promise.resolve([]);
|
||||||
|
if (query.length > 0) {
|
||||||
|
findPromise = this.config.database.find(
|
||||||
this.className,
|
this.className,
|
||||||
query, {});
|
{'$or': query}, {})
|
||||||
}).then((results) => {
|
}
|
||||||
this.storage['authProvider'] = provider;
|
|
||||||
if (results.length > 0) {
|
return findPromise;
|
||||||
if (!this.query) {
|
}
|
||||||
// We're signing up, but this user already exists. Short-circuit
|
|
||||||
delete results[0].password;
|
|
||||||
this.response = {
|
|
||||||
response: results[0],
|
|
||||||
location: this.location()
|
|
||||||
};
|
|
||||||
this.data.objectId = results[0].objectId;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this is a PUT for the same user, allow the linking
|
RestWrite.prototype.handleAuthData = function(authData) {
|
||||||
if (results[0].objectId === this.query.objectId) {
|
let results;
|
||||||
// Delete the rest format key before saving
|
return this.handleAuthDataValidation(authData).then(() => {
|
||||||
delete this.data.authData;
|
return this.findUsersWithAuthData(authData);
|
||||||
return;
|
}).then((r) => {
|
||||||
}
|
results = r;
|
||||||
// We're trying to create a duplicate oauth auth. Forbid it
|
if (results.length > 1) {
|
||||||
throw new Parse.Error(Parse.Error.ACCOUNT_ALREADY_LINKED,
|
// More than 1 user with the passed id's
|
||||||
|
throw new Parse.Error(Parse.Error.ACCOUNT_ALREADY_LINKED,
|
||||||
'this auth is already used');
|
'this auth is already used');
|
||||||
} else {
|
}
|
||||||
this.data.username = cryptoUtils.newToken();
|
|
||||||
|
this.storage['authProvider'] = Object.keys(authData).join(',');
|
||||||
|
|
||||||
|
if (results.length == 0) {
|
||||||
|
this.data.username = cryptoUtils.newToken();
|
||||||
|
} else if (!this.query) {
|
||||||
|
// Login with auth data
|
||||||
|
// Short circuit
|
||||||
|
delete results[0].password;
|
||||||
|
this.response = {
|
||||||
|
response: results[0],
|
||||||
|
location: this.location()
|
||||||
|
};
|
||||||
|
this.data.objectId = results[0].objectId;
|
||||||
|
} else if (this.query && this.query.objectId) {
|
||||||
|
// Trying to update auth data but users
|
||||||
|
// are different
|
||||||
|
if (results[0].objectId !== this.query.objectId) {
|
||||||
|
throw new Parse.Error(Parse.Error.ACCOUNT_ALREADY_LINKED,
|
||||||
|
'this auth is already used');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// This FB auth does not already exist, so transform it to a
|
return Promise.resolve();
|
||||||
// saveable format
|
});
|
||||||
this.data["_auth_data_" + provider ] = authData;
|
|
||||||
|
|
||||||
// Delete the rest format key before saving
|
|
||||||
delete this.data.authData;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The non-third-party parts of User transformation
|
// The non-third-party parts of User transformation
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ function createSchema(req) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return req.config.database.loadSchema()
|
return req.config.database.loadSchema()
|
||||||
.then(schema => schema.addClassIfNotExists(className, req.body.fields))
|
.then(schema => schema.addClassIfNotExists(className, req.body.fields, req.body.classLevelPermissions))
|
||||||
.then(result => ({ response: Schema.mongoSchemaToSchemaAPIResponse(result) }));
|
.then(result => ({ response: Schema.mongoSchemaToSchemaAPIResponse(result) }));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,52 +60,20 @@ function modifySchema(req) {
|
|||||||
|
|
||||||
return req.config.database.loadSchema()
|
return req.config.database.loadSchema()
|
||||||
.then(schema => {
|
.then(schema => {
|
||||||
if (!schema.data[className]) {
|
return schema.updateClass(className, submittedFields, req.body.classLevelPermissions, req.config.database);
|
||||||
throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, `Class ${req.params.className} does not exist.`);
|
}).then((result) => {
|
||||||
}
|
return Promise.resolve({response: result});
|
||||||
|
|
||||||
let existingFields = Object.assign(schema.data[className], { _id: className });
|
|
||||||
Object.keys(submittedFields).forEach(name => {
|
|
||||||
let field = submittedFields[name];
|
|
||||||
if (existingFields[name] && 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.`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let newSchema = Schema.buildMergedSchemaObject(existingFields, submittedFields);
|
|
||||||
let mongoObject = Schema.mongoSchemaFromFieldsAndClassName(newSchema, className);
|
|
||||||
if (!mongoObject.result) {
|
|
||||||
throw new Parse.Error(mongoObject.code, mongoObject.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finally we have checked to make sure the request is valid and we can start deleting fields.
|
|
||||||
// Do all deletions first, then add fields to avoid duplicate geopoint error.
|
|
||||||
let deletePromises = [];
|
|
||||||
let insertedFields = [];
|
|
||||||
Object.keys(submittedFields).forEach(fieldName => {
|
|
||||||
if (submittedFields[fieldName].__op === 'Delete') {
|
|
||||||
const promise = schema.deleteField(fieldName, className, req.config.database);
|
|
||||||
deletePromises.push(promise);
|
|
||||||
} else {
|
|
||||||
insertedFields.push(fieldName);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return Promise.all(deletePromises) // Delete Everything
|
|
||||||
.then(() => schema.reloadData()) // Reload our Schema, so we have all the new values
|
|
||||||
.then(() => {
|
|
||||||
let promises = insertedFields.map(fieldName => {
|
|
||||||
const mongoType = mongoObject.result[fieldName];
|
|
||||||
return schema.validateField(className, fieldName, mongoType);
|
|
||||||
});
|
|
||||||
return Promise.all(promises);
|
|
||||||
})
|
|
||||||
.then(() => ({ response: Schema.mongoSchemaToSchemaAPIResponse(mongoObject.result) }));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getSchemaPermissions(req) {
|
||||||
|
var className = req.params.className;
|
||||||
|
return req.config.database.loadSchema()
|
||||||
|
.then(schema => {
|
||||||
|
return Promise.resolve({response: schema.perms[className]});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// A helper function that removes all join tables for a schema. Returns a promise.
|
// A helper function that removes all join tables for a schema. Returns a promise.
|
||||||
var removeJoinTables = (database, mongoSchema) => {
|
var removeJoinTables = (database, mongoSchema) => {
|
||||||
return Promise.all(Object.keys(mongoSchema)
|
return Promise.all(Object.keys(mongoSchema)
|
||||||
|
|||||||
@@ -102,7 +102,20 @@ export class UsersRouter extends ClassesRouter {
|
|||||||
let token = 'r:' + cryptoUtils.newToken();
|
let token = 'r:' + cryptoUtils.newToken();
|
||||||
user.sessionToken = token;
|
user.sessionToken = token;
|
||||||
delete user.password;
|
delete user.password;
|
||||||
|
|
||||||
|
// Sometimes the authData still has null on that keys
|
||||||
|
// https://github.com/ParsePlatform/parse-server/issues/935
|
||||||
|
if (user.authData) {
|
||||||
|
Object.keys(user.authData).forEach((provider) => {
|
||||||
|
if (user.authData[provider] === null) {
|
||||||
|
delete user.authData[provider];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (Object.keys(user.authData).length == 0) {
|
||||||
|
delete user.authData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
req.config.filesController.expandFilesInObject(req.config, user);
|
req.config.filesController.expandFilesInObject(req.config, user);
|
||||||
|
|
||||||
let expiresAt = new Date();
|
let expiresAt = new Date();
|
||||||
|
|||||||
122
src/Schema.js
122
src/Schema.js
@@ -76,6 +76,50 @@ var requiredColumns = {
|
|||||||
_Role: ["name", "ACL"]
|
_Role: ["name", "ACL"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 10 alpha numberic chars + uppercase
|
||||||
|
const userIdRegex = /^[a-zA-Z0-9]{10}$/;
|
||||||
|
// Anything that start with role
|
||||||
|
const roleRegex = /^role:.*/;
|
||||||
|
// * permission
|
||||||
|
const publicRegex = /^\*$/
|
||||||
|
|
||||||
|
const permissionKeyRegex = [userIdRegex, roleRegex, publicRegex];
|
||||||
|
|
||||||
|
function verifyPermissionKey(key) {
|
||||||
|
let result = permissionKeyRegex.reduce((isGood, regEx) => {
|
||||||
|
isGood = isGood || key.match(regEx) != null;
|
||||||
|
return isGood;
|
||||||
|
}, false);
|
||||||
|
if (!result) {
|
||||||
|
throw new Parse.Error(Parse.Error.INVALID_JSON, `'${key}' is not a valid key for class level permissions`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let CLPValidKeys = ['find', 'get', 'create', 'update', 'delete', 'addField'];
|
||||||
|
let DefaultClassLevelPermissions = CLPValidKeys.reduce((perms, key) => {
|
||||||
|
perms[key] = {
|
||||||
|
'*': true
|
||||||
|
};
|
||||||
|
return perms;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
function validateCLP(perms) {
|
||||||
|
if (!perms) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Object.keys(perms).forEach((operation) => {
|
||||||
|
if (CLPValidKeys.indexOf(operation) == -1) {
|
||||||
|
throw new Parse.Error(Parse.Error.INVALID_JSON, `${operation} is not a valid operation for class level permissions`);
|
||||||
|
}
|
||||||
|
Object.keys(perms[operation]).forEach((key) => {
|
||||||
|
verifyPermissionKey(key);
|
||||||
|
let perm = perms[operation][key];
|
||||||
|
if (perm !== true) {
|
||||||
|
throw new Parse.Error(Parse.Error.INVALID_JSON, `'${perm}' is not a valid value for class level permissions ${operation}:${key}:${perm}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
// Valid classes must:
|
// Valid classes must:
|
||||||
// Be one of _User, _Installation, _Role, _Session OR
|
// Be one of _User, _Installation, _Role, _Session OR
|
||||||
// Be a join table OR
|
// Be a join table OR
|
||||||
@@ -221,12 +265,12 @@ class Schema {
|
|||||||
// on success, and rejects with an error on fail. Ensure you
|
// on success, and rejects with an error on fail. Ensure you
|
||||||
// have authorization (master key, or client class creation
|
// have authorization (master key, or client class creation
|
||||||
// enabled) before calling this function.
|
// enabled) before calling this function.
|
||||||
addClassIfNotExists(className, fields) {
|
addClassIfNotExists(className, fields, classLevelPermissions) {
|
||||||
if (this.data[className]) {
|
if (this.data[className]) {
|
||||||
throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, `Class ${className} already exists.`);
|
throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, `Class ${className} already exists.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mongoObject = mongoSchemaFromFieldsAndClassName(fields, className);
|
let mongoObject = mongoSchemaFromFieldsAndClassNameAndCLP(fields, className, classLevelPermissions);
|
||||||
if (!mongoObject.result) {
|
if (!mongoObject.result) {
|
||||||
return Promise.reject(mongoObject);
|
return Promise.reject(mongoObject);
|
||||||
}
|
}
|
||||||
@@ -240,6 +284,54 @@ class Schema {
|
|||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateClass(className, submittedFields, classLevelPermissions, database) {
|
||||||
|
if (!this.data[className]) {
|
||||||
|
throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, `Class ${className} does not exist.`);
|
||||||
|
}
|
||||||
|
let existingFields = Object.assign(this.data[className], {_id: className});
|
||||||
|
Object.keys(submittedFields).forEach(name => {
|
||||||
|
let field = submittedFields[name];
|
||||||
|
if (existingFields[name] && 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.`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let newSchema = buildMergedSchemaObject(existingFields, submittedFields);
|
||||||
|
let mongoObject = mongoSchemaFromFieldsAndClassNameAndCLP(newSchema, className, classLevelPermissions);
|
||||||
|
if (!mongoObject.result) {
|
||||||
|
throw new Parse.Error(mongoObject.code, mongoObject.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally we have checked to make sure the request is valid and we can start deleting fields.
|
||||||
|
// Do all deletions first, then a single save to _SCHEMA collection to handle all additions.
|
||||||
|
let deletePromises = [];
|
||||||
|
let insertedFields = [];
|
||||||
|
Object.keys(submittedFields).forEach(fieldName => {
|
||||||
|
if (submittedFields[fieldName].__op === 'Delete') {
|
||||||
|
const promise = this.deleteField(fieldName, className, database);
|
||||||
|
deletePromises.push(promise);
|
||||||
|
} else {
|
||||||
|
insertedFields.push(fieldName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return Promise.all(deletePromises) // Delete Everything
|
||||||
|
.then(() => this.reloadData()) // Reload our Schema, so we have all the new values
|
||||||
|
.then(() => {
|
||||||
|
let promises = insertedFields.map(fieldName => {
|
||||||
|
const mongoType = mongoObject.result[fieldName];
|
||||||
|
return this.validateField(className, fieldName, mongoType);
|
||||||
|
});
|
||||||
|
return Promise.all(promises);
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
return this.setPermissions(className, classLevelPermissions)
|
||||||
|
})
|
||||||
|
.then(() => { return mongoSchemaToSchemaAPIResponse(mongoObject.result) });
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Returns whether the schema knows the type of all these keys.
|
// Returns whether the schema knows the type of all these keys.
|
||||||
@@ -288,6 +380,10 @@ class Schema {
|
|||||||
|
|
||||||
// Sets the Class-level permissions for a given className, which must exist.
|
// Sets the Class-level permissions for a given className, which must exist.
|
||||||
setPermissions(className, perms) {
|
setPermissions(className, perms) {
|
||||||
|
if (typeof perms === 'undefined') {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
validateCLP(perms);
|
||||||
var update = {
|
var update = {
|
||||||
_metadata: {
|
_metadata: {
|
||||||
class_permissions: perms
|
class_permissions: perms
|
||||||
@@ -548,7 +644,7 @@ function load(collection) {
|
|||||||
|
|
||||||
// Returns { code, error } if invalid, or { result }, an object
|
// Returns { code, error } if invalid, or { result }, an object
|
||||||
// suitable for inserting into _SCHEMA collection, otherwise
|
// suitable for inserting into _SCHEMA collection, otherwise
|
||||||
function mongoSchemaFromFieldsAndClassName(fields, className) {
|
function mongoSchemaFromFieldsAndClassNameAndCLP(fields, className, classLevelPermissions) {
|
||||||
if (!classNameIsValid(className)) {
|
if (!classNameIsValid(className)) {
|
||||||
return {
|
return {
|
||||||
code: Parse.Error.INVALID_CLASS_NAME,
|
code: Parse.Error.INVALID_CLASS_NAME,
|
||||||
@@ -601,6 +697,16 @@ function mongoSchemaFromFieldsAndClassName(fields, className) {
|
|||||||
error: 'currently, only one GeoPoint field may exist in an object. Adding ' + geoPoints[1] + ' when ' + geoPoints[0] + ' already exists.',
|
error: 'currently, only one GeoPoint field may exist in an object. Adding ' + geoPoints[1] + ' when ' + geoPoints[0] + ' already exists.',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
validateCLP(classLevelPermissions);
|
||||||
|
if (typeof classLevelPermissions !== 'undefined') {
|
||||||
|
mongoObject._metadata = mongoObject._metadata || {};
|
||||||
|
if (!classLevelPermissions) {
|
||||||
|
delete mongoObject._metadata.class_permissions;
|
||||||
|
} else {
|
||||||
|
mongoObject._metadata.class_permissions = classLevelPermissions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return { result: mongoObject };
|
return { result: mongoObject };
|
||||||
}
|
}
|
||||||
@@ -776,17 +882,23 @@ function mongoSchemaAPIResponseFields(schema) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function mongoSchemaToSchemaAPIResponse(schema) {
|
function mongoSchemaToSchemaAPIResponse(schema) {
|
||||||
return {
|
let result = {
|
||||||
className: schema._id,
|
className: schema._id,
|
||||||
fields: mongoSchemaAPIResponseFields(schema),
|
fields: mongoSchemaAPIResponseFields(schema),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let classLevelPermissions = DefaultClassLevelPermissions;
|
||||||
|
if (schema._metadata && schema._metadata.class_permissions) {
|
||||||
|
classLevelPermissions = Object.assign(classLevelPermissions, schema._metadata.class_permissions);
|
||||||
|
}
|
||||||
|
result.classLevelPermissions = classLevelPermissions;
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
load: load,
|
load: load,
|
||||||
classNameIsValid: classNameIsValid,
|
classNameIsValid: classNameIsValid,
|
||||||
invalidClassNameMessage: invalidClassNameMessage,
|
invalidClassNameMessage: invalidClassNameMessage,
|
||||||
mongoSchemaFromFieldsAndClassName: mongoSchemaFromFieldsAndClassName,
|
|
||||||
schemaAPITypeToMongoFieldType: schemaAPITypeToMongoFieldType,
|
schemaAPITypeToMongoFieldType: schemaAPITypeToMongoFieldType,
|
||||||
buildMergedSchemaObject: buildMergedSchemaObject,
|
buildMergedSchemaObject: buildMergedSchemaObject,
|
||||||
mongoFieldTypeToSchemaAPIType: mongoFieldTypeToSchemaAPIType,
|
mongoFieldTypeToSchemaAPIType: mongoFieldTypeToSchemaAPIType,
|
||||||
|
|||||||
94
src/authDataManager/index.js
Normal file
94
src/authDataManager/index.js
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
let facebook = require('./facebook');
|
||||||
|
let instagram = require("./instagram");
|
||||||
|
let linkedin = require("./linkedin");
|
||||||
|
let meetup = require("./meetup");
|
||||||
|
let google = require("./google");
|
||||||
|
let github = require("./github");
|
||||||
|
let twitter = require("./twitter");
|
||||||
|
|
||||||
|
let anonymous = {
|
||||||
|
validateAuthData: () => {
|
||||||
|
return Promise.resolve();
|
||||||
|
},
|
||||||
|
validateAppId: () => {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let providers = {
|
||||||
|
facebook,
|
||||||
|
instagram,
|
||||||
|
linkedin,
|
||||||
|
meetup,
|
||||||
|
google,
|
||||||
|
github,
|
||||||
|
twitter,
|
||||||
|
anonymous
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = function(oauthOptions = {}, enableAnonymousUsers = true) {
|
||||||
|
let _enableAnonymousUsers = enableAnonymousUsers;
|
||||||
|
let setEnableAnonymousUsers = function(enable) {
|
||||||
|
_enableAnonymousUsers = enable;
|
||||||
|
}
|
||||||
|
// To handle the test cases on configuration
|
||||||
|
let getValidatorForProvider = function(provider) {
|
||||||
|
|
||||||
|
if (provider === 'anonymous' && !_enableAnonymousUsers) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let defaultProvider = providers[provider];
|
||||||
|
let optionalProvider = oauthOptions[provider];
|
||||||
|
|
||||||
|
if (!defaultProvider && !optionalProvider) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let appIds;
|
||||||
|
if (optionalProvider) {
|
||||||
|
appIds = optionalProvider.appIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
var validateAuthData;
|
||||||
|
var validateAppId;
|
||||||
|
|
||||||
|
if (defaultProvider) {
|
||||||
|
validateAuthData = defaultProvider.validateAuthData;
|
||||||
|
validateAppId = defaultProvider.validateAppId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try the configuration methods
|
||||||
|
if (optionalProvider) {
|
||||||
|
if (optionalProvider.module) {
|
||||||
|
validateAuthData = require(optionalProvider.module).validateAuthData;
|
||||||
|
validateAppId = require(optionalProvider.module).validateAppId;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (optionalProvider.validateAuthData) {
|
||||||
|
validateAuthData = optionalProvider.validateAuthData;
|
||||||
|
}
|
||||||
|
if (optionalProvider.validateAppId) {
|
||||||
|
validateAppId = optionalProvider.validateAppId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!validateAuthData || !validateAppId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return function(authData) {
|
||||||
|
return validateAuthData(authData, optionalProvider).then(() => {
|
||||||
|
if (appIds) {
|
||||||
|
return validateAppId(appIds, authData, optionalProvider);
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.freeze({
|
||||||
|
getValidatorForProvider,
|
||||||
|
setEnableAnonymousUsers,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -65,7 +65,7 @@ const app = express();
|
|||||||
const api = new ParseServer(options);
|
const api = new ParseServer(options);
|
||||||
app.use(options.mountPath, api);
|
app.use(options.mountPath, api);
|
||||||
|
|
||||||
app.listen(options.port, function() {
|
var server = app.listen(options.port, function() {
|
||||||
|
|
||||||
for (let key in options) {
|
for (let key in options) {
|
||||||
let value = options[key];
|
let value = options[key];
|
||||||
@@ -77,3 +77,12 @@ app.listen(options.port, function() {
|
|||||||
console.log('');
|
console.log('');
|
||||||
console.log('parse-server running on '+options.serverURL);
|
console.log('parse-server running on '+options.serverURL);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var handleShutdown = function() {
|
||||||
|
console.log('Termination signal received. Shutting down.');
|
||||||
|
server.close(function () {
|
||||||
|
process.exit(0);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
process.on('SIGTERM', handleShutdown);
|
||||||
|
process.on('SIGINT', handleShutdown);
|
||||||
|
|||||||
11
src/index.js
11
src/index.js
@@ -8,7 +8,8 @@ var batch = require('./batch'),
|
|||||||
express = require('express'),
|
express = require('express'),
|
||||||
middlewares = require('./middlewares'),
|
middlewares = require('./middlewares'),
|
||||||
multer = require('multer'),
|
multer = require('multer'),
|
||||||
Parse = require('parse/node').Parse;
|
Parse = require('parse/node').Parse,
|
||||||
|
authDataManager = require('./authDataManager');
|
||||||
|
|
||||||
//import passwordReset from './passwordReset';
|
//import passwordReset from './passwordReset';
|
||||||
import cache from './cache';
|
import cache from './cache';
|
||||||
@@ -84,6 +85,7 @@ function ParseServer({
|
|||||||
push,
|
push,
|
||||||
loggerAdapter,
|
loggerAdapter,
|
||||||
databaseURI = DatabaseAdapter.defaultDatabaseURI,
|
databaseURI = DatabaseAdapter.defaultDatabaseURI,
|
||||||
|
databaseOptions,
|
||||||
cloud,
|
cloud,
|
||||||
collectionPrefix = '',
|
collectionPrefix = '',
|
||||||
clientKey,
|
clientKey,
|
||||||
@@ -120,6 +122,10 @@ function ParseServer({
|
|||||||
DatabaseAdapter.setAppDatabaseURI(appId, databaseURI);
|
DatabaseAdapter.setAppDatabaseURI(appId, databaseURI);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (databaseOptions) {
|
||||||
|
DatabaseAdapter.setAppDatabaseOptions(appId, databaseOptions);
|
||||||
|
}
|
||||||
|
|
||||||
if (cloud) {
|
if (cloud) {
|
||||||
addParseCloud();
|
addParseCloud();
|
||||||
if (typeof cloud === 'function') {
|
if (typeof cloud === 'function') {
|
||||||
@@ -163,9 +169,8 @@ function ParseServer({
|
|||||||
hooksController: hooksController,
|
hooksController: hooksController,
|
||||||
userController: userController,
|
userController: userController,
|
||||||
verifyUserEmails: verifyUserEmails,
|
verifyUserEmails: verifyUserEmails,
|
||||||
enableAnonymousUsers: enableAnonymousUsers,
|
|
||||||
allowClientClassCreation: allowClientClassCreation,
|
allowClientClassCreation: allowClientClassCreation,
|
||||||
oauth: oauth,
|
authDataManager: authDataManager(oauth, enableAnonymousUsers),
|
||||||
appName: appName,
|
appName: appName,
|
||||||
publicServerURL: publicServerURL,
|
publicServerURL: publicServerURL,
|
||||||
customPages: customPages,
|
customPages: customPages,
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
var facebook = require('./facebook');
|
|
||||||
var instagram = require("./instagram");
|
|
||||||
var linkedin = require("./linkedin");
|
|
||||||
var meetup = require("./meetup");
|
|
||||||
var google = require("./google");
|
|
||||||
var github = require("./github");
|
|
||||||
var twitter = require("./twitter");
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
facebook: facebook,
|
|
||||||
github: github,
|
|
||||||
google: google,
|
|
||||||
instagram: instagram,
|
|
||||||
linkedin: linkedin,
|
|
||||||
meetup: meetup,
|
|
||||||
twitter: twitter
|
|
||||||
}
|
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
var Parse = require('parse/node').Parse;
|
var Parse = require('parse/node').Parse;
|
||||||
import cache from './cache';
|
import cache from './cache';
|
||||||
|
import Auth from './Auth';
|
||||||
|
|
||||||
var RestQuery = require('./RestQuery');
|
var RestQuery = require('./RestQuery');
|
||||||
var RestWrite = require('./RestWrite');
|
var RestWrite = require('./RestWrite');
|
||||||
@@ -42,7 +43,7 @@ function del(config, auth, className, objectId) {
|
|||||||
if (triggers.getTrigger(className, triggers.Types.beforeDelete, config.applicationId) ||
|
if (triggers.getTrigger(className, triggers.Types.beforeDelete, config.applicationId) ||
|
||||||
triggers.getTrigger(className, triggers.Types.afterDelete, config.applicationId) ||
|
triggers.getTrigger(className, triggers.Types.afterDelete, config.applicationId) ||
|
||||||
className == '_Session') {
|
className == '_Session') {
|
||||||
return find(config, auth, className, {objectId: objectId})
|
return find(config, Auth.master(config), className, {objectId: objectId})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (response && response.results && response.results.length) {
|
if (response && response.results && response.results.length) {
|
||||||
response.results[0].className = className;
|
response.results[0].className = className;
|
||||||
@@ -97,7 +98,7 @@ function update(config, auth, className, objectId, restObject) {
|
|||||||
return Promise.resolve().then(() => {
|
return Promise.resolve().then(() => {
|
||||||
if (triggers.getTrigger(className, triggers.Types.beforeSave, config.applicationId) ||
|
if (triggers.getTrigger(className, triggers.Types.beforeSave, config.applicationId) ||
|
||||||
triggers.getTrigger(className, triggers.Types.afterSave, config.applicationId)) {
|
triggers.getTrigger(className, triggers.Types.afterSave, config.applicationId)) {
|
||||||
return find(config, auth, className, {objectId: objectId});
|
return find(config, Auth.master(config), className, {objectId: objectId});
|
||||||
}
|
}
|
||||||
return Promise.resolve({});
|
return Promise.resolve({});
|
||||||
}).then((response) => {
|
}).then((response) => {
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ export function transformKeyValue(schema, className, restKey, restValue, options
|
|||||||
return transformWhere(schema, className, s);
|
return transformWhere(schema, className, s);
|
||||||
});
|
});
|
||||||
return {key: '$and', value: mongoSubqueries};
|
return {key: '$and', value: mongoSubqueries};
|
||||||
default:
|
default:
|
||||||
// Other auth data
|
// Other auth data
|
||||||
var authDataMatch = key.match(/^authData\.([a-zA-Z0-9_]+)\.id$/);
|
var authDataMatch = key.match(/^authData\.([a-zA-Z0-9_]+)\.id$/);
|
||||||
if (authDataMatch) {
|
if (authDataMatch) {
|
||||||
@@ -203,6 +203,9 @@ function transformWhere(schema, className, restWhere) {
|
|||||||
// restCreate is the "create" clause in REST API form.
|
// restCreate is the "create" clause in REST API form.
|
||||||
// Returns the mongo form of the object.
|
// Returns the mongo form of the object.
|
||||||
function transformCreate(schema, className, restCreate) {
|
function transformCreate(schema, className, restCreate) {
|
||||||
|
if (className == '_User') {
|
||||||
|
restCreate = transformAuthData(restCreate);
|
||||||
|
}
|
||||||
var mongoCreate = transformACL(restCreate);
|
var mongoCreate = transformACL(restCreate);
|
||||||
for (var restKey in restCreate) {
|
for (var restKey in restCreate) {
|
||||||
var out = transformKeyValue(schema, className, restKey, restCreate[restKey]);
|
var out = transformKeyValue(schema, className, restKey, restCreate[restKey]);
|
||||||
@@ -218,6 +221,10 @@ function transformUpdate(schema, className, restUpdate) {
|
|||||||
if (!restUpdate) {
|
if (!restUpdate) {
|
||||||
throw 'got empty restUpdate';
|
throw 'got empty restUpdate';
|
||||||
}
|
}
|
||||||
|
if (className == '_User') {
|
||||||
|
restUpdate = transformAuthData(restUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
var mongoUpdate = {};
|
var mongoUpdate = {};
|
||||||
var acl = transformACL(restUpdate);
|
var acl = transformACL(restUpdate);
|
||||||
if (acl._rperm || acl._wperm) {
|
if (acl._rperm || acl._wperm) {
|
||||||
@@ -250,6 +257,16 @@ function transformUpdate(schema, className, restUpdate) {
|
|||||||
return mongoUpdate;
|
return mongoUpdate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function transformAuthData(restObject) {
|
||||||
|
if (restObject.authData) {
|
||||||
|
Object.keys(restObject.authData).forEach((provider) => {
|
||||||
|
restObject[`_auth_data_${provider}`] = restObject.authData[provider];
|
||||||
|
});
|
||||||
|
delete restObject.authData;
|
||||||
|
}
|
||||||
|
return restObject;
|
||||||
|
}
|
||||||
|
|
||||||
// Transforms a REST API formatted ACL object to our two-field mongo format.
|
// Transforms a REST API formatted ACL object to our two-field mongo format.
|
||||||
// This mutates the restObject passed in to remove the ACL key.
|
// This mutates the restObject passed in to remove the ACL key.
|
||||||
function transformACL(restObject) {
|
function transformACL(restObject) {
|
||||||
|
|||||||
Reference in New Issue
Block a user