prevent users from referencing categories owned by other users

This commit is contained in:
Athou
2026-11-04 08:34:47 +01:00
parent ae707a2602
commit d691095746
5 changed files with 179 additions and 4 deletions

View File

@@ -39,6 +39,7 @@ import jakarta.validation.Valid;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DefaultValue;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
@@ -333,8 +334,10 @@ public class CategoryREST {
cat.setPosition(0);
String parentId = req.getParentId();
if (parentId != null && !ALL.equals(parentId)) {
FeedCategory parent = new FeedCategory();
parent.setId(Long.valueOf(parentId));
FeedCategory parent = feedCategoryDAO.findById(user, Long.valueOf(parentId));
if (parent == null) {
throw new NotFoundException("parent category not found");
}
cat.setParent(parent);
}
feedCategoryDAO.persist(cat);

View File

@@ -407,13 +407,17 @@ public class FeedREST {
Preconditions.checkNotNull(req.getUrl());
try {
User user = authenticationContext.getCurrentUser();
FeedCategory category = null;
if (req.getCategoryId() != null && !CategoryREST.ALL.equals(req.getCategoryId())) {
category = feedCategoryDAO.findById(Long.valueOf(req.getCategoryId()));
category = feedCategoryDAO.findById(user, Long.valueOf(req.getCategoryId()));
if (category == null) {
return Response.status(Status.NOT_FOUND).build();
}
}
FeedInfo info = fetchFeedInternal(prependHttp(req.getUrl()));
User user = authenticationContext.getCurrentUser();
long subscriptionId = feedSubscriptionService.subscribe(user, info.getUrl(), req.getTitle(), category, 0);
return Response.ok(subscriptionId).build();
} catch (Exception e) {

View File

@@ -6,6 +6,7 @@ import com.commafeed.frontend.model.Entries;
import com.commafeed.frontend.model.Subscription;
import com.commafeed.frontend.model.request.AddCategoryRequest;
import com.commafeed.frontend.model.request.InitialSetupRequest;
import com.commafeed.frontend.model.request.RegistrationRequest;
import com.commafeed.frontend.model.request.SubscribeRequest;
import io.restassured.RestAssured;
@@ -102,6 +103,26 @@ public abstract class BaseIT {
.statusCode(200);
}
protected void registerUser(String userName, String password) {
RegistrationRequest req = new RegistrationRequest();
req.setName(userName);
req.setPassword(password);
req.setEmail(userName + "@commafeed.com");
RestAssured.given()
.auth()
.none()
.body(req)
.contentType(ContentType.JSON)
.post("rest/user/register")
.then()
.statusCode(HttpStatus.SC_OK);
}
protected void authenticateAs(String userName, String password) {
RestAssured.authentication = RestAssured.preemptive().basic(userName, password);
}
protected List<HttpCookie> login() {
return login(TestConstants.ADMIN_USERNAME, TestConstants.ADMIN_PASSWORD);
}

View File

@@ -5,6 +5,7 @@ import com.commafeed.frontend.model.Category;
import com.commafeed.frontend.model.Entries;
import com.commafeed.frontend.model.Entry;
import com.commafeed.frontend.model.UnreadCount;
import com.commafeed.frontend.model.request.AddCategoryRequest;
import com.commafeed.frontend.model.request.CategoryModificationRequest;
import com.commafeed.frontend.model.request.CollapseRequest;
import com.commafeed.frontend.model.request.IDRequest;
@@ -87,6 +88,38 @@ class CategoryIT extends BaseIT {
Assertions.assertEquals("test-category-1", root.getChildren().get(1).getName());
}
@Test
void addCategoryWithParent() {
String parentId = createCategory("parent");
AddCategoryRequest request = new AddCategoryRequest();
request.setName("child");
request.setParentId(parentId);
RestAssured.given()
.body(request)
.contentType(ContentType.JSON)
.post("rest/category/add")
.then()
.statusCode(HttpStatus.SC_OK);
Category parent = getRootCategory().getChildren().getFirst();
Assertions.assertEquals("parent", parent.getName());
Assertions.assertEquals("child", parent.getChildren().getFirst().getName());
}
@Test
void addCategoryWithUnknownParent() {
AddCategoryRequest request = new AddCategoryRequest();
request.setName("child");
request.setParentId("999999");
RestAssured.given()
.body(request)
.contentType(ContentType.JSON)
.post("rest/category/add")
.then()
.statusCode(HttpStatus.SC_NOT_FOUND);
}
@Test
void collapseCategory() {
String categoryId = createCategory("test-category");
@@ -145,6 +178,60 @@ class CategoryIT extends BaseIT {
Assertions.assertEquals(2, counts[0].getUnreadCount());
}
@Nested
class ParentOwnedByAnotherUser {
private static final String VICTIM = "victim";
private static final String ATTACKER = "attacker";
private static final String PASSWORD = "MyPassword123!";
private String victimCategoryId;
@BeforeEach
void attemptToAddChildToVictimCategory() {
registerUser(VICTIM, PASSWORD);
registerUser(ATTACKER, PASSWORD);
authenticateAs(VICTIM, PASSWORD);
victimCategoryId = createCategory("victim-category");
authenticateAs(ATTACKER, PASSWORD);
AddCategoryRequest request = new AddCategoryRequest();
request.setName("attacker-category");
request.setParentId(victimCategoryId);
RestAssured.given()
.body(request)
.contentType(ContentType.JSON)
.post("rest/category/add")
.then()
.statusCode(HttpStatus.SC_NOT_FOUND);
}
@Test
void victimCanDeleteCategory() {
authenticateAs(VICTIM, PASSWORD);
IDRequest request = new IDRequest();
request.setId(Long.valueOf(victimCategoryId));
RestAssured.given()
.body(request)
.contentType(ContentType.JSON)
.post("rest/category/delete")
.then()
.statusCode(HttpStatus.SC_OK);
Assertions.assertEquals(0, getRootCategory().getChildren().size());
}
@Test
void victimCanDeleteAccount() {
authenticateAs(VICTIM, PASSWORD);
RestAssured.given()
.contentType(ContentType.JSON)
.post("rest/user/profile/deleteAccount")
.then()
.statusCode(HttpStatus.SC_OK);
}
}
@Nested
class MarkEntriesAsRead {
@Test

View File

@@ -9,6 +9,7 @@ import com.commafeed.frontend.model.request.FeedInfoRequest;
import com.commafeed.frontend.model.request.FeedModificationRequest;
import com.commafeed.frontend.model.request.IDRequest;
import com.commafeed.frontend.model.request.MarkRequest;
import com.commafeed.frontend.model.request.SubscribeRequest;
import com.commafeed.frontend.resource.CategoryREST;
import com.commafeed.integration.BaseIT;
import com.rometools.rome.feed.synd.SyndFeed;
@@ -56,6 +57,65 @@ class FeedIT extends BaseIT {
RestAssured.reset();
}
@Nested
class CategoryOwnedByAnotherUser {
private static final String VICTIM = "victim";
private static final String ATTACKER = "attacker";
private static final String PASSWORD = "MyPassword123!";
private String victimCategoryId;
@BeforeEach
void attemptToSubscribeInVictimCategory() {
registerUser(VICTIM, PASSWORD);
registerUser(ATTACKER, PASSWORD);
authenticateAs(VICTIM, PASSWORD);
victimCategoryId = createCategory("victim-category");
authenticateAs(ATTACKER, PASSWORD);
SubscribeRequest request = new SubscribeRequest();
request.setUrl(getFeedUrl());
request.setTitle("my title for this feed");
request.setCategoryId(victimCategoryId);
RestAssured.given()
.body(request)
.contentType(ContentType.JSON)
.post("rest/feed/subscribe")
.then()
.statusCode(HttpStatus.SC_NOT_FOUND);
}
@Test
void victimCanDeleteCategory() {
authenticateAs(VICTIM, PASSWORD);
IDRequest request = new IDRequest();
request.setId(Long.valueOf(victimCategoryId));
RestAssured.given()
.body(request)
.contentType(ContentType.JSON)
.post("rest/category/delete")
.then()
.statusCode(HttpStatus.SC_OK);
}
@Test
void victimCanDeleteAccount() {
authenticateAs(VICTIM, PASSWORD);
RestAssured.given()
.contentType(ContentType.JSON)
.post("rest/user/profile/deleteAccount")
.then()
.statusCode(HttpStatus.SC_OK);
}
@Test
void subscriptionIsNotCreated() {
Assertions.assertEquals(0, getCategoryEntries(CategoryREST.ALL).getEntries().size());
}
}
@Nested
class Fetch {
@Test