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

Commit 69534dd

Browse files
authored
FF-106 UpdateUser Endpoint (#34)
* fixed tests, added shallow logic. * updated feature files, and step def. * FF-106 Write Logic for User Edit * Tweaked Cucumber Steps. Fixed Serialization. * Added additional check, fixed some bugs. * FF-106 Wrote UnitTests for User Edit * Damn you password check. * Bumped Version to v0.0.5
1 parent d5b650d commit 69534dd

17 files changed

+384
-122
lines changed

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
</parent>
1111
<groupId>de.filefighter</groupId>
1212
<artifactId>rest</artifactId>
13-
<version>0.0.4</version>
13+
<version>0.0.5</version>
1414
<name>RestApi</name>
1515
<description>RestApi for FileFighter</description>
1616

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public AccessToken getValidAccessTokenForUser(User user) {
3737
accessTokenEntity = AccessTokenEntity
3838
.builder()
3939
.validUntil(currentTimeSeconds + ACCESS_TOKEN_DURATION_IN_SECONDS)
40-
.value(this.generateRandomTokenValue())
40+
.value(generateRandomTokenValue())
4141
.userId(user.getId())
4242
.build();
4343
accessTokenEntity = accessTokenRepository.save(accessTokenEntity);
@@ -47,7 +47,7 @@ public AccessToken getValidAccessTokenForUser(User user) {
4747
accessTokenEntity = AccessTokenEntity
4848
.builder()
4949
.validUntil(currentTimeSeconds + ACCESS_TOKEN_DURATION_IN_SECONDS)
50-
.value(this.generateRandomTokenValue())
50+
.value(generateRandomTokenValue())
5151
.userId(user.getId())
5252
.build();
5353
accessTokenEntity = accessTokenRepository.save(accessTokenEntity);
@@ -80,7 +80,7 @@ public AccessToken findAccessTokenByValue(String accessTokenValue) {
8080
}
8181

8282

83-
public AccessToken validateAccessTokenValue(String accessTokenValue) {
83+
public AccessToken validateAccessTokenValueWithHeader(String accessTokenValue) {
8484
String cleanValue = validateAuthorizationHeader(AUTHORIZATION_BEARER_PREFIX, accessTokenValue);
8585
AccessTokenEntity accessTokenEntity = accessTokenRepository.findByValue(cleanValue);
8686
if (null == accessTokenEntity)

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

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package de.filefighter.rest.domain.user.business;
22

3+
import de.filefighter.rest.domain.common.Utils;
34
import de.filefighter.rest.domain.token.data.dto.AccessToken;
45
import de.filefighter.rest.domain.user.data.dto.User;
56
import de.filefighter.rest.domain.user.data.persistance.UserEntity;
@@ -15,6 +16,9 @@
1516
import java.nio.charset.StandardCharsets;
1617
import java.util.Base64;
1718

19+
import static de.filefighter.rest.configuration.RestConfiguration.AUTHORIZATION_BASIC_PREFIX;
20+
import static de.filefighter.rest.configuration.RestConfiguration.AUTHORIZATION_BEARER_PREFIX;
21+
1822
@Service
1923
public class UserAuthorizationService {
2024

@@ -28,7 +32,9 @@ public UserAuthorizationService(UserRepository userRepository, UserDtoService us
2832
this.userDtoService = userDtoService;
2933
}
3034

31-
public User authenticateUserWithUsernameAndPassword(String base64encodedUserAndPassword) {
35+
public User authenticateUserWithUsernameAndPassword(String base64encodedUserAndPasswordWithHeader) {
36+
String base64encodedUserAndPassword = Utils.validateAuthorizationHeader(AUTHORIZATION_BASIC_PREFIX, base64encodedUserAndPasswordWithHeader);
37+
3238
String decodedUsernameAndPassword = "";
3339
try {
3440
byte[] decodedValue = Base64.getDecoder().decode(base64encodedUserAndPassword);
@@ -54,17 +60,20 @@ public User authenticateUserWithUsernameAndPassword(String base64encodedUserAndP
5460
}
5561

5662
public User authenticateUserWithRefreshToken(String refreshToken) {
57-
UserEntity userEntity = userRepository.findByRefreshToken(refreshToken);
63+
String cleanValue = Utils.validateAuthorizationHeader(AUTHORIZATION_BEARER_PREFIX, refreshToken);
64+
UserEntity userEntity = userRepository.findByRefreshToken(cleanValue);
5865
if (null == userEntity)
5966
throw new UserNotAuthenticatedException("No user found for this Refresh Token.");
6067

6168
return userDtoService.createDto(userEntity);
6269
}
6370

64-
public void authenticateUserWithAccessToken(AccessToken accessToken) {
71+
public User authenticateUserWithAccessToken(AccessToken accessToken) {
6572
UserEntity userEntity = userRepository.findByUserId(accessToken.getUserId());
6673
if (null == userEntity)
6774
throw new UserNotAuthenticatedException(accessToken.getUserId());
75+
76+
return userDtoService.createDto(userEntity);
6877
}
6978

7079
public void authenticateUserWithAccessTokenAndGroup(AccessToken accessToken, Groups groups) {

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

Lines changed: 99 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,20 @@
99
import de.filefighter.rest.domain.user.data.persistance.UserRepository;
1010
import de.filefighter.rest.domain.user.exceptions.UserNotFoundException;
1111
import de.filefighter.rest.domain.user.exceptions.UserNotRegisteredException;
12+
import de.filefighter.rest.domain.user.exceptions.UserNotUpdatedException;
1213
import de.filefighter.rest.domain.user.group.GroupRepository;
14+
import de.filefighter.rest.domain.user.group.Groups;
1315
import de.filefighter.rest.rest.exceptions.RequestDidntMeetFormalRequirementsException;
1416
import org.slf4j.Logger;
1517
import org.slf4j.LoggerFactory;
1618
import org.springframework.beans.factory.annotation.Value;
19+
import org.springframework.data.mongodb.core.MongoTemplate;
20+
import org.springframework.data.mongodb.core.query.Criteria;
21+
import org.springframework.data.mongodb.core.query.Query;
22+
import org.springframework.data.mongodb.core.query.Update;
1723
import org.springframework.stereotype.Service;
1824

25+
import java.util.Arrays;
1926
import java.util.regex.Pattern;
2027

2128
import static de.filefighter.rest.domain.common.Utils.stringIsValid;
@@ -26,16 +33,19 @@ public class UserBusinessService {
2633
private final UserRepository userRepository;
2734
private final UserDtoService userDtoService;
2835
private final GroupRepository groupRepository;
36+
private final MongoTemplate mongoTemplate;
37+
2938

3039
private static final Logger LOG = LoggerFactory.getLogger(UserBusinessService.class);
3140

3241
@Value("${filefighter.disable-password-check}")
3342
public boolean passwordCheckDisabled;
3443

35-
public UserBusinessService(UserRepository userRepository, UserDtoService userDtoService, GroupRepository groupRepository) {
44+
public UserBusinessService(UserRepository userRepository, UserDtoService userDtoService, GroupRepository groupRepository, MongoTemplate mongoTemplate) {
3645
this.userRepository = userRepository;
3746
this.userDtoService = userDtoService;
3847
this.groupRepository = groupRepository;
48+
this.mongoTemplate = mongoTemplate;
3949
}
4050

4151
public long getUserCount() {
@@ -97,14 +107,15 @@ public void registerNewUser(UserRegisterForm newUser) {
97107

98108
// check pws.
99109
String password = newUser.getPassword();
100-
passwordIsValid(password);
110+
if (!passwordIsValid(password))
111+
throw new UserNotRegisteredException("Password needs to be at least 8 characters long and, contains at least one uppercase and lowercase letter and a number.");
101112

102113
String confirmationPassword = newUser.getConfirmationPassword();
103114

104115
if (!password.contentEquals(confirmationPassword))
105116
throw new UserNotRegisteredException("Passwords do not match.");
106117

107-
if(password.toLowerCase().contains(username.toLowerCase()))
118+
if (password.toLowerCase().contains(username.toLowerCase()))
108119
throw new UserNotRegisteredException("Username must not appear in password.");
109120

110121
//check groups
@@ -130,14 +141,93 @@ public void registerNewUser(UserRegisterForm newUser) {
130141
.build());
131142
}
132143

133-
public void passwordIsValid(String password) {
144+
public boolean passwordIsValid(String password) {
134145
if (!Utils.stringIsValid(password))
135-
throw new UserNotRegisteredException("Password was empty.");
146+
return false;
136147

137-
if(this.passwordCheckDisabled) return;
148+
if (this.passwordCheckDisabled) return true;
138149

139-
Pattern pattern = Pattern.compile("^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=\\S+$).{8,20}$");
140-
if (!pattern.matcher(password).matches())
141-
throw new UserNotRegisteredException("Password needs to be at least 8 characters long and, contains at least one uppercase and lowercase letter and a number.");
150+
Pattern pattern = Pattern.compile("^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=\\S+).{8,20}$");
151+
return pattern.matcher(password).matches();
152+
}
153+
154+
public void updateUser(long userId, UserRegisterForm userToUpdate, User authenticatedUser) {
155+
if (null == userToUpdate)
156+
throw new UserNotUpdatedException("No updates specified.");
157+
158+
if(null == authenticatedUser.getGroups())
159+
throw new UserNotUpdatedException("Authenticated User is not allowed");
160+
161+
boolean authenticatedUserIsAdmin = Arrays.stream(authenticatedUser.getGroups()).anyMatch(g -> g == Groups.ADMIN);
162+
if (userId != authenticatedUser.getId() && !authenticatedUserIsAdmin)
163+
throw new UserNotUpdatedException("Only Admins are allowed to update other users.");
164+
165+
UserEntity userEntityToUpdate = userRepository.findByUserId(userId);
166+
Update newUpdate = new Update();
167+
boolean changesWereMade = false;
168+
169+
// username
170+
String username = userToUpdate.getUsername();
171+
if (null != username) {
172+
if (!stringIsValid(username))
173+
throw new UserNotUpdatedException("Wanted to change username, but username was not valid.");
174+
175+
User user = null;
176+
try {
177+
user = this.findUserByUsername(username);
178+
} catch (UserNotFoundException ignored) {
179+
LOG.info("Username '{}' is free to use.", username);
180+
}
181+
182+
if (null != user)
183+
throw new UserNotUpdatedException("Username already taken.");
184+
185+
changesWereMade = true;
186+
newUpdate.set("username", username);
187+
}
188+
189+
// pw
190+
if (null != userToUpdate.getPassword()) {
191+
String password = userToUpdate.getPassword();
192+
String confirmation = userToUpdate.getConfirmationPassword();
193+
194+
if (!stringIsValid(password) || !stringIsValid(confirmation))
195+
throw new UserNotUpdatedException("Wanted to change password, but password was not valid.");
196+
197+
if (!passwordIsValid(password))
198+
throw new UserNotUpdatedException("Password needs to be at least 8 characters long and, contains at least one uppercase and lowercase letter and a number.");
199+
200+
if (!password.contentEquals(confirmation))
201+
throw new UserNotUpdatedException("Passwords do not match.");
202+
203+
if (password.toLowerCase().contains(userEntityToUpdate.getLowercaseUsername()))
204+
throw new UserNotUpdatedException("Username must not appear in password.");
205+
206+
changesWereMade = true;
207+
newUpdate.set("password", password);
208+
}
209+
210+
// groups
211+
if (null != userToUpdate.getGroupIds()) {
212+
try {
213+
for (Groups group : groupRepository.getGroupsByIds(userToUpdate.getGroupIds())) {
214+
if (group == Groups.ADMIN && !authenticatedUserIsAdmin)
215+
throw new UserNotUpdatedException("Only admins can add users to group " + Groups.ADMIN.getDisplayName());
216+
}
217+
} catch (IllegalArgumentException exception) {
218+
throw new UserNotUpdatedException("One or more groups do not exist.");
219+
}
220+
221+
changesWereMade = true;
222+
newUpdate.set("groupIds", userToUpdate.getGroupIds());
223+
}
224+
225+
if(!changesWereMade)
226+
throw new UserNotUpdatedException("No changes were made.");
227+
228+
Query query = new Query();
229+
query.addCriteria(Criteria.where("userId").is(userId));
230+
mongoTemplate.findAndModify(query, newUpdate, UserEntity.class);
142231
}
143232
}
233+
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package de.filefighter.rest.domain.user.exceptions;
2+
3+
import de.filefighter.rest.rest.ServerResponse;
4+
import org.slf4j.LoggerFactory;
5+
import org.springframework.http.HttpStatus;
6+
import org.springframework.http.ResponseEntity;
7+
import org.springframework.web.bind.annotation.ControllerAdvice;
8+
import org.springframework.web.bind.annotation.ExceptionHandler;
9+
import org.springframework.web.bind.annotation.ResponseBody;
10+
import org.springframework.web.bind.annotation.ResponseStatus;
11+
12+
@ControllerAdvice
13+
class UserNotUpdatedAdvise {
14+
15+
@ResponseBody
16+
@ExceptionHandler(UserNotUpdatedException.class)
17+
@ResponseStatus(HttpStatus.CONFLICT)
18+
ResponseEntity<ServerResponse> userNotUpdatedExceptionHandler(UserNotUpdatedException ex) {
19+
LoggerFactory.getLogger(UserNotUpdatedException.class).warn(ex.getMessage());
20+
return new ResponseEntity<>(new ServerResponse(HttpStatus.CONFLICT, ex.getMessage()), HttpStatus.CONFLICT);
21+
}
22+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package de.filefighter.rest.domain.user.exceptions;
2+
3+
public class UserNotUpdatedException extends RuntimeException{
4+
public UserNotUpdatedException() {
5+
super("User could not get updated");
6+
}
7+
8+
public UserNotUpdatedException(String reason) {
9+
super("User could not get updated. "+reason);
10+
}
11+
}

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,21 +64,22 @@ public ResponseEntity<User> getUserInfo(
6464
return userRestService.getUserByUserIdAuthenticateWithAccessToken(accessToken, userId);
6565
}
6666

67-
@PutMapping(USER_BASE_URI + "edit")
68-
public ResponseEntity<User> updateUser(
67+
@PutMapping(USER_BASE_URI + "{userId}/edit")
68+
public ResponseEntity<ServerResponse> updateUser(
69+
@PathVariable long userId,
6970
@RequestHeader(value = "Authorization", defaultValue = AUTHORIZATION_BEARER_PREFIX + "token") String accessToken,
7071
@RequestBody UserRegisterForm updatedUser) {
7172

72-
LOG.info("Updated User and Token {}, with form {}.", accessToken, updatedUser);
73-
return userRestService.updateUserWithAccessToken(updatedUser, accessToken);
73+
LOG.info("Updated User {} and Token {}, with form {}.", userId, accessToken, updatedUser);
74+
return userRestService.updateUserByUserIdAuthenticateWithAccessToken(updatedUser, userId, accessToken);
7475
}
7576

7677
@GetMapping(USER_BASE_URI + "find")
7778
public ResponseEntity<User> findUserByUsername(
7879
@RequestHeader(value = "Authorization", defaultValue = AUTHORIZATION_BEARER_PREFIX + "token") String accessToken,
7980
@RequestParam(name = "username", value = "username") String username
8081
) {
81-
LOG.info("Requested finding User with the username {} and Token {}", username, accessToken);
82+
LOG.info("Requested finding User with the username {} and Token {}", username, accessToken);
8283
return userRestService.findUserByUsernameAndAccessToken(username, accessToken);
8384
}
8485
}

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

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

3-
import de.filefighter.rest.domain.common.Utils;
43
import de.filefighter.rest.domain.token.business.AccessTokenBusinessService;
54
import de.filefighter.rest.domain.token.data.dto.AccessToken;
65
import de.filefighter.rest.domain.token.data.dto.RefreshToken;
@@ -13,8 +12,6 @@
1312
import org.springframework.http.ResponseEntity;
1413
import org.springframework.stereotype.Service;
1514

16-
import static de.filefighter.rest.configuration.RestConfiguration.AUTHORIZATION_BASIC_PREFIX;
17-
import static de.filefighter.rest.configuration.RestConfiguration.AUTHORIZATION_BEARER_PREFIX;
1815
import static de.filefighter.rest.domain.user.group.Groups.ADMIN;
1916

2017

@@ -33,44 +30,46 @@ public UserRestService(UserBusinessService userBusinessService, UserAuthorizatio
3330

3431
@Override
3532
public ResponseEntity<User> getUserByUserIdAuthenticateWithAccessToken(String accessToken, long userId) {
36-
AccessToken validAccessToken = accessTokenBusinessService.validateAccessTokenValue(accessToken);
33+
AccessToken validAccessToken = accessTokenBusinessService.validateAccessTokenValueWithHeader(accessToken);
3734
userAuthorizationService.authenticateUserWithAccessToken(validAccessToken);
3835
User user = userBusinessService.getUserById(userId);
3936
return new ResponseEntity<>(user, HttpStatus.OK);
4037
}
4138

4239
@Override
43-
public ResponseEntity<RefreshToken> getRefreshTokenWithUsernameAndPassword(String base64encodedUserAndPassword) {
44-
String cleanValue = Utils.validateAuthorizationHeader(AUTHORIZATION_BASIC_PREFIX, base64encodedUserAndPassword);
45-
User authenticatedUser = userAuthorizationService.authenticateUserWithUsernameAndPassword(cleanValue);
40+
public ResponseEntity<RefreshToken> getRefreshTokenWithUsernameAndPassword(String base64encodedUserAndPasswordWithHeader) {
41+
User authenticatedUser = userAuthorizationService.authenticateUserWithUsernameAndPassword(base64encodedUserAndPasswordWithHeader);
4642
RefreshToken refreshToken = userBusinessService.getRefreshTokenForUser(authenticatedUser);
4743
return new ResponseEntity<>(refreshToken, HttpStatus.OK);
4844
}
4945

5046
@Override
51-
public ResponseEntity<AccessToken> getAccessTokenByRefreshToken(String refreshToken) {
52-
String cleanValue = Utils.validateAuthorizationHeader(AUTHORIZATION_BEARER_PREFIX, refreshToken);
53-
User user = userAuthorizationService.authenticateUserWithRefreshToken(cleanValue);
47+
public ResponseEntity<AccessToken> getAccessTokenByRefreshToken(String refreshTokenWithHeader) {
48+
User user = userAuthorizationService.authenticateUserWithRefreshToken(refreshTokenWithHeader);
5449
AccessToken accessToken = accessTokenBusinessService.getValidAccessTokenForUser(user);
5550
return new ResponseEntity<>(accessToken, HttpStatus.OK);
5651
}
5752

5853
@Override
59-
public ResponseEntity<User> updateUserWithAccessToken(UserRegisterForm updatedUser, String accessToken) {
60-
return null;
54+
public ResponseEntity<ServerResponse> updateUserByUserIdAuthenticateWithAccessToken(UserRegisterForm updatedUser, long userId, String accessTokenValue) {
55+
AccessToken accessToken = accessTokenBusinessService.validateAccessTokenValueWithHeader(accessTokenValue);
56+
User authenticatedUser = userAuthorizationService.authenticateUserWithAccessToken(accessToken);
57+
userBusinessService.updateUser(userId, updatedUser, authenticatedUser);
58+
ServerResponse response = new ServerResponse(HttpStatus.CREATED, "User successfully updated.");
59+
return new ResponseEntity<>(response, HttpStatus.CREATED);
6160
}
6261

6362
@Override
6463
public ResponseEntity<ServerResponse> registerNewUserWithAccessToken(UserRegisterForm newUser, String accessToken) {
65-
AccessToken validAccessToken = accessTokenBusinessService.validateAccessTokenValue(accessToken);
64+
AccessToken validAccessToken = accessTokenBusinessService.validateAccessTokenValueWithHeader(accessToken);
6665
userAuthorizationService.authenticateUserWithAccessTokenAndGroup(validAccessToken, ADMIN);
6766
userBusinessService.registerNewUser(newUser);
6867
return new ResponseEntity<>(new ServerResponse(HttpStatus.CREATED, "User successfully created."), HttpStatus.CREATED);
6968
}
7069

7170
@Override
7271
public ResponseEntity<User> findUserByUsernameAndAccessToken(String username, String accessToken) {
73-
AccessToken token = accessTokenBusinessService.validateAccessTokenValue(accessToken);
72+
AccessToken token = accessTokenBusinessService.validateAccessTokenValueWithHeader(accessToken);
7473
userAuthorizationService.authenticateUserWithAccessToken(token);
7574
User foundUser = userBusinessService.findUserByUsername(username);
7675
return new ResponseEntity<>(foundUser, HttpStatus.OK);

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ public interface UserRestServiceInterface {
1111
ResponseEntity<User> getUserByUserIdAuthenticateWithAccessToken(String accessToken, long userId);
1212
ResponseEntity<RefreshToken> getRefreshTokenWithUsernameAndPassword(String base64encodedUserAndPassword);
1313
ResponseEntity<AccessToken> getAccessTokenByRefreshToken(String refreshToken);
14-
ResponseEntity<User> updateUserWithAccessToken(UserRegisterForm updatedUser, String accessToken);
14+
ResponseEntity<ServerResponse> updateUserByUserIdAuthenticateWithAccessToken(UserRegisterForm updatedUser, long userId, String accessToken);
1515
ResponseEntity<ServerResponse> registerNewUserWithAccessToken(UserRegisterForm newUser, String accessToken);
1616
ResponseEntity<User> findUserByUsernameAndAccessToken(String username, String accessToken);
1717
}

0 commit comments

Comments
 (0)