fix: Master key does not bypass protectedFields on various endpoints (#10412)

This commit is contained in:
Manuel
2026-04-07 13:48:39 +01:00
committed by GitHub
parent 32680e3046
commit c0889c8575
4 changed files with 130 additions and 32 deletions
+56
View File
@@ -2031,6 +2031,62 @@ describe('ProtectedFields', function () {
expect(response.data.objectId).toBe(user.id);
});
it('/login with master key bypasses protectedFields', async function () {
await reconfigureServer({
protectedFields: {
_User: {
'*': ['phone'],
},
},
protectedFieldsOwnerExempt: false,
});
const user = await Parse.User.signUp('user1', 'password');
const sessionToken = user.getSessionToken();
user.set('phone', '555-1234');
await user.save(null, { sessionToken });
const response = await request({
method: 'POST',
url: 'http://localhost:8378/1/login',
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-Master-Key': 'test',
'Content-Type': 'application/json',
},
body: JSON.stringify({ username: 'user1', password: 'password' }),
});
expect(response.data.phone).toBe('555-1234');
expect(response.data.sessionToken).toBeDefined();
});
it('/verifyPassword with master key bypasses protectedFields', async function () {
await reconfigureServer({
protectedFields: {
_User: {
'*': ['phone'],
},
},
protectedFieldsOwnerExempt: false,
verifyUserEmails: false,
});
const user = await Parse.User.signUp('user1', 'password');
const sessionToken = user.getSessionToken();
user.set('phone', '555-1234');
await user.save(null, { sessionToken });
const response = await request({
method: 'POST',
url: 'http://localhost:8378/1/verifyPassword',
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-Master-Key': 'test',
'Content-Type': 'application/json',
},
body: JSON.stringify({ username: 'user1', password: 'password' }),
});
expect(response.data.phone).toBe('555-1234');
});
it('owner sees non-protected fields like email when protectedFieldsOwnerExempt is true', async function () {
await reconfigureServer({
protectedFields: {
+26
View File
@@ -5745,5 +5745,31 @@ describe('Vulnerabilities', () => {
expect(meResponse.data.objectId).toBeDefined();
expect(meResponse.data.user).toBeDefined();
});
it('should return protected fields on GET /sessions/me with master key', async () => {
await reconfigureServer({
protectedFields: {
_Session: { '*': ['createdWith'] },
},
});
const user = new Parse.User();
user.setUsername('session-pf-mk');
user.setPassword('password123');
user.set('email', 'session-pf-mk@example.com');
await user.signUp();
const sessionToken = user.getSessionToken();
const meResponse = await request({
method: 'GET',
url: 'http://localhost:8378/1/sessions/me',
headers: {
...headers,
'X-Parse-Session-Token': sessionToken,
'X-Parse-Master-Key': 'test',
},
});
expect(meResponse.data.createdWith).toBeDefined();
expect(meResponse.data.sessionToken).toBe(sessionToken);
});
});
});
+24 -16
View File
@@ -34,16 +34,20 @@ export class SessionsRouter extends ClassesRouter {
const sessionObjectId = sessionResponse.results[0].objectId;
const userId = sessionResponse.results[0].user.objectId;
// Re-fetch the session with the caller's auth context so that
// protectedFields and CLP apply correctly
const userAuth = new Auth.Auth({
config: req.config,
isMaster: false,
user: Parse.Object.fromJSON({ className: '_User', objectId: userId }),
installationId: req.info.installationId,
});
// protectedFields and CLP apply correctly; if the caller used master key,
// protectedFields are bypassed, matching the behavior of GET /sessions/:id
const refetchAuth =
req.auth?.isMaster || req.auth?.isMaintenance
? req.auth
: new Auth.Auth({
config: req.config,
isMaster: false,
user: Parse.Object.fromJSON({ className: '_User', objectId: userId }),
installationId: req.info.installationId,
});
const response = await rest.get(
req.config,
userAuth,
refetchAuth,
'_Session',
sessionObjectId,
{},
@@ -82,16 +86,20 @@ export class SessionsRouter extends ClassesRouter {
{ sessionToken: { __op: 'Delete' } }
);
// Re-fetch the session with the caller's auth context so that
// protectedFields filtering applies correctly
const userAuth = new Auth.Auth({
config,
isMaster: false,
user: Parse.Object.fromJSON({ className: '_User', objectId: user.id }),
installationId: req.auth.installationId,
});
// protectedFields filtering applies correctly; if the caller used master key,
// protectedFields are bypassed, matching the behavior of GET /sessions/:id
const refetchAuth =
req.auth.isMaster || req.auth.isMaintenance
? req.auth
: new Auth.Auth({
config,
isMaster: false,
user: Parse.Object.fromJSON({ className: '_User', objectId: user.id }),
installationId: req.auth.installationId,
});
const response = await rest.find(
config,
userAuth,
refetchAuth,
'_Session',
{ sessionToken: sessionData.sessionToken },
{},
+24 -16
View File
@@ -365,18 +365,22 @@ export class UsersRouter extends ClassesRouter {
);
// Re-fetch the user with the caller's auth context so that
// protectedFields and CLP apply correctly
const userAuth = new Auth.Auth({
config: req.config,
isMaster: false,
user: Parse.Object.fromJSON({ className: '_User', objectId: user.objectId }),
installationId: req.info.installationId,
});
// protectedFields and CLP apply correctly; if the caller used master key,
// protectedFields are bypassed, matching the behavior of GET /users/:id
const refetchAuth =
req.auth.isMaster || req.auth.isMaintenance
? req.auth
: new Auth.Auth({
config: req.config,
isMaster: false,
user: Parse.Object.fromJSON({ className: '_User', objectId: user.objectId }),
installationId: req.info.installationId,
});
let filteredUser;
try {
const filteredUserResponse = await rest.get(
req.config,
userAuth,
refetchAuth,
'_User',
user.objectId,
{},
@@ -464,18 +468,22 @@ export class UsersRouter extends ClassesRouter {
// Remove hidden properties.
UsersRouter.removeHiddenProperties(user);
// Re-fetch the user with the caller's auth context so that
// protectedFields and CLP apply correctly
const userAuth = new Auth.Auth({
config: req.config,
isMaster: false,
user: Parse.Object.fromJSON({ className: '_User', objectId: user.objectId }),
installationId: req.info.installationId,
});
// protectedFields and CLP apply correctly; if the caller used master key,
// protectedFields are bypassed, matching the behavior of GET /users/:id
const refetchAuth =
req.auth.isMaster || req.auth.isMaintenance
? req.auth
: new Auth.Auth({
config: req.config,
isMaster: false,
user: Parse.Object.fromJSON({ className: '_User', objectId: user.objectId }),
installationId: req.info.installationId,
});
let filteredUser;
try {
const filteredUserResponse = await rest.get(
req.config,
userAuth,
refetchAuth,
'_User',
user.objectId,
{},