Skip to content

Commit

Permalink
Merge pull request #703 from it-at-m/553-authenticationhandler-in-wls…
Browse files Browse the repository at this point in the history
…-common-auslagern

Handler um Informationen aus Authentication zu ziehen in wls-common erstellt
  • Loading branch information
MrSebastian authored Jan 13, 2025
2 parents 4a46bb3 + 827bd01 commit 62c1fdd
Show file tree
Hide file tree
Showing 9 changed files with 312 additions and 0 deletions.
8 changes: 8 additions & 0 deletions wls-common/security/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@
public interface Profiles {
// Test Profiles
String NO_BEZIRKS_ID_CHECK = "dummy.nobezirkid.check";

String NO_SECURITY = "no-security";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package de.muenchen.oss.wahllokalsystem.wls.common.security.authentication;

import de.muenchen.oss.wahllokalsystem.wls.common.security.Profiles;
import java.util.Optional;
import org.springframework.context.annotation.Profile;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

@Component
@Profile(Profiles.NO_SECURITY)
public class AnonymousDetailRetriever implements AuthDetailRetriever {

@Override
public boolean canHandle(Authentication authentication) {
Assert.notNull(authentication, "Authentication must not be null");
return authentication instanceof AnonymousAuthenticationToken;
}

@Override
public Optional<String> getDetail(String detailKey, Authentication authentication) {
Assert.notNull(authentication, "Authentication must not be null");
Assert.notNull(detailKey, "detailKey must not be null");
return Optional.empty();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package de.muenchen.oss.wahllokalsystem.wls.common.security.authentication;

import java.util.Optional;
import org.springframework.security.core.Authentication;

public interface AuthDetailRetriever {

/**
* @throws IllegalArgumentException when authentication is null
*/
boolean canHandle(Authentication authentication);

/**
* @throws IllegalArgumentException when any parameter is null
*/
Optional<String> getDetail(String detailKey, Authentication authentication);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package de.muenchen.oss.wahllokalsystem.wls.common.security.authentication;

import java.util.Optional;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

@Component
public class JWTDetailRetriever implements AuthDetailRetriever {

@Override
public boolean canHandle(final Authentication authentication) {
Assert.notNull(authentication, "authentication must not be null");
return authentication instanceof JwtAuthenticationToken;
}

public Optional<String> getDetail(final String detailKey, final Authentication authentication) {
Assert.notNull(authentication, "authentication must not be null");
Assert.notNull(detailKey, "detailKey must not be null");
if (authentication instanceof JwtAuthenticationToken jwtToken) {
return Optional.ofNullable(jwtToken.getToken().getClaimAsString(detailKey));
} else {
return Optional.empty();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
@NonNullApi
package de.muenchen.oss.wahllokalsystem.wls.common.security.authentication;

import org.springframework.lang.NonNullApi;
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package de.muenchen.oss.wahllokalsystem.wls.common.security;

import de.muenchen.oss.wahllokalsystem.wls.common.security.authentication.AnonymousDetailRetriever;
import de.muenchen.oss.wahllokalsystem.wls.common.security.authentication.AuthDetailRetriever;
import de.muenchen.oss.wahllokalsystem.wls.common.security.authentication.JWTDetailRetriever;
import java.util.Collection;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -38,10 +42,38 @@ class NoSpecialProfile {
@Autowired
private OAuth2TokenInterceptor oAuth2TokenInterceptor;

@Autowired
private Collection<AuthDetailRetriever> authDetailRetrievers;

@Test
void should_haveImplementationWithChecksInContext_when_noAdditionalProfilesAreActive() {
Assertions.assertThat(permissionEvaluator).isExactlyInstanceOf(BezirkIDPermissionEvaluatorImpl.class);
}

@Test
void should_findOnlyJwtHandlerAsAuthenticationHandler_when_contextIsInitalized() {
Assertions.assertThat(authDetailRetrievers).hasSize(1);
Assertions.assertThat(authDetailRetrievers).allMatch(handler -> handler instanceof JWTDetailRetriever);
}
}

@SpringBootTest(
properties = { "app.crypto.key = 770A8A65DA156D24EE2A093277530142", "service.info.oid=My app name" }
)
@ActiveProfiles(Profiles.NO_SECURITY)
@Nested
class NoSecurityProfile {

@Autowired
private Collection<AuthDetailRetriever> authDetailRetrievers;

@Test
void should_findJwtAndAnonymousHandler_when_contextIsInitialized() {
Assertions.assertThat(authDetailRetrievers).hasSize(2);
Assertions.assertThat(
authDetailRetrievers).allMatch(handler -> handler instanceof JWTDetailRetriever || handler instanceof AnonymousDetailRetriever);
}

}

@SpringBootApplication(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package de.muenchen.oss.wahllokalsystem.wls.common.security.authentication;

import java.util.List;
import lombok.val;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;

class AnonymousDetailRetrieverTest {

private final AnonymousDetailRetriever unitUnderTest = new AnonymousDetailRetriever();

@Nested
class CanHandle {

@Test
void should_throwIllegalArgumentException_when_authenticationIsNull() {
Assertions.assertThatThrownBy(() -> unitUnderTest.canHandle(null)).isInstanceOf(IllegalArgumentException.class);
}

@Test
void should_returnTrue_when_authenticationIsAnonymousAuthenticationToken() {
Assertions.assertThat(unitUnderTest.canHandle(new AnonymousAuthenticationToken("key", "principal", List.of(new SimpleGrantedAuthority("role")))))
.isTrue();
}

@Test
void should_returnTrue_when_authenticationSubclassOfJwtAuthenticationToken() {
Assertions.assertThat(unitUnderTest.canHandle(new AnonymousAuthenticationToken("key", "principal", List.of(new SimpleGrantedAuthority("role"))) {
})).isTrue();
}

@Test
void should_returnFalse_when_authenticationIsNotJwtAuthenticationToken() {
Assertions.assertThat(unitUnderTest.canHandle(new AbstractAuthenticationToken(List.of(new SimpleGrantedAuthority("role"))) {
@Override
public Object getCredentials() {
return null;
}

@Override
public Object getPrincipal() {
return null;
}
})).isFalse();
}
}

@Nested
class GetDetail {

@Test
void should_returnEmptyOptional_when_called() {
val authentication = new AnonymousAuthenticationToken("key", "principal", List.of(new SimpleGrantedAuthority("role")));

val result = unitUnderTest.getDetail("key", authentication);

Assertions.assertThat(result).isEmpty();
}

@Test
void should_throwIllegalArgumentException_when_authenticationIsNull() {
Assertions.assertThatThrownBy(() -> unitUnderTest.getDetail("key", null)).isInstanceOf(IllegalArgumentException.class);
}

@Test
void should_throwIllegalArgumentException_when_detailKeyIsNull() {
Assertions.assertThatThrownBy(
() -> unitUnderTest.getDetail(null, new AnonymousAuthenticationToken("key", "principal", List.of(new SimpleGrantedAuthority("role")))))
.isInstanceOf(IllegalArgumentException.class);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package de.muenchen.oss.wahllokalsystem.wls.common.security.authentication;

import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import lombok.val;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;

class JWTDetailRetrieverTest {

private final JWTDetailRetriever unitUnderTest = new JWTDetailRetriever();

@Nested
class CanHandle {

@Test
void should_throwIllegalArgumentException_when_authenticationIsNull() {
Assertions.assertThatThrownBy(() -> unitUnderTest.canHandle(null)).isInstanceOf(IllegalArgumentException.class);
}

@Test
void should_returnTrue_when_authenticationIsJwtAuthenticationToken() {
val detailKey = "requestedKey";
val detailValue = "detailValue";
val jwt = createJWT(Map.of(detailKey, detailValue));

Assertions.assertThat(unitUnderTest.canHandle(new JwtAuthenticationToken(jwt))).isTrue();
}

@Test
void should_returnTrue_when_authenticationSubclassOfJwtAuthenticationToken() {
val detailKey = "requestedKey";
val detailValue = "detailValue";
val jwt = createJWT(Map.of(detailKey, detailValue));

Assertions.assertThat(unitUnderTest.canHandle(new JwtAuthenticationToken(jwt) {
})).isTrue();
}

@Test
void should_returnFalse_when_authenticationIsNotJwtAuthenticationToken() {
Assertions.assertThat(unitUnderTest.canHandle(new AbstractAuthenticationToken(Collections.emptyList()) {
@Override
public Object getCredentials() {
return null;
}

@Override
public Object getPrincipal() {
return null;
}
})).isFalse();
}
}

@Nested
class GetDetail {

@Test
void should_returnEmptyOptional_when_authenticationIsNotInstanceOfJwtAuthenticationToken() {
val result = unitUnderTest.getDetail("key", new UsernamePasswordAuthenticationToken("principal", "credentials"));

Assertions.assertThat(result).isEmpty();
}

@Test
void should_returnValues_when_claimWithKeyExists() {
val detailKey = "requestedKey";
val detailValue = "detailValue";

val jwt = createJWT(Map.of(detailKey, detailValue));

val expectedResult = Optional.of(detailValue);

val result = unitUnderTest.getDetail(detailKey, new JwtAuthenticationToken(jwt));

Assertions.assertThat(result).isEqualTo(expectedResult);
}

@Test
void should_returnValues_when_claimWithKeyDoesNotExists() {
val detailKey = "requestedKey";

val jwt = createJWT(Map.of(detailKey + "extra", detailKey));

val result = unitUnderTest.getDetail(detailKey, new JwtAuthenticationToken(jwt));

Assertions.assertThat(result).isEmpty();
}

@Test
void should_throwsIllegalArgumentException_when_keyIsNull() {
val detailValue = "detailValue";

val jwt = createJWT(Map.of("detailKey", detailValue));

Assertions.assertThatThrownBy(() -> unitUnderTest.getDetail(null, new JwtAuthenticationToken(jwt))).isInstanceOf(IllegalArgumentException.class);
}

@Test
void should_throwsIllegalArgumentException_when_authenticationIsNull() {
Assertions.assertThatThrownBy(() -> unitUnderTest.getDetail("key", null)).isInstanceOf(IllegalArgumentException.class);
}
}

private Jwt createJWT(final Map<String, Object> claims) {
return new Jwt("tokenValue", Instant.now().minus(1, ChronoUnit.HOURS), Instant.now().plus(1, ChronoUnit.HOURS), Map.of("key1", "value1"), claims);
}

}

0 comments on commit 62c1fdd

Please sign in to comment.