Skip to content

Commit 9fd4516

Browse files
authored
fix: protected fields exposed via LiveQuery (GHSA-crrq-vr9j-fxxh) [skip release] (#8076)
1 parent e8eb546 commit 9fd4516

File tree

4 files changed

+125
-23
lines changed

4 files changed

+125
-23
lines changed

Diff for: spec/ParseLiveQuery.spec.js

+46
Original file line numberDiff line numberDiff line change
@@ -1110,6 +1110,52 @@ describe('ParseLiveQuery', function () {
11101110
}
11111111
});
11121112

1113+
it('should strip out protected fields', async () => {
1114+
await reconfigureServer({
1115+
liveQuery: { classNames: ['Test'] },
1116+
startLiveQueryServer: true,
1117+
});
1118+
const obj1 = new Parse.Object('Test');
1119+
obj1.set('foo', 'foo');
1120+
obj1.set('bar', 'bar');
1121+
obj1.set('qux', 'qux');
1122+
await obj1.save();
1123+
const config = Config.get(Parse.applicationId);
1124+
const schemaController = await config.database.loadSchema();
1125+
await schemaController.updateClass(
1126+
'Test',
1127+
{},
1128+
{
1129+
get: { '*': true },
1130+
find: { '*': true },
1131+
update: { '*': true },
1132+
protectedFields: {
1133+
'*': ['foo'],
1134+
},
1135+
}
1136+
);
1137+
const object = await obj1.fetch();
1138+
expect(object.get('foo')).toBe(undefined);
1139+
expect(object.get('bar')).toBeDefined();
1140+
expect(object.get('qux')).toBeDefined();
1141+
1142+
const subscription = await new Parse.Query('Test').subscribe();
1143+
await Promise.all([
1144+
new Promise(resolve => {
1145+
subscription.on('update', (obj, original) => {
1146+
expect(obj.get('foo')).toBe(undefined);
1147+
expect(obj.get('bar')).toBeDefined();
1148+
expect(obj.get('qux')).toBeDefined();
1149+
expect(original.get('foo')).toBe(undefined);
1150+
expect(original.get('bar')).toBeDefined();
1151+
expect(original.get('qux')).toBeDefined();
1152+
resolve();
1153+
});
1154+
}),
1155+
obj1.save({ foo: 'abc' }),
1156+
]);
1157+
});
1158+
11131159
afterEach(async function (done) {
11141160
const client = await Parse.CoreManager.getLiveQueryController().getDefaultLiveQueryClient();
11151161
client.close();

Diff for: src/Controllers/DatabaseController.js

+10-4
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ const filterSensitiveData = (
127127
aclGroup: any[],
128128
auth: any,
129129
operation: any,
130-
schema: SchemaController.SchemaController,
130+
schema: SchemaController.SchemaController | any,
131131
className: string,
132132
protectedFields: null | Array<any>,
133133
object: any
@@ -136,7 +136,8 @@ const filterSensitiveData = (
136136
if (auth && auth.user) userId = auth.user.id;
137137

138138
// replace protectedFields when using pointer-permissions
139-
const perms = schema.getClassLevelPermissions(className);
139+
const perms =
140+
schema && schema.getClassLevelPermissions ? schema.getClassLevelPermissions(className) : {};
140141
if (perms) {
141142
const isReadOperation = ['get', 'find'].indexOf(operation) > -1;
142143

@@ -1533,14 +1534,17 @@ class DatabaseController {
15331534
}
15341535

15351536
addProtectedFields(
1536-
schema: SchemaController.SchemaController,
1537+
schema: SchemaController.SchemaController | any,
15371538
className: string,
15381539
query: any = {},
15391540
aclGroup: any[] = [],
15401541
auth: any = {},
15411542
queryOptions: FullQueryOptions = {}
15421543
): null | string[] {
1543-
const perms = schema.getClassLevelPermissions(className);
1544+
const perms =
1545+
schema && schema.getClassLevelPermissions
1546+
? schema.getClassLevelPermissions(className)
1547+
: schema;
15441548
if (!perms) return null;
15451549

15461550
const protectedFields = perms.protectedFields;
@@ -1806,8 +1810,10 @@ class DatabaseController {
18061810
}
18071811

18081812
static _validateQuery: any => void;
1813+
static filterSensitiveData: (boolean, any[], any, any, any, string, any[], any) => void;
18091814
}
18101815

18111816
module.exports = DatabaseController;
18121817
// Expose validateQuery for tests
18131818
module.exports._validateQuery = validateQuery;
1819+
module.exports.filterSensitiveData = filterSensitiveData;

Diff for: src/LiveQuery/ParseCloudCodePublisher.js

+3
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ class ParseCloudCodePublisher {
4040
if (request.original) {
4141
message.originalParseObject = request.original._toFullJSON();
4242
}
43+
if (request.classLevelPermissions) {
44+
message.classLevelPermissions = request.classLevelPermissions;
45+
}
4346
this.parsePublisher.publish(type, JSON.stringify(message));
4447
}
4548
}

Diff for: src/LiveQuery/ParseLiveQueryServer.js

+66-19
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@ import {
1818
toJSONwithObjects,
1919
} from '../triggers';
2020
import { getAuthForSessionToken, Auth } from '../Auth';
21-
import { getCacheController } from '../Controllers';
21+
import { getCacheController, getDatabaseController } from '../Controllers';
2222
import LRU from 'lru-cache';
2323
import UserRouter from '../Routers/UsersRouter';
24+
import DatabaseController from '../Controllers/DatabaseController';
2425

2526
class ParseLiveQueryServer {
2627
clients: Map;
@@ -196,14 +197,14 @@ class ParseLiveQueryServer {
196197
if (res.object && typeof res.object.toJSON === 'function') {
197198
deletedParseObject = toJSONwithObjects(res.object, res.object.className || className);
198199
}
199-
if (
200-
(deletedParseObject.className === '_User' ||
201-
deletedParseObject.className === '_Session') &&
202-
!client.hasMasterKey
203-
) {
204-
delete deletedParseObject.sessionToken;
205-
delete deletedParseObject.authData;
206-
}
200+
await this._filterSensitiveData(
201+
classLevelPermissions,
202+
res,
203+
client,
204+
requestId,
205+
op,
206+
subscription.query
207+
);
207208
client.pushDelete(requestId, deletedParseObject);
208209
} catch (e) {
209210
const error = resolveError(e);
@@ -350,16 +351,14 @@ class ParseLiveQueryServer {
350351
res.original.className || className
351352
);
352353
}
353-
if (
354-
(currentParseObject.className === '_User' ||
355-
currentParseObject.className === '_Session') &&
356-
!client.hasMasterKey
357-
) {
358-
delete currentParseObject.sessionToken;
359-
delete originalParseObject?.sessionToken;
360-
delete currentParseObject.authData;
361-
delete originalParseObject?.authData;
362-
}
354+
await this._filterSensitiveData(
355+
classLevelPermissions,
356+
res,
357+
client,
358+
requestId,
359+
op,
360+
subscription.query
361+
);
363362
const functionName = 'push' + res.event.charAt(0).toUpperCase() + res.event.slice(1);
364363
if (client[functionName]) {
365364
client[functionName](requestId, currentParseObject, originalParseObject);
@@ -577,6 +576,54 @@ class ParseLiveQueryServer {
577576
// return rolesQuery.find({useMasterKey:true});
578577
}
579578

579+
async _filterSensitiveData(
580+
classLevelPermissions: ?any,
581+
res: any,
582+
client: any,
583+
requestId: number,
584+
op: string,
585+
query: any
586+
) {
587+
const subscriptionInfo = client.getSubscriptionInfo(requestId);
588+
const aclGroup = ['*'];
589+
let clientAuth;
590+
if (typeof subscriptionInfo !== 'undefined') {
591+
const { userId, auth } = await this.getAuthForSessionToken(subscriptionInfo.sessionToken);
592+
if (userId) {
593+
aclGroup.push(userId);
594+
}
595+
clientAuth = auth;
596+
}
597+
const filter = obj => {
598+
if (!obj) {
599+
return;
600+
}
601+
let protectedFields = classLevelPermissions?.protectedFields || [];
602+
if (!client.hasMasterKey && !Array.isArray(protectedFields)) {
603+
protectedFields = getDatabaseController(this.config).addProtectedFields(
604+
classLevelPermissions,
605+
res.object.className,
606+
query,
607+
aclGroup,
608+
clientAuth
609+
);
610+
}
611+
return DatabaseController.filterSensitiveData(
612+
client.hasMasterKey,
613+
aclGroup,
614+
clientAuth,
615+
op,
616+
classLevelPermissions,
617+
res.object.className,
618+
protectedFields,
619+
obj,
620+
query
621+
);
622+
};
623+
res.object = filter(res.object);
624+
res.original = filter(res.original);
625+
}
626+
580627
_getCLPOperation(query: any) {
581628
return typeof query === 'object' &&
582629
Object.keys(query).length == 1 &&

0 commit comments

Comments
 (0)