Skip to content

Commit 5ce8ea9

Browse files
committed
fix
1 parent e9b020b commit 5ce8ea9

File tree

2 files changed

+118
-8
lines changed

2 files changed

+118
-8
lines changed

spec/ProtectedFields.spec.js

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1700,4 +1700,106 @@ describe('ProtectedFields', function () {
17001700
done();
17011701
});
17021702
});
1703+
1704+
describe('query on protected fields via logical operators', function () {
1705+
let user;
1706+
let otherUser;
1707+
const testEmail = 'victim@example.com';
1708+
const otherEmail = 'other@example.com';
1709+
1710+
beforeEach(async function () {
1711+
await reconfigureServer({
1712+
protectedFields: {
1713+
_User: { '*': ['email'] },
1714+
},
1715+
});
1716+
user = new Parse.User();
1717+
user.setUsername('victim' + Date.now());
1718+
user.setPassword('password');
1719+
user.setEmail(testEmail);
1720+
const acl = new Parse.ACL();
1721+
acl.setPublicReadAccess(true);
1722+
user.setACL(acl);
1723+
await user.save(null, { useMasterKey: true });
1724+
1725+
otherUser = new Parse.User();
1726+
otherUser.setUsername('attacker' + Date.now());
1727+
otherUser.setPassword('password');
1728+
otherUser.setEmail(otherEmail);
1729+
const acl2 = new Parse.ACL();
1730+
acl2.setPublicReadAccess(true);
1731+
otherUser.setACL(acl2);
1732+
await otherUser.save(null, { useMasterKey: true });
1733+
await Parse.User.logIn(otherUser.getUsername(), 'password');
1734+
});
1735+
1736+
it('should deny query on protected field via $or', async function () {
1737+
const q1 = new Parse.Query(Parse.User);
1738+
q1.equalTo('email', testEmail);
1739+
const query = Parse.Query.or(q1);
1740+
await expectAsync(query.find()).toBeRejectedWith(
1741+
jasmine.objectContaining({
1742+
code: Parse.Error.OPERATION_FORBIDDEN,
1743+
})
1744+
);
1745+
});
1746+
1747+
it('should deny query on protected field via $and', async function () {
1748+
const query = new Parse.Query(Parse.User);
1749+
query.withJSON({ where: { $and: [{ email: testEmail }] } });
1750+
await expectAsync(query.find()).toBeRejectedWith(
1751+
jasmine.objectContaining({
1752+
code: Parse.Error.OPERATION_FORBIDDEN,
1753+
})
1754+
);
1755+
});
1756+
1757+
it('should deny query on protected field via $nor', async function () {
1758+
const query = new Parse.Query(Parse.User);
1759+
query.withJSON({ where: { $nor: [{ email: testEmail }] } });
1760+
await expectAsync(query.find()).toBeRejectedWith(
1761+
jasmine.objectContaining({
1762+
code: Parse.Error.OPERATION_FORBIDDEN,
1763+
})
1764+
);
1765+
});
1766+
1767+
it('should deny query on protected field via nested $or inside $and', async function () {
1768+
const query = new Parse.Query(Parse.User);
1769+
query.withJSON({ where: { $and: [{ $or: [{ email: testEmail }] }] } });
1770+
await expectAsync(query.find()).toBeRejectedWith(
1771+
jasmine.objectContaining({
1772+
code: Parse.Error.OPERATION_FORBIDDEN,
1773+
})
1774+
);
1775+
});
1776+
1777+
it('should deny query on protected field via $or with $regex', async function () {
1778+
const query = new Parse.Query(Parse.User);
1779+
query.withJSON({ where: { $or: [{ email: { $regex: '^victim' } }] } });
1780+
await expectAsync(query.find()).toBeRejectedWith(
1781+
jasmine.objectContaining({
1782+
code: Parse.Error.OPERATION_FORBIDDEN,
1783+
})
1784+
);
1785+
});
1786+
1787+
it('should allow $or query on non-protected fields', async function () {
1788+
const q1 = new Parse.Query(Parse.User);
1789+
q1.equalTo('username', user.getUsername());
1790+
const query = Parse.Query.or(q1);
1791+
const results = await query.find();
1792+
expect(results.length).toBe(1);
1793+
expect(results[0].id).toBe(user.id);
1794+
});
1795+
1796+
it('should allow master key to query on protected fields via $or', async function () {
1797+
const q1 = new Parse.Query(Parse.User);
1798+
q1.equalTo('email', testEmail);
1799+
const query = Parse.Query.or(q1);
1800+
const results = await query.find({ useMasterKey: true });
1801+
expect(results.length).toBe(1);
1802+
expect(results[0].id).toBe(user.id);
1803+
});
1804+
});
17031805
});

src/RestQuery.js

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -847,15 +847,23 @@ _UnsafeRestQuery.prototype.denyProtectedFields = async function () {
847847
this.auth,
848848
this.findOptions
849849
) || [];
850-
for (const key of protectedFields) {
851-
if (this.restWhere[key]) {
852-
throw createSanitizedError(
853-
Parse.Error.OPERATION_FORBIDDEN,
854-
`This user is not allowed to query ${key} on class ${this.className}`,
855-
this.config
856-
);
850+
const checkWhere = (where) => {
851+
for (const key of protectedFields) {
852+
if (where[key]) {
853+
throw createSanitizedError(
854+
Parse.Error.OPERATION_FORBIDDEN,
855+
`This user is not allowed to query ${key} on class ${this.className}`,
856+
this.config
857+
);
858+
}
857859
}
858-
}
860+
for (const op of ['$or', '$and', '$nor']) {
861+
if (Array.isArray(where[op])) {
862+
where[op].forEach(subQuery => checkWhere(subQuery));
863+
}
864+
}
865+
};
866+
checkWhere(this.restWhere);
859867
};
860868

861869
// Augments this.response with all pointers on an object

0 commit comments

Comments
 (0)