Interview question
Test a resource ownership boundary
Proves that one authenticated user cannot read or modify another user's private resource.
TL;DR
Proves that one authenticated user cannot read or modify another user's private resource.
Object-level authorization, ownership queries, information disclosure, privileged capabilities, and multi-user test data.
Practice the problem like a real interview: restate, reason, implement, and test.
Seed two users and one private practice record in an isolated test store. The owner can read and update it. An ordinary non-owner receives the product's deliberate denial status. A support user succeeds only with the explicit recovery capability.
User B cannot infer or modify user A's record by changing a route id. The API returns the chosen 404-style concealment response, while audit-worthy support access requires a distinct capability.
I test object authorization through HTTP because route binding, current-user resolution, query filters, and policies all participate. The most important assertion after a denied PUT is that the owner record did not change.
[Fact]
public async Task Ownership_and_recovery_capability_are_enforced()
{
await using var factory = await ApiTestFactory.CreateWithPracticeAsync(
ownerId: "user-a",
practiceId: PracticeIds.PrivateOne);
var path = $"/api/practice/{PracticeIds.PrivateOne}";
var owner = factory.CreateAuthenticatedClient(
"user-a",
"practice.read.own",
"practice.write.own");
Assert.Equal(HttpStatusCode.OK, (await owner.GetAsync(path)).StatusCode);
Assert.Equal(
HttpStatusCode.NoContent,
(await owner.PutAsJsonAsync(path, new { Notes = "owner update" })).StatusCode);
var outsider = factory.CreateAuthenticatedClient(
"user-b",
"practice.read.own",
"practice.write.own");
Assert.Equal(HttpStatusCode.NotFound, (await outsider.GetAsync(path)).StatusCode);
Assert.Equal(
HttpStatusCode.NotFound,
(await outsider.PutAsJsonAsync(path, new { Notes = "overwritten" })).StatusCode);
Assert.Equal(
"owner update",
(await factory.ReadPracticeAsync(PracticeIds.PrivateOne)).Notes);
var supportWithoutRecovery = factory.CreateAuthenticatedClient(
"support-1",
"support.read");
Assert.Equal(
HttpStatusCode.NotFound,
(await supportWithoutRecovery.GetAsync(path)).StatusCode);
var supportWithRecovery = factory.CreateAuthenticatedClient(
"support-2",
"practice.recover");
Assert.Equal(
HttpStatusCode.OK,
(await supportWithRecovery.GetAsync(path)).StatusCode);
Assert.Equal(
HttpStatusCode.NoContent,
(await supportWithRecovery.PutAsJsonAsync(
path,
new { Notes = "recovered by support" })).StatusCode);
var stored = await factory.ReadPracticeAsync(PracticeIds.PrivateOne);
Assert.Equal("user-a", stored.UserId);
Assert.Equal("recovered by support", stored.Notes);
}
This test needs isolated multi-user state. An in-memory repository is acceptable for policy orchestration; use a disposable relational database when ownership is enforced by EF filters or query shape that must be verified.
Move to the linked follow-up, next path step, prerequisite, or deeper variant.
Practice the next layer of the same subject.