Skip to content
This repository was archived by the owner on Apr 5, 2024. It is now read-only.

Commit 7576a60

Browse files
authored
FF-109, FF-110 Rework Authentication Architecture (#27)
* FF-109 Rework BusinessServices Logic * FF-110 Fixed Tests and Bugs. * Fixed Tests.
1 parent 38c237f commit 7576a60

19 files changed

+387
-275
lines changed

.run/JUnit Tests.run.xml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
<component name="ProjectRunConfigurationManager">
22
<configuration default="false" name="Run only Unit Tests" type="JUnit" factoryName="JUnit">
3-
<module name="RestApi" />
43
<useClassPathOnly />
4+
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" />
5+
<option name="ALTERNATIVE_JRE_PATH" value="11" />
56
<option name="PACKAGE_NAME" value="" />
67
<option name="MAIN_CLASS_NAME" value="" />
78
<option name="METHOD_NAME" value="" />
89
<option name="TEST_OBJECT" value="pattern" />
910
<option name="PARAMETERS" value="" />
11+
<option name="TEST_SEARCH_SCOPE">
12+
<value defaultName="wholeProject" />
13+
</option>
1014
<patterns>
1115
<pattern testClass="^(.*)UnitTest$" />
1216
</patterns>

src/main/java/de/filefighter/rest/configuration/RestConfiguration.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
public class RestConfiguration {
1111

1212
//Custom static constants
13-
public static final String BASE_API_URI = "/api/v1/";
13+
public static final String BASE_API_URI = "/api/v1";
1414
public static final String AUTHORIZATION_BASIC_PREFIX = "Basic: ";
1515
public static final String AUTHORIZATION_BEARER_PREFIX = "Bearer: ";
1616
public static final String FS_BASE_URI = "/filesystem/";
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,20 @@
11
package de.filefighter.rest.domain.common;
22

3+
import de.filefighter.rest.rest.exceptions.RequestDidntMeetFormalRequirementsException;
4+
35
public class Utils {
46

57
public static boolean stringIsValid(String s){
68
return !(null == s || s.isEmpty() || s.isBlank());
79
}
10+
11+
public static String validateAuthorizationHeader(String header, String testString){
12+
if(!stringIsValid(testString))
13+
throw new RequestDidntMeetFormalRequirementsException("Header does not contain a valid String.");
14+
15+
if (!testString.matches("^" + header + "[^\\s](.*)$"))
16+
throw new RequestDidntMeetFormalRequirementsException("Header does not contain '" + header + "', or format is invalid.");
17+
String[] split = testString.split(header);
18+
return split[1];
19+
}
820
}

src/main/java/de/filefighter/rest/domain/token/business/AccessTokenBusinessService.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import static de.filefighter.rest.configuration.RestConfiguration.AUTHORIZATION_BASIC_PREFIX;
1616
import static de.filefighter.rest.configuration.RestConfiguration.AUTHORIZATION_BEARER_PREFIX;
1717
import static de.filefighter.rest.domain.common.Utils.stringIsValid;
18+
import static de.filefighter.rest.domain.common.Utils.validateAuthorizationHeader;
1819

1920
@Service
2021
public class AccessTokenBusinessService {
@@ -81,10 +82,13 @@ public AccessToken findAccessTokenByValue(String accessTokenValue) {
8182
}
8283

8384

84-
public String checkBearerHeader(String accessTokenValue) {
85-
if (!accessTokenValue.matches("^" + AUTHORIZATION_BEARER_PREFIX + "[^\\s](.*)$"))
86-
throw new RequestDidntMeetFormalRequirementsException("Header does not contain '" + AUTHORIZATION_BEARER_PREFIX + "', or format is invalid.");
87-
return accessTokenValue.split(AUTHORIZATION_BEARER_PREFIX)[1];
85+
public AccessToken validateAccessTokenValue(String accessTokenValue) {
86+
String cleanValue = validateAuthorizationHeader(AUTHORIZATION_BEARER_PREFIX, accessTokenValue);
87+
AccessTokenEntity accessTokenEntity = accessTokenRepository.findByValue(cleanValue);
88+
if (null == accessTokenEntity)
89+
throw new UserNotAuthenticatedException("AccessToken not found.");
90+
91+
return accessTokenDtoService.createDto(accessTokenEntity);
8892
}
8993

9094
public String generateRandomTokenValue() {
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package de.filefighter.rest.domain.user.business;
2+
3+
import de.filefighter.rest.domain.common.Utils;
4+
import de.filefighter.rest.domain.token.data.dto.AccessToken;
5+
import de.filefighter.rest.domain.user.data.dto.User;
6+
import de.filefighter.rest.domain.user.data.persistance.UserEntity;
7+
import de.filefighter.rest.domain.user.data.persistance.UserRepository;
8+
import de.filefighter.rest.domain.user.exceptions.UserNotAuthenticatedException;
9+
import de.filefighter.rest.rest.exceptions.RequestDidntMeetFormalRequirementsException;
10+
import org.slf4j.Logger;
11+
import org.slf4j.LoggerFactory;
12+
import org.springframework.stereotype.Service;
13+
14+
import java.io.UnsupportedEncodingException;
15+
import java.nio.charset.StandardCharsets;
16+
import java.util.Base64;
17+
18+
import static de.filefighter.rest.configuration.RestConfiguration.AUTHORIZATION_BASIC_PREFIX;
19+
import static de.filefighter.rest.configuration.RestConfiguration.AUTHORIZATION_BEARER_PREFIX;
20+
21+
@Service
22+
public class UserAuthorizationService {
23+
24+
private final UserRepository userRepository;
25+
private final UserDtoService userDtoService;
26+
27+
private static final Logger LOG = LoggerFactory.getLogger(UserAuthorizationService.class);
28+
29+
public UserAuthorizationService(UserRepository userRepository, UserDtoService userDtoService) {
30+
this.userRepository = userRepository;
31+
this.userDtoService = userDtoService;
32+
}
33+
34+
public User authenticateUserWithUsernameAndPassword(String base64encodedUserAndPassword) {
35+
String decodedUsernameUndPassword;
36+
try {
37+
byte[] decodedValue = Base64.getDecoder().decode(base64encodedUserAndPassword);
38+
decodedUsernameUndPassword = new String(decodedValue, StandardCharsets.UTF_8.toString());
39+
} catch (UnsupportedEncodingException | IllegalArgumentException ex) {
40+
LOG.warn("Found UnsupportedEncodingException in {}", base64encodedUserAndPassword);
41+
throw new RuntimeException(ex);
42+
}
43+
44+
String[] split = decodedUsernameUndPassword.strip().split(":");
45+
46+
if (split.length != 2)
47+
throw new RequestDidntMeetFormalRequirementsException("Credentials didnt meet formal requirements.");
48+
49+
String username = split[0];
50+
String password = split[1];
51+
52+
UserEntity userEntity = userRepository.findByUsernameAndPassword(username, password);
53+
if (null == userEntity)
54+
throw new UserNotAuthenticatedException("No User found with this username and password.");
55+
56+
return userDtoService.createDto(userEntity);
57+
}
58+
59+
public User authenticateUserWithRefreshToken(String refreshToken) {
60+
UserEntity userEntity = userRepository.findByRefreshToken(refreshToken);
61+
if (null == userEntity)
62+
throw new UserNotAuthenticatedException("No user found for this Refresh Token.");
63+
64+
return userDtoService.createDto(userEntity);
65+
}
66+
67+
public void authenticateUserWithAccessToken(AccessToken accessToken) {
68+
UserEntity userEntity = userRepository.findByUserId(accessToken.getUserId());
69+
if (null == userEntity)
70+
throw new UserNotAuthenticatedException(accessToken.getUserId());
71+
}
72+
}

src/main/java/de/filefighter/rest/domain/user/business/UserBusinessService.java

Lines changed: 6 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@ public class UserBusinessService {
2727
private final UserRepository userRepository;
2828
private final UserDtoService userDtoService;
2929

30-
private static final Logger LOG = LoggerFactory.getLogger(UserBusinessService.class);
31-
3230
public UserBusinessService(UserRepository userRepository, UserDtoService userDtoService) {
3331
this.userRepository = userRepository;
3432
this.userDtoService = userDtoService;
@@ -38,45 +36,19 @@ public long getUserCount() {
3836
return userRepository.count();
3937
}
4038

41-
public User getUserByUsernameAndPassword(String base64encodedUserAndPasswordWithHeaderPrefix) {
42-
if (!stringIsValid(base64encodedUserAndPasswordWithHeaderPrefix))
43-
throw new RequestDidntMeetFormalRequirementsException("Header was empty.");
44-
45-
//TODO: maybe filter unsupported characters?
46-
if (!base64encodedUserAndPasswordWithHeaderPrefix.matches("^" + AUTHORIZATION_BASIC_PREFIX + "[^\\s](.*)$"))
47-
throw new RequestDidntMeetFormalRequirementsException("Header does not contain '" + AUTHORIZATION_BASIC_PREFIX + "', or format is invalid.");
48-
49-
String[] split = base64encodedUserAndPasswordWithHeaderPrefix.split(AUTHORIZATION_BASIC_PREFIX);
50-
51-
base64encodedUserAndPasswordWithHeaderPrefix = split[1];
52-
String decodedUsernameUndPassword;
53-
try {
54-
byte[] decodedValue = Base64.getDecoder().decode(base64encodedUserAndPasswordWithHeaderPrefix);
55-
decodedUsernameUndPassword = new String(decodedValue, StandardCharsets.UTF_8.toString());
56-
} catch (UnsupportedEncodingException | IllegalArgumentException ex) {
57-
LOG.warn("Found UnsupportedEncodingException in {}", base64encodedUserAndPasswordWithHeaderPrefix);
58-
throw new RuntimeException(ex);
39+
public User getUserById(long userId) {
40+
UserEntity userEntity = userRepository.findByUserId(userId);
41+
if (null == userEntity) {
42+
throw new UserNotFoundException(userId);
5943
}
6044

61-
split = decodedUsernameUndPassword.strip().split(":");
62-
63-
if (split.length != 2)
64-
throw new RequestDidntMeetFormalRequirementsException("Credentials didnt meet formal requirements.");
65-
66-
String username = split[0];
67-
String password = split[1];
68-
69-
UserEntity userEntity = userRepository.findByUsernameAndPassword(username, password);
70-
if (null == userEntity)
71-
throw new UserNotAuthenticatedException("No User found with this username and password.");
72-
7345
return userDtoService.createDto(userEntity);
7446
}
7547

7648
public RefreshToken getRefreshTokenForUser(User user) {
7749
UserEntity userEntity = userRepository.findByUserIdAndUsername(user.getId(), user.getUsername());
7850
if (null == userEntity)
79-
throw new UserNotAuthenticatedException(user.getId());
51+
throw new UserNotFoundException(user.getId());
8052

8153
String refreshTokenValue = userEntity.getRefreshToken();
8254

@@ -90,33 +62,11 @@ public RefreshToken getRefreshTokenForUser(User user) {
9062
.build();
9163
}
9264

93-
public User getUserByRefreshTokenAndUserId(String refreshToken, long userId) {
94-
if (!stringIsValid(refreshToken))
95-
throw new RequestDidntMeetFormalRequirementsException("RefreshToken was not valid.");
96-
97-
UserEntity userEntity = userRepository.findByRefreshTokenAndUserId(refreshToken, userId);
98-
if (null == userEntity)
99-
throw new UserNotAuthenticatedException(userId);
100-
101-
return userDtoService.createDto(userEntity);
102-
}
103-
104-
public User getUserByAccessTokenAndUserId(AccessToken accessToken, long userId) {
105-
if (accessToken.getUserId() != userId)
106-
throw new UserNotAuthenticatedException(userId);
107-
108-
UserEntity userEntity = userRepository.findByUserId(userId);
109-
if (null == userEntity)
110-
throw new UserNotFoundException(userId);
111-
112-
return userDtoService.createDto(userEntity);
113-
}
114-
11565
public User findUserByUsername(String username) {
11666
if (!stringIsValid(username))
11767
throw new RequestDidntMeetFormalRequirementsException("Username was not valid.");
11868

119-
String lowercaseUsername = username.toLowerCase().replace(" ","");
69+
String lowercaseUsername = username.toLowerCase().replace(" ", "");
12070

12171
UserEntity entity = userRepository.findByLowercaseUsername(lowercaseUsername);
12272
if (null == entity)

src/main/java/de/filefighter/rest/domain/user/data/persistance/UserRepository.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
public interface UserRepository extends MongoRepository<UserEntity, String> {
88
UserEntity findByUserIdAndUsername(long userId, String username);
99
UserEntity findByUsernameAndPassword(String username, String password);
10-
UserEntity findByRefreshTokenAndUserId(String refreshToken, long userId);
10+
UserEntity findByRefreshToken(String refreshToken);
1111
UserEntity findByUserId(long userId);
1212
UserEntity findByLowercaseUsername(String lowercaseUsername);
1313
}

src/main/java/de/filefighter/rest/domain/user/rest/UserRestController.java

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -37,40 +37,38 @@ public ResponseEntity<User> registerNewUser(
3737
}
3838

3939
@GetMapping(USER_BASE_URI + "login")
40-
public ResponseEntity<RefreshToken> loginUserWithUsernameAndPassword(
40+
public ResponseEntity<RefreshToken> loginWithUsernameAndPassword(
4141
@RequestHeader(value = "Authorization", defaultValue = AUTHORIZATION_BASIC_PREFIX + "S2V2aW46MTIzNA==") String base64encodedUserAndPassword) {
4242

4343
LOG.info("Requested Login.");
4444
return userRestService.getRefreshTokenWithUsernameAndPassword(base64encodedUserAndPassword);
4545
}
4646

47-
@GetMapping(USER_BASE_URI + "{userId}/login")
48-
public ResponseEntity<AccessToken> getAccessTokenAndUserInfoByRefreshTokenAndUserId(
49-
@PathVariable long userId,
47+
@GetMapping(USER_BASE_URI + "auth")
48+
public ResponseEntity<AccessToken> getAccessToken(
5049
@RequestHeader(value = "Authorization", defaultValue = AUTHORIZATION_BEARER_PREFIX + "token") String refreshToken) {
5150

52-
LOG.info("Requested login for user {} with token {}.", userId, refreshToken);
53-
return userRestService.getAccessTokenByRefreshTokenAndUserId(refreshToken, userId);
51+
LOG.info("Requested login for token {}.", refreshToken);
52+
return userRestService.getAccessTokenByRefreshToken(refreshToken);
5453
}
5554

5655

5756
@GetMapping(USER_BASE_URI + "{userId}/info")
58-
public ResponseEntity<User> getUserInfoWithAccessToken(
57+
public ResponseEntity<User> getUserInfo(
5958
@PathVariable long userId,
6059
@RequestHeader(value = "Authorization", defaultValue = AUTHORIZATION_BEARER_PREFIX + "token") String accessToken) {
6160

6261
LOG.info("Requested User {} with token {}.", userId, accessToken);
63-
return userRestService.getUserByAccessTokenAndUserId(accessToken, userId);
62+
return userRestService.getUserByUserIdAuthenticateWithAccessToken(accessToken, userId);
6463
}
6564

66-
@PutMapping(USER_BASE_URI + "{userId}/edit")
67-
public ResponseEntity<User> updateUserWithAccessToken(
68-
@PathVariable long userId,
65+
@PutMapping(USER_BASE_URI + "edit")
66+
public ResponseEntity<User> updateUser(
6967
@RequestHeader(value = "Authorization", defaultValue = AUTHORIZATION_BEARER_PREFIX + "token") String accessToken,
7068
@RequestBody UserRegisterForm updatedUser) {
7169

72-
LOG.info("Updated User with the id {} and Token {}, with form {}.", userId, accessToken, updatedUser);
73-
return userRestService.updateUserByAccessTokenAndUserId(updatedUser, accessToken, userId);
70+
LOG.info("Updated User and Token {}, with form {}.", accessToken, updatedUser);
71+
return userRestService.updateUserWithAccessToken(updatedUser, accessToken);
7472
}
7573

7674
@GetMapping(USER_BASE_URI + "find")

src/main/java/de/filefighter/rest/domain/user/rest/UserRestService.java

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,61 @@
11
package de.filefighter.rest.domain.user.rest;
22

3+
import de.filefighter.rest.configuration.RestConfiguration;
4+
import de.filefighter.rest.domain.common.Utils;
35
import de.filefighter.rest.domain.token.business.AccessTokenBusinessService;
46
import de.filefighter.rest.domain.token.data.dto.AccessToken;
57
import de.filefighter.rest.domain.token.data.dto.RefreshToken;
8+
import de.filefighter.rest.domain.user.business.UserAuthorizationService;
69
import de.filefighter.rest.domain.user.business.UserBusinessService;
710
import de.filefighter.rest.domain.user.data.dto.User;
811
import de.filefighter.rest.domain.user.data.dto.UserRegisterForm;
912
import org.springframework.http.HttpStatus;
1013
import org.springframework.http.ResponseEntity;
1114
import org.springframework.stereotype.Service;
1215

16+
import static de.filefighter.rest.configuration.RestConfiguration.AUTHORIZATION_BASIC_PREFIX;
17+
import static de.filefighter.rest.configuration.RestConfiguration.AUTHORIZATION_BEARER_PREFIX;
18+
1319

1420
@Service
1521
public class UserRestService implements UserRestServiceInterface {
1622

1723
private final UserBusinessService userBusinessService;
24+
private final UserAuthorizationService userAuthorizationService;
1825
private final AccessTokenBusinessService accessTokenBusinessService;
1926

20-
public UserRestService(UserBusinessService userBusinessService, AccessTokenBusinessService accessTokenBusinessService) {
27+
public UserRestService(UserBusinessService userBusinessService, UserAuthorizationService userAuthorizationService, AccessTokenBusinessService accessTokenBusinessService) {
2128
this.userBusinessService = userBusinessService;
29+
this.userAuthorizationService = userAuthorizationService;
2230
this.accessTokenBusinessService = accessTokenBusinessService;
2331
}
2432

2533
@Override
26-
public ResponseEntity<User> getUserByAccessTokenAndUserId(String accessTokenValue, long userId) {
27-
String cleanValue = accessTokenBusinessService.checkBearerHeader(accessTokenValue);
28-
AccessToken accessToken = accessTokenBusinessService.findAccessTokenByValueAndUserId(cleanValue, userId);
29-
User user = userBusinessService.getUserByAccessTokenAndUserId(accessToken, userId);
34+
public ResponseEntity<User> getUserByUserIdAuthenticateWithAccessToken(String accessToken, long userId) {
35+
AccessToken validAccessToken = accessTokenBusinessService.validateAccessTokenValue(accessToken);
36+
userAuthorizationService.authenticateUserWithAccessToken(validAccessToken);
37+
User user = userBusinessService.getUserById(userId);
3038
return new ResponseEntity<>(user, HttpStatus.OK);
3139
}
3240

3341
@Override
3442
public ResponseEntity<RefreshToken> getRefreshTokenWithUsernameAndPassword(String base64encodedUserAndPassword) {
35-
User user = userBusinessService.getUserByUsernameAndPassword(base64encodedUserAndPassword);
36-
RefreshToken refreshToken = userBusinessService.getRefreshTokenForUser(user);
43+
String cleanValue = Utils.validateAuthorizationHeader(AUTHORIZATION_BASIC_PREFIX, base64encodedUserAndPassword);
44+
User authenticatedUser = userAuthorizationService.authenticateUserWithUsernameAndPassword(cleanValue);
45+
RefreshToken refreshToken = userBusinessService.getRefreshTokenForUser(authenticatedUser);
3746
return new ResponseEntity<>(refreshToken, HttpStatus.OK);
3847
}
3948

4049
@Override
41-
public ResponseEntity<AccessToken> getAccessTokenByRefreshTokenAndUserId(String refreshToken, long userId) {
42-
String cleanValue = accessTokenBusinessService.checkBearerHeader(refreshToken);
43-
User user = userBusinessService.getUserByRefreshTokenAndUserId(cleanValue, userId);
50+
public ResponseEntity<AccessToken> getAccessTokenByRefreshToken(String refreshToken) {
51+
String cleanValue = Utils.validateAuthorizationHeader(AUTHORIZATION_BEARER_PREFIX, refreshToken);
52+
User user = userAuthorizationService.authenticateUserWithRefreshToken(cleanValue);
4453
AccessToken accessToken = accessTokenBusinessService.getValidAccessTokenForUser(user);
4554
return new ResponseEntity<>(accessToken, HttpStatus.OK);
4655
}
4756

4857
@Override
49-
public ResponseEntity<User> updateUserByAccessTokenAndUserId(UserRegisterForm updatedUser, String accessToken, long userId) {
58+
public ResponseEntity<User> updateUserWithAccessToken(UserRegisterForm updatedUser, String accessToken) {
5059
return null;
5160
}
5261

@@ -57,8 +66,8 @@ public ResponseEntity<User> registerNewUserWithAccessToken(UserRegisterForm newU
5766

5867
@Override
5968
public ResponseEntity<User> findUserByUsernameAndAccessToken(String username, String accessToken) {
60-
String cleanValue = accessTokenBusinessService.checkBearerHeader(accessToken);
61-
AccessToken token = accessTokenBusinessService.findAccessTokenByValue(cleanValue);
69+
AccessToken token = accessTokenBusinessService.validateAccessTokenValue(accessToken);
70+
userAuthorizationService.authenticateUserWithAccessToken(token);
6271
User foundUser = userBusinessService.findUserByUsername(username);
6372
return new ResponseEntity<>(foundUser, HttpStatus.OK);
6473
}

0 commit comments

Comments
 (0)