Strip personally identifiable information from user table for unauthorized users.
- add a config option to explicitly enumerate pii fields beyond email - in query controller, strip pii of user table results before sending out the door.
This commit is contained in:
committed by
Florent Vilmart
parent
a270632570
commit
01b05b060f
503
spec/UserPII.spec.js
Normal file
503
spec/UserPII.spec.js
Normal file
@@ -0,0 +1,503 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const Parse = require('parse/node');
|
||||||
|
const request = require('request-promise');
|
||||||
|
|
||||||
|
// const Config = require('../src/Config');
|
||||||
|
|
||||||
|
const EMAIL = 'foo@bar.com';
|
||||||
|
const ZIP = '10001';
|
||||||
|
const SSN = '999-99-9999';
|
||||||
|
|
||||||
|
describe('Personally Identifiable Information', () => {
|
||||||
|
let user;
|
||||||
|
|
||||||
|
beforeEach(done => {
|
||||||
|
return Parse.User.signUp('tester', 'abc')
|
||||||
|
.then(loggedInUser => user = loggedInUser)
|
||||||
|
.then(() => Parse.User.logIn(user.get('username'), 'abc'))
|
||||||
|
.then(() => user
|
||||||
|
.set('email', EMAIL)
|
||||||
|
.set('zip', ZIP)
|
||||||
|
.set('ssn', SSN)
|
||||||
|
.save())
|
||||||
|
.then(() => done());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to get own PII via API with object', (done) => {
|
||||||
|
const userObj = new (Parse.Object.extend(Parse.User));
|
||||||
|
userObj.id = user.id;
|
||||||
|
userObj.fetch().then(
|
||||||
|
fetchedUser => {
|
||||||
|
expect(fetchedUser.get('email')).toBe(EMAIL);
|
||||||
|
}, e => console.error('error', e))
|
||||||
|
.done(() => done());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not be able to get PII via API with object', (done) => {
|
||||||
|
Parse.User.logOut()
|
||||||
|
.then(() => {
|
||||||
|
const userObj = new (Parse.Object.extend(Parse.User));
|
||||||
|
userObj.id = user.id;
|
||||||
|
userObj.fetch().then(
|
||||||
|
fetchedUser => {
|
||||||
|
expect(fetchedUser.get('email')).toBe(undefined);
|
||||||
|
})
|
||||||
|
.fail(e => {
|
||||||
|
done.fail(JSON.stringify(e));
|
||||||
|
})
|
||||||
|
.done(() => done());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to get PII via API with object using master key', (done) => {
|
||||||
|
Parse.User.logOut()
|
||||||
|
.then(() => {
|
||||||
|
const userObj = new (Parse.Object.extend(Parse.User));
|
||||||
|
userObj.id = user.id;
|
||||||
|
userObj.fetch({ useMasterKey: true }).then(
|
||||||
|
fetchedUser => {
|
||||||
|
expect(fetchedUser.get('email')).toBe(EMAIL);
|
||||||
|
}, e => console.error('error', e))
|
||||||
|
.done(() => done());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should be able to get own PII via API with Find', (done) => {
|
||||||
|
new Parse.Query(Parse.User)
|
||||||
|
.first()
|
||||||
|
.then(fetchedUser => {
|
||||||
|
expect(fetchedUser.get('email')).toBe(EMAIL);
|
||||||
|
expect(fetchedUser.get('zip')).toBe(ZIP);
|
||||||
|
expect(fetchedUser.get('ssn')).toBe(SSN);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not get PII via API with Find', (done) => {
|
||||||
|
Parse.User.logOut()
|
||||||
|
.then(() => new Parse.Query(Parse.User)
|
||||||
|
.first()
|
||||||
|
.then(fetchedUser => {
|
||||||
|
expect(fetchedUser.get('email')).toBe(undefined);
|
||||||
|
expect(fetchedUser.get('zip')).toBe(ZIP);
|
||||||
|
expect(fetchedUser.get('ssn')).toBe(SSN);
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get PII via API with Find using master key', (done) => {
|
||||||
|
Parse.User.logOut()
|
||||||
|
.then(() => new Parse.Query(Parse.User)
|
||||||
|
.first({ useMasterKey: true })
|
||||||
|
.then(fetchedUser => {
|
||||||
|
expect(fetchedUser.get('email')).toBe(EMAIL);
|
||||||
|
expect(fetchedUser.get('zip')).toBe(ZIP);
|
||||||
|
expect(fetchedUser.get('ssn')).toBe(SSN);
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should be able to get own PII via API with Get', (done) => {
|
||||||
|
new Parse.Query(Parse.User)
|
||||||
|
.get(user.id)
|
||||||
|
.then(fetchedUser => {
|
||||||
|
expect(fetchedUser.get('email')).toBe(EMAIL);
|
||||||
|
expect(fetchedUser.get('zip')).toBe(ZIP);
|
||||||
|
expect(fetchedUser.get('ssn')).toBe(SSN);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not get PII via API with Get', (done) => {
|
||||||
|
Parse.User.logOut()
|
||||||
|
.then(() => new Parse.Query(Parse.User)
|
||||||
|
.get(user.id)
|
||||||
|
.then(fetchedUser => {
|
||||||
|
expect(fetchedUser.get('email')).toBe(undefined);
|
||||||
|
expect(fetchedUser.get('zip')).toBe(ZIP);
|
||||||
|
expect(fetchedUser.get('ssn')).toBe(SSN);
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get PII via API with Get using master key', (done) => {
|
||||||
|
Parse.User.logOut()
|
||||||
|
.then(() => new Parse.Query(Parse.User)
|
||||||
|
.get(user.id, { useMasterKey: true })
|
||||||
|
.then(fetchedUser => {
|
||||||
|
expect(fetchedUser.get('email')).toBe(EMAIL);
|
||||||
|
expect(fetchedUser.get('zip')).toBe(ZIP);
|
||||||
|
expect(fetchedUser.get('ssn')).toBe(SSN);
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not get PII via REST', (done) => {
|
||||||
|
request.get({
|
||||||
|
url: 'http://localhost:8378/1/classes/_User',
|
||||||
|
json: true,
|
||||||
|
headers: {
|
||||||
|
'X-Parse-Application-Id': 'test',
|
||||||
|
'X-Parse-Javascript-Key': 'test'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(
|
||||||
|
result => {
|
||||||
|
const fetchedUser = result.results[0];
|
||||||
|
expect(fetchedUser.zip).toBe(ZIP);
|
||||||
|
expect(fetchedUser.email).toBe(undefined);
|
||||||
|
},
|
||||||
|
e => console.error('error', e.message)
|
||||||
|
).done(() => done());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get PII via REST with self credentials', (done) => {
|
||||||
|
request.get({
|
||||||
|
url: 'http://localhost:8378/1/classes/_User',
|
||||||
|
json: true,
|
||||||
|
headers: {
|
||||||
|
'X-Parse-Application-Id': 'test',
|
||||||
|
'X-Parse-Javascript-Key': 'test',
|
||||||
|
'X-Parse-Session-Token': user.getSessionToken()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(
|
||||||
|
result => {
|
||||||
|
const fetchedUser = result.results[0];
|
||||||
|
expect(fetchedUser.zip).toBe(ZIP);
|
||||||
|
expect(fetchedUser.email).toBe(EMAIL);
|
||||||
|
},
|
||||||
|
e => console.error('error', e.message)
|
||||||
|
).done(() => done());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get PII via REST using master key', (done) => {
|
||||||
|
request.get({
|
||||||
|
url: 'http://localhost:8378/1/classes/_User',
|
||||||
|
json: true,
|
||||||
|
headers: {
|
||||||
|
'X-Parse-Application-Id': 'test',
|
||||||
|
'X-Parse-Master-Key': 'test'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(
|
||||||
|
result => {
|
||||||
|
const fetchedUser = result.results[0];
|
||||||
|
expect(fetchedUser.zip).toBe(ZIP);
|
||||||
|
expect(fetchedUser.email).toBe(EMAIL);
|
||||||
|
},
|
||||||
|
e => console.error('error', e.message)
|
||||||
|
).done(() => done());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not get PII via REST by ID', (done) => {
|
||||||
|
request.get({
|
||||||
|
url: `http://localhost:8378/1/classes/_User/${user.id}`,
|
||||||
|
json: true,
|
||||||
|
headers: {
|
||||||
|
'X-Parse-Application-Id': 'test',
|
||||||
|
'X-Parse-Javascript-Key': 'test'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(
|
||||||
|
result => {
|
||||||
|
const fetchedUser = result;
|
||||||
|
expect(fetchedUser.zip).toBe(ZIP);
|
||||||
|
expect(fetchedUser.email).toBe(undefined);
|
||||||
|
},
|
||||||
|
e => console.error('error', e.message)
|
||||||
|
).done(() => done());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get PII via REST by ID with self credentials', (done) => {
|
||||||
|
request.get({
|
||||||
|
url: `http://localhost:8378/1/classes/_User/${user.id}`,
|
||||||
|
json: true,
|
||||||
|
headers: {
|
||||||
|
'X-Parse-Application-Id': 'test',
|
||||||
|
'X-Parse-Javascript-Key': 'test',
|
||||||
|
'X-Parse-Session-Token': user.getSessionToken()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(
|
||||||
|
result => {
|
||||||
|
const fetchedUser = result;
|
||||||
|
expect(fetchedUser.zip).toBe(ZIP);
|
||||||
|
expect(fetchedUser.email).toBe(EMAIL);
|
||||||
|
},
|
||||||
|
e => console.error('error', e.message)
|
||||||
|
).done(() => done());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get PII via REST by ID with master key', (done) => {
|
||||||
|
request.get({
|
||||||
|
url: `http://localhost:8378/1/classes/_User/${user.id}`,
|
||||||
|
json: true,
|
||||||
|
headers: {
|
||||||
|
'X-Parse-Application-Id': 'test',
|
||||||
|
'X-Parse-Javascript-Key': 'test',
|
||||||
|
'X-Parse-Master-Key': 'test',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(
|
||||||
|
result => {
|
||||||
|
const fetchedUser = result;
|
||||||
|
expect(fetchedUser.zip).toBe(ZIP);
|
||||||
|
expect(fetchedUser.email).toBe(EMAIL);
|
||||||
|
},
|
||||||
|
e => console.error('error', e.message)
|
||||||
|
).done(() => done());
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('with configured sensitive fields', () => {
|
||||||
|
beforeEach((done) => {
|
||||||
|
reconfigureServer({ userSensitiveFields: ['ssn', 'zip'] })
|
||||||
|
.then(() => done());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to get own PII via API with object', (done) => {
|
||||||
|
const userObj = new (Parse.Object.extend(Parse.User));
|
||||||
|
userObj.id = user.id;
|
||||||
|
userObj.fetch().then(
|
||||||
|
fetchedUser => {
|
||||||
|
expect(fetchedUser.get('email')).toBe(EMAIL);
|
||||||
|
expect(fetchedUser.get('zip')).toBe(ZIP);
|
||||||
|
expect(fetchedUser.get('ssn')).toBe(SSN);
|
||||||
|
done();
|
||||||
|
}, e => done.fail(e));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not be able to get PII via API with object', (done) => {
|
||||||
|
Parse.User.logOut()
|
||||||
|
.then(() => {
|
||||||
|
const userObj = new (Parse.Object.extend(Parse.User));
|
||||||
|
userObj.id = user.id;
|
||||||
|
userObj.fetch().then(
|
||||||
|
fetchedUser => {
|
||||||
|
expect(fetchedUser.get('email')).toBe(undefined);
|
||||||
|
expect(fetchedUser.get('zip')).toBe(undefined);
|
||||||
|
expect(fetchedUser.get('ssn')).toBe(undefined);
|
||||||
|
}, e => console.error('error', e))
|
||||||
|
.done(() => done());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to get PII via API with object using master key', (done) => {
|
||||||
|
Parse.User.logOut()
|
||||||
|
.then(() => {
|
||||||
|
const userObj = new (Parse.Object.extend(Parse.User));
|
||||||
|
userObj.id = user.id;
|
||||||
|
userObj.fetch({ useMasterKey: true }).then(
|
||||||
|
fetchedUser => {
|
||||||
|
expect(fetchedUser.get('email')).toBe(EMAIL);
|
||||||
|
expect(fetchedUser.get('zip')).toBe(ZIP);
|
||||||
|
expect(fetchedUser.get('ssn')).toBe(SSN);
|
||||||
|
}, e => console.error('error', e))
|
||||||
|
.done(() => done());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should be able to get own PII via API with Find', (done) => {
|
||||||
|
new Parse.Query(Parse.User)
|
||||||
|
.first()
|
||||||
|
.then(fetchedUser => {
|
||||||
|
expect(fetchedUser.get('email')).toBe(EMAIL);
|
||||||
|
expect(fetchedUser.get('zip')).toBe(ZIP);
|
||||||
|
expect(fetchedUser.get('ssn')).toBe(SSN);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not get PII via API with Find', (done) => {
|
||||||
|
Parse.User.logOut()
|
||||||
|
.then(() => new Parse.Query(Parse.User)
|
||||||
|
.first()
|
||||||
|
.then(fetchedUser => {
|
||||||
|
expect(fetchedUser.get('email')).toBe(undefined);
|
||||||
|
expect(fetchedUser.get('zip')).toBe(undefined);
|
||||||
|
expect(fetchedUser.get('ssn')).toBe(undefined);
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get PII via API with Find using master key', (done) => {
|
||||||
|
Parse.User.logOut()
|
||||||
|
.then(() => new Parse.Query(Parse.User)
|
||||||
|
.first({ useMasterKey: true })
|
||||||
|
.then(fetchedUser => {
|
||||||
|
expect(fetchedUser.get('email')).toBe(EMAIL);
|
||||||
|
expect(fetchedUser.get('zip')).toBe(ZIP);
|
||||||
|
expect(fetchedUser.get('ssn')).toBe(SSN);
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should be able to get own PII via API with Get', (done) => {
|
||||||
|
new Parse.Query(Parse.User)
|
||||||
|
.get(user.id)
|
||||||
|
.then(fetchedUser => {
|
||||||
|
expect(fetchedUser.get('email')).toBe(EMAIL);
|
||||||
|
expect(fetchedUser.get('zip')).toBe(ZIP);
|
||||||
|
expect(fetchedUser.get('ssn')).toBe(SSN);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not get PII via API with Get', (done) => {
|
||||||
|
Parse.User.logOut()
|
||||||
|
.then(() => new Parse.Query(Parse.User)
|
||||||
|
.get(user.id)
|
||||||
|
.then(fetchedUser => {
|
||||||
|
expect(fetchedUser.get('email')).toBe(undefined);
|
||||||
|
expect(fetchedUser.get('zip')).toBe(undefined);
|
||||||
|
expect(fetchedUser.get('ssn')).toBe(undefined);
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get PII via API with Get using master key', (done) => {
|
||||||
|
Parse.User.logOut()
|
||||||
|
.then(() => new Parse.Query(Parse.User)
|
||||||
|
.get(user.id, { useMasterKey: true })
|
||||||
|
.then(fetchedUser => {
|
||||||
|
expect(fetchedUser.get('email')).toBe(EMAIL);
|
||||||
|
expect(fetchedUser.get('zip')).toBe(ZIP);
|
||||||
|
expect(fetchedUser.get('ssn')).toBe(SSN);
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not get PII via REST', (done) => {
|
||||||
|
request.get({
|
||||||
|
url: 'http://localhost:8378/1/classes/_User',
|
||||||
|
json: true,
|
||||||
|
headers: {
|
||||||
|
'X-Parse-Application-Id': 'test',
|
||||||
|
'X-Parse-Javascript-Key': 'test'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(
|
||||||
|
result => {
|
||||||
|
const fetchedUser = result.results[0];
|
||||||
|
expect(fetchedUser.zip).toBe(undefined);
|
||||||
|
expect(fetchedUser.ssn).toBe(undefined);
|
||||||
|
expect(fetchedUser.email).toBe(undefined);
|
||||||
|
},
|
||||||
|
e => console.error('error', e.message)
|
||||||
|
).done(() => done());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get PII via REST with self credentials', (done) => {
|
||||||
|
request.get({
|
||||||
|
url: 'http://localhost:8378/1/classes/_User',
|
||||||
|
json: true,
|
||||||
|
headers: {
|
||||||
|
'X-Parse-Application-Id': 'test',
|
||||||
|
'X-Parse-Javascript-Key': 'test',
|
||||||
|
'X-Parse-Session-Token': user.getSessionToken()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(
|
||||||
|
result => {
|
||||||
|
const fetchedUser = result.results[0];
|
||||||
|
expect(fetchedUser.zip).toBe(ZIP);
|
||||||
|
expect(fetchedUser.email).toBe(EMAIL);
|
||||||
|
expect(fetchedUser.ssn).toBe(SSN);
|
||||||
|
},
|
||||||
|
e => console.error('error', e.message)
|
||||||
|
).done(() => done());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get PII via REST using master key', (done) => {
|
||||||
|
request.get({
|
||||||
|
url: 'http://localhost:8378/1/classes/_User',
|
||||||
|
json: true,
|
||||||
|
headers: {
|
||||||
|
'X-Parse-Application-Id': 'test',
|
||||||
|
'X-Parse-Master-Key': 'test'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(
|
||||||
|
result => {
|
||||||
|
const fetchedUser = result.results[0];
|
||||||
|
expect(fetchedUser.zip).toBe(ZIP);
|
||||||
|
expect(fetchedUser.email).toBe(EMAIL);
|
||||||
|
expect(fetchedUser.ssn).toBe(SSN);
|
||||||
|
},
|
||||||
|
e => console.error('error', e.message)
|
||||||
|
).done(() => done());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not get PII via REST by ID', (done) => {
|
||||||
|
request.get({
|
||||||
|
url: `http://localhost:8378/1/classes/_User/${user.id}`,
|
||||||
|
json: true,
|
||||||
|
headers: {
|
||||||
|
'X-Parse-Application-Id': 'test',
|
||||||
|
'X-Parse-Javascript-Key': 'test'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(
|
||||||
|
result => {
|
||||||
|
const fetchedUser = result;
|
||||||
|
expect(fetchedUser.zip).toBe(undefined);
|
||||||
|
expect(fetchedUser.email).toBe(undefined);
|
||||||
|
},
|
||||||
|
e => console.error('error', e.message)
|
||||||
|
).done(() => done());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get PII via REST by ID with self credentials', (done) => {
|
||||||
|
request.get({
|
||||||
|
url: `http://localhost:8378/1/classes/_User/${user.id}`,
|
||||||
|
json: true,
|
||||||
|
headers: {
|
||||||
|
'X-Parse-Application-Id': 'test',
|
||||||
|
'X-Parse-Javascript-Key': 'test',
|
||||||
|
'X-Parse-Session-Token': user.getSessionToken()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(
|
||||||
|
result => {
|
||||||
|
const fetchedUser = result;
|
||||||
|
expect(fetchedUser.zip).toBe(ZIP);
|
||||||
|
expect(fetchedUser.email).toBe(EMAIL);
|
||||||
|
},
|
||||||
|
e => console.error('error', e.message)
|
||||||
|
).done(() => done());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get PII via REST by ID with master key', (done) => {
|
||||||
|
request.get({
|
||||||
|
url: `http://localhost:8378/1/classes/_User/${user.id}`,
|
||||||
|
json: true,
|
||||||
|
headers: {
|
||||||
|
'X-Parse-Application-Id': 'test',
|
||||||
|
'X-Parse-Javascript-Key': 'test',
|
||||||
|
'X-Parse-Master-Key': 'test',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(
|
||||||
|
result => {
|
||||||
|
const fetchedUser = result;
|
||||||
|
expect(fetchedUser.zip).toBe(ZIP);
|
||||||
|
expect(fetchedUser.email).toBe(EMAIL);
|
||||||
|
},
|
||||||
|
e => console.error('error', e.message)
|
||||||
|
).done(() => done());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -34,6 +34,7 @@ export class Config {
|
|||||||
this.fileKey = cacheInfo.fileKey;
|
this.fileKey = cacheInfo.fileKey;
|
||||||
this.facebookAppIds = cacheInfo.facebookAppIds;
|
this.facebookAppIds = cacheInfo.facebookAppIds;
|
||||||
this.allowClientClassCreation = cacheInfo.allowClientClassCreation;
|
this.allowClientClassCreation = cacheInfo.allowClientClassCreation;
|
||||||
|
this.userSensitiveFields = cacheInfo.userSensitiveFields;
|
||||||
|
|
||||||
// Create a new DatabaseController per request
|
// Create a new DatabaseController per request
|
||||||
if (cacheInfo.databaseController) {
|
if (cacheInfo.databaseController) {
|
||||||
|
|||||||
@@ -113,6 +113,7 @@ class ParseServer {
|
|||||||
webhookKey,
|
webhookKey,
|
||||||
fileKey,
|
fileKey,
|
||||||
facebookAppIds = [],
|
facebookAppIds = [],
|
||||||
|
userSensitiveFields = [],
|
||||||
enableAnonymousUsers = defaults.enableAnonymousUsers,
|
enableAnonymousUsers = defaults.enableAnonymousUsers,
|
||||||
allowClientClassCreation = defaults.allowClientClassCreation,
|
allowClientClassCreation = defaults.allowClientClassCreation,
|
||||||
oauth = {},
|
oauth = {},
|
||||||
@@ -155,6 +156,11 @@ class ParseServer {
|
|||||||
throw 'When using an explicit database adapter, you must also use an explicit filesAdapter.';
|
throw 'When using an explicit database adapter, you must also use an explicit filesAdapter.';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
userSensitiveFields = Array.from(new Set(userSensitiveFields.concat(
|
||||||
|
defaults.userSensitiveFields,
|
||||||
|
userSensitiveFields
|
||||||
|
)));
|
||||||
|
|
||||||
const loggerControllerAdapter = loadAdapter(loggerAdapter, WinstonLoggerAdapter, { jsonLogs, logsFolder, verbose, logLevel, silent });
|
const loggerControllerAdapter = loadAdapter(loggerAdapter, WinstonLoggerAdapter, { jsonLogs, logsFolder, verbose, logLevel, silent });
|
||||||
const loggerController = new LoggerController(loggerControllerAdapter, appId);
|
const loggerController = new LoggerController(loggerControllerAdapter, appId);
|
||||||
logging.setLogger(loggerController);
|
logging.setLogger(loggerController);
|
||||||
@@ -222,7 +228,8 @@ class ParseServer {
|
|||||||
revokeSessionOnPasswordReset,
|
revokeSessionOnPasswordReset,
|
||||||
databaseController,
|
databaseController,
|
||||||
schemaCacheTTL,
|
schemaCacheTTL,
|
||||||
enableSingleSchemaCache
|
enableSingleSchemaCache,
|
||||||
|
userSensitiveFields
|
||||||
});
|
});
|
||||||
|
|
||||||
// To maintain compatibility. TODO: Remove in some version that breaks backwards compatibility
|
// To maintain compatibility. TODO: Remove in some version that breaks backwards compatibility
|
||||||
|
|||||||
@@ -386,6 +386,32 @@ RestQuery.prototype.replaceDontSelect = function() {
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const cleanResultOfSensitiveUserInfo = function (result, auth, config) {
|
||||||
|
delete result.password;
|
||||||
|
|
||||||
|
if (auth.isMaster || (auth.user && auth.user.id === result.objectId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const field of config.userSensitiveFields) {
|
||||||
|
delete result[field];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const cleanResultAuthData = function (result) {
|
||||||
|
if (result.authData) {
|
||||||
|
Object.keys(result.authData).forEach((provider) => {
|
||||||
|
if (result.authData[provider] === null) {
|
||||||
|
delete result.authData[provider];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (Object.keys(result.authData).length == 0) {
|
||||||
|
delete result.authData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Returns a promise for whether it was successful.
|
// Returns a promise for whether it was successful.
|
||||||
// Populates this.response with an object that only has 'results'.
|
// Populates this.response with an object that only has 'results'.
|
||||||
RestQuery.prototype.runFind = function(options = {}) {
|
RestQuery.prototype.runFind = function(options = {}) {
|
||||||
@@ -406,18 +432,8 @@ RestQuery.prototype.runFind = function(options = {}) {
|
|||||||
this.className, this.restWhere, findOptions).then((results) => {
|
this.className, this.restWhere, findOptions).then((results) => {
|
||||||
if (this.className === '_User') {
|
if (this.className === '_User') {
|
||||||
for (var result of results) {
|
for (var result of results) {
|
||||||
delete result.password;
|
cleanResultOfSensitiveUserInfo(result, this.auth, this.config);
|
||||||
|
cleanResultAuthData(result);
|
||||||
if (result.authData) {
|
|
||||||
Object.keys(result.authData).forEach((provider) => {
|
|
||||||
if (result.authData[provider] === null) {
|
|
||||||
delete result.authData[provider];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (Object.keys(result.authData).length == 0) {
|
|
||||||
delete result.authData;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -160,6 +160,10 @@ export default {
|
|||||||
help: "Max file size for uploads.",
|
help: "Max file size for uploads.",
|
||||||
default: "20mb"
|
default: "20mb"
|
||||||
},
|
},
|
||||||
|
"userSensitiveFields": {
|
||||||
|
help: "Personally identifiable information fields in the user table the should be removed for non-authorized users.",
|
||||||
|
default: "email"
|
||||||
|
},
|
||||||
"sessionLength": {
|
"sessionLength": {
|
||||||
env: "PARSE_SERVER_SESSION_LENGTH",
|
env: "PARSE_SERVER_SESSION_LENGTH",
|
||||||
help: "Session duration, defaults to 1 year",
|
help: "Session duration, defaults to 1 year",
|
||||||
|
|||||||
@@ -31,5 +31,6 @@ export default {
|
|||||||
sessionLength: 31536000,
|
sessionLength: 31536000,
|
||||||
expireInactiveSessions: true,
|
expireInactiveSessions: true,
|
||||||
revokeSessionOnPasswordReset: true,
|
revokeSessionOnPasswordReset: true,
|
||||||
schemaCacheTTL: 5000 // in ms
|
schemaCacheTTL: 5000, // in ms
|
||||||
|
userSensitiveFields: ['email']
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user