diff --git a/commafeed-server/src/main/java/com/commafeed/frontend/resource/CategoryREST.java b/commafeed-server/src/main/java/com/commafeed/frontend/resource/CategoryREST.java index cf4917c1..80add096 100644 --- a/commafeed-server/src/main/java/com/commafeed/frontend/resource/CategoryREST.java +++ b/commafeed-server/src/main/java/com/commafeed/frontend/resource/CategoryREST.java @@ -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); diff --git a/commafeed-server/src/main/java/com/commafeed/frontend/resource/FeedREST.java b/commafeed-server/src/main/java/com/commafeed/frontend/resource/FeedREST.java index d5c21220..0dac2fec 100644 --- a/commafeed-server/src/main/java/com/commafeed/frontend/resource/FeedREST.java +++ b/commafeed-server/src/main/java/com/commafeed/frontend/resource/FeedREST.java @@ -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) { diff --git a/commafeed-server/src/test/java/com/commafeed/integration/BaseIT.java b/commafeed-server/src/test/java/com/commafeed/integration/BaseIT.java index e92a4d70..3fda331d 100644 --- a/commafeed-server/src/test/java/com/commafeed/integration/BaseIT.java +++ b/commafeed-server/src/test/java/com/commafeed/integration/BaseIT.java @@ -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 login() { return login(TestConstants.ADMIN_USERNAME, TestConstants.ADMIN_PASSWORD); } diff --git a/commafeed-server/src/test/java/com/commafeed/integration/rest/CategoryIT.java b/commafeed-server/src/test/java/com/commafeed/integration/rest/CategoryIT.java index aeea6186..e8c7bf6c 100644 --- a/commafeed-server/src/test/java/com/commafeed/integration/rest/CategoryIT.java +++ b/commafeed-server/src/test/java/com/commafeed/integration/rest/CategoryIT.java @@ -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 diff --git a/commafeed-server/src/test/java/com/commafeed/integration/rest/FeedIT.java b/commafeed-server/src/test/java/com/commafeed/integration/rest/FeedIT.java index c8a01d5a..daff819c 100644 --- a/commafeed-server/src/test/java/com/commafeed/integration/rest/FeedIT.java +++ b/commafeed-server/src/test/java/com/commafeed/integration/rest/FeedIT.java @@ -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