Skip to content
Snippets Groups Projects
Commit d2666fc1 authored by OZGCloud's avatar OZGCloud
Browse files

OZG-4939 added PR13 comments

parent 3025b05b
Branches
Tags
No related merge requests found
Showing
with 80 additions and 111 deletions
......@@ -119,11 +119,6 @@
<version>${keycloak-admin-client.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<profiles>
<profile>
......
......@@ -25,11 +25,9 @@ import java.util.Map;
import jakarta.validation.ConstraintViolationException;
import org.apache.http.HttpHeaders;
import org.springframework.data.rest.webmvc.ResourceNotFoundException;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.web.ErrorResponse;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
......@@ -62,11 +60,4 @@ public class AdminExceptionHandler extends ResponseEntityExceptionHandler {
return ErrorResponse.builder(ex, status, ex.getLocalizedMessage()).build();
}
@ExceptionHandler(InsufficientAuthenticationException.class)
public ErrorResponse handleInsufficientException(InsufficientAuthenticationException ex) {
return ErrorResponse
.builder(ex, HttpStatus.UNAUTHORIZED, ex.getLocalizedMessage())
.header(HttpHeaders.WWW_AUTHENTICATE, "Bearer realm=\"Restricted Content\"")
.build();
}
}
......@@ -43,7 +43,7 @@ import lombok.RequiredArgsConstructor;
@Component
@RequiredArgsConstructor
public class AdminAuthenticationEntryPoint implements AuthenticationEntryPoint {
class AdminAuthenticationEntryPoint implements AuthenticationEntryPoint {
private final List<HttpMessageConverter<?>> converters;
......@@ -51,11 +51,11 @@ public class AdminAuthenticationEntryPoint implements AuthenticationEntryPoint {
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
throws IOException, ServletException {
ServerResponse
.from(errorResponse(authException, request.getRequestURI()))
.from(buildErrorResponse(authException, request.getRequestURI()))
.writeTo(request, response, () -> converters);
}
ErrorResponse errorResponse(AuthenticationException ex, String requestUri) {
ErrorResponse buildErrorResponse(AuthenticationException ex, String requestUri) {
return ErrorResponse
.builder(ex, HttpStatus.UNAUTHORIZED, ex.getLocalizedMessage())
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_PROBLEM_JSON_VALUE)
......
......@@ -28,7 +28,6 @@ import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.core.oidc.StandardClaimNames;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
......@@ -47,13 +46,9 @@ public class SecurityConfiguration {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// Configure a resource server with JWT decoder
http.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));
// State-less session (state in access-token only)
http.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
// Disable CSRF because of state-less session-management
http.csrf(AbstractHttpConfigurer::disable);
http.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
http.exceptionHandling(eh -> eh.authenticationEntryPoint(authenticationEntryPoint));
......
ozgcloud:
production: false
keycloak:
realm: by-kiel-dev
auth-server-url: https://sso.dev.by.ozg-cloud.de
\ No newline at end of file
keycloak:
realm: by-kiel-dev
auth-server-url: https://sso.dev.by.ozg-cloud.de
\ No newline at end of file
......@@ -54,7 +54,6 @@ spring:
authentication-database: admin
rest:
basePath: /api/configuration
cloud:
config:
server:
......@@ -63,4 +62,9 @@ spring:
oauth2:
resourceserver:
jwt:
issuer-uri: https://sso.dev.by.ozg-cloud.de/realms/by-kiel-dev
\ No newline at end of file
issuer-uri: ${ozgcloud.oauth2.issuer-uri}
ozgcloud:
oauth2:
auth-server-url: ${keycloak.auth-server-url}
realm: ${keycloak.realm}
issuer-uri: ${ozgcloud.oauth2.auth-server-url}/realms/${ozgcloud.oauth2.realm}
\ No newline at end of file
......@@ -22,6 +22,7 @@
package de.ozgcloud.admin.security;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import java.net.URI;
......@@ -48,6 +49,7 @@ import org.springframework.http.ProblemDetail;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.ErrorResponse;
import org.springframework.web.servlet.function.ServerResponse;
import org.springframework.web.servlet.function.ServerResponse.Context;
import lombok.SneakyThrows;
......@@ -61,28 +63,27 @@ public class AdminAuthenticationEntryPointTest {
private AdminAuthenticationEntryPoint authenticationEntryPoint;
@Mock
List<HttpMessageConverter<?>> converters;
private List<HttpMessageConverter<?>> converters;
@Mock
private HttpServletRequest request;
@DisplayName("consume")
@Nested
class TestConsume {
@Mock
private HttpServletResponse response;
@Mock
private ServerResponse serverResponse;
@DisplayName("consume")
@Nested
class TestConsume {
@BeforeEach
void initMock() {
when(request.getRequestURI()).thenReturn(REQUEST_URI);
}
@Captor
ArgumentCaptor<ServerResponse.Context> contextCaptor;
ArgumentCaptor<Context> contextCaptor;
@DisplayName("call writeTo")
@SneakyThrows
......@@ -102,71 +103,63 @@ public class AdminAuthenticationEntryPointTest {
@DisplayName("error response")
@Nested
class TestErrorResponse {
class TestBuildErrorResponse {
@DisplayName("body")
@Nested
class TestBody {
@DisplayName("have instance")
@Test
void shouldHaveInstance() {
var problemDetail = body();
var problemDetail = getErrorResponseBody();
assertThat(problemDetail.getInstance()).isEqualTo(URI.create(REQUEST_URI));
}
@DisplayName("have detail")
@Test
void shouldHaveDetail() {
var problemDetail = body();
var problemDetail = getErrorResponseBody();
assertThat(problemDetail.getDetail()).isEqualTo(AuthenticationExceptionTestFactory.AUTH_MESSAGE);
}
private ProblemDetail body() {
return serverErrorResponse().getBody();
private ProblemDetail getErrorResponseBody() {
return getServerErrorResponse().getBody();
}
}
@DisplayName("headers")
@Nested
class TestHeaders {
@DisplayName("have content type")
@Test
void shouldHaveContentType() {
var headers = httpHeaders();
var headers = getHttpHeaders();
assertThat(headers.getContentType()).isEqualTo(MediaType.APPLICATION_PROBLEM_JSON);
}
@DisplayName("have www authentication")
@Test
void shouldHaveWwwAuthentication() {
var headers = httpHeaders();
var headers = getHttpHeaders();
assertThat(headers.getFirst(HttpHeaders.WWW_AUTHENTICATE)).isEqualTo("Bearer realm=\"Restricted Content\"");
}
private HttpHeaders httpHeaders() {
return serverErrorResponse().getHeaders();
private HttpHeaders getHttpHeaders() {
return getServerErrorResponse().getHeaders();
}
}
@DisplayName("have status")
@Test
void shouldHaveStatus() {
var response = serverErrorResponse();
var response = getServerErrorResponse();
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
}
private ErrorResponse serverErrorResponse() {
return authenticationEntryPoint.errorResponse(
private ErrorResponse getServerErrorResponse() {
return authenticationEntryPoint.buildErrorResponse(
AuthenticationExceptionTestFactory.create(),
REQUEST_URI
);
REQUEST_URI);
}
}
......
......@@ -40,10 +40,10 @@ public class AuthenticationExceptionTestFactory {
}
public static DummyAuthenticationException create() {
return builder().msg(AUTH_MESSAGE).build();
return builder().build();
}
public static DummyAuthenticationException.DummyAuthenticationExceptionBuilder builder() {
return DummyAuthenticationException.builder();
return DummyAuthenticationException.builder().msg(AUTH_MESSAGE);
}
}
......@@ -21,12 +21,9 @@
*/
package de.ozgcloud.admin.security;
import static org.junit.jupiter.api.Assertions.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import java.net.URI;
import org.apache.http.HttpHeaders;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
......@@ -36,23 +33,15 @@ import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.web.ErrorResponse;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.ozgcloud.common.test.DataITCase;
import lombok.SneakyThrows;
@DataITCase
@AutoConfigureMockMvc
public class SecurityConfigurationLocalITCase {
class SecurityConfigurationITCase {
@Autowired
private MockMvc mockMvc;
......@@ -121,30 +110,16 @@ public class SecurityConfigurationLocalITCase {
@Test
@SneakyThrows
void shouldHaveErrorInfoInBody() {
var expected = getExpectedProblemDetailsAsString("/api");
void shouldHaveErrorDetailInBody() {
var result = doPerform("/api");
result.andExpect(content().string(expected));
}
result.andExpect(jsonPath("$.detail").value("Full authentication is required to access this resource"));
private String getExpectedProblemDetailsAsString(String requestUri) throws JsonProcessingException {
var ex = new AuthenticationException("Full authentication is required to access this resource") {
};
var problemDetail = ErrorResponse
.builder(ex, HttpStatus.UNAUTHORIZED, ex.getLocalizedMessage())
.instance(URI.create(requestUri))
.build()
.getBody();
var objectMapper = new ObjectMapper().setSerializationInclusion(Include.NON_NULL);
return objectMapper.writeValueAsString(problemDetail);
}
@Test
@SneakyThrows
void shouldHaveWWW_AUTHENTICATEHeader2() {
void shouldHaveHeader() {
var result = doPerform("/api");
result.andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, "Bearer realm=\"Restricted Content\""));
......@@ -183,13 +158,7 @@ public class SecurityConfigurationLocalITCase {
@SneakyThrows
private ResultActions doPerformAuthenticated(String path) {
var auth = authentication();
assertTrue(auth.isAuthenticated());
return mockMvc.perform(get(path));
}
}
private Authentication authentication() {
return SecurityContextHolder.getContext().getAuthentication();
}
}
......@@ -25,6 +25,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import java.net.URI;
import java.util.Collections;
import java.util.Map;
import org.apache.http.client.utils.URIBuilder;
import org.junit.jupiter.api.AfterAll;
......@@ -33,7 +34,6 @@ import org.junit.jupiter.api.Nested;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.json.JacksonJsonParser;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.http.MediaType;
import org.springframework.test.context.DynamicPropertyRegistry;
......@@ -41,8 +41,7 @@ import org.springframework.test.context.DynamicPropertySource;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.client.RestClient;
import dasniko.testcontainers.keycloak.KeycloakContainer;
import de.ozgcloud.admin.RootController;
......@@ -94,9 +93,9 @@ class SecurityConfigurationWithKeycloakITCase {
String getToken() {
MultiValueMap<String, String> formData = setPostBodyForToken();
String result = performPostRequestToKeycloak(formData);
Map<String, String> resultBody = performPostRequestToKeycloak(formData);
return extractTokenFromResponse(result);
return "Bearer " + resultBody.get("access_token").toString();
}
MultiValueMap<String, String> setPostBodyForToken() {
......@@ -108,24 +107,17 @@ class SecurityConfigurationWithKeycloakITCase {
return formData;
}
@SuppressWarnings("unchecked")
@SneakyThrows
String performPostRequestToKeycloak(MultiValueMap<String, String> formData) {
Map<String, String> performPostRequestToKeycloak(MultiValueMap<String, String> formData) {
RestClient restClient = RestClient.create();
URI authorizationURI = new URIBuilder(keycloak.getAuthServerUrl() + "/realms/by-kiel-dev/protocol/openid-connect/token").build();
WebClient webclient = WebClient.builder().build();
return webclient.post()
.uri(authorizationURI)
var response = restClient.post().uri(authorizationURI)
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.body(BodyInserters.fromFormData(formData))
.retrieve()
.bodyToMono(String.class)
.block();
}
.body(formData);
return response.retrieve().body(Map.class);
String extractTokenFromResponse(String result) {
JacksonJsonParser jsonParser = new JacksonJsonParser();
return "Bearer " + jsonParser.parseMap(result)
.get("access_token")
.toString();
}
}
}
\ No newline at end of file
management:
server:
port: 8081
spring:
application:
name: OzgCloud_Administration
data:
mongodb:
authentication-database: admin
rest:
basePath: /api/configuration
cloud:
config:
server:
prefix: /configserver
security:
oauth2:
resourceserver:
jwt:
issuer-uri: ${keycloak.auth-server-url}/realms/${keycloak.realm}
keycloak:
auth-server-url: https://sso.dev.by.ozg-cloud.de
realm: by-kiel-dev
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment