mirror of
https://github.com/parse-community/parse-server
synced 2026-08-09 13:03:18 +00:00
fix: Master key does not bypass protectedFields on various endpoints (#10412)
This commit is contained in:
@@ -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: {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
@@ -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,
|
||||
{},
|
||||
|
||||
Reference in New Issue
Block a user