@@ -953,6 +953,117 @@ describe('rest update', () => {
953953 } ) ;
954954} ) ;
955955
956+ describe ( '_Join table security' , ( ) => {
957+ let config ;
958+
959+ beforeEach ( ( ) => {
960+ config = Config . get ( 'test' ) ;
961+ } ) ;
962+
963+ it ( 'cannot create object in _Join table without masterKey' , ( ) => {
964+ expect ( ( ) =>
965+ rest . create ( config , auth . nobody ( config ) , '_Join:users:_Role' , {
966+ relatedId : 'someUserId' ,
967+ owningId : 'someRoleId' ,
968+ } )
969+ ) . toThrowError ( / P e r m i s s i o n d e n i e d / ) ;
970+ } ) ;
971+
972+ it ( 'cannot find objects in _Join table without masterKey' , async ( ) => {
973+ await expectAsync (
974+ rest . find ( config , auth . nobody ( config ) , '_Join:users:_Role' , { } )
975+ ) . toBeRejectedWith (
976+ jasmine . objectContaining ( {
977+ code : Parse . Error . OPERATION_FORBIDDEN ,
978+ } )
979+ ) ;
980+ } ) ;
981+
982+ it ( 'cannot update object in _Join table without masterKey' , ( ) => {
983+ expect ( ( ) =>
984+ rest . update ( config , auth . nobody ( config ) , '_Join:users:_Role' , { relatedId : 'someUserId' } , { owningId : 'newRoleId' } )
985+ ) . toThrowError ( / P e r m i s s i o n d e n i e d / ) ;
986+ } ) ;
987+
988+ it ( 'cannot delete object in _Join table without masterKey' , ( ) => {
989+ expect ( ( ) =>
990+ rest . del ( config , auth . nobody ( config ) , '_Join:users:_Role' , 'someObjectId' )
991+ ) . toThrowError ( / P e r m i s s i o n d e n i e d / ) ;
992+ } ) ;
993+
994+ it ( 'cannot get object in _Join table without masterKey' , async ( ) => {
995+ await expectAsync (
996+ rest . get ( config , auth . nobody ( config ) , '_Join:users:_Role' , 'someObjectId' )
997+ ) . toBeRejectedWith (
998+ jasmine . objectContaining ( {
999+ code : Parse . Error . OPERATION_FORBIDDEN ,
1000+ } )
1001+ ) ;
1002+ } ) ;
1003+
1004+ it ( 'can find objects in _Join table with masterKey' , async ( ) => {
1005+ await expectAsync (
1006+ rest . find ( config , auth . master ( config ) , '_Join:users:_Role' , { } )
1007+ ) . toBeResolved ( ) ;
1008+ } ) ;
1009+
1010+ it ( 'can find objects in _Join table with maintenance key' , async ( ) => {
1011+ await expectAsync (
1012+ rest . find ( config , auth . maintenance ( config ) , '_Join:users:_Role' , { } )
1013+ ) . toBeResolved ( ) ;
1014+ } ) ;
1015+
1016+ it ( 'legitimate relation operations still work' , async ( ) => {
1017+ const role = new Parse . Role ( 'admin' , new Parse . ACL ( ) ) ;
1018+ const user = await Parse . User . signUp ( 'testuser' , 'password123' ) ;
1019+ role . getUsers ( ) . add ( user ) ;
1020+ await role . save ( null , { useMasterKey : true } ) ;
1021+ const result = await rest . find ( config , auth . master ( config ) , '_Join:users:_Role' , { } ) ;
1022+ expect ( result . results . length ) . toBe ( 1 ) ;
1023+ } ) ;
1024+
1025+ it ( 'blocks _Join table access for any relation, not just _Role' , ( ) => {
1026+ expect ( ( ) =>
1027+ rest . create ( config , auth . nobody ( config ) , '_Join:viewers:ConfidentialDoc' , {
1028+ relatedId : 'someUserId' ,
1029+ owningId : 'someDocId' ,
1030+ } )
1031+ ) . toThrowError ( / P e r m i s s i o n d e n i e d / ) ;
1032+ } ) ;
1033+
1034+ it ( 'cannot escalate role via direct _Join table write' , async ( ) => {
1035+ const role = new Parse . Role ( 'superadmin' , new Parse . ACL ( ) ) ;
1036+ await role . save ( null , { useMasterKey : true } ) ;
1037+ const user = await Parse . User . signUp ( 'attacker' , 'password123' ) ;
1038+ const sessionToken = user . getSessionToken ( ) ;
1039+ const userAuth = await auth . getAuthForSessionToken ( {
1040+ config,
1041+ sessionToken,
1042+ } ) ;
1043+ expect ( ( ) =>
1044+ rest . create ( config , userAuth , '_Join:users:_Role' , {
1045+ relatedId : user . id ,
1046+ owningId : role . id ,
1047+ } )
1048+ ) . toThrowError ( / P e r m i s s i o n d e n i e d / ) ;
1049+ } ) ;
1050+
1051+ it ( 'cannot write to _Join table with read-only masterKey' , ( ) => {
1052+ expect ( ( ) =>
1053+ rest . create ( config , auth . readOnly ( config ) , '_Join:users:_Role' , {
1054+ relatedId : 'someUserId' ,
1055+ owningId : 'someRoleId' ,
1056+ } )
1057+ ) . toThrowError ( / P e r m i s s i o n d e n i e d / ) ;
1058+ } ) ;
1059+
1060+ it ( 'can read _Join table with read-only masterKey' , async ( ) => {
1061+ await expectAsync (
1062+ rest . find ( config , auth . readOnly ( config ) , '_Join:users:_Role' , { } )
1063+ ) . toBeResolved ( ) ;
1064+ } ) ;
1065+ } ) ;
1066+
9561067describe ( 'read-only masterKey' , ( ) => {
9571068 let loggerErrorSpy ;
9581069 let logger ;
0 commit comments