Allow protectedFields for Authenticated users and Public. Fix userField with keys/excludedKeys (#6415)
* fix error message and test it * protected fields fixes * clean * remove duplicate test, add some comments * no need for 'requiresAuthentication'
This commit is contained in:
@@ -5,6 +5,8 @@ const fetch = require('node-fetch');
|
||||
const FormData = require('form-data');
|
||||
const ws = require('ws');
|
||||
require('./helper');
|
||||
const { updateCLP } = require('./dev');
|
||||
|
||||
const pluralize = require('pluralize');
|
||||
const { getMainDefinition } = require('apollo-utilities');
|
||||
const { ApolloLink, split } = require('apollo-link');
|
||||
@@ -4632,6 +4634,84 @@ describe('ParseGraphQLServer', () => {
|
||||
).toBeDefined();
|
||||
});
|
||||
|
||||
it('should respect protectedFields', async done => {
|
||||
await prepareData();
|
||||
await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear();
|
||||
|
||||
const className = 'GraphQLClass';
|
||||
|
||||
await updateCLP(
|
||||
{
|
||||
get: { '*': true },
|
||||
find: { '*': true },
|
||||
|
||||
protectedFields: {
|
||||
'*': ['someField', 'someOtherField'],
|
||||
authenticated: ['someField'],
|
||||
'userField:pointerToUser': [],
|
||||
[user2.id]: [],
|
||||
},
|
||||
},
|
||||
className
|
||||
);
|
||||
|
||||
const getObject = async (className, id, user) => {
|
||||
const headers = user
|
||||
? { ['X-Parse-Session-Token']: user.getSessionToken() }
|
||||
: undefined;
|
||||
|
||||
const specificQueryResult = await apolloClient.query({
|
||||
query: gql`
|
||||
query GetSomeObject($id: ID!) {
|
||||
get: graphQLClass(id: $id) {
|
||||
pointerToUser {
|
||||
username
|
||||
id
|
||||
}
|
||||
someField
|
||||
someOtherField
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
id: id,
|
||||
},
|
||||
context: {
|
||||
headers: headers,
|
||||
},
|
||||
});
|
||||
|
||||
return specificQueryResult.data.get;
|
||||
};
|
||||
|
||||
const id = object3.id;
|
||||
|
||||
/* not authenticated */
|
||||
const objectPublic = await getObject(className, id, undefined);
|
||||
|
||||
expect(objectPublic.someField).toBeNull();
|
||||
expect(objectPublic.someOtherField).toBeNull();
|
||||
|
||||
/* authenticated */
|
||||
const objectAuth = await getObject(className, id, user1);
|
||||
|
||||
expect(objectAuth.someField).toBeNull();
|
||||
expect(objectAuth.someOtherField).toBe('B');
|
||||
|
||||
/* pointer field */
|
||||
const objectPointed = await getObject(className, id, user5);
|
||||
|
||||
expect(objectPointed.someField).toBe('someValue3');
|
||||
expect(objectPointed.someOtherField).toBe('B');
|
||||
|
||||
/* for user id */
|
||||
const objectForUser = await getObject(className, id, user2);
|
||||
|
||||
expect(objectForUser.someField).toBe('someValue3');
|
||||
expect(objectForUser.someOtherField).toBe('B');
|
||||
|
||||
done();
|
||||
});
|
||||
describe_only_db('mongo')('read preferences', () => {
|
||||
it('should read from primary by default', async () => {
|
||||
try {
|
||||
|
||||
@@ -4868,4 +4868,36 @@ describe('Parse.Query testing', () => {
|
||||
const results = await query.find();
|
||||
equal(results[0].get('array').length, 105);
|
||||
});
|
||||
|
||||
it('exclude keys (sdk query)', async done => {
|
||||
const obj = new TestObject({ foo: 'baz', hello: 'world' });
|
||||
await obj.save();
|
||||
|
||||
const query = new Parse.Query('TestObject');
|
||||
query.exclude('foo');
|
||||
|
||||
const object = await query.get(obj.id);
|
||||
expect(object.get('foo')).toBeUndefined();
|
||||
expect(object.get('hello')).toBe('world');
|
||||
done();
|
||||
});
|
||||
|
||||
xit('todo: exclude keys with select key (sdk query get)', async done => {
|
||||
// there is some problem with js sdk caching
|
||||
|
||||
const obj = new TestObject({ foo: 'baz', hello: 'world' });
|
||||
await obj.save();
|
||||
|
||||
const query = new Parse.Query('TestObject');
|
||||
|
||||
query.withJSON({
|
||||
keys: 'hello',
|
||||
excludeKeys: 'hello',
|
||||
});
|
||||
|
||||
const object = await query.get(obj.id);
|
||||
expect(object.get('foo')).toBeUndefined();
|
||||
expect(object.get('hello')).toBeUndefined();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2047,7 +2047,7 @@ describe('Pointer Permissions', () => {
|
||||
}
|
||||
|
||||
async function logIn(userObject) {
|
||||
await Parse.User.logIn(userObject.getUsername(), 'password');
|
||||
return await Parse.User.logIn(userObject.getUsername(), 'password');
|
||||
}
|
||||
|
||||
async function updateCLP(clp) {
|
||||
@@ -3098,5 +3098,55 @@ describe('Pointer Permissions', () => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('using pointer-fields and queries with keys projection', () => {
|
||||
let user1;
|
||||
/**
|
||||
* owner: user1
|
||||
*
|
||||
* testers: [user1]
|
||||
*/
|
||||
let obj;
|
||||
|
||||
/**
|
||||
* Clear cache, create user and object, login user
|
||||
*/
|
||||
async function initialize() {
|
||||
await Config.get(Parse.applicationId).database.schemaCache.clear();
|
||||
|
||||
user1 = await createUser('user1');
|
||||
user1 = await logIn(user1);
|
||||
|
||||
obj = new Parse.Object(className);
|
||||
|
||||
obj.set('owner', user1);
|
||||
obj.set('field', 'field');
|
||||
obj.set('test', 'test');
|
||||
|
||||
await Parse.Object.saveAll([obj], { useMasterKey: true });
|
||||
|
||||
await obj.fetch();
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
await initialize();
|
||||
});
|
||||
|
||||
it('should be enforced regardless of pointer-field being included in keys (select)', async done => {
|
||||
await updateCLP({
|
||||
get: { '*': true },
|
||||
find: { pointerFields: ['owner'] },
|
||||
update: { pointerFields: ['owner'] },
|
||||
});
|
||||
|
||||
const query = new Parse.Query('AnObject');
|
||||
query.select('field', 'test');
|
||||
|
||||
const [object] = await query.find({ objectId: obj.id });
|
||||
expect(object.get('field')).toBe('field');
|
||||
expect(object.get('test')).toBe('test');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
98
spec/dev.js
Normal file
98
spec/dev.js
Normal file
@@ -0,0 +1,98 @@
|
||||
const Config = require('../lib/Config');
|
||||
const Parse = require('parse/node');
|
||||
|
||||
const className = 'AnObject';
|
||||
const defaultRoleName = 'tester';
|
||||
|
||||
let schemaCache;
|
||||
|
||||
module.exports = {
|
||||
/* AnObject */
|
||||
className,
|
||||
schemaCache,
|
||||
|
||||
/**
|
||||
* Creates and returns new user.
|
||||
*
|
||||
* This method helps to avoid 'User already exists' when re-running/debugging a single test.
|
||||
* @param {string} username - username base, will be postfixed with current time in millis;
|
||||
* @param {string} [password='password'] - optional, defaults to "password" if not set;
|
||||
*/
|
||||
createUser: async (username, password = 'password') => {
|
||||
const user = new Parse.User({
|
||||
username: username + Date.now(),
|
||||
password,
|
||||
});
|
||||
await user.save();
|
||||
return user;
|
||||
},
|
||||
|
||||
/**
|
||||
* Logs the user in.
|
||||
*
|
||||
* If password not provided, default 'password' is used.
|
||||
* @param {string} username - username base, will be postfixed with current time in millis;
|
||||
* @param {string} [password='password'] - optional, defaults to "password" if not set;
|
||||
*/
|
||||
logIn: async (userObject, password) => {
|
||||
return await Parse.User.logIn(
|
||||
userObject.getUsername(),
|
||||
password || 'password'
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets up Class-Level Permissions for 'AnObject' class.
|
||||
* @param clp {ClassLevelPermissions}
|
||||
*/
|
||||
updateCLP: async (clp, targetClass = className) => {
|
||||
const config = Config.get(Parse.applicationId);
|
||||
const schemaController = await config.database.loadSchema();
|
||||
|
||||
await schemaController.updateClass(targetClass, {}, clp);
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates and returns role. Adds user(s) if provided.
|
||||
*
|
||||
* This method helps to avoid errors when re-running/debugging a single test.
|
||||
*
|
||||
* @param {Parse.User|Parse.User[]} [users] - user or array of users to be related with this role;
|
||||
* @param {string?} [roleName] - uses this name for role if provided. Generates from datetime if not set;
|
||||
* @param {string?} [exactName] - sets exact name (no generated part added);
|
||||
* @param {Parse.Role[]} [roles] - uses this name for role if provided. Generates from datetime if not set;
|
||||
* @param {boolean} [read] - value for role's acl public read. Defaults to true;
|
||||
* @param {boolean} [write] - value for role's acl public write. Defaults to true;
|
||||
*/
|
||||
createRole: async ({
|
||||
users = null,
|
||||
exactName = defaultRoleName + Date.now(),
|
||||
roleName = null,
|
||||
roles = null,
|
||||
read = true,
|
||||
write = true,
|
||||
}) => {
|
||||
const acl = new Parse.ACL();
|
||||
acl.setPublicReadAccess(read);
|
||||
acl.setPublicWriteAccess(write);
|
||||
|
||||
const role = new Parse.Object('_Role');
|
||||
role.setACL(acl);
|
||||
|
||||
// generate name based on roleName or use exactName (if botth not provided name is generated)
|
||||
const name = roleName ? roleName + Date.now() : exactName;
|
||||
role.set('name', name);
|
||||
|
||||
if (roles) {
|
||||
role.relation('roles').add(roles);
|
||||
}
|
||||
|
||||
if (users) {
|
||||
role.relation('users').add(users);
|
||||
}
|
||||
|
||||
await role.save({ useMasterKey: true });
|
||||
|
||||
return role;
|
||||
},
|
||||
};
|
||||
@@ -2861,6 +2861,35 @@ describe('schemas', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
it('should be rejected if CLP pointerFields is not an array', async done => {
|
||||
const config = Config.get(Parse.applicationId);
|
||||
const schemaController = await config.database.loadSchema();
|
||||
|
||||
const operationKey = 'get';
|
||||
const entity = 'pointerFields';
|
||||
const value = {};
|
||||
|
||||
const schemaSetup = async () =>
|
||||
await schemaController.addClassIfNotExists(
|
||||
'AnObject',
|
||||
{},
|
||||
{
|
||||
[operationKey]: {
|
||||
[entity]: value,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
await expectAsync(schemaSetup()).toBeRejectedWith(
|
||||
new Parse.Error(
|
||||
Parse.Error.INVALID_JSON,
|
||||
`'${value}' is not a valid value for ${operationKey}[${entity}] - expected an array.`
|
||||
)
|
||||
);
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
describe('index management', () => {
|
||||
beforeEach(() => require('../lib/TestUtils').destroyAllDataPermanently());
|
||||
it('cannot create index if field does not exist', done => {
|
||||
|
||||
Reference in New Issue
Block a user