Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add file beforeFind triggers, file ACL, file cleanup, file references #8385

Draft
wants to merge 17 commits into
base: alpha
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions spec/CloudCode.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3812,6 +3812,73 @@ describe('saveFile hooks', () => {
);
}
});

it('can run find hooks', async () => {
await reconfigureServer({
fileUpload: {
enableForPublic: true,
enableForAnonymousUser: true,
enableForAuthenticatedUser: true,
enableLegacyAccess: false,
},
});
const user = new Parse.User();
user.setUsername('triggeruser');
user.setPassword('triggeruser');
await user.signUp();
const base64 = 'V29ya2luZyBhdCBQYXJzZSBpcyBncmVhdCE=';
const file = new Parse.File('myfile.txt', { base64 });
await file.save({ sessionToken: user.getSessionToken() });
const hooks = {
beforeFind(req) {
expect(req.user.id).toEqual(user.id);
expect(req.file instanceof Parse.File).toBeTrue();
expect(req.file._name).toEqual(file._name);
expect(req.file._url).toEqual(file._url.split('?')[0]);
},
afterFind(req) {
expect(req.user.id).toEqual(user.id);
expect(req.file instanceof Parse.File).toBeTrue();
expect(req.file._name).toEqual(file._name);
expect(req.file._url).toEqual(file._url.split('?')[0]);
},
};
for (const key in hooks) {
spyOn(hooks, key).and.callThrough();
Parse.Cloud[key](Parse.File, hooks[key]);
}
const response = await request({ url: file.url() });
expect(response.text).toEqual('Working at Parse is great!');
for (const key in hooks) {
expect(hooks[key]).toHaveBeenCalled();
}
});

it('can clean up files', async () => {
const server = await reconfigureServer();
const base64 = 'V29ya2luZyBhdCBQYXJzZSBpcyBncmVhdCE=';
const file = new Parse.File('myfile.txt', { base64 });
const obj = await new Parse.Object('TestObject').save({ file });
await new Promise(resolve => setTimeout(resolve, 1000));
await Promise.all([
(async () => {
const objects = await new Parse.Query('_FileObject').find({ useMasterKey: true });
expect(objects.length).toBe(1);
})(),
(async () => {
const objects = await new Parse.Query('_FileReference').find({ useMasterKey: true });
expect(objects.length).toBe(1);
})(),
]);
await obj.destroy();
await new Promise(resolve => setTimeout(resolve, 1000));
const references = await new Parse.Query('_FileReference').find({ useMasterKey: true });
expect(references.length).toBe(0);
await server.cleanupFiles();
await new Promise(resolve => setTimeout(resolve, 1000));
const objects = await new Parse.Query('_FileObject').find({ useMasterKey: true });
expect(objects.length).toBe(0);
});
});

