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

OZG-5176 Cleaned security Tests

parent a3337fdf
Branches
Tags
No related merge requests found
...@@ -31,6 +31,7 @@ public class AuthenticationExceptionTestFactory { ...@@ -31,6 +31,7 @@ public class AuthenticationExceptionTestFactory {
@Builder @Builder
public static class DummyAuthenticationException extends AuthenticationException { public static class DummyAuthenticationException extends AuthenticationException {
@SuppressWarnings("unused")
private String msg; private String msg;
DummyAuthenticationException(String msg) { DummyAuthenticationException(String msg) {
......
/*
* Copyright
* (C) 2024 Das Land Schleswig-Holstein vertreten durch das
* Minis
* erium für Energiewende, Klimaschutz, Umwelt und Natur Zentrales
* IT-Management
*
* Lizenziert unter der EUPL, Version 1.2 oder - sobald diese von der
* Europäischen Kommission genehmigt wurden - Folgeversionen der EUPL
*
*
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
*
* Sofern nicht durch anwendbare Rechtsvorschriften gefordert oder in
* schriftlicher Form vereinbart, wird die unter der Lizenz verbreitete Software
* "so wie sie ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
* ausdrücklich oder stillschweigend - verbreitet. Die sprachspezifischen
* Genehmigungen und Beschränkungen unter der Lizenz sind dem Lizenztext zu
* entnehmen.
*/
package de.ozgcloud.admin.security;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
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;
import org.junit.jupiter.api.BeforeAll;
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.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.http.MediaType;
import org.springframework.test.context.DynamicPropertyRegistry;
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.client.RestClient;
import dasniko.testcontainers.keycloak.KeycloakContainer;
import de.ozgcloud.admin.RootController;
import de.ozgcloud.common.test.DataITCase;
import lombok.SneakyThrows;
@DataITCase
@AutoConfigureMockMvc
class SecurityConfigurationWithKeycloakITCase {
@Autowired
private MockMvc mockMvc;
static KeycloakContainer keycloak;
@BeforeAll
static void setupKeycloakContainer() {
keycloak = new KeycloakContainer().withRealmImportFile("keycloak/realm-export.json");
keycloak.start();
}
@AfterAll
static void closeKeycloakContainer() {
keycloak.close();
}
@DynamicPropertySource
static void registerResourceServerIssuerProperty(DynamicPropertyRegistry registry) {
registry.add("spring.security.oauth2.resourceserver.jwt.issuer-uri", () -> keycloak.getAuthServerUrl() + "/realms/by-kiel-dev");
}
@Nested
class TestSecuredEndpointWithKeycloakToken {
@SneakyThrows
@ParameterizedTest
@ValueSource(strings = {
"/api/environment",
"/configserver/name/profile",
"/api", "/api/configuration", "/api/configuration/param",
})
void shouldGetAccessWithToken() {
String token = getToken();
var result = mockMvc.perform(get(RootController.PATH).header("Authorization", token));
result.andExpect(status().isOk());
}
@SneakyThrows
String getToken() {
MultiValueMap<String, String> formData = setPostBodyForToken();
Map<String, String> resultBody = performPostRequestToKeycloak(formData);
return "Bearer " + resultBody.get("access_token").toString();
}
MultiValueMap<String, String> setPostBodyForToken() {
MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
formData.put("grant_type", Collections.singletonList("password"));
formData.put("client_id", Collections.singletonList("admin"));
formData.put("username", Collections.singletonList("admin-test"));
formData.put("password", Collections.singletonList("Password"));
return formData;
}
@SuppressWarnings("unchecked")
@SneakyThrows
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();
var response = restClient.post().uri(authorizationURI)
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.body(formData);
return response.retrieve().body(Map.class);
}
}
}
\ No newline at end of file
/*
* Copyright (c) 2024. Das Land Schleswig-Holstein vertreten durch das Ministerium für Energiewende, Klimaschutz, Umwelt und Natur
* Zentrales IT-Management
*
* Lizenziert unter der EUPL, Version 1.2 oder - sobald
* diese von der Europäischen Kommission genehmigt wurden -
* Folgeversionen der EUPL ("Lizenz");
* Sie dürfen dieses Werk ausschließlich gemäß
* dieser Lizenz nutzen.
* Eine Kopie der Lizenz finden Sie hier:
*
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
*
* Sofern nicht durch anwendbare Rechtsvorschriften
* gefordert oder in schriftlicher Form vereinbart, wird
* die unter der Lizenz verbreitete Software "so wie sie
* ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
* ausdrücklich oder stillschweigend - verbreitet.
* Die sprachspezifischen Genehmigungen und Beschränkungen
* unter der Lizenz sind dem Lizenztext zu entnehmen.
*/
package de.ozgcloud.admin.security;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.time.Instant;
import java.util.Map;
import java.util.Optional;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.test.context.support.WithSecurityContext;
import org.springframework.security.test.context.support.WithSecurityContextFactory;
import org.springframework.util.StringUtils;
import com.nimbusds.jwt.JWTClaimNames;
import lombok.RequiredArgsConstructor;
import net.minidev.json.JSONObject;
import net.minidev.json.parser.JSONParser;
import net.minidev.json.parser.ParseException;
/**
* Annotation to setup test {@link SecurityContext} with an {@link Authentication}. Adjusted from source:
* com.c4_soft.springaddons.security.oauth2.test.annotations.WithJwt Author: Jérôme Wacongne &lt;ch4mp&#64;c4-soft.com&gt;
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@WithSecurityContext(factory = WithJwt.AuthenticationFactory.class)
public @interface WithJwt {
String value() default "";
String bearerString() default AuthenticationFactory.DEFAULT_BEARER;
String headers() default AuthenticationFactory.DEFAULT_HEADERS;
@RequiredArgsConstructor
final class AuthenticationFactory implements WithSecurityContextFactory<WithJwt> {
static final String DEFAULT_BEARER = "test.jwt.bearer";
static final String DEFAULT_HEADERS = "{\"alg\": \"none\"}";
private final Converter<Jwt, ? extends AbstractAuthenticationToken> jwtAuthenticationConverter;
@Override
public SecurityContext createSecurityContext(WithJwt annotation) {
var auth = authentication(annotation);
var securityContext = SecurityContextHolder.createEmptyContext();
securityContext.setAuthentication(auth);
return securityContext;
}
private AbstractAuthenticationToken authentication(WithJwt annotation) {
var claims = parseJson(annotation.value());
var headers = parseJson(annotation.headers());
var bearerString = annotation.bearerString();
var now = Instant.now();
var iat = Optional.ofNullable((Integer) claims.get(JWTClaimNames.ISSUED_AT)).map(Instant::ofEpochSecond).orElse(now);
var exp = Optional.ofNullable((Integer) claims.get(JWTClaimNames.EXPIRATION_TIME)).map(Instant::ofEpochSecond)
.orElse(now.plusSeconds(42));
var jwt = new Jwt(bearerString, iat, exp, headers, claims);
return jwtAuthenticationConverter.convert(jwt);
}
private static Map<String, Object> parseJson(String json) {
if (!StringUtils.hasText(json)) {
return Map.of();
}
try {
return new JSONParser(JSONParser.MODE_PERMISSIVE).parse(json, JSONObject.class);
} catch (final ParseException e) {
throw new RuntimeException("Invalid JSON payload in @WithJwt");
}
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment