Compare commits

...
6 Commits
8 changed files with 221 additions and 104 deletions
-3
View File
@@ -1,6 +1,3 @@
branches:
only:
- master
language: node_js
node_js:
- '4.3'
+7
View File
@@ -1,5 +1,12 @@
## Parse Server Changelog
### 2.2.6 (4/5/2016)
* Important Fix: Disables find on installation from clients [\#1374](https://github.com/ParsePlatform/parse-server/pull/1374) ([flovilmart](https://github.com/flovilmart))
* Adds missing options to the CLI [\#1368](https://github.com/ParsePlatform/parse-server/pull/1368) ([flovilmart](https://github.com/flovilmart))
* Removes only master on travis [\#1367](https://github.com/ParsePlatform/parse-server/pull/1367) ([flovilmart](https://github.com/flovilmart))
* Auth.\_loadRoles should not query the same role twice. [\#1366](https://github.com/ParsePlatform/parse-server/pull/1366) ([blacha](https://github.com/blacha))
### 2.2.5 (4/4/2016)
* Improves config loading and tests [\#1363](https://github.com/ParsePlatform/parse-server/pull/1363) ([flovilmart](https://github.com/flovilmart))
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "parse-server",
"version": "2.2.5",
"version": "2.2.6",
"description": "An express module providing a Parse-compatible API server",
"main": "lib/index.js",
"repository": {
+44
View File
@@ -119,6 +119,50 @@ describe('Installations', () => {
}).catch((error) => { console.log(error); });
});
it('should properly fail queying installations', (done) => {
var installId = '12345678-abcd-abcd-abcd-123456789abc';
var device = 'android';
var input = {
'installationId': installId,
'deviceType': device
};
rest.create(config, auth.nobody(config), '_Installation', input)
.then(() => {
let query = new Parse.Query(Parse.Installation);
return query.find()
}).then((results) => {
fail('Should not succeed!');
done();
}).catch((error) => {
expect(error.code).toBe(119);
expect(error.message).toBe('Clients aren\'t allowed to perform the find operation on the installation collection.')
done();
});
});
it('should properly queying installations with masterKey', (done) => {
var installId = '12345678-abcd-abcd-abcd-123456789abc';
var device = 'android';
var input = {
'installationId': installId,
'deviceType': device
};
rest.create(config, auth.nobody(config), '_Installation', input)
.then(() => {
let query = new Parse.Query(Parse.Installation);
return query.find({useMasterKey: true});
}).then((results) => {
expect(results.length).toEqual(1);
var obj = results[0].toJSON();
expect(obj.installationId).toEqual(installId);
expect(obj.deviceType).toEqual(device);
done();
}).catch((error) => {
fail('Should not fail');
done();
});
});
it('fails with missing ids', (done) => {
var input = {
'deviceType': 'android',
+86 -19
View File
@@ -2,6 +2,7 @@
// Roles are not accessible without the master key, so they are not intended
// for use by clients. We can manually test them using the master key.
var RestQuery = require("../src/RestQuery");
var Auth = require("../src/Auth").Auth;
var Config = require("../src/Config");
@@ -60,21 +61,87 @@ describe('Parse Role testing', () => {
});
it("should recursively load roles", (done) => {
var rolesNames = ["FooRole", "BarRole", "BazRole"];
var createRole = function(name, sibling, user) {
var role = new Parse.Role(name, new Parse.ACL());
if (user) {
var users = role.relation('users');
users.add(user);
}
if (sibling) {
role.relation('roles').add(sibling);
}
return role.save({}, { useMasterKey: true });
var createRole = function(name, sibling, user) {
var role = new Parse.Role(name, new Parse.ACL());
if (user) {
var users = role.relation('users');
users.add(user);
}
if (sibling) {
role.relation('roles').add(sibling);
}
return role.save({}, { useMasterKey: true });
};
it("should not recursively load the same role multiple times", (done) => {
var rootRole = "RootRole";
var roleNames = ["FooRole", "BarRole", "BazRole"];
var allRoles = [rootRole].concat(roleNames);
var roleObjs = {};
var createAllRoles = function(user) {
var promises = allRoles.map(function(roleName) {
return createRole(roleName, null, user)
.then(function(roleObj) {
roleObjs[roleName] = roleObj;
return roleObj;
});
});
return Promise.all(promises);
};
var restExecute = spyOn(RestQuery.prototype, "execute").and.callThrough();
var user,
auth,
getAllRolesSpy;
createTestUser().then( (newUser) => {
user = newUser;
return createAllRoles(user);
}).then ( (roles) => {
var rootRoleObj = roleObjs[rootRole];
roles.forEach(function(role, i) {
// Add all roles to the RootRole
if (role.id !== rootRoleObj.id) {
role.relation("roles").add(rootRoleObj);
}
// Add all "roleNames" roles to the previous role
if (i > 0) {
role.relation("roles").add(roles[i - 1]);
}
});
return Parse.Object.saveAll(roles, { useMasterKey: true });
}).then( () => {
auth = new Auth({config: new Config("test"), isMaster: true, user: user});
getAllRolesSpy = spyOn(auth, "_getAllRoleNamesForId").and.callThrough();
return auth._loadRoles();
}).then ( (roles) => {
expect(roles.length).toEqual(4);
allRoles.forEach(function(name) {
expect(roles.indexOf("role:"+name)).not.toBe(-1);
});
// 1 Query for the initial setup
// 4 Queries for all the specific roles
// 1 Query for the final $in
expect(restExecute.calls.count()).toEqual(6);
// 4 One query for each of the roles
// 3 Queries for the "RootRole"
expect(getAllRolesSpy.calls.count()).toEqual(7);
done()
}).catch( () => {
fail("should succeed");
done();
});
});
it("should recursively load roles", (done) => {
var rolesNames = ["FooRole", "BarRole", "BazRole"];
var roleIds = {};
createTestUser().then( (user) => {
// Put the user on the 1st role
@@ -97,7 +164,7 @@ describe('Parse Role testing', () => {
expect(roles.length).toEqual(3);
rolesNames.forEach( (name) => {
expect(roles.indexOf('role:'+name)).not.toBe(-1);
})
});
done();
}, function(err){
fail("should succeed")
@@ -122,7 +189,7 @@ describe('Parse Role testing', () => {
});
});
});
it("Should properly resolve roles", (done) => {
let admin = new Parse.Role("Admin", new Parse.ACL());
let moderator = new Parse.Role("Moderator", new Parse.ACL());
@@ -134,7 +201,7 @@ describe('Parse Role testing', () => {
moderator.getRoles().add([admin, superModerator]);
superContentManager.getRoles().add(superModerator);
return Parse.Object.saveAll([admin, moderator, contentManager, superModerator, superContentManager], {useMasterKey: true});
}).then(() => {
}).then(() => {
var auth = new Auth({ config: new Config("test"), isMaster: true });
// For each role, fetch their sibling, what they inherit
// return with result and roleId for later comparison
@@ -147,7 +214,7 @@ describe('Parse Role testing', () => {
});
})
});
return Parse.Promise.when(promises);
}).then((results) => {
results.forEach((result) => {
@@ -174,7 +241,7 @@ describe('Parse Role testing', () => {
console.error(err);
done();
})
});
it('can create role and query empty users', (done)=> {
+11 -6
View File
@@ -63,7 +63,7 @@ var getAuthForSessionToken = function({ config, sessionToken, installationId } =
return nobody(config);
}
var now = new Date(),
var now = new Date(),
expiresAt = new Date(results[0].expiresAt.iso);
if(expiresAt < now) {
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN,
@@ -117,8 +117,9 @@ Auth.prototype._loadRoles = function() {
var roleIDs = results.map(r => r.objectId);
var promises = [Promise.resolve(roleIDs)];
var queriedRoles = {};
for (var role of roleIDs) {
promises.push(this._getAllRoleNamesForId(role));
promises.push(this._getAllRoleNamesForId(role, queriedRoles));
}
return Promise.all(promises).then((results) => {
var allIDs = [];
@@ -146,8 +147,12 @@ Auth.prototype._loadRoles = function() {
};
// Given a role object id, get any other roles it is part of
Auth.prototype._getAllRoleNamesForId = function(roleID) {
Auth.prototype._getAllRoleNamesForId = function(roleID, queriedRoles = {}) {
// Don't need to requery this role as it is already being queried for.
if (queriedRoles[roleID] != null) {
return Promise.resolve([]);
}
queriedRoles[roleID] = true;
// As per documentation, a Role inherits AnotherRole
// if this Role is in the roles pointer of this AnotherRole
// Let's find all the roles where this role is in a roles relation
@@ -167,13 +172,13 @@ Auth.prototype._getAllRoleNamesForId = function(roleID) {
return Promise.resolve([]);
}
var roleIDs = results.map(r => r.objectId);
// we found a list of roles where the roleID
// is referenced in the roles relation,
// Get the roles where those found roles are also
// referenced the same way
var parentRolesPromises = roleIDs.map( (roleId) => {
return this._getAllRoleNamesForId(roleId);
return this._getAllRoleNamesForId(roleId, queriedRoles);
});
parentRolesPromises.push(Promise.resolve(roleIDs));
return Promise.all(parentRolesPromises);
+67 -70
View File
@@ -1,3 +1,37 @@
function numberParser(key) {
return function(opt) {
opt = parseInt(opt);
if (!Number.isInteger(opt)) {
throw new Error(`The ${key} is invalid`);
}
return opt;
}
}
function objectParser(opt) {
if (typeof opt == 'object') {
return opt;
}
return JSON.parse(opt)
}
function moduleOrObjectParser(opt) {
if (typeof opt == 'object') {
return opt;
}
try {
return JSON.parse(opt);
} catch(e) {}
return opt;
}
function booleanParser(opt) {
if (opt == true || opt == "true" || opt == "1") {
return true;
}
return false;
}
export default {
"appId": {
env: "PARSE_SERVER_APPLICATION_ID",
@@ -13,18 +47,21 @@ export default {
env: "PORT",
help: "The port to run the ParseServer. defaults to 1337.",
default: 1337,
action: function(opt) {
opt = parseInt(opt);
if (!Number.isInteger(opt)) {
throw new Error("The port is invalid");
}
return opt;
}
action: numberParser("port")
},
"databaseURI": {
env: "PARSE_SERVER_DATABASE_URI",
help: "The full URI to your mongodb database"
},
"databaseOptions": {
env: "PARSE_SERVER_DATABASE_OPTIONS",
help: "Options to pass to the mongodb client",
action: objectParser
},
"collectionPrefix": {
env: "PARSE_SERVER_COLLECTION_PREFIX",
help: 'A collection prefix for the classes'
},
"serverURL": {
env: "PARSE_SERVER_URL",
help: "URL to your parse server with http:// or https://.",
@@ -56,22 +93,12 @@ export default {
"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)
}
action: objectParser
},
"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)
}
action: objectParser
},
"fileKey": {
env: "PARSE_SERVER_FILE_KEY",
@@ -88,22 +115,12 @@ export default {
"enableAnonymousUsers": {
env: "PARSE_SERVER_ENABLE_ANON_USERS",
help: "Enable (or disable) anon users, defaults to true",
action: function(opt) {
if (opt == "true" || opt == "1") {
return true;
}
return false;
}
action: booleanParser
},
"allowClientClassCreation": {
env: "PARSE_SERVER_ALLOW_CLIENT_CLASS_CREATION",
help: "Enable (or disable) client class creation, defaults to true",
action: function(opt) {
if (opt == "true" || opt == "1") {
return true;
}
return false;
}
action: booleanParser
},
"mountPath": {
env: "PARSE_SERVER_MOUNT_PATH",
@@ -113,65 +130,45 @@ export default {
"filesAdapter": {
env: "PARSE_SERVER_FILES_ADAPTER",
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;
}
action: moduleOrObjectParser
},
"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;
}
action: moduleOrObjectParser
},
"verifyUserEmails": {
env: "PARSE_SERVER_VERIFY_USER_EMAILS",
help: "Enable (or disable) user email validation, defaults to false",
action: booleanParser
},
"appName": {
env: "PARSE_SERVER_APP_NAME",
help: "Sets the app name"
},
"loggerAdapter": {
env: "PARSE_SERVER_LOGGER_ADAPTER",
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;
}
action: moduleOrObjectParser
},
"liveQuery": {
env: "PARSE_SERVER_LIVE_QUERY_OPTIONS",
help: "liveQuery options",
action: function action(opt) {
if (typeof opt == 'object') {
return opt;
}
return JSON.parse(opt);
}
action: objectParser
},
"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);
}
action: objectParser
},
"maxUploadSize": {
env: "PARSE_SERVER_MAX_UPLOAD_SIZE",
help: "Max file size for uploads.",
default: "20mb"
},
"sessionLength": {
env: "PARSE_SERVER_SESSION_LENGTH",
help: "Session duration, defaults to 1 year",
action: numberParser("sessionLength")
}
};
+5 -5
View File
@@ -119,11 +119,11 @@ function update(config, auth, className, objectId, restObject) {
// Disallowing access to the _Role collection except by master key
function enforceRoleSecurity(method, className, auth) {
if (method === 'delete' && className === '_Installation' && !auth.isMaster) {
throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN,
'Clients aren\'t allowed to perform the ' +
'delete operation on the installation collection.');
if (className === '_Installation' && !auth.isMaster) {
if (method === 'delete' || method === 'find') {
let error = `Clients aren't allowed to perform the ${method} operation on the installation collection.`
throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, error);
}
}
}