diff --git a/spec/Schema.spec.js b/spec/Schema.spec.js index ee22e0dd..0f23a108 100644 --- a/spec/Schema.spec.js +++ b/spec/Schema.spec.js @@ -881,3 +881,230 @@ describe('SchemaController', () => { }); }); }); + +describe('Class Level Permissions for requiredAuth', () => { + + beforeEach(() => { + config = new Config('test'); + }); + + function createUser() { + let user = new Parse.User(); + user.set("username", "hello"); + user.set("password", "world"); + return user.signUp(null); + } + + it('required auth test find', (done) => { + config.database.loadSchema().then((schema) => { + // Just to create a valid class + return schema.validateObject('Stuff', {foo: 'bar'}); + }).then((schema) => { + return schema.setPermissions('Stuff', { + 'find': { + 'requiresAuthentication': true + } + }); + }).then(() => { + var query = new Parse.Query('Stuff'); + return query.find(); + }).then(() => { + fail('Class permissions should have rejected this query.'); + done(); + }, (e) => { + expect(e.message).toEqual('Permission denied, user needs to be authenticated.'); + done(); + }); + }); + + it('required auth test find authenticated', (done) => { + config.database.loadSchema().then((schema) => { + // Just to create a valid class + return schema.validateObject('Stuff', {foo: 'bar'}); + }).then((schema) => { + return schema.setPermissions('Stuff', { + 'find': { + 'requiresAuthentication': true + } + }); + }).then(() => { + return createUser(); + }).then(() => { + var query = new Parse.Query('Stuff'); + return query.find(); + }).then((results) => { + expect(results.length).toEqual(0); + done(); + }, (e) => { + console.error(e); + fail("Should not have failed"); + done(); + }); + }); + + it('required auth should allow create authenticated', (done) => { + config.database.loadSchema().then((schema) => { + // Just to create a valid class + return schema.validateObject('Stuff', {foo: 'bar'}); + }).then((schema) => { + return schema.setPermissions('Stuff', { + 'create': { + 'requiresAuthentication': true + } + }); + }).then(() => { + return createUser(); + }).then(() => { + let stuff = new Parse.Object('Stuff'); + stuff.set('foo', 'bar'); + return stuff.save(); + }).then(() => { + done(); + }, (e) => { + console.error(e); + fail("Should not have failed"); + done(); + }); + }); + + it('required auth should reject create when not authenticated', (done) => { + config.database.loadSchema().then((schema) => { + // Just to create a valid class + return schema.validateObject('Stuff', {foo: 'bar'}); + }).then((schema) => { + return schema.setPermissions('Stuff', { + 'create': { + 'requiresAuthentication': true + } + }); + }).then(() => { + let stuff = new Parse.Object('Stuff'); + stuff.set('foo', 'bar'); + return stuff.save(); + }).then(() => { + fail('Class permissions should have rejected this query.'); + done(); + }, (e) => { + expect(e.message).toEqual('Permission denied, user needs to be authenticated.'); + done(); + }); + }); + + it('required auth test create/get/update/delete authenticated', (done) => { + config.database.loadSchema().then((schema) => { + // Just to create a valid class + return schema.validateObject('Stuff', {foo: 'bar'}); + }).then((schema) => { + return schema.setPermissions('Stuff', { + 'create': { + 'requiresAuthentication': true + }, + 'get': { + 'requiresAuthentication': true + }, + 'delete': { + 'requiresAuthentication': true + }, + 'update': { + 'requiresAuthentication': true + } + }); + }).then(() => { + return createUser(); + }).then(() => { + let stuff = new Parse.Object('Stuff'); + stuff.set('foo', 'bar'); + return stuff.save().then(() => { + let query = new Parse.Query('Stuff'); + return query.get(stuff.id); + }); + }).then((gotStuff) => { + return gotStuff.save({'foo': 'baz'}).then(() => { + return gotStuff.destroy(); + }) + }).then(() => { + done(); + }, (e) => { + console.error(e); + fail("Should not have failed"); + done(); + }); + }); + + it('required auth test create/get/update/delete not authenitcated', (done) => { + config.database.loadSchema().then((schema) => { + // Just to create a valid class + return schema.validateObject('Stuff', {foo: 'bar'}); + }).then((schema) => { + return schema.setPermissions('Stuff', { + 'get': { + 'requiresAuthentication': true + }, + 'delete': { + 'requiresAuthentication': true + }, + 'update': { + 'requiresAuthentication': true + }, + 'create': { + '*': true + } + }); + }).then(() => { + let stuff = new Parse.Object('Stuff'); + stuff.set('foo', 'bar'); + return stuff.save().then(() => { + let query = new Parse.Query('Stuff'); + return query.get(stuff.id); + }); + }).then(() => { + fail("Should not succeed!"); + done(); + }, (e) => { + expect(e.message).toEqual('Permission denied, user needs to be authenticated.'); + done(); + }); + }); + + it('required auth test create/get/update/delete not authenitcated', (done) => { + config.database.loadSchema().then((schema) => { + // Just to create a valid class + return schema.validateObject('Stuff', {foo: 'bar'}); + }).then((schema) => { + return schema.setPermissions('Stuff', { + 'find': { + 'requiresAuthentication': true + }, + 'delete': { + 'requiresAuthentication': true + }, + 'update': { + 'requiresAuthentication': true + }, + 'create': { + '*': true + }, + 'get': { + '*': true + } + }); + }).then(() => { + let stuff = new Parse.Object('Stuff'); + stuff.set('foo', 'bar'); + return stuff.save().then(() => { + let query = new Parse.Query('Stuff'); + return query.get(stuff.id); + }) + }).then((result) => { + expect(result.get('foo')).toEqual('bar'); + let query = new Parse.Query('Stuff'); + return query.find(); + }).then(() => { + fail("Should not succeed!"); + done(); + }, (e) => { + expect(e.message).toEqual('Permission denied, user needs to be authenticated.'); + done(); + }); + }); +}) diff --git a/src/Controllers/SchemaController.js b/src/Controllers/SchemaController.js index 65634181..21d252c6 100644 --- a/src/Controllers/SchemaController.js +++ b/src/Controllers/SchemaController.js @@ -123,7 +123,9 @@ const roleRegex = /^role:.*/; // * permission const publicRegex = /^\*$/ -const permissionKeyRegex = Object.freeze([userIdRegex, roleRegex, publicRegex]); +const requireAuthenticationRegex = /^requiresAuthentication$/ + +const permissionKeyRegex = Object.freeze([userIdRegex, roleRegex, publicRegex, requireAuthenticationRegex]); function verifyPermissionKey(key) { let result = permissionKeyRegex.reduce((isGood, regEx) => { @@ -771,6 +773,26 @@ export default class SchemaController { return true; } let classPerms = this.perms[className]; + let perms = classPerms[operation]; + + // If only for authenticated users + // make sure we have an aclGroup + if (perms['requiresAuthentication']) { + // If aclGroup has * (public) + if (!aclGroup || aclGroup.length == 0) { + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, + 'Permission denied, user needs to be authenticated.'); + } else if (aclGroup.indexOf('*') > -1 && aclGroup.length == 1) { + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, + 'Permission denied, user needs to be authenticated.'); + } + // no other CLP than requiresAuthentication + // let's resolve that! + if (Object.keys(perms).length == 1) { + return Promise.resolve(); + } + } + // No matching CLP, let's check the Pointer permissions // And handle those later let permissionField = ['get', 'find'].indexOf(operation) > -1 ? 'readUserFields' : 'writeUserFields';