describe('sendEmail', () => {
Expand Down
8 changes: 3 additions & 5 deletions spec/FilesController.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ const mockAdapter = {

// Small additional tests to improve overall coverage
describe('FilesController', () => {
it('should properly expand objects', done => {
it('should properly expand objects', async () => {
const config = Config.get(Parse.applicationId);
const gridFSAdapter = new GridFSBucketAdapter('mongodb://localhost:27017/parse');
const filesController = new FilesController(gridFSAdapter);
const result = filesController.expandFilesInObject(config, function () {});
const result = await filesController.expandFilesInObject(config, function () {});

expect(result).toBeUndefined();

Expand All @@ -37,10 +37,8 @@ describe('FilesController', () => {
const anObject = {
aFile: fullFile,
};
filesController.expandFilesInObject(config, anObject);
await filesController.expandFilesInObject(config, anObject);
expect(anObject.aFile.url).toEqual('http://an.url');

done();
});

it_only_db('mongo')('should pass databaseOptions to GridFSBucketAdapter', async () => {
Expand Down
138 changes: 70 additions & 68 deletions spec/ParseFile.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,15 @@ describe('Parse.File testing', () => {
ok(objectAgain.get('file') instanceof Parse.File);
});

it('autosave file in object', async done => {
it('autosave file in object', async () => {
await reconfigureServer({
fileUpload: {
enableForPublic: true,
enableForAnonymousUser: true,
enableForAuthenticatedUser: true,
enableLegacyAccess: false,
},
});
let file = new Parse.File('hello.txt', data, 'text/plain');
ok(!file.url());
const object = new Parse.Object('TestObject');
Expand All @@ -276,7 +284,60 @@ describe('Parse.File testing', () => {
ok(file.name());
ok(file.url());
notEqual(file.name(), 'hello.txt');
done();
await Promise.all([
(async () => {
const fileObjects = await new Parse.Query('_FileObject').find({
useMasterKey: true,
json: true,
});
expect(fileObjects.length).toBe(1);
const fileObject = fileObjects[0];
expect(fileObject.className).toBe('_FileObject');
expect(fileObject.file).toEqual({
__type: 'File',
name: file.name(),
url: file.url().split('?token')[0],
});
expect(fileObject.ACL).toEqual({
'*': {
read: true,
},
});
})(),
(async () => {
const fileReferences = await new Parse.Query('_FileReference').find({
useMasterKey: true,
json: true,
});
expect(fileReferences.length).toBe(1);
const fileReference = fileReferences[0];
expect(fileReference.className).toBe('_FileReference');
expect(fileReference.file).toEqual({
__type: 'Pointer',
className: '_FileObject',
objectId: fileReference.file.objectId,
});
expect(fileReference.referenceId).toBe(objectAgain.id);
expect(fileReference.referenceClass).toBe('TestObject');
})(),
(async () => {
const fileSessions = await new Parse.Query('_FileSession').find({
useMasterKey: true,
json: true,
});
expect(fileSessions.length).toBe(3);
const fileSession = fileSessions[2];
expect(fileSession.file).toEqual({
__type: 'Pointer',
className: '_FileObject',
objectId: fileSession.file.objectId,
});
expect(fileSession.token).toBe(file.url().split('?token=')[1]);
expect(fileSession.master).toBe(false);
expect(new Date(fileSession.expiry.iso) instanceof Date).toBeTrue();
})(),
]);
expect(file.url()).toContain('?token=');
});

it('autosave file in object in object', async done => {
Expand Down Expand Up @@ -390,7 +451,7 @@ describe('Parse.File testing', () => {
});
});

it('supports array of files', done => {
it('supports array of files', async () => {
const file = {
__type: 'File',
url: 'http://meep.meep',
Expand All @@ -399,19 +460,12 @@ describe('Parse.File testing', () => {
const files = [file, file];
const obj = new Parse.Object('FilesArrayTest');
obj.set('files', files);
obj
.save()
.then(() => {
const query = new Parse.Query('FilesArrayTest');
return query.first();
})
.then(result => {
const filesAgain = result.get('files');
expect(filesAgain.length).toEqual(2);
expect(filesAgain[0].name()).toEqual('meep');
expect(filesAgain[0].url()).toEqual('http://meep.meep');
done();
});
await obj.save();
const result = await new Parse.Query('FilesArrayTest').first();
const filesAgain = result.get('files');
expect(filesAgain.length).toEqual(2);
expect(filesAgain[0].name()).toEqual('meep');
expect(filesAgain[0].url()).toEqual('http://meep.meep');
});

it('validates filename characters', done => {
Expand Down Expand Up @@ -486,58 +540,6 @@ describe('Parse.File testing', () => {
});
});

it('creates correct url for old files hosted on files.parsetfss.com', done => {
const file = {
__type: 'File',
url: 'http://irrelevant.elephant/',
name: 'tfss-123.txt',
};
const obj = new Parse.Object('OldFileTest');
obj.set('oldfile', file);
obj
.save()
.then(() => {
const query = new Parse.Query('OldFileTest');
return query.first();
})
.then(result => {
const fileAgain = result.get('oldfile');
expect(fileAgain.url()).toEqual('http://files.parsetfss.com/test/tfss-123.txt');
done();
})
.catch(e => {
jfail(e);
done();
});
});

it('creates correct url for old files hosted on files.parse.com', done => {
const file = {
__type: 'File',
url: 'http://irrelevant.elephant/',
name: 'd6e80979-a128-4c57-a167-302f874700dc-123.txt',
};
const obj = new Parse.Object('OldFileTest');
obj.set('oldfile', file);
obj
.save()
.then(() => {
const query = new Parse.Query('OldFileTest');
return query.first();
})
.then(result => {
const fileAgain = result.get('oldfile');
expect(fileAgain.url()).toEqual(
'http://files.parse.com/test/d6e80979-a128-4c57-a167-302f874700dc-123.txt'
);
done();
})
.catch(e => {
jfail(e);
done();
});
});

it('supports files in objects without urls', done => {
const file = {
__type: 'File',
Expand Down
31 changes: 10 additions & 21 deletions spec/ParseUser.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1361,30 +1361,19 @@ describe('Parse.User testing', () => {
.catch(done.fail);
});

it('log in with provider with files', done => {
it('log in with provider with files', async () => {
const provider = getMockFacebookProvider();
Parse.User._registerAuthenticationProvider(provider);
const file = new Parse.File('yolo.txt', [1, 2, 3], 'text/plain');
file
.save()
.then(file => {
const user = new Parse.User();
user.set('file', file);
return user._linkWith('facebook', {});
})
.then(user => {
expect(user._isLinked('facebook')).toBeTruthy();
return Parse.User._logInWith('facebook', {});
})
.then(user => {
const fileAgain = user.get('file');
expect(fileAgain.name()).toMatch(/yolo.txt$/);
expect(fileAgain.url()).toMatch(/yolo.txt$/);
})
.then(() => {
done();
})
.catch(done.fail);
await file.save();
let user = new Parse.User();
user.set('file', file);
await user._linkWith('facebook', {});
expect(user._isLinked('facebook')).toBeTruthy();
user = await Parse.User._logInWith('facebook', {});
const fileAgain = user.get('file');
expect(fileAgain.name()).toMatch(/yolo.txt$/);
expect(fileAgain.url()).toMatch(/yolo.txt$/);
});

it('log in with provider twice', async done => {
Expand Down
4 changes: 3 additions & 1 deletion spec/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ on_db(
);

let logLevel;
let silent = true;
let silent = false;
if (process.env.VERBOSE) {
silent = false;
logLevel = 'verbose';
Expand Down Expand Up @@ -112,6 +112,8 @@ const defaultConfiguration = {
enableForPublic: true,
enableForAnonymousUser: true,
enableForAuthenticatedUser: true,
enableLegacyAccess: true,
tokenValidityDuration: 5 * 60,
},
push: {
android: {
Expand Down
13 changes: 13 additions & 0 deletions src/Config.js
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,19 @@ export class Config {
} else if (typeof fileUpload.enableForAuthenticatedUser !== 'boolean') {
throw 'fileUpload.enableForAuthenticatedUser must be a boolean value.';
}
if (fileUpload.enableLegacyAccess === undefined) {
fileUpload.enableLegacyAccess = FileUploadOptions.enableLegacyAccess.default;
} else if (typeof fileUpload.enableLegacyAccess !== 'boolean') {
throw 'fileUpload.enableLegacyAccess must be a boolean value.';
}
if (fileUpload.tokenValidityDuration === undefined) {
fileUpload.tokenValidityDuration = FileUploadOptions.tokenValidityDuration.default;
} else if (
typeof fileUpload.tokenValidityDuration !== 'number' ||
fileUpload.tokenValidityDuration <= 0
) {
throw 'fileUpload.tokenValidityDuration must be a positive number.';
}
}

static validateIps(field, masterKeyIps) {
Expand Down
Loading