3434import org .labkey .api .action .NullSafeBindException ;
3535import org .labkey .api .action .SpringActionController ;
3636import org .labkey .api .collections .CaseInsensitiveHashMap ;
37+ import org .labkey .api .query .FieldKey ;
3738import org .labkey .api .query .SchemaKey ;
3839import org .labkey .api .security .permissions .DeletePermission ;
3940import org .labkey .api .security .permissions .InsertPermission ;
@@ -183,8 +184,21 @@ public void doUpdate() throws SQLException
183184 throw new UnauthorizedException ();
184185 }
185186
186- if (null != _tinfo .getColumn ("container" ))
187+ FieldKey containerFK = FieldKey .fromParts ("Container" );
188+ if (null != _tinfo .getColumn (containerFK ))
189+ {
190+ // The hasPermission() check above only proves the user can update the *current* container. The UPDATE below
191+ // keys on the PK alone and stamps the row into the current container, so without this guard a user with
192+ // update permission here could edit (and re-home) a row that actually lives in another container simply by
193+ // POSTing its PK. Confirm the existing row is in this container; 404 otherwise. PkFilter validates and
194+ // converts the PK as well, so a missing or malformed key likewise 404s here rather than later.
195+ SimpleFilter filter = new PkFilter (_tinfo , getPkVals ());
196+ filter .addCondition (containerFK , _c .getId ());
197+ if (!new TableSelector (_tinfo , filter , null ).exists ())
198+ throw new NotFoundException ("No matching row found in this folder" );
199+
187200 set ("container" , _c .getId ());
201+ }
188202
189203 Object [] pkVal = getPkVals ();
190204 Map <String , Object > newMap = Table .update (_user , _tinfo , getTypedValues (), pkVal );
@@ -207,21 +221,50 @@ public void doDelete()
207221 throw new UnauthorizedException ();
208222 }
209223
210- if (null != _selectedRows && _selectedRows .length > 0 )
211- {
212- for (String selectedRow : _selectedRows )
213- Table .delete (_tinfo , selectedRow );
214- }
215- else
224+ // Table.delete() keys on the PK alone. As with doUpdate(), the DeletePermission check only proves the user can
225+ // delete in the *current* container, so for container-scoped tables we must confirm each target row lives here;
226+ // otherwise a user could delete a row that belongs to another container by POSTing (or grid-selecting) its PK.
227+ FieldKey containerFK = FieldKey .fromParts ("Container" );
228+ boolean scopeToContainer = null != _tinfo .getColumn (containerFK );
229+
230+ try (DbScope .Transaction t = _tinfo .getSchema ().getScope ().ensureTransaction ())
216231 {
217- Object [] pkVal = getPkVals ();
218- if (null != pkVal && null != pkVal [0 ])
219- Table .delete (_tinfo , pkVal );
220- else //Hmm, throw an exception here????
221- _log .warn ("Nothing to delete for table " + _tinfo .getName () + " on request " + _request .getRequestURI ());
232+ if (null != _selectedRows && _selectedRows .length > 0 )
233+ {
234+ for (String selectedRow : _selectedRows )
235+ {
236+ if (scopeToContainer )
237+ deleteInContainer (selectedRow , containerFK );
238+ else
239+ Table .delete (_tinfo , selectedRow );
240+ }
241+ }
242+ else
243+ {
244+ Object [] pkVal = getPkVals ();
245+ if (null != pkVal && null != pkVal [0 ])
246+ {
247+ if (scopeToContainer )
248+ deleteInContainer (pkVal , containerFK );
249+ else
250+ Table .delete (_tinfo , pkVal );
251+ }
252+ else //Hmm, throw an exception here????
253+ _log .warn ("Nothing to delete for table " + _tinfo .getName () + " on request " + _request .getRequestURI ());
254+ }
222255 }
223256 }
224257
258+ // Deletes a single row only if it lives in the form's container, scoping the DELETE's WHERE clause to the PK and the
259+ // container together. 404s on a miss (cross-container or already gone), mirroring doUpdate().
260+ private void deleteInContainer (Object pkVal , FieldKey containerFK )
261+ {
262+ SimpleFilter filter = new PkFilter (_tinfo , pkVal );
263+ filter .addCondition (containerFK , _c .getId ());
264+ if (Table .delete (_tinfo , filter ) == 0 )
265+ throw new NotFoundException ("No matching row found in this folder" );
266+ }
267+
225268 /**
226269 * Pulls in the data from the current row of the database.
227270 */
0 commit comments