Skip to content

Commit 90beaca

Browse files
committed
fix
1 parent 0a86d5c commit 90beaca

File tree

3 files changed

+133
-64
lines changed

3 files changed

+133
-64
lines changed

spec/ParseLiveQuery.spec.js

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1308,4 +1308,88 @@ describe('ParseLiveQuery', function () {
13081308
await new Promise(resolve => setTimeout(resolve, 100));
13091309
expect(createSpy).toHaveBeenCalledTimes(1);
13101310
});
1311+
1312+
describe('class level permissions', () => {
1313+
async function setPermissionsOnClass(className, permissions, doPut) {
1314+
const method = doPut ? 'PUT' : 'POST';
1315+
const response = await fetch(Parse.serverURL + '/schemas/' + className, {
1316+
method,
1317+
headers: {
1318+
'X-Parse-Application-Id': Parse.applicationId,
1319+
'X-Parse-Master-Key': Parse.masterKey,
1320+
'Content-Type': 'application/json',
1321+
},
1322+
body: JSON.stringify({
1323+
classLevelPermissions: permissions,
1324+
}),
1325+
});
1326+
const body = await response.json();
1327+
if (body.error) {
1328+
throw body;
1329+
}
1330+
return body;
1331+
}
1332+
1333+
it('delivers LiveQuery event to authenticated client when CLP allows find', async () => {
1334+
await reconfigureServer({
1335+
liveQuery: {
1336+
classNames: ['SecureChat'],
1337+
},
1338+
startLiveQueryServer: true,
1339+
verbose: false,
1340+
silent: true,
1341+
});
1342+
1343+
const user = new Parse.User();
1344+
user.setUsername('admin');
1345+
user.setPassword('password');
1346+
await user.signUp();
1347+
1348+
await setPermissionsOnClass('SecureChat', {
1349+
create: { '*': true },
1350+
find: { [user.id]: true },
1351+
});
1352+
1353+
// Subscribe as the authorized user
1354+
const query = new Parse.Query('SecureChat');
1355+
const subscription = await query.subscribe(user.getSessionToken());
1356+
1357+
const spy = jasmine.createSpy('create');
1358+
subscription.on('create', spy);
1359+
1360+
const obj = new Parse.Object('SecureChat');
1361+
obj.set('secret', 'data');
1362+
await obj.save(null, { useMasterKey: true });
1363+
1364+
await sleep(500);
1365+
expect(spy).toHaveBeenCalledTimes(1);
1366+
});
1367+
1368+
it('rejects LiveQuery subscription when CLP denies find at subscription time', async () => {
1369+
await reconfigureServer({
1370+
liveQuery: {
1371+
classNames: ['SecureChat'],
1372+
},
1373+
startLiveQueryServer: true,
1374+
verbose: false,
1375+
silent: true,
1376+
});
1377+
1378+
const user = new Parse.User();
1379+
user.setUsername('admin');
1380+
user.setPassword('password');
1381+
await user.signUp();
1382+
1383+
await setPermissionsOnClass('SecureChat', {
1384+
create: { '*': true },
1385+
find: { [user.id]: true },
1386+
});
1387+
1388+
// Log out so subscription is unauthenticated
1389+
await Parse.User.logOut();
1390+
1391+
const query = new Parse.Query('SecureChat');
1392+
await expectAsync(query.subscribe()).toBeRejected();
1393+
});
1394+
});
13111395
});

spec/ParseLiveQueryServer.spec.js

Lines changed: 15 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1563,11 +1563,8 @@ describe('ParseLiveQueryServer', function () {
15631563
});
15641564

