Skip to content

Commit cbcc2e1

Browse files
authored
Merge pull request #57 from redlink-gmbh/umm/401-auth-token-module
MORE-Platform#401 auth token module
2 parents c6d5e04 + c88eb7c commit cbcc2e1

17 files changed

Lines changed: 822 additions & 5 deletions

pom.xml

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
<modules>
1212
<module>studymanager-core</module>
13+
<module>studymanager-auth-token</module>
1314
<module>studymanager-observation</module>
1415
<module>studymanager-intervention</module>
1516
<module>studymanager-goaltemplates</module>
@@ -445,11 +446,6 @@
445446
<version>0.10.1</version>
446447
</dependency>
447448

448-
<dependency>
449-
<groupId>org.reflections</groupId>
450-
<artifactId>reflections</artifactId>
451-
<version>0.10.2</version>
452-
</dependency>
453449
<dependency>
454450
<groupId>jakarta.json</groupId>
455451
<artifactId>jakarta.json-api</artifactId>

studymanager-auth-token/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# More Authentication Token
2+
3+
This module supports token based authentication as used by the mobile application.
4+
5+
Registration of new Users it out of scope for this module. Only already registered users can log in.

studymanager-auth-token/pom.xml

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
6+
<parent>
7+
<groupId>io.redlink.more</groupId>
8+
<artifactId>studymanager-parent</artifactId>
9+
<version>1.0.${revision}${sha1}${changelist}</version>
10+
</parent>
11+
12+
<artifactId>studymanager-auth-token</artifactId>
13+
<name>More Authentication - Login Token</name>
14+
15+
<dependencies>
16+
<dependency>
17+
<groupId>com.fasterxml.jackson.core</groupId>
18+
<artifactId>jackson-databind</artifactId>
19+
</dependency>
20+
<dependency>
21+
<groupId>org.springframework.boot</groupId>
22+
<artifactId>spring-boot-properties-migrator</artifactId>
23+
<scope>runtime</scope>
24+
</dependency>
25+
<dependency>
26+
<groupId>org.springframework.boot</groupId>
27+
<artifactId>spring-boot-configuration-processor</artifactId>
28+
<optional>true</optional>
29+
</dependency>
30+
31+
32+
<!-- Security -->
33+
<dependency>
34+
<groupId>org.springframework.security</groupId>
35+
<artifactId>spring-security-core</artifactId>
36+
</dependency>
37+
<dependency>
38+
<groupId>org.springframework.boot</groupId>
39+
<artifactId>spring-boot-starter-security</artifactId>
40+
</dependency>
41+
42+
<!-- Persistence -->
43+
<dependency>
44+
<groupId>org.springframework.boot</groupId>
45+
<artifactId>spring-boot-starter-data-jdbc</artifactId>
46+
</dependency>
47+
<dependency>
48+
<groupId>org.flywaydb</groupId>
49+
<artifactId>flyway-database-postgresql</artifactId>
50+
</dependency>
51+
<dependency>
52+
<groupId>org.postgresql</groupId>
53+
<artifactId>postgresql</artifactId>
54+
<scope>runtime</scope>
55+
</dependency>
56+
57+
<dependency>
58+
<groupId>commons-codec</groupId>
59+
<artifactId>commons-codec</artifactId>
60+
</dependency>
61+
62+
<dependency>
63+
<groupId>org.junit.jupiter</groupId>
64+
<artifactId>junit-jupiter-engine</artifactId>
65+
<scope>test</scope>
66+
</dependency>
67+
<dependency>
68+
<groupId>org.mockito</groupId>
69+
<artifactId>mockito-core</artifactId>
70+
<scope>test</scope>
71+
</dependency>
72+
<dependency>
73+
<groupId>org.springframework.boot</groupId>
74+
<artifactId>spring-boot-starter-test</artifactId>
75+
<scope>test</scope>
76+
</dependency>
77+
<dependency>
78+
<groupId>com.fasterxml.jackson.datatype</groupId>
79+
<artifactId>jackson-datatype-jsr310</artifactId>
80+
</dependency>
81+
<dependency>
82+
<groupId>org.slf4j</groupId>
83+
<artifactId>slf4j-api</artifactId>
84+
</dependency>
85+
</dependencies>
86+
87+
</project>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package io.redlink.more.auth.event;
2+
3+
public enum ParticipantUpdateAction {
4+
DELETE;
5+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package io.redlink.more.auth.event;
2+
3+
import org.springframework.context.ApplicationEvent;
4+
5+
/**
6+
* Event to be thrown if a study participant is updated
7+
*/
8+
public class ParticipantUpdateEvent extends ApplicationEvent {
9+
private final Long studyId;
10+
private final Integer participantId;
11+
private final ParticipantUpdateAction action;
12+
13+
public ParticipantUpdateEvent(Object source, Long studyId, Integer participantId, ParticipantUpdateAction action) {
14+
super(source);
15+
this.studyId = studyId;
16+
this.participantId = participantId;
17+
this.action = action;
18+
}
19+
20+
public Long getStudyId() {
21+
return studyId;
22+
}
23+
24+
public Integer getParticipantId() {
25+
return participantId;
26+
}
27+
28+
public ParticipantUpdateAction getAction() {
29+
return action;
30+
}
31+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
3+
* contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
4+
* for Digital Health and Prevention -- A research institute of the
5+
* Ludwig Boltzmann Gesellschaft, Oesterreichische Vereinigung zur
6+
* Foerderung der wissenschaftlichen Forschung).
7+
* Licensed under the Elastic License 2.0.
8+
*/
9+
package io.redlink.more.auth.model;
10+
11+
import java.io.Serializable;
12+
import java.util.OptionalInt;
13+
import java.util.Set;
14+
15+
/**
16+
* Provide information about the study context of the authenticated user
17+
* @param studyId the id of the study the authenticated user is participating in
18+
* @param participantId the id of the study participant for the authenticated user
19+
* @param rawStudyGroupId the optional study group id (negative number if not applicable)
20+
* @param observationGroupIds the observation group ids for the study participant
21+
* @param studyActive if the study is currently active
22+
* @param participantActive if the participant is currently active
23+
*/
24+
public record RoutingInfo(
25+
long studyId,
26+
int participantId,
27+
int rawStudyGroupId,
28+
Set<Integer> observationGroupIds,
29+
boolean studyActive,
30+
boolean participantActive
31+
) implements Serializable {
32+
33+
public RoutingInfo(long studyId,
34+
int participantId,
35+
@SuppressWarnings("OptionalUsedAsFieldOrParameterType") OptionalInt studyGroupId,
36+
Set<Integer> observationGroupIds,
37+
boolean studyActive,
38+
boolean participantActive
39+
) {
40+
this(studyId, participantId, studyGroupId.orElse(Integer.MIN_VALUE), observationGroupIds, studyActive, participantActive);
41+
}
42+
43+
public OptionalInt studyGroupId() {
44+
if (this.rawStudyGroupId < 0) {
45+
return OptionalInt.empty();
46+
} else {
47+
return OptionalInt.of(rawStudyGroupId);
48+
}
49+
}
50+
51+
public boolean acceptData() {
52+
return studyActive && participantActive;
53+
}
54+
55+
public String participantRef() {
56+
return studyId + ":" + participantId;
57+
}
58+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
3+
* contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
4+
* for Digital Health and Prevention -- A research institute of the
5+
* Ludwig Boltzmann Gesellschaft, Oesterreichische Vereinigung zur
6+
* Foerderung der wissenschaftlichen Forschung).
7+
* Licensed under the Elastic License 2.0.
8+
*/
9+
package io.redlink.more.auth.token.configuration;
10+
11+
import io.redlink.more.auth.token.model.LoginTokenUserDetails;
12+
import org.springframework.security.access.AccessDeniedException;
13+
import org.springframework.security.core.Authentication;
14+
import org.springframework.security.core.GrantedAuthority;
15+
import org.springframework.security.core.context.SecurityContextHolder;
16+
import org.springframework.stereotype.Service;
17+
18+
@Service
19+
public class AuthenticationFacade {
20+
21+
/**
22+
* Retrieve the current authentication context.
23+
* @return the {@link Authentication authentication context}.
24+
*/
25+
public Authentication getAuthentication() {
26+
return SecurityContextHolder.getContext().getAuthentication();
27+
}
28+
29+
/**
30+
* Asserts that hte current authentication context contains the provided authority ("role").
31+
* @param authority the required authority
32+
* @return the authentication principal as {@link LoginTokenUserDetails}.
33+
* @throws IllegalArgumentException if authority is {@code null}
34+
* @throws AccessDeniedException if not authentication context is available or the required authority is not present.
35+
*/
36+
public LoginTokenUserDetails assertAuthority(String authority) {
37+
if (authority == null) throw new IllegalArgumentException("authority must not be null");
38+
39+
final Authentication authentication = getAuthentication();
40+
if (authentication == null) {
41+
throw new AccessDeniedException("Authentication required");
42+
}
43+
44+
if (authentication.getAuthorities().stream()
45+
.map(GrantedAuthority::getAuthority)
46+
.anyMatch(authority::equals)) {
47+
final Object principal = authentication.getPrincipal();
48+
if (principal instanceof LoginTokenUserDetails userDetails) {
49+
return userDetails;
50+
}
51+
}
52+
throw new AccessDeniedException(String.format("Authority '%s' required", authority));
53+
}
54+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package io.redlink.more.auth.token.configuration;
2+
3+
import io.redlink.more.auth.token.service.LoginTokenUserDetailService;
4+
import org.springframework.context.annotation.Bean;
5+
import org.springframework.context.annotation.Configuration;
6+
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
7+
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder;
8+
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
9+
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
10+
import org.springframework.security.crypto.password.PasswordEncoder;
11+
import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder;
12+
import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder;
13+
14+
import java.util.HashMap;
15+
import java.util.Map;
16+
17+
@Configuration
18+
public class DaoAuthenticationConfiguration {
19+
20+
private final LoginTokenUserDetailService loginTokenUserDetailService;
21+
22+
DaoAuthenticationConfiguration(LoginTokenUserDetailService loginTokenUserDetailService) {
23+
this.loginTokenUserDetailService = loginTokenUserDetailService;
24+
}
25+
26+
@Bean
27+
public DaoAuthenticationProvider authenticationProvider(PasswordEncoder passwordEncoder) {
28+
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
29+
provider.setPasswordEncoder(passwordEncoder);
30+
provider.setUserDetailsService(loginTokenUserDetailService);
31+
return provider;
32+
}
33+
34+
35+
@Bean
36+
@SuppressWarnings("deprecation")
37+
public PasswordEncoder passwordEncoder() {
38+
final String encodingId = "bcrypt";
39+
Map<String, PasswordEncoder> encoders = new HashMap<>();
40+
encoders.put(encodingId, new BCryptPasswordEncoder());
41+
encoders.put("noop", org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance());
42+
encoders.put(null, org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance());
43+
encoders.put("pbkdf2", Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8());
44+
encoders.put("scrypt", SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8());
45+
encoders.put("argon2", Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8());
46+
return new DelegatingPasswordEncoder(encodingId, encoders);
47+
}
48+
49+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright LBI-DHP and/or licensed to LBI-DHP under one or more
3+
* contributor license agreements (LBI-DHP: Ludwig Boltzmann Institute
4+
* for Digital Health and Prevention -- A research institute of the
5+
* Ludwig Boltzmann Gesellschaft, Österreichische Vereinigung zur
6+
* Förderung der wissenschaftlichen Forschung).
7+
* Licensed under the Elastic License 2.0.
8+
*/
9+
package io.redlink.more.auth.token.configuration;
10+
11+
import jakarta.annotation.PostConstruct;
12+
import org.springframework.boot.context.properties.ConfigurationProperties;
13+
import org.springframework.stereotype.Component;
14+
15+
import java.security.MessageDigest;
16+
import java.security.NoSuchAlgorithmException;
17+
18+
@Component
19+
@ConfigurationProperties(prefix = "more.login-token")
20+
public class LoginTokenProperties {
21+
22+
private String hashAlgorithm = "SHA-256";
23+
24+
public String getHashAlgorithm() {
25+
return hashAlgorithm;
26+
}
27+
28+
public void setHashAlgorithm(String hashAlgorithm) {
29+
this.hashAlgorithm = hashAlgorithm;
30+
}
31+
32+
@PostConstruct
33+
public void validateConfiguration() {
34+
try {
35+
MessageDigest.getInstance(getHashAlgorithm());
36+
} catch (NoSuchAlgorithmException e) {
37+
throw new IllegalStateException("Invalid hash algorithm: " + getHashAlgorithm(), e);
38+
}
39+
}
40+
}

0 commit comments

Comments
 (0)