Merge pull request #791 from ParsePlatform/nlutsenko.triggers.installationId

Propagate installationId to all Cloud Code triggers.
This commit is contained in:
Drew
2016-03-02 21:21:27 -08:00
6 changed files with 108 additions and 35 deletions

View File

@@ -749,6 +749,80 @@ describe('miscellaneous', function() {
}); });
}); });
it('test beforeSave/afterSave get installationId', function(done) {
let triggerTime = 0;
Parse.Cloud.beforeSave('GameScore', function(req, res) {
triggerTime++;
expect(triggerTime).toEqual(1);
expect(req.installationId).toEqual('yolo');
res.success();
});
Parse.Cloud.afterSave('GameScore', function(req) {
triggerTime++;
expect(triggerTime).toEqual(2);
expect(req.installationId).toEqual('yolo');
});
var headers = {
'Content-Type': 'application/json',
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
'X-Parse-Installation-Id': 'yolo'
};
request.post({
headers: headers,
url: 'http://localhost:8378/1/classes/GameScore',
body: JSON.stringify({ a: 'b' })
}, (error, response, body) => {
expect(error).toBe(null);
expect(triggerTime).toEqual(2);
Parse.Cloud._removeHook("Triggers", "beforeSave", "GameScore");
Parse.Cloud._removeHook("Triggers", "afterSave", "GameScore");
done();
});
});
it('test beforeDelete/afterDelete get installationId', function(done) {
let triggerTime = 0;
Parse.Cloud.beforeDelete('GameScore', function(req, res) {
triggerTime++;
expect(triggerTime).toEqual(1);
expect(req.installationId).toEqual('yolo');
res.success();
});
Parse.Cloud.afterDelete('GameScore', function(req) {
triggerTime++;
expect(triggerTime).toEqual(2);
expect(req.installationId).toEqual('yolo');
});
var headers = {
'Content-Type': 'application/json',
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
'X-Parse-Installation-Id': 'yolo'
};
request.post({
headers: headers,
url: 'http://localhost:8378/1/classes/GameScore',
body: JSON.stringify({ a: 'b' })
}, (error, response, body) => {
expect(error).toBe(null);
request.del({
headers: headers,
url: 'http://localhost:8378/1/classes/GameScore/' + JSON.parse(body).objectId
}, (error, response, body) => {
expect(error).toBe(null);
expect(triggerTime).toEqual(2);
Parse.Cloud._removeHook("Triggers", "beforeDelete", "GameScore");
Parse.Cloud._removeHook("Triggers", "afterDelete", "GameScore");
done();
});
});
});
it('test cloud function query parameters', (done) => { it('test cloud function query parameters', (done) => {
Parse.Cloud.define('echoParams', (req, res) => { Parse.Cloud.define('echoParams', (req, res) => {
res.success(req.params); res.success(req.params);

View File

@@ -86,7 +86,7 @@ describe('Parse Role testing', () => {
return createRole(rolesNames[2], anotherRole, user); return createRole(rolesNames[2], anotherRole, user);
}).then( (lastRole) => { }).then( (lastRole) => {
roleIds[lastRole.get("name")] = lastRole.id; roleIds[lastRole.get("name")] = lastRole.id;
var auth = new Auth(new Config("test") , true, user); var auth = new Auth({ config: new Config("test"), isMaster: true, user: user });
return auth._loadRoles(); return auth._loadRoles();
}) })
}).then( (roles) => { }).then( (roles) => {

View File

@@ -7,10 +7,11 @@ import cache from './cache';
// An Auth object tells you who is requesting something and whether // An Auth object tells you who is requesting something and whether
// the master key was used. // the master key was used.
// userObject is a Parse.User and can be null if there's no user. // userObject is a Parse.User and can be null if there's no user.
function Auth(config, isMaster, userObject) { function Auth({ config, isMaster = false, user, installationId } = {}) {
this.config = config; this.config = config;
this.installationId = installationId;
this.isMaster = isMaster; this.isMaster = isMaster;
this.user = userObject; this.user = user;
// Assuming a users roles won't change during a single request, we'll // Assuming a users roles won't change during a single request, we'll
// only load them once. // only load them once.
@@ -33,19 +34,19 @@ Auth.prototype.couldUpdateUserId = function(userId) {
// A helper to get a master-level Auth object // A helper to get a master-level Auth object
function master(config) { function master(config) {
return new Auth(config, true, null); return new Auth({ config, isMaster: true });
} }
// A helper to get a nobody-level Auth object // A helper to get a nobody-level Auth object
function nobody(config) { function nobody(config) {
return new Auth(config, false, null); return new Auth({ config, isMaster: false });
} }
// Returns a promise that resolves to an Auth object // Returns a promise that resolves to an Auth object
var getAuthForSessionToken = function(config, sessionToken) { var getAuthForSessionToken = function({ config, sessionToken, installationId } = {}) {
var cachedUser = cache.users.get(sessionToken); var cachedUser = cache.users.get(sessionToken);
if (cachedUser) { if (cachedUser) {
return Promise.resolve(new Auth(config, false, cachedUser)); return Promise.resolve(new Auth({ config, isMaster: false, installationId, user: cachedUser }));
} }
var restOptions = { var restOptions = {
limit: 1, limit: 1,
@@ -67,7 +68,7 @@ var getAuthForSessionToken = function(config, sessionToken) {
obj['sessionToken'] = sessionToken; obj['sessionToken'] = sessionToken;
let userObject = Parse.Object.fromJSON(obj); let userObject = Parse.Object.fromJSON(obj);
cache.users.set(sessionToken, userObject); cache.users.set(sessionToken, userObject);
return new Auth(config, false, userObject); return new Auth({ config, isMaster: false, installationId, user: userObject });
}); });
}; };

View File

@@ -22,23 +22,22 @@ export class UserController extends AdaptableController {
} }
super.validateAdapter(adapter); super.validateAdapter(adapter);
} }
expectedAdapterType() { expectedAdapterType() {
return MailAdapter; return MailAdapter;
} }
get shouldVerifyEmails() { get shouldVerifyEmails() {
return this.options.verifyUserEmails; return this.options.verifyUserEmails;
} }
setEmailVerifyToken(user) { setEmailVerifyToken(user) {
if (this.shouldVerifyEmails) { if (this.shouldVerifyEmails) {
user._email_verify_token = randomString(25); user._email_verify_token = randomString(25);
user.emailVerified = false; user.emailVerified = false;
} }
} }
verifyEmail(username, token) { verifyEmail(username, token) {
if (!this.shouldVerifyEmails) { if (!this.shouldVerifyEmails) {
// Trying to verify email when not enabled // Trying to verify email when not enabled
@@ -62,7 +61,7 @@ export class UserController extends AdaptableController {
return document; return document;
}); });
} }
checkResetTokenValidity(username, token) { checkResetTokenValidity(username, token) {
return this.config.database.adaptiveCollection('_User') return this.config.database.adaptiveCollection('_User')
.then(collection => { .then(collection => {
@@ -78,7 +77,7 @@ export class UserController extends AdaptableController {
return results[0]; return results[0];
}); });
} }
getUserIfNeeded(user) { getUserIfNeeded(user) {
if (user.username && user.email) { if (user.username && user.email) {
return Promise.resolve(user); return Promise.resolve(user);
@@ -90,7 +89,7 @@ export class UserController extends AdaptableController {
if (user.email) { if (user.email) {
where.email = user.email; where.email = user.email;
} }
var query = new RestQuery(this.config, Auth.master(this.config), '_User', where); var query = new RestQuery(this.config, Auth.master(this.config), '_User', where);
return query.execute().then(function(result){ return query.execute().then(function(result){
if (result.results.length != 1) { if (result.results.length != 1) {
@@ -99,7 +98,7 @@ export class UserController extends AdaptableController {
return result.results[0]; return result.results[0];
}) })
} }
sendVerificationEmail(user) { sendVerificationEmail(user) {
if (!this.shouldVerifyEmails) { if (!this.shouldVerifyEmails) {
@@ -122,7 +121,7 @@ export class UserController extends AdaptableController {
} }
}); });
} }
setPasswordResetToken(email) { setPasswordResetToken(email) {
let token = randomString(25); let token = randomString(25);
return this.config.database return this.config.database
@@ -142,11 +141,11 @@ export class UserController extends AdaptableController {
// TODO: No adapter? // TODO: No adapter?
return; return;
} }
return this.setPasswordResetToken(email).then((user) => { return this.setPasswordResetToken(email).then((user) => {
const token = encodeURIComponent(user._perishable_token); const token = encodeURIComponent(user._perishable_token);
const username = encodeURIComponent(user.username); const username = encodeURIComponent(user.username);
let link = `${this.config.requestResetPasswordURL}?token=${token}&username=${username}` let link = `${this.config.requestResetPasswordURL}?token=${token}&username=${username}`
let options = { let options = {
@@ -154,7 +153,7 @@ export class UserController extends AdaptableController {
link: link, link: link,
user: inflate('_User', user), user: inflate('_User', user),
}; };
if (this.adapter.sendPasswordResetEmail) { if (this.adapter.sendPasswordResetEmail) {
this.adapter.sendPasswordResetEmail(options); this.adapter.sendPasswordResetEmail(options);
} else { } else {
@@ -164,13 +163,13 @@ export class UserController extends AdaptableController {
return Promise.resolve(user); return Promise.resolve(user);
}); });
} }
updatePassword(username, token, password, config) { updatePassword(username, token, password, config) {
return this.checkResetTokenValidity(username, token).then(() => { return this.checkResetTokenValidity(username, token).then(() => {
return updateUserPassword(username, token, password, this.config); return updateUserPassword(username, token, password, this.config);
}); });
} }
defaultVerificationEmail({link, user, appName, }) { defaultVerificationEmail({link, user, appName, }) {
let text = "Hi,\n\n" + let text = "Hi,\n\n" +
"You are being asked to confirm the e-mail address " + user.email + " with " + appName + "\n\n" + "You are being asked to confirm the e-mail address " + user.email + " with " + appName + "\n\n" +
@@ -180,9 +179,9 @@ export class UserController extends AdaptableController {
let subject = 'Please verify your e-mail for ' + appName; let subject = 'Please verify your e-mail for ' + appName;
return { text, to, subject }; return { text, to, subject };
} }
defaultResetPasswordEmail({link, user, appName, }) { defaultResetPasswordEmail({link, user, appName, }) {
let text = "Hi,\n\n" + let text = "Hi,\n\n" +
"You requested to reset your password for " + appName + ".\n\n" + "You requested to reset your password for " + appName + ".\n\n" +
"" + "" +
"Click here to reset it:\n" + link; "Click here to reset it:\n" + link;
@@ -193,9 +192,9 @@ export class UserController extends AdaptableController {
} }
// Mark this private // Mark this private
function updateUserPassword(username, token, password, config) { function updateUserPassword(username, token, password, config) {
var write = new RestWrite(config, Auth.master(config), '_User', { var write = new RestWrite(config, Auth.master(config), '_User', {
username: username, username: username,
_perishable_token: token _perishable_token: token
}, {password: password, _perishable_token: null }, undefined); }, {password: password, _perishable_token: null }, undefined);
return write.execute(); return write.execute();

View File

@@ -89,7 +89,7 @@ function handleParseHeaders(req, res, next) {
var isMaster = (info.masterKey === req.config.masterKey); var isMaster = (info.masterKey === req.config.masterKey);
if (isMaster) { if (isMaster) {
req.auth = new auth.Auth(req.config, true); req.auth = new auth.Auth({ config: req.config, installationId: info.installationId, isMaster: true });
next(); next();
return; return;
} }
@@ -114,23 +114,23 @@ function handleParseHeaders(req, res, next) {
} }
if (!info.sessionToken) { if (!info.sessionToken) {
req.auth = new auth.Auth(req.config, false); req.auth = new auth.Auth({ config: req.config, installationId: info.installationId, isMaster: false });
next(); next();
return; return;
} }
return auth.getAuthForSessionToken( return auth.getAuthForSessionToken({ config: req.config, installationId: info.installationId, sessionToken: info.sessionToken })
req.config, info.sessionToken).then((auth) => { .then((auth) => {
if (auth) { if (auth) {
req.auth = auth; req.auth = auth;
next(); next();
} }
}).catch((error) => { })
.catch((error) => {
// TODO: Determine the correct error scenario. // TODO: Determine the correct error scenario.
console.log(error); console.log(error);
throw new Parse.Error(Parse.Error.UNKNOWN_ERROR, error); throw new Parse.Error(Parse.Error.UNKNOWN_ERROR, error);
}); });
} }
var allowCrossDomain = function(req, res, next) { var allowCrossDomain = function(req, res, next) {

View File

@@ -110,12 +110,11 @@ export function getRequestObject(triggerType, auth, parseObject, originalParseOb
if (auth.user) { if (auth.user) {
request['user'] = auth.user; request['user'] = auth.user;
} }
// TODO: Add installation to Auth?
if (auth.installationId) { if (auth.installationId) {
request['installationId'] = auth.installationId; request['installationId'] = auth.installationId;
} }
return request; return request;
}; }
// Creates the response object, and uses the request object to pass data // Creates the response object, and uses the request object to pass data
// The API will call this with REST API formatted objects, this will // The API will call this with REST API formatted objects, this will