15651565
describe('class level permissions', () => {
1566-
it('matches CLP when find is closed', done => {
1566+
it('rejects CLP when find is closed', async () => {
15671567
const parseLiveQueryServer = new ParseLiveQueryServer({});
1568-
const acl = new Parse.ACL();
1569-
acl.setReadAccess(testUserId, true);
1570-
// Mock sessionTokenCache will return false when sessionToken is undefined
15711568
const client = {
15721569
sessionToken: 'sessionToken',
15731570
getSubscriptionInfo: jasmine.createSpy('getSubscriptionInfo').and.returnValue({
@@ -1576,27 +1573,19 @@ describe('ParseLiveQueryServer', function () {
15761573
};
15771574
const requestId = 0;
15781575

1579-
parseLiveQueryServer
1580-
._matchesCLP(
1581-
{
1582-
find: {},
1583-
},
1576+
await expectAsync(
1577+
parseLiveQueryServer._matchesCLP(
1578+
{ find: {} },
15841579
{ className: 'Yolo' },
15851580
client,
15861581
requestId,
15871582
'find'
15881583
)
1589-
.then(isMatched => {
1590-
expect(isMatched).toBe(false);
1591-
done();
1592-
});
1584+
).toBeRejected();
15931585
});
15941586

1595-
it('matches CLP when find is open', done => {
1587+
it('resolves CLP when find is open', async () => {
15961588
const parseLiveQueryServer = new ParseLiveQueryServer({});
1597-
const acl = new Parse.ACL();
1598-
acl.setReadAccess(testUserId, true);
1599-
// Mock sessionTokenCache will return false when sessionToken is undefined
16001589
const client = {
16011590
sessionToken: 'sessionToken',
16021591
getSubscriptionInfo: jasmine.createSpy('getSubscriptionInfo').and.returnValue({
@@ -1605,27 +1594,19 @@ describe('ParseLiveQueryServer', function () {
16051594
};
16061595
const requestId = 0;
16071596

1608-
parseLiveQueryServer
1609-
._matchesCLP(
1610-
{
1611-
find: { '*': true },
1612-
},
1597+
await expectAsync(
1598+
parseLiveQueryServer._matchesCLP(
1599+
{ find: { '*': true } },
16131600
{ className: 'Yolo' },
16141601
client,
16151602
requestId,
16161603
'find'
16171604
)
1618-
.then(isMatched => {
1619-
expect(isMatched).toBe(true);
1620-
done();
1621-
});
1605+
).toBeResolved();
16221606
});
16231607

1624-
it('matches CLP when find is restricted to userIds', done => {
1608+
it('rejects CLP when find is restricted to userIds', async () => {
16251609
const parseLiveQueryServer = new ParseLiveQueryServer({});
1626-
const acl = new Parse.ACL();
1627-
acl.setReadAccess(testUserId, true);
1628-
// Mock sessionTokenCache will return false when sessionToken is undefined
16291610
const client = {
16301611
sessionToken: 'sessionToken',
16311612
getSubscriptionInfo: jasmine.createSpy('getSubscriptionInfo').and.returnValue({
@@ -1634,20 +1615,15 @@ describe('ParseLiveQueryServer', function () {
16341615
};
16351616
const requestId = 0;
16361617

1637-
parseLiveQueryServer
1638-
._matchesCLP(
1639-
{
1640-
find: { userId: true },
1641-
},
1618+
await expectAsync(
1619+
parseLiveQueryServer._matchesCLP(
1620+
{ find: { userId: true } },
16421621
{ className: 'Yolo' },
16431622
client,
16441623
requestId,
16451624
'find'
16461625
)
1647-
.then(isMatched => {
1648-
expect(isMatched).toBe(false);
1649-
done();
1650-
});
1626+
).toBeRejected();
16511627
});
16521628
});
16531629

src/LiveQuery/ParseLiveQueryServer.ts

Lines changed: 34 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
} from '../triggers';
2121
import { getAuthForSessionToken, Auth } from '../Auth';
2222
import { getCacheController, getDatabaseController } from '../Controllers';
23+
import Config from '../Config';
2324
import { LRUCache as LRU } from 'lru-cache';
2425
import UserRouter from '../Routers/UsersRouter';
2526
import DatabaseController from '../Controllers/DatabaseController';
@@ -590,39 +591,20 @@ class ParseLiveQueryServer {
590591
requestId?: number,
591592
op?: string
592593
): Promise<any> {
593-
// try to match on user first, less expensive than with roles
594594
const subscriptionInfo = client.getSubscriptionInfo(requestId);
595595
const aclGroup = ['*'];
596-
let userId;
597596
if (typeof subscriptionInfo !== 'undefined') {
598597
const { userId } = await this.getAuthForSessionToken(subscriptionInfo.sessionToken);
599598
if (userId) {
600599
aclGroup.push(userId);
601600
}
602601
}
603-
try {
604-
await SchemaController.validatePermission(
605-
classLevelPermissions,
606-
object.className,
607-
aclGroup,
608-
op
609-
);
610-
return true;
611-
} catch (e) {
612-
logger.verbose(`Failed matching CLP for ${object.id} ${userId} ${e}`);
613-
return false;
614-
}
615-
// TODO: handle roles permissions
616-
// Object.keys(classLevelPermissions).forEach((key) => {
617-
// const perm = classLevelPermissions[key];
618-
// Object.keys(perm).forEach((key) => {
619-
// if (key.indexOf('role'))
620-
// });
621-
// })
622-
// // it's rejected here, check the roles
623-
// var rolesQuery = new Parse.Query(Parse.Role);
624-
// rolesQuery.equalTo("users", user);
625-
// return rolesQuery.find({useMasterKey:true});
602+
await SchemaController.validatePermission(
603+
classLevelPermissions,
604+
object.className,
605+
aclGroup,
606+
op
607+
);
626608
}
627609

628610
async _filterSensitiveData(
@@ -907,6 +889,33 @@ class ParseLiveQueryServer {
907889
return;
908890
}
909891
}
892+
// Check CLP for subscribe operation
893+
const appConfig = Config.get(this.config.appId);
894+
const schemaController = await appConfig.database.loadSchema();
895+
const classLevelPermissions = schemaController.getClassLevelPermissions(className);
896+
const op = this._getCLPOperation(request.query);
897+
const aclGroup = ['*'];
898+
if (!authCalled) {
899+
const auth = await this.getAuthFromClient(
900+
client,
901+
request.requestId,
902+
request.sessionToken
903+
);
904+
authCalled = true;
905+
if (auth && auth.user) {
906+
request.user = auth.user;
907+
aclGroup.push(auth.user.id);
908+
}
909+
} else if (request.user) {
910+
aclGroup.push(request.user.id);
911+
}
912+
await SchemaController.validatePermission(
913+
classLevelPermissions,
914+
className,
915+
aclGroup,
916+
op
917+
);
918+
910919
// Get subscription from subscriptions, create one if necessary
911920
const subscriptionHash = queryHash(request.query);
912921
// Add className to subscriptions if necessary

0 commit comments

Comments
 (0)