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

TSPS-191 add b2c authentication #106

Merged
merged 18 commits into from
Aug 20, 2024
Merged
Show file tree
Hide file tree
Changes from 16 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
11 changes: 10 additions & 1 deletion common/openapi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -777,10 +777,19 @@ components:
format: string

securitySchemes:
oidc:
type: oauth2
x-tokenName: id_token
flows:
authorizationCode:
authorizationUrl: "[[${authorityEndpoint}]]"
scopes: { }
tokenUrl: "[[${tokenEndpoint}]]"
bearerAuth:
type: http
scheme: bearer


security:
- oidc:
- openid
- bearerAuth: [ ]
2 changes: 1 addition & 1 deletion service/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ dependencies {
implementation "bio.terra:terra-common-lib" // 1.1.11-SNAPSHOT is defined in java-common-conventions

// terra clients
implementation "org.broadinstitute.dsde.workbench:sam-client_2.13:0.1-5a5bf28"
implementation "org.broadinstitute.dsde.workbench:sam-client_2.13:v0.0.213"
implementation "org.broadinstitute.dsde.workbench:leonardo-client_2.13:1.3.6-22ee00b"
implementation "org.databiosphere:workspacedataservice-client-okhttp-jakarta:0.2.115-20240227.195850-1"
implementation "bio.terra:cbas-client:0.0.199"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package bio.terra.pipelines.app.configuration.internal;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties("oidc")
public record OidcConfiguration(String clientId, String authorityEndpoint, String tokenEndpoint) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package bio.terra.pipelines.app.configuration.internal;

import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver;

@Component
public class TemplateResolvers {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move this to an app/components/ directory


/**
* This bean is used to resolve the location of the Thymeleaf templates that are used to generate
* the OpenAPI documentation, i.e. static/openapi.yml as referenced in templates/index.html. On
* the other hand, the default resolver is used to resolve the location of the Swagger UI
* index.html file in templates/.
*/
@Bean
public ClassLoaderTemplateResolver secondaryTemplateResolver() {
ClassLoaderTemplateResolver secondaryTemplateResolver = new ClassLoaderTemplateResolver();
secondaryTemplateResolver.setPrefix("static/");
secondaryTemplateResolver.setSuffix(".yml");
secondaryTemplateResolver.setTemplateMode(TemplateMode.TEXT);
secondaryTemplateResolver.setCharacterEncoding("UTF-8");
secondaryTemplateResolver.setOrder(1);
secondaryTemplateResolver.setCheckExistence(true);

return secondaryTemplateResolver;
}
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,31 @@
package bio.terra.pipelines.app.controller;

import bio.terra.pipelines.app.configuration.internal.OidcConfiguration;
import bio.terra.pipelines.generated.api.PublicApi;
import bio.terra.pipelines.generated.model.ApiVersionProperties;
import bio.terra.pipelines.service.StatusService;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class PublicApiController implements PublicApi {
private final ApiVersionProperties versionProperties;
private final StatusService statusService;
private final OidcConfiguration oidcConfiguration;

@Autowired
public PublicApiController(ApiVersionProperties versionProperties, StatusService statusService) {
public PublicApiController(
ApiVersionProperties versionProperties,
StatusService statusService,
OidcConfiguration oidcConfiguration) {
this.versionProperties = versionProperties;
this.statusService = statusService;
this.oidcConfiguration = oidcConfiguration;
new ApiVersionProperties()
.build(versionProperties.getBuild())
.gitHash(versionProperties.getGitHash())
Expand All @@ -42,4 +51,22 @@ public ResponseEntity<ApiVersionProperties> getVersion() {
.github(versionProperties.getGithub())
.gitTag(versionProperties.getGitTag()));
}

@GetMapping(value = "/")
public String index() {
return "redirect:/swagger-ui.html";
}

@GetMapping({"/index.html", "/swagger-ui.html"})
public String getSwagger(Model model) {
model.addAttribute("clientId", oidcConfiguration.clientId());
return "index";
}

@GetMapping(value = "/openapi.yml")
public String getOpenApiYaml(Model model, HttpServletResponse response) {
model.addAttribute("authorityEndpoint", oidcConfiguration.authorityEndpoint());
model.addAttribute("tokenEndpoint", oidcConfiguration.tokenEndpoint());
return "openapi";
}
}

This file was deleted.

9 changes: 9 additions & 0 deletions service/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ env:
leonardo: ${LEONARDO_ADDRESS:https://leonardo.dsde-dev.broadinstitute.org}
workspacemanager: ${WORKSPACE_MANAGER_ADDRESS:https://workspace.dsde-dev.broadinstitute.org}
rawls: ${RAWLS_ADDRESS:https://rawls.dsde-dev.broadinstitute.org}
oidc:
clientId: ${OIDC_CLIENT_ID:bbd07d43-01cb-4b69-8fd0-5746d9a5c9fe}
authorityEndpoint: ${OIDC_AUTHORITY_ENDPOINT:https://terradevb2c.b2clogin.com/terradevb2c.onmicrosoft.com/oauth2/v2.0/authorize?p=b2c_1a_signup_signin_dev}
tokenEndpoint: ${OIDC_TOKEN_ENDPOINT:https://terradevb2c.b2clogin.com/terradevb2c.onmicrosoft.com/oauth2/v2.0/token?p=b2c_1a_signup_signin_dev}
ingress:
domainName: ${TSPS_INGRESS_DOMAIN_NAME:localhost:8080}
kubernetes:
Expand Down Expand Up @@ -49,6 +53,11 @@ logging:
# org:
# hibernate: DEBUG

oidc:
clientId: ${env.oidc.clientId}
authorityEndpoint: ${env.oidc.authorityEndpoint}
tokenEndpoint: ${env.oidc.tokenEndpoint}

server:
compression:
enabled: true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,9 @@

ui.initOAuth({
clientId: [[${clientId}]],
scopes: "openid email profile"
scopes: "openid",
additionalQueryStringParams: {prompt: "login"},
usePkceWithAuthorizationCodeGrant: true
})

window.ui = ui
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package bio.terra.pipelines.configuration.internal;

import static org.junit.jupiter.api.Assertions.assertEquals;

import bio.terra.pipelines.app.configuration.internal.OidcConfiguration;
import bio.terra.pipelines.testutils.BaseEmbeddedDbTest;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;

class OidcConfigurationTest extends BaseEmbeddedDbTest {

@Autowired private OidcConfiguration oidcConfiguration;

@Test
void testOidcConfiguration() {
assertEquals("test_client_id", oidcConfiguration.clientId());
assertEquals("test_authority_endpoint", oidcConfiguration.authorityEndpoint());
assertEquals("test_token_endpoint", oidcConfiguration.tokenEndpoint());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package bio.terra.pipelines.configuration.internal;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import bio.terra.pipelines.app.configuration.internal.TemplateResolvers;
import bio.terra.pipelines.testutils.BaseEmbeddedDbTest;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver;

class TemplateResolversTest extends BaseEmbeddedDbTest {

@Autowired TemplateResolvers templateResolvers;

@Test
void secondaryTemplateResolver() {
ClassLoaderTemplateResolver secondaryTemplateResolver =
templateResolvers.secondaryTemplateResolver();
assertNotNull(secondaryTemplateResolver);
assertEquals("static/", secondaryTemplateResolver.getPrefix());
assertEquals(".yml", secondaryTemplateResolver.getSuffix());
assertEquals(TemplateMode.TEXT, secondaryTemplateResolver.getTemplateMode());
assertEquals("UTF-8", secondaryTemplateResolver.getCharacterEncoding());
assertEquals(1, secondaryTemplateResolver.getOrder());
assertTrue(secondaryTemplateResolver.getCheckExistence());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import bio.terra.pipelines.app.configuration.internal.OidcConfiguration;
import bio.terra.pipelines.app.configuration.internal.TemplateResolvers;
import bio.terra.pipelines.app.controller.PublicApiController;
import bio.terra.pipelines.generated.model.ApiVersionProperties;
import bio.terra.pipelines.service.StatusService;
import bio.terra.pipelines.testutils.BaseTest;
import java.util.Set;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
Expand All @@ -19,14 +24,22 @@
import org.springframework.test.web.servlet.MockMvc;

@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = PublicApiController.class)
@ContextConfiguration(classes = {PublicApiController.class, TemplateResolvers.class})
@WebMvcTest
class PublicApiControllerTest extends BaseTest {

@Autowired private MockMvc mockMvc;

@MockBean private ApiVersionProperties versionProperties;
@MockBean private StatusService statusService;
@MockBean private OidcConfiguration oidcConfiguration;

@BeforeEach
void setup() {
when(oidcConfiguration.clientId()).thenReturn("test_client_id");
when(oidcConfiguration.authorityEndpoint()).thenReturn("test_authority_endpoint");
when(oidcConfiguration.tokenEndpoint()).thenReturn("test_token_endpoint");
}

@Test
void testStatus() throws Exception {
Expand Down Expand Up @@ -60,4 +73,29 @@ void testVersion() throws Exception {
.andExpect(jsonPath("$.github").value(github))
.andExpect(jsonPath("$.build").value(build));
}

@Test
void testIndex() throws Exception {
this.mockMvc.perform(get("/")).andExpect(status().is3xxRedirection());
}

@Test
void testGetSwagger() throws Exception {
var swaggerPaths = Set.of("/index.html", "/swagger-ui.html");
for (var path : swaggerPaths) {
this.mockMvc
.perform(get(path))
.andExpect(status().isOk())
.andExpect(model().attributeExists("clientId"));
}
}

@Test
void testGetOpenApiYaml() throws Exception {
this.mockMvc
.perform(get("/openapi.yml"))
.andExpect(status().isOk())
.andExpect(model().attributeExists("authorityEndpoint"))
.andExpect(model().attributeExists("tokenEndpoint"));
}
}

This file was deleted.

6 changes: 5 additions & 1 deletion service/src/test/resources/application-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ spring:
liquibase:
contexts: test

oidc:
clientId: "test_client_id"
authorityEndpoint: "test_authority_endpoint"
tokenEndpoint: "test_token_endpoint"

# this is used to make sure zonky embedded databases are used for teaspoons and stairway datasources
datasource:
testWithEmbeddedDatabase: true
Expand Down Expand Up @@ -56,7 +61,6 @@ rawls:
baseUri: "https://test_rawls_url/"
debugApiLogging: true


pipelines:
ingress:
domainName: "some-teaspoons-domain.com"
Expand Down
Loading