diff --git a/spec/Auth.spec.js b/spec/Auth.spec.js index a4d97639f0..81aacb01aa 100644 --- a/spec/Auth.spec.js +++ b/spec/Auth.spec.js @@ -1,6 +1,6 @@ describe('Auth', () => { - const Auth = require('../lib/Auth.js').Auth; - + const { Auth, getAuthForSessionToken } = require('../lib/Auth.js'); + const Config = require('../lib/Config'); describe('getUserRoles', () => { let auth; let config; @@ -90,4 +90,33 @@ describe('Auth', () => { }); }); + + it('should load auth without a config', async () => { + const user = new Parse.User(); + await user.signUp({ + username: 'hello', + password: 'password' + }); + expect(user.getSessionToken()).not.toBeUndefined(); + const userAuth = await getAuthForSessionToken({ + sessionToken: user.getSessionToken() + }); + expect(userAuth.user instanceof Parse.User).toBe(true); + expect(userAuth.user.id).toBe(user.id); + }); + + it('should load auth with a config', async () => { + const user = new Parse.User(); + await user.signUp({ + username: 'hello', + password: 'password' + }); + expect(user.getSessionToken()).not.toBeUndefined(); + const userAuth = await getAuthForSessionToken({ + sessionToken: user.getSessionToken(), + config: Config.get('test'), + }); + expect(userAuth.user instanceof Parse.User).toBe(true); + expect(userAuth.user.id).toBe(user.id); + }); }); diff --git a/spec/CloudCode.spec.js b/spec/CloudCode.spec.js index 4d52bbe369..ba8956a684 100644 --- a/spec/CloudCode.spec.js +++ b/spec/CloudCode.spec.js @@ -832,10 +832,7 @@ describe('Cloud Code', () => { expect(body.result).toEqual('second data'); done(); }) - .catch(error => { - fail(JSON.stringify(error)); - done(); - }); + .catch(done.fail); }); it('trivial beforeSave should not affect fetched pointers (regression test for #1238)', done => { diff --git a/spec/ParseRole.spec.js b/spec/ParseRole.spec.js index 19f34bb447..283f1cafaa 100644 --- a/spec/ParseRole.spec.js +++ b/spec/ParseRole.spec.js @@ -142,7 +142,7 @@ describe('Parse Role testing', () => { }); - it("should recursively load roles", (done) => { + function testLoadRoles(config, done) { const rolesNames = ["FooRole", "BarRole", "BazRole"]; const roleIds = {}; createTestUser().then((user) => { @@ -159,7 +159,7 @@ describe('Parse Role testing', () => { return createRole(rolesNames[2], anotherRole, null); }).then((lastRole) => { roleIds[lastRole.get("name")] = lastRole.id; - const auth = new Auth({ config: Config.get("test"), isMaster: true, user: user }); + const auth = new Auth({ config, isMaster: true, user: user }); return auth._loadRoles(); }) }).then((roles) => { @@ -172,6 +172,14 @@ describe('Parse Role testing', () => { fail("should succeed") done(); }); + } + + it("should recursively load roles", (done) => { + testLoadRoles(Config.get('test'), done); + }); + + it("should recursively load roles without config", (done) => { + testLoadRoles(undefined, done); }); it("_Role object should not save without name.", (done) => { diff --git a/src/Auth.js b/src/Auth.js index 8658f13025..c4ec748b7a 100644 --- a/src/Auth.js +++ b/src/Auth.js @@ -5,8 +5,9 @@ const Parse = require('parse/node'); // An Auth object tells you who is requesting something and whether // the master key was used. // userObject is a Parse.User and can be null if there's no user. -function Auth({ config, isMaster = false, isReadOnly = false, user, installationId } = {}) { +function Auth({ config, cacheController = undefined, isMaster = false, isReadOnly = false, user, installationId }) { this.config = config; + this.cacheController = cacheController || (config && config.cacheController); this.installationId = installationId; this.isMaster = isMaster; this.user = user; @@ -48,47 +49,58 @@ function nobody(config) { // Returns a promise that resolves to an Auth object -var getAuthForSessionToken = function({ config, sessionToken, installationId } = {}) { - return config.cacheController.user.get(sessionToken).then((userJSON) => { +const getAuthForSessionToken = async function({ config, cacheController, sessionToken, installationId }) { + cacheController = cacheController || (config && config.cacheController); + if (cacheController) { + const userJSON = await cacheController.user.get(sessionToken); if (userJSON) { const cachedUser = Parse.Object.fromJSON(userJSON); - return Promise.resolve(new Auth({config, isMaster: false, installationId, user: cachedUser})); + return Promise.resolve(new Auth({config, cacheController, isMaster: false, installationId, user: cachedUser})); } + } - var restOptions = { + let results; + if (config) { + const restOptions = { limit: 1, include: 'user' }; - var query = new RestQuery(config, master(config), '_Session', {sessionToken}, restOptions); - return query.execute().then((response) => { - var results = response.results; - if (results.length !== 1 || !results[0]['user']) { - throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token'); - } + const query = new RestQuery(config, master(config), '_Session', { sessionToken }, restOptions); + results = (await query.execute()).results; + } else { + results = (await new Parse.Query(Parse.Session) + .limit(1) + .include('user') + .equalTo('sessionToken', sessionToken) + .find({ useMasterKey: true })).map((obj) => obj.toJSON()) + } - var now = new Date(), - expiresAt = results[0].expiresAt ? new Date(results[0].expiresAt.iso) : undefined; - if (expiresAt < now) { - throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, - 'Session token is expired.'); - } - var obj = results[0]['user']; - delete obj.password; - obj['className'] = '_User'; - obj['sessionToken'] = sessionToken; - config.cacheController.user.put(sessionToken, obj); - const userObject = Parse.Object.fromJSON(obj); - return new Auth({config, isMaster: false, installationId, user: userObject}); - }); - }); + if (results.length !== 1 || !results[0]['user']) { + throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token'); + } + const now = new Date(), + expiresAt = results[0].expiresAt ? new Date(results[0].expiresAt.iso) : undefined; + if (expiresAt < now) { + throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, + 'Session token is expired.'); + } + const obj = results[0]['user']; + delete obj.password; + obj['className'] = '_User'; + obj['sessionToken'] = sessionToken; + if (cacheController) { + cacheController.user.put(sessionToken, obj); + } + const userObject = Parse.Object.fromJSON(obj); + return new Auth({ config, cacheController, isMaster: false, installationId, user: userObject }); }; -var getAuthForLegacySessionToken = function({config, sessionToken, installationId } = {}) { +var getAuthForLegacySessionToken = function({ config, sessionToken, installationId }) { var restOptions = { limit: 1 }; - var query = new RestQuery(config, master(config), '_User', { sessionToken: sessionToken}, restOptions); + var query = new RestQuery(config, master(config), '_User', { sessionToken }, restOptions); return query.execute().then((response) => { var results = response.results; if (results.length !== 1) { @@ -97,7 +109,7 @@ var getAuthForLegacySessionToken = function({config, sessionToken, installationI const obj = results[0]; obj.className = '_User'; const userObject = Parse.Object.fromJSON(obj); - return new Auth({config, isMaster: false, installationId, user: userObject}); + return new Auth({ config, isMaster: false, installationId, user: userObject }); }); } @@ -116,84 +128,113 @@ Auth.prototype.getUserRoles = function() { return this.rolePromise; }; -// Iterates through the role tree and compiles a users roles -Auth.prototype._loadRoles = function() { - var cacheAdapter = this.config.cacheController; - return cacheAdapter.role.get(this.user.id).then((cachedRoles) => { - if (cachedRoles != null) { - this.fetchedRoles = true; - this.userRoles = cachedRoles; - return Promise.resolve(cachedRoles); - } - - var restWhere = { +Auth.prototype.getRolesForUser = function() { + if (this.config) { + const restWhere = { 'users': { __type: 'Pointer', className: '_User', objectId: this.user.id } }; - // First get the role ids this user is directly a member of - var query = new RestQuery(this.config, master(this.config), '_Role', restWhere, {}); - return query.execute().then((response) => { - var results = response.results; - if (!results.length) { - this.userRoles = []; - this.fetchedRoles = true; - this.rolePromise = null; - - cacheAdapter.role.put(this.user.id, Array(...this.userRoles)); - return Promise.resolve(this.userRoles); - } - var rolesMap = results.reduce((m, r) => { - m.names.push(r.name); - m.ids.push(r.objectId); - return m; - }, {ids: [], names: []}); - - // run the recursive finding - return this._getAllRolesNamesForRoleIds(rolesMap.ids, rolesMap.names) - .then((roleNames) => { - this.userRoles = roleNames.map((r) => { - return 'role:' + r; - }); - this.fetchedRoles = true; - this.rolePromise = null; - cacheAdapter.role.put(this.user.id, Array(...this.userRoles)); - return Promise.resolve(this.userRoles); - }); - }); + const query = new RestQuery(this.config, master(this.config), '_Role', restWhere, {}); + return query.execute().then(({ results }) => results); + } + + return new Parse.Query(Parse.Role) + .equalTo('users', this.user) + .find({ useMasterKey: true }) + .then((results) => results.map((obj) => obj.toJSON())); +} + +// Iterates through the role tree and compiles a user's roles +Auth.prototype._loadRoles = async function() { + if (this.cacheController) { + const cachedRoles = await this.cacheController.role.get(this.user.id); + if (cachedRoles != null) { + this.fetchedRoles = true; + this.userRoles = cachedRoles; + return cachedRoles; + } + } + + // First get the role ids this user is directly a member of + const results = await this.getRolesForUser(); + if (!results.length) { + this.userRoles = []; + this.fetchedRoles = true; + this.rolePromise = null; + + this.cacheRoles(); + return this.userRoles; + } + + const rolesMap = results.reduce((m, r) => { + m.names.push(r.name); + m.ids.push(r.objectId); + return m; + }, {ids: [], names: []}); + + // run the recursive finding + const roleNames = await this._getAllRolesNamesForRoleIds(rolesMap.ids, rolesMap.names); + this.userRoles = roleNames.map((r) => { + return 'role:' + r; }); + this.fetchedRoles = true; + this.rolePromise = null; + this.cacheRoles(); + return this.userRoles; }; -// Given a list of roleIds, find all the parent roles, returns a promise with all names -Auth.prototype._getAllRolesNamesForRoleIds = function(roleIDs, names = [], queriedRoles = {}) { - const ins = roleIDs.filter((roleID) => { - return queriedRoles[roleID] !== true; - }).map((roleID) => { - // mark as queried - queriedRoles[roleID] = true; +Auth.prototype.cacheRoles = function() { + if (!this.cacheController) { + return false; + } + this.cacheController.role.put(this.user.id, Array(...this.userRoles)); + return true; +} + +Auth.prototype.getRolesByIds = function(ins) { + const roles = ins.map((id) => { return { __type: 'Pointer', className: '_Role', - objectId: roleID + objectId: id } }); + const restWhere = { 'roles': { '$in': roles }}; + + // Build an OR query across all parentRoles + if (!this.config) { + return new Parse.Query(Parse.Role) + .containedIn('roles', ins.map((id) => { + const role = new Parse.Object(Parse.Role); + role.id = id; + return role; + })) + .find({ useMasterKey: true }) + .then((results) => results.map((obj) => obj.toJSON())); + } + + return new RestQuery(this.config, master(this.config), '_Role', restWhere, {}) + .execute() + .then(({ results }) => results); +} + +// Given a list of roleIds, find all the parent roles, returns a promise with all names +Auth.prototype._getAllRolesNamesForRoleIds = function(roleIDs, names = [], queriedRoles = {}) { + const ins = roleIDs.filter((roleID) => { + const wasQueried = queriedRoles[roleID] !== true; + queriedRoles[roleID] = true; + return wasQueried; + }); // all roles are accounted for, return the names if (ins.length == 0) { return Promise.resolve([...new Set(names)]); } - // Build an OR query across all parentRoles - let restWhere; - if (ins.length == 1) { - restWhere = { 'roles': ins[0] }; - } else { - restWhere = { 'roles': { '$in': ins }} - } - const query = new RestQuery(this.config, master(this.config), '_Role', restWhere, {}); - return query.execute().then((response) => { - var results = response.results; + + return this.getRolesByIds(ins).then((results) => { // Nothing found if (!results.length) { return Promise.resolve(names);