limit permission check perms to api key scope, closes #808

also adds proper DB based integration tests for the perm controller
This commit is contained in:
MiniDigger | Martin 2022-07-31 10:51:16 +02:00
parent 2b43e0aa4d
commit 75e2759ea5
7 changed files with 131 additions and 5 deletions

13
pom.xml
View File

@ -39,6 +39,8 @@
<apache-commons-lang.version>3.12.0</apache-commons-lang.version>
<jwt.version>4.0.0</jwt.version>
<!-- test -->
<pg.version>1.17.3</pg.version>
<!-- plugins -->
<maven-compiler-plugin.version>3.10.1</maven-compiler-plugin.version>
@ -246,6 +248,17 @@
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<version>${pg.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>

View File

@ -42,7 +42,7 @@ public class JDBIConfig {
@Bean
DataSource dataSource(@Value("${spring.datasource.url}") String url, @Value("${spring.datasource.username}") String username, @Value("${spring.datasource.password}") String password) {
HikariDataSource dataSource = DataSourceBuilder.create().type(HikariDataSource.class).url(url).username(username).password(password).driverClassName("org.postgresql.Driver").build();
HikariDataSource dataSource = DataSourceBuilder.create().type(HikariDataSource.class).url(url).username(username).password(password).build();
dataSource.setPoolName(UUID.randomUUID().toString());
return dataSource;
}

View File

@ -52,12 +52,15 @@ public class PermissionsController extends HangarComponent implements IPermissio
private Pair<PermissionType, Permission> getPermissionsInScope(String author, String slug, String organization) {
if (author != null && slug != null && organization == null) { // author & slug
Permission perms = permissionService.getProjectPermissions(getHangarUserId(), author, slug);
perms = getHangarPrincipal().getPossiblePermissions().intersect(perms);
return new ImmutablePair<>(PermissionType.PROJECT, perms);
} else if (author == null && slug == null && organization == null) { // current user (I don't think there's a need to see other user's global permissions)
Permission perms = permissionService.getGlobalPermissions(getHangarUserId());
perms = getHangarPrincipal().getPossiblePermissions().intersect(perms);
return new ImmutablePair<>(PermissionType.GLOBAL, perms);
} else if (author == null && slug == null) { // just org
Permission perms = permissionService.getOrganizationPermissions(getHangarUserId(), organization);
perms = getHangarPrincipal().getPossiblePermissions().intersect(perms);
return new ImmutablePair<>(PermissionType.ORGANIZATION, perms);
} else {
throw new HangarApiException(HttpStatus.BAD_REQUEST, "Incorrect request parameters");

View File

@ -1,15 +1,21 @@
package io.papermc.hangar;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@Disabled // would require a DB on test
@SpringBootTest
import io.papermc.hangar.controller.ApplicationController;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class HangarApplicationTests {
@Autowired
private ApplicationController controller;
@Test
void contextLoads() {
assertThat(this.controller).isNotNull();
}
}

View File

@ -0,0 +1,75 @@
package io.papermc.hangar.controller.api.v1;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import io.papermc.hangar.model.api.auth.ApiSession;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
@SpringBootTest
@AutoConfigureMockMvc
class PermissionsControllerTest {
private static final String all = "a02f13bb-4329-4c85-984a-a817daacedcd.a240400b-bde2-4f4b-a432-ad7f9fb3ee0b";
private static final String seeHidden = "bcbca881-87ba-4136-880d-4b387cb6cf03.14424bbc-9f4c-460e-ae13-cb4c4bae76d2";
private static final String projectOnly = "b28dbeee-e1b6-44db-aa0d-a641208517ea.71c46fb3-47fc-4a12-8421-a121649fcb1d";
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
private String getJwt(String apiKey) throws Exception {
String response = this.mockMvc.perform(post("/api/v1/authenticate?apiKey=" + apiKey)).andReturn().getResponse().getContentAsString();
ApiSession apiSession = objectMapper.readValue(response, ApiSession.class);
return apiSession.getToken();
}
@Test
void testHasAllWithProjectOnly() throws Exception {
this.mockMvc.perform(get("/api/v1/permissions/hasAll?permissions=create_organization&permissions=create_project")
.header("Authorization", "HangarAuth " + getJwt(projectOnly)))
.andExpect(jsonPath("$.result").value(false));
}
@Test
void testHasAllWithAll() throws Exception {
this.mockMvc.perform(get("/api/v1/permissions/hasAll?permissions=create_organization&permissions=create_project")
.header("Authorization", "HangarAuth " + getJwt(all)))
.andExpect(jsonPath("$.result").value(true));
}
@Test
void testHasAnyWithProjectOnly() throws Exception {
this.mockMvc.perform(get("/api/v1/permissions/hasAny?permissions=create_organization&permissions=create_project")
.header("Authorization", "HangarAuth " + getJwt(projectOnly)))
.andExpect(jsonPath("$.result").value(true));
}
@Test
void testHasAnyWithAll() throws Exception {
this.mockMvc.perform(get("/api/v1/permissions/hasAny?permissions=create_organization&permissions=create_project")
.header("Authorization", "HangarAuth " + getJwt(all)))
.andExpect(jsonPath("$.result").value(true));
}
@Test
void testHiddenProjectSeeHidden() throws Exception {
this.mockMvc.perform(get("/api/v1/permissions/?author=paper&slug=Test")
.header("Authorization", "HangarAuth " + getJwt(seeHidden)))
.andExpect(jsonPath("$.permissionBinString").value("10000000000000000000000000"));
}
@Test
void testHiddenProjectProjectOnly() throws Exception {
this.mockMvc.perform(get("/api/v1/permissions/?author=paper&slug=Test")
.header("Authorization", "HangarAuth " + getJwt(projectOnly)))
.andExpect(jsonPath("$.permissionBinString").value("100000000"));
}
}

View File

@ -0,0 +1,20 @@
spring:
datasource:
url: jdbc:tc:postgresql:12.8:///
username: ""
password: ""
flyway:
default-schema: public
schemas: public
locations:
- classpath:db/test_migrations
- classpath:db/migration
- classpath:db/dummy_data
baseline-version: 0.0
fake-user:
enabled: true
name: test
username: test
email: test@papermc.io

View File

@ -0,0 +1,9 @@
INSERT INTO api_keys (id, created_at, name, owner_id, token_identifier, token, raw_key_permissions)
VALUES (1, '2022-07-30 18:14:43.016556 +00:00', 'all', 1, 'a02f13bb-4329-4c85-984a-a817daacedcd', '92453bc498244ba555e4533894cee3c764a4f0a1daec18ca77cb3712dda1d888', '0000000000000000000011110000111100001111001100001111011111110111');
INSERT INTO api_keys (id, created_at, name, owner_id, token_identifier, token, raw_key_permissions)
VALUES (2, '2022-07-30 18:19:52.775045 +00:00', 'onlyProject', 1, 'b28dbeee-e1b6-44db-aa0d-a641208517ea', '1bbdf12a58684e947d020d4a6856fae0025a037428c8d72429bdef3af1177f5b', '0000000000000000000000000000000000000000000000000000000100000000');
INSERT INTO api_keys (id, created_at, name, owner_id, token_identifier, token, raw_key_permissions)
VALUES (3, '2022-07-30 18:42:24.280005 +00:00', 'seeHidden', 1, 'bcbca881-87ba-4136-880d-4b387cb6cf03', '4c903e22e01448afc8ab50becc9d15152ec5131b6d54b24b443b3453fc85e5cb', '0000000000000000000000000000000000000010000000000000000000000000');
INSERT INTO projects (id, created_at, name, slug, owner_name, owner_id, topic_id, post_id, category, description, visibility, keywords, homepage, issues, source, support, license_type, license_name, license_url, forum_sync, donation_enabled, donation_subject, sponsors, wiki)
VALUES (2, '2022-07-26 07:35:56.341943 +00:00', 'Test', 'Test', 'paper', 3, null, null, 0, 'Test', 1, '', null, null, null, null, 'Unspecified', null, null, true, false, null, '', null);