Skip to content

Commit

Permalink
allow disabling rate limits + add tests for pages
Browse files Browse the repository at this point in the history
  • Loading branch information
MiniDigger committed Sep 3, 2023
1 parent 1d943cb commit e8bb0ac
Show file tree
Hide file tree
Showing 9 changed files with 165 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public class HangarConfig {
private List<String> licenses = new ArrayList<>();
private boolean allowIndexing = true;
private boolean disableJGroups = false;
private boolean disableRateLimiting = false;

@NestedConfigurationProperty
public UpdateTasksConfig updateTasks;
Expand Down Expand Up @@ -188,4 +189,12 @@ public boolean isDisableJGroups() {
public void setDisableJGroups(final boolean disableJGroups) {
this.disableJGroups = disableJGroups;
}

public boolean isDisableRateLimiting() {
return this.disableRateLimiting;
}

public void setDisableRateLimiting(final boolean disableRateLimiting) {
this.disableRateLimiting = disableRateLimiting;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,6 @@ public PagesController(final ProjectPageService projectPageService) {
@ResponseStatus(HttpStatus.OK)
public String getMainPage(final String slug) {
final ExtendedProjectPage projectPage = this.projectPageService.getProjectPage(slug, "");
if (projectPage == null) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND);
}
return projectPage.getContents();
}

Expand All @@ -51,9 +48,6 @@ public String getMainPage(final String slug) {
@ResponseStatus(HttpStatus.OK)
public String getPage(final String slug, final String path) {
final ExtendedProjectPage projectPage = this.projectPageService.getProjectPage(slug, path);
if (projectPage == null) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND);
}
return projectPage.getContents();
}

Expand All @@ -73,10 +67,6 @@ public void editMainPage(final String slug, final StringContent pageEditForm) {
@ResponseStatus(HttpStatus.OK)
public void editPage(final String slug, final PageEditForm pageEditForm) {
final ExtendedProjectPage projectPage = this.projectPageService.getProjectPage(slug, pageEditForm.path());
if (projectPage == null) {
throw new HangarApiException(HttpStatus.NOT_FOUND, "Project page not found");
}

this.projectPageService.saveProjectPage(projectPage.getProjectId(), projectPage.getId(), pageEditForm.content());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ public class StringContent {

private @NotBlank(message = "general.error.fieldEmpty") @Schema(description = "A non-null, non-empty string") String content;

public StringContent() {
}

public StringContent(final String content) {
this.content = content;
}

public String getContent() {
return this.content;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.papermc.hangar.security.annotations.ratelimit;

import io.github.bucket4j.Bucket;
import io.papermc.hangar.config.hangar.HangarConfig;
import io.papermc.hangar.exceptions.HangarApiException;
import io.papermc.hangar.service.internal.BucketService;
import jakarta.servlet.http.HttpServletRequest;
Expand All @@ -19,10 +20,12 @@ public class RateLimitInterceptor implements HandlerInterceptor {

private static final Logger LOGGER = LoggerFactory.getLogger(RateLimitInterceptor.class);
private final BucketService bucketService;
private final HangarConfig hangarConfig;

@Autowired
public RateLimitInterceptor(final BucketService bucketService) {
public RateLimitInterceptor(final BucketService bucketService, final HangarConfig hangarConfig) {
this.bucketService = bucketService;
this.hangarConfig = hangarConfig;
}

@Override
Expand All @@ -31,6 +34,10 @@ public boolean preHandle(final @NotNull HttpServletRequest request, final @NotNu
return true;
}

if (this.hangarConfig.isDisableRateLimiting()) {
return true;
}

final Method method = handlerMethod.getMethod();
final RateLimit limit = method.getAnnotation(RateLimit.class);
if (limit != null) {
Expand Down
1 change: 1 addition & 0 deletions backend/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ hangar:
ga-code: "UA-38006759-9"
allow-indexing: true
disable-jgroups: true
disable-ratelimiting: false

licenses:
- "Unspecified"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;


class ApiKeysControllerTest extends ControllerTest {

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package io.papermc.hangar.controller.api.v1;

import io.papermc.hangar.controller.api.v1.helper.ControllerTest;
import io.papermc.hangar.controller.api.v1.helper.TestData;
import io.papermc.hangar.model.api.project.PageEditForm;
import io.papermc.hangar.model.common.NamedPermission;
import io.papermc.hangar.model.internal.api.requests.CreateAPIKeyForm;
import io.papermc.hangar.model.internal.api.requests.StringContent;
import java.util.Set;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;

import static org.hamcrest.Matchers.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

class PagesControllerTest extends ControllerTest {

@Test
void testGetMainPage() throws Exception {
this.mockMvc.perform(get("/api/v1/pages/main/" + TestData.PROJECT.getSlug())
.with(this.apiKey(TestData.KEY_ADMIN)))
.andExpect(status().is(200))
.andExpect(content().string(startsWith("# Test")));
}

@Test
void testEditMainPage() throws Exception {
// edit
this.mockMvc.perform(patch("/api/v1/pages/editmain/" + TestData.PROJECT.getSlug())
.content(this.objectMapper.writeValueAsBytes(new StringContent("# Test\nEdited")))
.contentType(MediaType.APPLICATION_JSON)
.with(this.apiKey(TestData.KEY_ADMIN)))
.andExpect(status().is(200));

// validate
this.mockMvc.perform(get("/api/v1/pages/main/" + TestData.PROJECT.getSlug())
.with(this.apiKey(TestData.KEY_ADMIN)))
.andExpect(status().is(200))
.andExpect(content().string(containsString("Edited")));
}

@Test
void testGetOtherPage() throws Exception {
this.mockMvc.perform(get("/api/v1/pages/page/" + TestData.PROJECT.getSlug() + "?path=" + TestData.PAGE_PARENT.getSlug())
.with(this.apiKey(TestData.KEY_ADMIN)))
.andExpect(status().is(200))
.andExpect(content().string(startsWith("# TestParentPage")));
}

@Test
void testSlashes() throws Exception {
this.mockMvc.perform(get("/api/v1/pages/page/" + TestData.PROJECT.getSlug() + "?path=/" + TestData.PAGE_PARENT.getSlug() + "/")
.with(this.apiKey(TestData.KEY_ADMIN)))
.andExpect(status().is(200))
.andExpect(content().string(startsWith("# TestParentPage")));
}

@Test
void testEditOtherPage() throws Exception {
// edit
this.mockMvc.perform(patch("/api/v1/pages/edit/" + TestData.PROJECT.getSlug())
.content(this.objectMapper.writeValueAsBytes(new PageEditForm(TestData.PAGE_PARENT.getSlug(), "# TestParentPage\nEdited")))
.contentType(MediaType.APPLICATION_JSON)
.with(this.apiKey(TestData.KEY_ADMIN)))
.andExpect(status().is(200));

// validate
this.mockMvc.perform(get("/api/v1/pages/page/" + TestData.PROJECT.getSlug() + "?path=" + TestData.PAGE_PARENT.getSlug())
.with(this.apiKey(TestData.KEY_ADMIN)))
.andExpect(status().is(200))
.andExpect(content().string(containsString("Edited")));
}

@Test
void testGetChildPage() throws Exception {
this.mockMvc.perform(get("/api/v1/pages/page/" + TestData.PROJECT.getSlug() + "?path=" + TestData.PAGE_CHILD.getSlug())
.with(this.apiKey(TestData.KEY_ADMIN)))
.andExpect(status().is(200))
.andExpect(content().string(startsWith("# TestChildPage")));
}

@Test
void testEditChildPage() throws Exception {
// edit
this.mockMvc.perform(patch("/api/v1/pages/edit/" + TestData.PROJECT.getSlug())
.content(this.objectMapper.writeValueAsBytes(new PageEditForm(TestData.PAGE_CHILD.getSlug(), "# TestChildPage\nEdited")))
.contentType(MediaType.APPLICATION_JSON)
.with(this.apiKey(TestData.KEY_ADMIN)))
.andExpect(status().is(200));

// validate
this.mockMvc.perform(get("/api/v1/pages/page/" + TestData.PROJECT.getSlug() + "?path=" + TestData.PAGE_CHILD.getSlug())
.with(this.apiKey(TestData.KEY_ADMIN)))
.andExpect(status().is(200))
.andExpect(content().string(containsString("Edited")));
}

@Test
void testGetInvalidProject() throws Exception {
this.mockMvc.perform(get("/api/v1/pages/main/Dum")
.with(this.apiKey(TestData.KEY_ADMIN)))
.andExpect(status().is(404));
}

@Test
void testGetInvalidPage() throws Exception {
this.mockMvc.perform(get("/api/v1/pages/page/" + TestData.PROJECT.getSlug() + "?path=Dum")
.with(this.apiKey(TestData.KEY_ADMIN)))
.andExpect(status().is(404));
}

@Test
void testEditInvalidProject() throws Exception {
this.mockMvc.perform(patch("/api/v1/pages/editmain/Dum")
.content(this.objectMapper.writeValueAsBytes(new StringContent("# Dum")))
.contentType(MediaType.APPLICATION_JSON)
.with(this.apiKey(TestData.KEY_ADMIN)))
.andExpect(status().is(404));
}

@Test
void testEditInvalidPage() throws Exception {
this.mockMvc.perform(patch("/api/v1/pages/edit/" + TestData.PROJECT.getSlug())
.content(this.objectMapper.writeValueAsBytes(new PageEditForm(TestData.PAGE_PARENT.getSlug(), "# TestParentPage\nEdited")))
.contentType(MediaType.APPLICATION_JSON)
.with(this.apiKey(TestData.KEY_ADMIN)))
.andExpect(status().is(200));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import io.papermc.hangar.model.common.roles.OrganizationRole;
import io.papermc.hangar.model.db.OrganizationTable;
import io.papermc.hangar.model.db.UserTable;
import io.papermc.hangar.model.db.projects.ProjectPageTable;
import io.papermc.hangar.model.db.projects.ProjectTable;
import io.papermc.hangar.model.db.roles.GlobalRoleTable;
import io.papermc.hangar.model.internal.api.requests.CreateAPIKeyForm;
Expand All @@ -22,6 +23,7 @@
import io.papermc.hangar.service.internal.perms.members.OrganizationMemberService;
import io.papermc.hangar.service.internal.perms.roles.GlobalRoleService;
import io.papermc.hangar.service.internal.projects.ProjectFactory;
import io.papermc.hangar.service.internal.projects.ProjectPageService;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
Expand Down Expand Up @@ -50,6 +52,9 @@ public class TestData {

public static ProjectTable PROJECT;

public static ProjectPageTable PAGE_PARENT;
public static ProjectPageTable PAGE_CHILD;

@Autowired
private AuthService authService;
@Autowired
Expand All @@ -62,6 +67,8 @@ public class TestData {
private OrganizationMemberService organizationMemberService;
@Autowired
private ProjectFactory projectFactory;
@Autowired
private ProjectPageService projectPageService;

@EventListener(ApplicationStartedEvent.class)
public void prepare() {
Expand All @@ -83,6 +90,8 @@ public void prepare() {
logger.info("Creating some test projects...");
PROJECT = this.projectFactory.createProject(new NewProjectForm(new ProjectSettings(List.of(), List.of(), new ProjectLicense(null, null, "MIT"), List.of(), null),
Category.CHAT, "", ORG.getUserId(), "TestProject", "# Test", null));
PAGE_PARENT = this.projectPageService.createPage(PROJECT.getProjectId(), "TestParentPage", "testparentpage", "# TestParentPage", true, null, false);
PAGE_CHILD = this.projectPageService.createPage(PROJECT.getProjectId(), "TestChildPage", "testparentpage/testchild", "# TestChildPage", true, PAGE_PARENT.getId(), false);

logger.info("Creating test api keys...");
KEY_ADMIN = this.apiKeyService.createApiKey(USER_ADMIN, new CreateAPIKeyForm("Admin", Set.of(NamedPermission.values())), Permission.All);
Expand Down
1 change: 1 addition & 0 deletions backend/src/test/resources/application-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ spring:
baseline-version: 0.0

hangar:
disable-ratelimiting: true
storage:
type: "local"

0 comments on commit e8bb0ac

Please sign in to comment.