Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved IT and adding ignore group membership to build User #14

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ The meaning of chosen environment variables:
`SPRING_PROFILES_ACTIVE` | (default) `noauth` - authN/Z switched off. `auth` - authN/Z switched on.
`TESK_API_AUTHORISATION_*` | A set of env variables configuring authorisation using Elixir group membership
`TESK_API_SWAGGER_OAUTH_*` | A set of env variables configuring OAuth2/OIDC client built in Swagger UI
`TESK_API_AUTHORISATION_IGNORE_GROUP_MEMBERSHIP` | If `true` a user can see all the tasks he created, irrespective of group memberships


### Generating new API version stub
Expand Down
2 changes: 1 addition & 1 deletion src/integration-test/java/uk/ac/ebi/tsc/tesk/AuthIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
@AutoConfigureMockMvc
@TestPropertySource(locations = {"classpath:application.properties"},
properties = {"security.oauth2.resource.user-info-uri = http://localhost:8090",
"spring.profiles.active=auth"})
"spring.profiles.active=auth","tesk.api.authorisation.ignoreGroupMembership=false"})
public class AuthIT {

@Autowired
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
package uk.ac.ebi.tsc.tesk;

import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.junit.WireMockRule;
import io.kubernetes.client.ApiClient;
import io.kubernetes.client.util.Config;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
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.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.http.MediaType;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static uk.ac.ebi.tsc.tesk.TestUtils.getFileContentFromResources;

/**
* @author Ania Niewielska <[email protected]>
* <p>
* Integration testing of security (authentication and authorisation using OIDC and Elixir groups)
* Kubernetes API and OIDC userInfo endpoint are WireMocked
*/
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
@TestPropertySource(locations = {"classpath:application.properties"},
properties = {"security.oauth2.resource.user-info-uri = http://localhost:8090",
"spring.profiles.active=auth", "tesk.api.authorisation.ignoreGroupMembership=true"})
public class AuthIgnoreGroupMembershipIT {

@Rule
public WireMockRule mockElixir = new WireMockRule(8090);

@Rule
public WireMockRule mockKubernetes = new WireMockRule(wireMockConfig().port(9000).usingFilesUnderDirectory("src/integration-test/resources"));

@Autowired
private MockMvc mvc;

@TestConfiguration
static class KubernetesClientMock {
@Bean
@Primary
public ApiClient kubernetesApiClient() {
return Config.fromUrl("http://localhost:9000", false);
}
}

/**
* Any authenticated user should be able to create a task
*
* @throws Exception
*/
@Test
public void author_not_in_group_createTask() throws Exception {

mockElixir.givenThat(
WireMock.get("/")
.willReturn(okJson("{\"sub\" : \"123\" }")));
mockKubernetes.givenThat(
WireMock.post("/apis/batch/v1/namespaces/default/jobs")
.willReturn(okJson("{\"metadata\":{\"name\":\"task-fe99716a\"}}")));

String path = "fromTesToK8s_minimal/task.json";
this.mvc.perform(post("/v1/tasks")
.content(getFileContentFromResources(path))
.header("Authorization", "Bearer BAR")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}

@Test
public void author_in_a_group_createTask() throws Exception {

mockElixir.givenThat(
WireMock.get("/")
.willReturn(okJson("{\"sub\" : \"123\", " +
" \"eduperson_entitlement\" : [\"urn:geant:elixir-europe.org:group:elixir:GA4GH:GA4GH-CAP:EBI#perun.elixir-czech.cz\", \"urn:geant:elixir-europe.org:group:elixir:GA4GH:GA4GH-CAP:EBI:TEST#perun.elixir-czech.cz\"]}")));

mockKubernetes.givenThat(
WireMock.post("/apis/batch/v1/namespaces/default/jobs")
.withRequestBody(matchingJsonPath("$.metadata.labels[?(@.creator-group-name == 'TEST')]"))
.willReturn(okJson("{\"metadata\":{\"name\":\"task-fe99716a\"}}")));

String path = "fromTesToK8s_minimal/task.json";
this.mvc.perform(post("/v1/tasks")
.content(getFileContentFromResources(path))
.header("Authorization", "Bearer BAR")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}


@Test
public void author_in_a_group_getTask() throws Exception {

mockElixir.givenThat(
WireMock.get("/")
.willReturn(okJson("{\"sub\" : \"123\", \"eduperson_entitlement\" : [\"urn:geant:elixir-europe.org:group:elixir:GA4GH:GA4GH-CAP:EBI#perun.elixir-czech.cz\", \"urn:geant:elixir-europe.org:group:elixir:GA4GH:GA4GH-CAP:EBI:TEST#perun.elixir-czech.cz\"]}")));

MockUtil.mockGetTaskKubernetesResponses(this.mockKubernetes);


this.mvc.perform(get("/v1/tasks/{id}", "task-123")
.header("Authorization", "Bearer BAR"))
.andExpect(status().isOk());
this.mvc.perform(get("/v1/tasks/{id}?view=BASIC", "task-123")
.header("Authorization", "Bearer BAR"))
.andExpect(status().isOk());
this.mvc.perform(get("/v1/tasks/{id}?view=FULL", "task-123")
.header("Authorization", "Bearer BAR"))
.andExpect(status().isOk());
}

@Test
public void author_not_in_a_group_getTask() throws Exception {

mockElixir.givenThat(
WireMock.get("/")
.willReturn(okJson("{\"sub\" : \"123\"}")));

MockUtil.mockGetTaskKubernetesResponses(this.mockKubernetes);


this.mvc.perform(get("/v1/tasks/{id}", "task-123")
.header("Authorization", "Bearer BAR"))
.andExpect(status().isOk());

this.mvc.perform(get("/v1/tasks/{id}?view=BASIC", "task-123")
.header("Authorization", "Bearer BAR"))
.andExpect(status().isOk());
this.mvc.perform(get("/v1/tasks/{id}?view=FULL", "task-123")
.header("Authorization", "Bearer BAR"))
.andExpect(status().isOk());
}

@Test
public void nonAuthor_getTask() throws Exception {

mockElixir.givenThat(
WireMock.get("/")
.willReturn(okJson("{\"sub\" : \"124\", \"eduperson_entitlement\" : [\"urn:geant:elixir-europe.org:group:elixir:GA4GH:GA4GH-CAP:EBI#perun.elixir-czech.cz\", \"urn:geant:elixir-europe.org:group:elixir:GA4GH:GA4GH-CAP:EBI:TEST#perun.elixir-czech.cz\"]}")));

MockUtil.mockGetTaskKubernetesResponses(this.mockKubernetes);

this.mvc.perform(get("/v1/tasks/{id}", "task-123")
.header("Authorization", "Bearer BAR"))
.andExpect(status().isForbidden());
this.mvc.perform(get("/v1/tasks/{id}?view=BASIC", "task-123")
.header("Authorization", "Bearer BAR"))
.andExpect(status().isForbidden());
this.mvc.perform(get("/v1/tasks/{id}?view=FULL", "task-123")
.header("Authorization", "Bearer BAR"))
.andExpect(status().isForbidden());
}

@Test
public void author_not_in_group_getList() throws Exception {

mockElixir.givenThat(
WireMock.get("/")
.willReturn(okJson("{\"sub\" : \"123\"}")));

mockKubernetes.givenThat(
WireMock.get("/apis/batch/v1/namespaces/default/jobs?labelSelector=job-type%3Dtaskmaster" +
"%2Ccreator-user-id%3D123")
.willReturn(aResponse().withBodyFile("list/taskmasters_nogroup.json")));

MockUtil.mockListTaskKubernetesResponses(this.mockKubernetes);

perFormListTask(5);

}



@Test
public void author_in_group_getList() throws Exception {

mockElixir.givenThat(
WireMock.get("/")
.willReturn(okJson("{\"sub\" : \"123\", \"eduperson_entitlement\" : [\"urn:geant:elixir-europe.org:group:elixir:GA4GH:GA4GH-CAP:EBI#perun.elixir-czech.cz\",\"urn:geant:elixir-europe.org:group:elixir:GA4GH:GA4GH-CAP:EBI:TEST#perun.elixir-czech.cz\"]}")));

mockKubernetes.givenThat(
WireMock.get("/apis/batch/v1/namespaces/default/jobs?labelSelector=job-type%3Dtaskmaster%2Ccreator-user-id%3D123")
.willReturn(aResponse().withBodyFile("list/taskmasters_nogroup.json")));
MockUtil.mockListTaskKubernetesResponses(this.mockKubernetes);

this.mvc.perform(get("/v1/tasks")
.header("Authorization", "Bearer BAR"))
.andExpect(status().isOk());

verify(exactly(0), getRequestedFor(urlEqualTo("/apis/batch/v1/namespaces/default/jobs?labelSelector=job-type%3Dtaskmaster" +
"%2Ccreator-group-name%20in%20%28TEST%29%2Ccreator-user-id%3D123")));

perFormListTask(5);

}

@Test
public void unauthenicated_createTask() throws Exception {

String path = "fromTesToK8s_minimal/task.json";
this.mvc.perform(post("/v1/tasks")
.content(getFileContentFromResources(path))
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)).andExpect(status().isUnauthorized());
}

@Test
public void unauthenticated_getTask() throws Exception {
this.mvc.perform(get("/v1/tasks/{id}", 123))
.andExpect(status().isUnauthorized());
}

@Test
public void unauthenticated_getList() throws Exception {

this.mvc.perform(get("/v1/tasks")
.header("Authorization", "different BAR"))
.andExpect(status().isUnauthorized());
this.mvc.perform(get("/v1/tasks?view=BASIC")
.header("Different", "Bearer BAR"))
.andExpect(status().isUnauthorized());
this.mvc.perform(get("/v1/tasks?view=FULL"))
.andExpect(status().isUnauthorized());

}

private void perFormListTask(int expectedTasksLength) throws Exception {

this.mvc.perform(get("/v1/tasks")
.header("Authorization", "Bearer BAR"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.tasks.length()").value(expectedTasksLength));
this.mvc.perform(get("/v1/tasks?view=BASIC")
.header("Authorization", "Bearer BAR"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.tasks.length()").value(expectedTasksLength));
this.mvc.perform(get("/v1/tasks?view=FULL")
.header("Authorization", "Bearer BAR"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.tasks.length()").value(expectedTasksLength));
}


}
2 changes: 1 addition & 1 deletion src/integration-test/java/uk/ac/ebi/tsc/tesk/NoAuthIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
@AutoConfigureMockMvc
@TestPropertySource(locations = {"classpath:application.properties"},
properties = {"security.oauth2.resource.user-info-uri = http://localhost:8090",
"spring.profiles.active=noauth"})
"spring.profiles.active=noauth","tesk.api.authorisation.ignoreGroupMembership=false"})
public class NoAuthIT {

@Autowired
Expand Down
103 changes: 103 additions & 0 deletions src/integration-test/resources/__files/list/taskmasters_nogroup.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
{
"items": [
{
"metadata": {
"labels": {
"creator-group-name": "TEST",
"creator-user-id": "123",
"job-type": "taskmaster"
},
"name": "task-123"
},
"spec": {
"selector": {
"matchLabels": {
"controller-uid": "24a0504a-4a2b-11e8-a06f-fa163ecf0042"
}
}
},
"status": {
"succeeded": 1
}
},
{
"metadata": {
"labels": {
"creator-user-id": "123",
"job-type": "taskmaster"
},
"name": "task-124"
},
"spec": {
"selector": {
"matchLabels": {
"controller-uid": "24a0504a-4a2b-11e8-a06f-fa163ecf0043"
}
}
},
"status": {
"succeeded": 1
}
},
{
"metadata": {
"labels": {
"creator-group-name": "ABC",
"creator-user-id": "123",
"job-type": "taskmaster"
},
"name": "task-125"
},
"spec": {
"selector": {
"matchLabels": {
"controller-uid": "24a0504a-4a2b-11e8-a06f-fa163ecf0044"
}
}
},
"status": {
"succeeded": 1
}
},
{
"metadata": {
"labels": {
"creator-group-name": "ABC",
"creator-user-id": "124",
"job-type": "taskmaster"
},
"name": "task-126"
},
"spec": {
"selector": {
"matchLabels": {
"controller-uid": "24a0504a-4a2b-11e8-a06f-fa163ecf0045"
}
}
},
"status": {
"succeeded": 1
}
},
{
"metadata": {
"labels": {
"creator-user-id": "123",
"job-type": "taskmaster"
},
"name": "task-127"
},
"spec": {
"selector": {
"matchLabels": {
"controller-uid": "24a0504a-4a2b-11e8-a06f-fa163ecf0045"
}
}
},
"status": {
"succeeded": 1
}
}
],
"metadata": {}
}
Loading