More RecipeService security and tests.
This commit is contained in:
parent
d3a532fb12
commit
35ef2aa039
@ -64,7 +64,7 @@ public class RecipeServiceTests {
|
|||||||
public void getByIdPublic() throws RecipeException {
|
public void getByIdPublic() throws RecipeException {
|
||||||
final User owner = this.createTestUser("recipeOwner");
|
final User owner = this.createTestUser("recipeOwner");
|
||||||
Recipe recipe = this.createTestRecipe(owner);
|
Recipe recipe = this.createTestRecipe(owner);
|
||||||
recipe = this.recipeService.setPublic(recipe, true);
|
recipe = this.recipeService.setPublic(recipe, owner, true);
|
||||||
final Recipe byId = this.recipeService.getById(recipe.getId());
|
final Recipe byId = this.recipeService.getById(recipe.getId());
|
||||||
assertThat(byId.getId(), is(recipe.getId()));
|
assertThat(byId.getId(), is(recipe.getId()));
|
||||||
assertThat(byId.getTitle(), is("My Recipe"));
|
assertThat(byId.getTitle(), is("My Recipe"));
|
||||||
@ -93,7 +93,7 @@ public class RecipeServiceTests {
|
|||||||
public void getByIdOkayWhenPublic() {
|
public void getByIdOkayWhenPublic() {
|
||||||
final User owner = this.createTestUser("recipeOwner");
|
final User owner = this.createTestUser("recipeOwner");
|
||||||
final Recipe notYetPublicRecipe = this.createTestRecipe(owner);
|
final Recipe notYetPublicRecipe = this.createTestRecipe(owner);
|
||||||
final Recipe publicRecipe = this.recipeService.setPublic(notYetPublicRecipe, true);
|
final Recipe publicRecipe = this.recipeService.setPublic(notYetPublicRecipe, owner, true);
|
||||||
assertDoesNotThrow(() -> this.recipeService.getById(publicRecipe.getId()));
|
assertDoesNotThrow(() -> this.recipeService.getById(publicRecipe.getId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,7 +103,7 @@ public class RecipeServiceTests {
|
|||||||
final User owner = this.createTestUser("recipeOwner");
|
final User owner = this.createTestUser("recipeOwner");
|
||||||
final User viewer = this.createTestUser("viewer");
|
final User viewer = this.createTestUser("viewer");
|
||||||
final Recipe notYetPublicRecipe = this.createTestRecipe(owner);
|
final Recipe notYetPublicRecipe = this.createTestRecipe(owner);
|
||||||
final Recipe publicRecipe = this.recipeService.setPublic(notYetPublicRecipe, true);
|
final Recipe publicRecipe = this.recipeService.setPublic(notYetPublicRecipe, owner, true);
|
||||||
assertDoesNotThrow(() -> this.recipeService.getById(publicRecipe.getId(), viewer));
|
assertDoesNotThrow(() -> this.recipeService.getById(publicRecipe.getId(), viewer));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,7 +112,7 @@ public class RecipeServiceTests {
|
|||||||
public void getByIdWithStarsPublic() throws RecipeException {
|
public void getByIdWithStarsPublic() throws RecipeException {
|
||||||
final User owner = this.createTestUser("recipeOwner");
|
final User owner = this.createTestUser("recipeOwner");
|
||||||
Recipe recipe = this.createTestRecipe(owner);
|
Recipe recipe = this.createTestRecipe(owner);
|
||||||
recipe = this.recipeService.setPublic(recipe, true);
|
recipe = this.recipeService.setPublic(recipe, owner, true);
|
||||||
final RecipeStar star = this.recipeService.addStar(recipe, owner);
|
final RecipeStar star = this.recipeService.addStar(recipe, owner);
|
||||||
final Recipe byIdWithStars = this.recipeService.getByIdWithStars(recipe.getId());
|
final Recipe byIdWithStars = this.recipeService.getByIdWithStars(recipe.getId());
|
||||||
assertThat(byIdWithStars.getStars(), containsStars(star));
|
assertThat(byIdWithStars.getStars(), containsStars(star));
|
||||||
@ -132,7 +132,7 @@ public class RecipeServiceTests {
|
|||||||
public void getByIdWithStarsOkayWhenPublic() {
|
public void getByIdWithStarsOkayWhenPublic() {
|
||||||
final User owner = this.createTestUser("recipeOwner");
|
final User owner = this.createTestUser("recipeOwner");
|
||||||
final Recipe notYetPublicRecipe = this.createTestRecipe(owner);
|
final Recipe notYetPublicRecipe = this.createTestRecipe(owner);
|
||||||
final Recipe publicRecipe = this.recipeService.setPublic(notYetPublicRecipe, true);
|
final Recipe publicRecipe = this.recipeService.setPublic(notYetPublicRecipe, owner, true);
|
||||||
assertDoesNotThrow(() -> this.recipeService.getByIdWithStars(publicRecipe.getId()));
|
assertDoesNotThrow(() -> this.recipeService.getByIdWithStars(publicRecipe.getId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,7 +142,7 @@ public class RecipeServiceTests {
|
|||||||
final User owner = this.createTestUser("recipeOwner");
|
final User owner = this.createTestUser("recipeOwner");
|
||||||
final User viewer = this.createTestUser("viewer");
|
final User viewer = this.createTestUser("viewer");
|
||||||
final Recipe notYetPublicRecipe = this.createTestRecipe(owner);
|
final Recipe notYetPublicRecipe = this.createTestRecipe(owner);
|
||||||
final Recipe publicRecipe = this.recipeService.setPublic(notYetPublicRecipe, true);
|
final Recipe publicRecipe = this.recipeService.setPublic(notYetPublicRecipe, owner, true);
|
||||||
assertDoesNotThrow(() -> this.recipeService.getByIdWithStars(publicRecipe.getId(), viewer));
|
assertDoesNotThrow(() -> this.recipeService.getByIdWithStars(publicRecipe.getId(), viewer));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,9 +157,9 @@ public class RecipeServiceTests {
|
|||||||
Recipe r1 = this.createTestRecipe(owner);
|
Recipe r1 = this.createTestRecipe(owner);
|
||||||
Recipe r2 = this.createTestRecipe(owner);
|
Recipe r2 = this.createTestRecipe(owner);
|
||||||
|
|
||||||
r0 = this.recipeService.setPublic(r0, true);
|
r0 = this.recipeService.setPublic(r0, owner, true);
|
||||||
r1 = this.recipeService.setPublic(r1, true);
|
r1 = this.recipeService.setPublic(r1, owner, true);
|
||||||
r2 = this.recipeService.setPublic(r2, true);
|
r2 = this.recipeService.setPublic(r2, owner, true);
|
||||||
|
|
||||||
// r0.stars = 0, r1.stars = 1, r2.stars = 2
|
// r0.stars = 0, r1.stars = 1, r2.stars = 2
|
||||||
this.recipeService.addStar(r1, u0);
|
this.recipeService.addStar(r1, u0);
|
||||||
@ -236,8 +236,8 @@ public class RecipeServiceTests {
|
|||||||
Recipe r0 = this.createTestRecipe(owner);
|
Recipe r0 = this.createTestRecipe(owner);
|
||||||
Recipe r1 = this.createTestRecipe(owner);
|
Recipe r1 = this.createTestRecipe(owner);
|
||||||
|
|
||||||
r0 = this.recipeService.setPublic(r0, true);
|
r0 = this.recipeService.setPublic(r0, owner, true);
|
||||||
r1 = this.recipeService.setPublic(r1, true);
|
r1 = this.recipeService.setPublic(r1, owner, true);
|
||||||
|
|
||||||
final List<Recipe> publicRecipes = this.recipeService.getPublicRecipes();
|
final List<Recipe> publicRecipes = this.recipeService.getPublicRecipes();
|
||||||
assertThat(publicRecipes.size(), is(2));
|
assertThat(publicRecipes.size(), is(2));
|
||||||
@ -274,10 +274,19 @@ public class RecipeServiceTests {
|
|||||||
final Recipe recipe = this.recipeService.create(
|
final Recipe recipe = this.recipeService.create(
|
||||||
owner, "My Recipe", "# A Heading"
|
owner, "My Recipe", "# A Heading"
|
||||||
);
|
);
|
||||||
final String rendered = this.recipeService.getRenderedMarkdown(recipe);
|
final String rendered = this.recipeService.getRenderedMarkdown(recipe, owner);
|
||||||
assertThat(rendered, is("<h1>A Heading</h1>"));
|
assertThat(rendered, is("<h1>A Heading</h1>"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
|
public void getRenderedMarkThrowsIfNotViewable() {
|
||||||
|
final User owner = this.createTestUser("recipeOwner");
|
||||||
|
final User notViewer = this.createTestUser("notViewer");
|
||||||
|
final Recipe recipe = this.createTestRecipe(owner);
|
||||||
|
assertThrows(AccessDeniedException.class, () -> this.recipeService.getRenderedMarkdown(recipe, notViewer));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DirtiesContext
|
@DirtiesContext
|
||||||
public void updateRawText() {
|
public void updateRawText() {
|
||||||
@ -286,10 +295,22 @@ public class RecipeServiceTests {
|
|||||||
owner, "My Recipe", "# A Heading"
|
owner, "My Recipe", "# A Heading"
|
||||||
);
|
);
|
||||||
final String newRawText = "# A Heading\n## A Subheading";
|
final String newRawText = "# A Heading\n## A Subheading";
|
||||||
recipe = this.recipeService.updateRawText(recipe, newRawText);
|
recipe = this.recipeService.updateRawText(recipe, owner, newRawText);
|
||||||
assertThat(recipe.getRawText(), is(newRawText));
|
assertThat(recipe.getRawText(), is(newRawText));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
|
public void updateRawTextThrowsIfNotOwner() {
|
||||||
|
final User owner = this.createTestUser("recipeOwner");
|
||||||
|
final User notOwner = this.createTestUser("notOwner");
|
||||||
|
final Recipe recipe = this.createTestRecipe(owner);
|
||||||
|
assertThrows(
|
||||||
|
AccessDeniedException.class,
|
||||||
|
() -> this.recipeService.updateRawText(recipe, notOwner, "should fail")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DirtiesContext
|
@DirtiesContext
|
||||||
public void updateOwnerViaUser() throws RecipeException {
|
public void updateOwnerViaUser() throws RecipeException {
|
||||||
@ -343,4 +364,22 @@ public class RecipeServiceTests {
|
|||||||
assertThat(recipe.getStars(), is(empty()));
|
assertThat(recipe.getStars(), is(empty()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
|
public void deleteRecipe() {
|
||||||
|
final User owner = this.createTestUser("recipeOwner");
|
||||||
|
final Recipe toDelete = this.createTestRecipe(owner);
|
||||||
|
this.recipeService.deleteRecipe(toDelete, owner);
|
||||||
|
assertThrows(RecipeException.class, () -> this.recipeService.getById(toDelete.getId(), owner));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DirtiesContext
|
||||||
|
public void deleteRecipeThrowsIfNotOwner() {
|
||||||
|
final User owner = this.createTestUser("recipeOwner");
|
||||||
|
final User notOwner = this.createTestUser("notOwner");
|
||||||
|
final Recipe toDelete = this.createTestRecipe(owner);
|
||||||
|
assertThrows(AccessDeniedException.class, () -> this.recipeService.deleteRecipe(toDelete, notOwner));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -4,5 +4,6 @@ import app.mealsmadeeasy.api.user.User;
|
|||||||
|
|
||||||
public interface RecipeSecurity {
|
public interface RecipeSecurity {
|
||||||
boolean isOwner(Recipe recipe, User user);
|
boolean isOwner(Recipe recipe, User user);
|
||||||
|
boolean isOwner(long recipeId, User user) throws RecipeException;
|
||||||
boolean isViewableBy(Recipe recipe, User user);
|
boolean isViewableBy(Recipe recipe, User user);
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,15 @@ public class RecipeSecurityImpl implements RecipeSecurity {
|
|||||||
return recipe.getOwner() != null && recipe.getOwner().getId().equals(user.getId());
|
return recipe.getOwner() != null && recipe.getOwner().getId().equals(user.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOwner(long recipeId, User user) throws RecipeException {
|
||||||
|
final Recipe recipe = this.recipeRepository.findById(recipeId).orElseThrow(() -> new RecipeException(
|
||||||
|
RecipeException.Type.INVALID_ID,
|
||||||
|
"No such Recipe with id " + recipeId
|
||||||
|
));
|
||||||
|
return this.isOwner(recipe, user);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isViewableBy(Recipe recipe, User user) {
|
public boolean isViewableBy(Recipe recipe, User user) {
|
||||||
if (Objects.equals(recipe.getOwner().getId(), user.getId())) {
|
if (Objects.equals(recipe.getOwner().getId(), user.getId())) {
|
||||||
|
@ -24,9 +24,9 @@ public interface RecipeService {
|
|||||||
List<Recipe> getRecipesViewableBy(User user);
|
List<Recipe> getRecipesViewableBy(User user);
|
||||||
List<Recipe> getRecipesOwnedBy(User user);
|
List<Recipe> getRecipesOwnedBy(User user);
|
||||||
|
|
||||||
String getRenderedMarkdown(Recipe recipe);
|
String getRenderedMarkdown(Recipe recipe, User viewer);
|
||||||
|
|
||||||
Recipe updateRawText(Recipe recipe, String newRawText);
|
Recipe updateRawText(Recipe recipe, User owner, String newRawText);
|
||||||
|
|
||||||
Recipe updateOwner(Recipe recipe, User oldOwner, User newOwner) throws RecipeException;
|
Recipe updateOwner(Recipe recipe, User oldOwner, User newOwner) throws RecipeException;
|
||||||
|
|
||||||
@ -34,7 +34,7 @@ public interface RecipeService {
|
|||||||
void deleteStarByUser(Recipe recipe, User giver) throws RecipeException;
|
void deleteStarByUser(Recipe recipe, User giver) throws RecipeException;
|
||||||
void deleteStar(RecipeStar recipeStar);
|
void deleteStar(RecipeStar recipeStar);
|
||||||
|
|
||||||
Recipe setPublic(Recipe recipe, boolean isPublic);
|
Recipe setPublic(Recipe recipe, User owner, boolean isPublic);
|
||||||
|
|
||||||
Recipe addViewer(Recipe recipe, User user);
|
Recipe addViewer(Recipe recipe, User user);
|
||||||
Recipe removeViewer(Recipe recipe, User user);
|
Recipe removeViewer(Recipe recipe, User user);
|
||||||
@ -47,7 +47,7 @@ public interface RecipeService {
|
|||||||
void deleteComment(RecipeComment comment);
|
void deleteComment(RecipeComment comment);
|
||||||
Recipe clearComments(Recipe recipe);
|
Recipe clearComments(Recipe recipe);
|
||||||
|
|
||||||
void deleteRecipe(Recipe recipe);
|
void deleteRecipe(Recipe recipe, User owner);
|
||||||
void deleteById(long id);
|
void deleteById(long id, User owner);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -136,7 +136,8 @@ public class RecipeServiceImpl implements RecipeService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getRenderedMarkdown(Recipe recipe) {
|
@PreAuthorize("#recipe.isPublic || @recipeSecurity.isViewableBy(#recipe, #viewer)")
|
||||||
|
public String getRenderedMarkdown(Recipe recipe, User viewer) {
|
||||||
RecipeEntity entity = (RecipeEntity) recipe;
|
RecipeEntity entity = (RecipeEntity) recipe;
|
||||||
if (entity.getCachedRenderedText() == null) {
|
if (entity.getCachedRenderedText() == null) {
|
||||||
entity.setCachedRenderedText(renderAndCleanMarkdown(entity.getRawText()));
|
entity.setCachedRenderedText(renderAndCleanMarkdown(entity.getRawText()));
|
||||||
@ -146,7 +147,8 @@ public class RecipeServiceImpl implements RecipeService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Recipe updateRawText(Recipe recipe, String newRawText) {
|
@PreAuthorize("@recipeSecurity.isOwner(#recipe, #owner)")
|
||||||
|
public Recipe updateRawText(Recipe recipe, User owner, String newRawText) {
|
||||||
final RecipeEntity entity = (RecipeEntity) recipe;
|
final RecipeEntity entity = (RecipeEntity) recipe;
|
||||||
entity.setCachedRenderedText(null);
|
entity.setCachedRenderedText(null);
|
||||||
entity.setRawText(newRawText);
|
entity.setRawText(newRawText);
|
||||||
@ -188,7 +190,8 @@ public class RecipeServiceImpl implements RecipeService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Recipe setPublic(Recipe recipe, boolean isPublic) {
|
@PreAuthorize("@recipeSecurity.isOwner(#recipe, #owner)")
|
||||||
|
public Recipe setPublic(Recipe recipe, User owner, boolean isPublic) {
|
||||||
final RecipeEntity entity = (RecipeEntity) recipe;
|
final RecipeEntity entity = (RecipeEntity) recipe;
|
||||||
entity.setPublic(isPublic);
|
entity.setPublic(isPublic);
|
||||||
return this.recipeRepository.save(entity);
|
return this.recipeRepository.save(entity);
|
||||||
@ -266,12 +269,14 @@ public class RecipeServiceImpl implements RecipeService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void deleteRecipe(Recipe recipe) {
|
@PreAuthorize("@recipeSecurity.isOwner(#recipe, #owner)")
|
||||||
|
public void deleteRecipe(Recipe recipe, User owner) {
|
||||||
this.recipeRepository.delete((RecipeEntity) recipe);
|
this.recipeRepository.delete((RecipeEntity) recipe);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void deleteById(long id) {
|
@PreAuthorize("@recipeSecurity.isOwner(#id, #owner)")
|
||||||
|
public void deleteById(long id, User owner) {
|
||||||
this.recipeRepository.deleteById(id);
|
this.recipeRepository.deleteById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user