Revize 381df0b2
Přidáno uživatelem Jakub Šmíd před asi 3 roky(ů)
backend/pom.xml | ||
---|---|---|
85 | 85 |
<version>2.4.2</version> |
86 | 86 |
<optional>true</optional> |
87 | 87 |
</dependency> |
88 |
|
|
89 |
<dependency> |
|
90 |
<groupId>org.springdoc</groupId> |
|
91 |
<artifactId>springdoc-openapi-ui</artifactId> |
|
92 |
<version>1.6.6</version> |
|
93 |
</dependency> |
|
88 | 94 |
</dependencies> |
89 | 95 |
|
90 | 96 |
<build> |
backend/src/main/java/cz/zcu/kiv/backendapi/exception/ApiExceptionHandler.java | ||
---|---|---|
1 | 1 |
package cz.zcu.kiv.backendapi.exception; |
2 | 2 |
|
3 | 3 |
|
4 |
import org.springframework.http.HttpStatus; |
|
4 | 5 |
import org.springframework.http.ResponseEntity; |
6 |
import org.springframework.security.core.userdetails.UsernameNotFoundException; |
|
7 |
import org.springframework.validation.BindException; |
|
5 | 8 |
import org.springframework.web.bind.annotation.ControllerAdvice; |
6 | 9 |
import org.springframework.web.bind.annotation.ExceptionHandler; |
7 | 10 |
|
8 | 11 |
@ControllerAdvice |
9 | 12 |
public class ApiExceptionHandler { |
10 | 13 |
|
14 |
/** |
|
15 |
* Handles bind exception |
|
16 |
* |
|
17 |
* @param exception bind exception |
|
18 |
* @return response entity with message and status |
|
19 |
*/ |
|
20 |
@ExceptionHandler(value = {BindException.class}) |
|
21 |
public ResponseEntity<Object> handleBindException(BindException exception) { |
|
22 |
return new ResponseEntity<>(exception.getBindingResult().getAllErrors(), HttpStatus.BAD_REQUEST); |
|
23 |
} |
|
24 |
|
|
25 |
/** |
|
26 |
* Handles username not found exception |
|
27 |
* |
|
28 |
* @param exception username not found exception |
|
29 |
* @return response entity with message and status |
|
30 |
*/ |
|
31 |
@ExceptionHandler(value = {UsernameNotFoundException.class}) |
|
32 |
public ResponseEntity<Object> handleUsernameNotFoundException(UsernameNotFoundException exception) { |
|
33 |
return new ResponseEntity<>(exception.getMessage(), HttpStatus.NOT_FOUND); |
|
34 |
} |
|
35 |
|
|
36 |
/** |
|
37 |
* Handles api request exception |
|
38 |
* |
|
39 |
* @param exception api request found exception |
|
40 |
* @return response entity with message and status |
|
41 |
*/ |
|
11 | 42 |
@ExceptionHandler(value = {ApiRequestException.class}) |
12 | 43 |
public ResponseEntity<Object> handleApiRequestException(ApiRequestException exception) { |
13 |
return new ResponseEntity<>(exception, exception.getHttpStatus()); |
|
44 |
return new ResponseEntity<>(exception.getMessage(), exception.getHttpStatus());
|
|
14 | 45 |
} |
15 | 46 |
} |
backend/src/main/java/cz/zcu/kiv/backendapi/security/SecurityConfig.java | ||
---|---|---|
11 | 11 |
import org.springframework.security.authentication.dao.DaoAuthenticationProvider; |
12 | 12 |
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; |
13 | 13 |
import org.springframework.security.config.annotation.web.builders.HttpSecurity; |
14 |
import org.springframework.security.config.annotation.web.builders.WebSecurity; |
|
14 | 15 |
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; |
15 | 16 |
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; |
16 | 17 |
import org.springframework.security.config.http.SessionCreationPolicy; |
17 | 18 |
import org.springframework.security.core.userdetails.UserDetailsService; |
18 | 19 |
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; |
20 |
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint; |
|
19 | 21 |
|
20 | 22 |
/** |
21 | 23 |
* Security config class |
... | ... | |
42 | 44 |
/** |
43 | 45 |
* List of permitted pages without login |
44 | 46 |
*/ |
45 |
private final String[] permittedUrls = new String[]{"/login", "/token/refresh"}; |
|
47 |
private final String[] permittedUrls = new String[]{"/login", "/token/refresh", "/register", "/swagger-ui/**", |
|
48 |
"/swagger-ui.html", "/v3/api-docs", "/v3/api-docs/swagger-config"}; |
|
46 | 49 |
|
47 | 50 |
/** |
48 | 51 |
* Security configuration |
... | ... | |
57 | 60 |
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) |
58 | 61 |
.and() |
59 | 62 |
.authorizeRequests() |
60 |
.antMatchers("/login").permitAll() |
|
61 | 63 |
.antMatchers(permittedUrls).permitAll() |
62 | 64 |
.antMatchers("/admin/**").hasRole(Role.ADMIN.name()) |
63 | 65 |
.antMatchers("/write/**").hasAuthority(Permission.WRITE.name()) |
... | ... | |
66 | 68 |
.anyRequest() |
67 | 69 |
.authenticated() |
68 | 70 |
.and() |
71 |
.logout() |
|
72 |
.and() |
|
69 | 73 |
.addFilter(new JwtUsernameAndPasswordAuthenticationFilter(authenticationManager(), jwtUtils)) |
70 | 74 |
.addFilterAfter(new JwtTokenVerifier(jwtUtils, permittedUrls), JwtUsernameAndPasswordAuthenticationFilter.class); |
75 |
|
|
71 | 76 |
} |
77 |
// @Override |
|
78 |
// public void configure(WebSecurity web) throws Exception { |
|
79 |
// web.ignoring().antMatchers("/swagger-ui/**", "/v3/api-docs","/v3/api-docs/swagger-config", "/swagger-ui.html", "/swagger-ui-custom.html"); |
|
80 |
// } |
|
81 |
|
|
72 | 82 |
|
73 | 83 |
/** |
74 | 84 |
* Sets authentication provider to authentication manager |
... | ... | |
93 | 103 |
provider.setPasswordEncoder(bCryptPasswordEncoder); |
94 | 104 |
return provider; |
95 | 105 |
} |
96 |
|
|
97 | 106 |
} |
backend/src/main/java/cz/zcu/kiv/backendapi/user/IUserService.java | ||
---|---|---|
1 | 1 |
package cz.zcu.kiv.backendapi.user; |
2 | 2 |
|
3 |
import java.util.List; |
|
4 |
|
|
3 | 5 |
/** |
4 | 6 |
* User service interface |
5 | 7 |
*/ |
... | ... | |
12 | 14 |
* @return user by username |
13 | 15 |
*/ |
14 | 16 |
UserEntity getUserByName(String username); |
17 |
|
|
18 |
void registerNewUser(UserDto userDto); |
|
19 |
|
|
20 |
void updateUser(UserDto userDto); |
|
21 |
|
|
22 |
void deleteUser(String username); |
|
23 |
|
|
24 |
List<UserDto> getAllUsers(); |
|
15 | 25 |
} |
backend/src/main/java/cz/zcu/kiv/backendapi/user/UserController.java | ||
---|---|---|
11 | 11 |
import lombok.extern.slf4j.Slf4j; |
12 | 12 |
import org.springframework.http.HttpHeaders; |
13 | 13 |
import org.springframework.http.HttpStatus; |
14 |
import org.springframework.http.ResponseEntity; |
|
14 | 15 |
import org.springframework.security.core.GrantedAuthority; |
15 |
import org.springframework.web.bind.annotation.GetMapping; |
|
16 |
import org.springframework.web.bind.annotation.RestController; |
|
16 |
import org.springframework.web.bind.annotation.*; |
|
17 | 17 |
|
18 | 18 |
import javax.servlet.http.HttpServletRequest; |
19 | 19 |
import javax.servlet.http.HttpServletResponse; |
20 |
import javax.validation.Valid; |
|
20 | 21 |
import java.io.IOException; |
21 | 22 |
import java.time.LocalDateTime; |
22 | 23 |
import java.time.ZoneId; |
23 | 24 |
import java.util.Date; |
25 |
import java.util.List; |
|
24 | 26 |
import java.util.stream.Collectors; |
25 | 27 |
|
26 | 28 |
/** |
... | ... | |
42 | 44 |
private final JwtUtils jwtUtils; |
43 | 45 |
|
44 | 46 |
|
47 |
/** |
|
48 |
* Registers new user |
|
49 |
* |
|
50 |
* @param userDto user DTO |
|
51 |
*/ |
|
52 |
@PostMapping("/register") |
|
53 |
public void registerNewUser(@RequestBody @Valid UserDto userDto) { |
|
54 |
userService.registerNewUser(userDto); |
|
55 |
} |
|
56 |
|
|
57 |
/** |
|
58 |
* Returns list of all users |
|
59 |
* |
|
60 |
* @return list of all users |
|
61 |
*/ |
|
62 |
@GetMapping("/users") |
|
63 |
public ResponseEntity<List<UserDto>> getAllUsers() { |
|
64 |
return new ResponseEntity<>(userService.getAllUsers(), HttpStatus.OK); |
|
65 |
} |
|
66 |
|
|
67 |
//TODO check if need, if needed probably change new dto without email, comment otherwise |
|
68 |
@PutMapping("/user/{username}") |
|
69 |
@ResponseStatus(value = HttpStatus.OK) |
|
70 |
public void updateUser(@PathVariable String username, @RequestBody @Valid UserDto userDto) { |
|
71 |
userService.updateUser(userDto); |
|
72 |
} |
|
73 |
|
|
74 |
//TODO check if needed, comment otherwise |
|
75 |
@DeleteMapping("/user/{username}") |
|
76 |
@ResponseStatus(value = HttpStatus.OK) |
|
77 |
public void deleteUser(@PathVariable String username) { |
|
78 |
userService.deleteUser(username); |
|
79 |
} |
|
80 |
|
|
81 |
|
|
45 | 82 |
/** |
46 | 83 |
* Refreshes access token if refresh token is valid |
47 | 84 |
* |
backend/src/main/java/cz/zcu/kiv/backendapi/user/UserDto.java | ||
---|---|---|
1 |
package cz.zcu.kiv.backendapi.user; |
|
2 |
|
|
3 |
import com.sun.istack.NotNull; |
|
4 |
import cz.zcu.kiv.backendapi.validation.ValidEmail; |
|
5 |
import lombok.Data; |
|
6 |
|
|
7 |
import javax.validation.constraints.NotEmpty; |
|
8 |
|
|
9 |
/** |
|
10 |
* User data object |
|
11 |
*/ |
|
12 |
@Data |
|
13 |
public class UserDto { |
|
14 |
/** |
|
15 |
* Name |
|
16 |
*/ |
|
17 |
private String name; |
|
18 |
|
|
19 |
/** |
|
20 |
* Email - must be valid email |
|
21 |
*/ |
|
22 |
@ValidEmail |
|
23 |
@NotNull |
|
24 |
@NotEmpty(message = "Email must not be empty") |
|
25 |
private String email; |
|
26 |
|
|
27 |
/** |
|
28 |
* Whether user has 'READ' authority |
|
29 |
*/ |
|
30 |
private boolean canRead; |
|
31 |
|
|
32 |
/** |
|
33 |
* Whether user has 'WRITE' authority |
|
34 |
*/ |
|
35 |
private boolean canWrite; |
|
36 |
|
|
37 |
/** |
|
38 |
* Whether user has 'DELETE' authority |
|
39 |
*/ |
|
40 |
private boolean canDelete; |
|
41 |
|
|
42 |
|
|
43 |
} |
backend/src/main/java/cz/zcu/kiv/backendapi/user/UserRepository.java | ||
---|---|---|
15 | 15 |
public interface UserRepository extends JpaRepository<UserEntity, String> { |
16 | 16 |
/** |
17 | 17 |
* Returns user by email (username) |
18 |
* |
|
18 | 19 |
* @param username username |
19 | 20 |
* @return optional user by email (username) |
20 | 21 |
*/ |
21 | 22 |
Optional<UserEntity> findByEmail(String username); |
23 |
|
|
22 | 24 |
} |
backend/src/main/java/cz/zcu/kiv/backendapi/user/UserServiceImpl.java | ||
---|---|---|
1 | 1 |
package cz.zcu.kiv.backendapi.user; |
2 | 2 |
|
3 |
import cz.zcu.kiv.backendapi.exception.ApiRequestException; |
|
3 | 4 |
import lombok.RequiredArgsConstructor; |
4 | 5 |
import lombok.extern.slf4j.Slf4j; |
6 |
import org.springframework.http.HttpStatus; |
|
7 |
import org.springframework.security.core.context.SecurityContextHolder; |
|
5 | 8 |
import org.springframework.security.core.userdetails.UserDetails; |
6 | 9 |
import org.springframework.security.core.userdetails.UserDetailsService; |
7 | 10 |
import org.springframework.security.core.userdetails.UsernameNotFoundException; |
8 | 11 |
import org.springframework.stereotype.Service; |
9 | 12 |
import org.springframework.transaction.annotation.Transactional; |
10 | 13 |
|
14 |
import java.util.List; |
|
15 |
import java.util.stream.Collectors; |
|
16 |
|
|
11 | 17 |
/** |
12 | 18 |
* User service implementation |
13 | 19 |
*/ |
... | ... | |
16 | 22 |
@RequiredArgsConstructor |
17 | 23 |
@Slf4j |
18 | 24 |
public class UserServiceImpl implements IUserService, UserDetailsService { |
25 |
/** |
|
26 |
* Message for exception when user is not found by username |
|
27 |
*/ |
|
28 |
private static final String USER_NOT_FOUND = "User with username %s not found"; |
|
29 |
|
|
19 | 30 |
/** |
20 | 31 |
* User repository |
21 | 32 |
*/ |
22 | 33 |
private final UserRepository userRepository; |
23 | 34 |
|
35 |
|
|
24 | 36 |
@Override |
25 | 37 |
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { |
26 | 38 |
return userRepository.findByEmail(username) |
27 | 39 |
.orElseThrow(() -> { |
28 |
log.error(String.format("Username %s not found", username));
|
|
29 |
throw new UsernameNotFoundException(String.format("Username %s not found", username));
|
|
40 |
log.error(String.format(USER_NOT_FOUND, username));
|
|
41 |
throw new UsernameNotFoundException(String.format(USER_NOT_FOUND, username));
|
|
30 | 42 |
}); |
31 | 43 |
} |
32 | 44 |
|
33 | 45 |
@Override |
34 | 46 |
public UserEntity getUserByName(String username) { |
35 | 47 |
return userRepository.findByEmail(username).orElseThrow(() -> { |
36 |
log.error(String.format("Username %s not found", username)); |
|
37 |
throw new UsernameNotFoundException(String.format("Username %s not found", username)); |
|
48 |
log.error(String.format(USER_NOT_FOUND, username)); |
|
49 |
throw new UsernameNotFoundException(String.format(USER_NOT_FOUND, username)); |
|
50 |
}); |
|
51 |
} |
|
52 |
|
|
53 |
@Override |
|
54 |
public void registerNewUser(UserDto userDto) { |
|
55 |
if (userRepository.findByEmail(userDto.getEmail()).isPresent()) { |
|
56 |
log.error("Trying to register new user with username that is already taken: " + userDto.getEmail()); |
|
57 |
throw new ApiRequestException(String.format("User with username %s already exists", userDto.getEmail()), HttpStatus.CONFLICT); |
|
58 |
} |
|
59 |
UserEntity userEntity = new UserEntity(); |
|
60 |
convertDtoToEntity(userDto, userEntity); |
|
61 |
userRepository.save(userEntity); |
|
62 |
} |
|
63 |
|
|
64 |
@Override |
|
65 |
public void updateUser(UserDto userDto) { |
|
66 |
UserEntity userEntity = userRepository.findByEmail(userDto.getEmail()).orElseThrow(() ->{ |
|
67 |
log.error(String.format(USER_NOT_FOUND, userDto.getEmail())); |
|
68 |
throw new UsernameNotFoundException(String.format(USER_NOT_FOUND, userDto.getEmail())); |
|
38 | 69 |
}); |
70 |
convertDtoToEntity(userDto, userEntity); |
|
71 |
userRepository.save(userEntity); |
|
72 |
} |
|
73 |
|
|
74 |
//TODO maybe chceck if user is not deleting himself - or it might be ok |
|
75 |
@Override |
|
76 |
public void deleteUser(String username) { |
|
77 |
UserEntity userEntity = userRepository.findByEmail(username).orElseThrow(() ->{ |
|
78 |
log.error(String.format(USER_NOT_FOUND, username)); |
|
79 |
throw new UsernameNotFoundException(String.format(USER_NOT_FOUND, username)); |
|
80 |
}); |
|
81 |
userRepository.delete(userEntity); |
|
82 |
} |
|
83 |
|
|
84 |
@Override |
|
85 |
public List<UserDto> getAllUsers() { |
|
86 |
return userRepository.findAll().stream().map(this::convertEntityToDto).collect(Collectors.toList()); |
|
87 |
} |
|
88 |
|
|
89 |
/** |
|
90 |
* Converts user DTO to user entity |
|
91 |
* |
|
92 |
* @param userDto user DTO |
|
93 |
* @param userEntity user entity |
|
94 |
*/ |
|
95 |
private void convertDtoToEntity(UserDto userDto, UserEntity userEntity) { |
|
96 |
userEntity.setName(userDto.getName()); |
|
97 |
userEntity.setEmail(userDto.getEmail()); |
|
98 |
userEntity.setPermissions(getPermissionsFromDto(userDto)); |
|
99 |
} |
|
100 |
|
|
101 |
/** |
|
102 |
* Converts user entity to user DTO |
|
103 |
* |
|
104 |
* @param userEntity user entity |
|
105 |
* @return user DTO from user entity |
|
106 |
*/ |
|
107 |
private UserDto convertEntityToDto(UserEntity userEntity) { |
|
108 |
UserDto userDto = new UserDto(); |
|
109 |
userDto.setName(userEntity.getName()); |
|
110 |
userDto.setEmail(userEntity.getEmail()); |
|
111 |
setPermissionsToDto(userDto, userEntity); |
|
112 |
return userDto; |
|
113 |
} |
|
114 |
|
|
115 |
/** |
|
116 |
* Sets permission to user DTO based on user entity |
|
117 |
* |
|
118 |
* @param userDto user DTO |
|
119 |
* @param userEntity user entity |
|
120 |
*/ |
|
121 |
private void setPermissionsToDto(UserDto userDto, UserEntity userEntity) { |
|
122 |
byte userPermissions = userEntity.getPermissions(); |
|
123 |
if ((userPermissions & Permission.READ.getBit()) == Permission.READ.getBit()) { |
|
124 |
userDto.setCanRead(true); |
|
125 |
} |
|
126 |
if ((userPermissions & Permission.WRITE.getBit()) == Permission.WRITE.getBit()) { |
|
127 |
userDto.setCanWrite(true); |
|
128 |
} |
|
129 |
if ((userPermissions & Permission.DELETE.getBit()) == Permission.DELETE.getBit()) { |
|
130 |
userDto.setCanDelete(true); |
|
131 |
} |
|
132 |
} |
|
133 |
|
|
134 |
/** |
|
135 |
* Returns permissions as byte from user DTO |
|
136 |
* |
|
137 |
* @param userDto user DTO |
|
138 |
* @return permissions as byte |
|
139 |
*/ |
|
140 |
private byte getPermissionsFromDto(UserDto userDto) { |
|
141 |
byte permissions = (byte) 0; |
|
142 |
if (userDto.isCanRead()) { |
|
143 |
permissions |= Permission.READ.getBit(); |
|
144 |
} |
|
145 |
if (userDto.isCanWrite()) { |
|
146 |
permissions |= Permission.WRITE.getBit(); |
|
147 |
} |
|
148 |
if (userDto.isCanDelete()) { |
|
149 |
permissions |= Permission.DELETE.getBit(); |
|
150 |
} |
|
151 |
return permissions; |
|
39 | 152 |
} |
40 | 153 |
} |
backend/src/main/java/cz/zcu/kiv/backendapi/validation/EmailValidator.java | ||
---|---|---|
1 |
package cz.zcu.kiv.backendapi.validation; |
|
2 |
|
|
3 |
import javax.validation.ConstraintValidator; |
|
4 |
import javax.validation.ConstraintValidatorContext; |
|
5 |
import java.util.regex.Matcher; |
|
6 |
import java.util.regex.Pattern; |
|
7 |
|
|
8 |
/** |
|
9 |
* Validator for email |
|
10 |
*/ |
|
11 |
public class EmailValidator implements ConstraintValidator<ValidEmail, String> { |
|
12 |
/** |
|
13 |
* Email string pattern |
|
14 |
*/ |
|
15 |
private static final String EMAIL_PATTERN = "^[_A-Za-z0-9-+]+(.[_A-Za-z0-9-]+)*@[A-Za-z0-9-]+(.[A-Za-z0-9]+)*(.[A-Za-z]{2,})$"; |
|
16 |
|
|
17 |
/** |
|
18 |
* Email patter |
|
19 |
*/ |
|
20 |
private static final Pattern PATTERN = Pattern.compile(EMAIL_PATTERN); |
|
21 |
|
|
22 |
/** |
|
23 |
* Check if email is valid (matches pattern) |
|
24 |
* |
|
25 |
* @param email email |
|
26 |
* @param constraintValidatorContext constraint validator context |
|
27 |
* @return true if email is valid, false otherwise |
|
28 |
*/ |
|
29 |
@Override |
|
30 |
public boolean isValid(final String email, final ConstraintValidatorContext constraintValidatorContext) { |
|
31 |
final Matcher matcher = PATTERN.matcher(email); |
|
32 |
return matcher.matches(); |
|
33 |
} |
|
34 |
} |
backend/src/main/java/cz/zcu/kiv/backendapi/validation/ValidEmail.java | ||
---|---|---|
1 |
package cz.zcu.kiv.backendapi.validation; |
|
2 |
|
|
3 |
import javax.validation.Constraint; |
|
4 |
import javax.validation.Payload; |
|
5 |
import java.lang.annotation.*; |
|
6 |
|
|
7 |
/** |
|
8 |
* Constraint to email is valid |
|
9 |
*/ |
|
10 |
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.ANNOTATION_TYPE}) |
|
11 |
@Retention(RetentionPolicy.RUNTIME) |
|
12 |
@Constraint(validatedBy = EmailValidator.class) |
|
13 |
@Documented |
|
14 |
public @interface ValidEmail { |
|
15 |
/** |
|
16 |
* Error message when validation fails |
|
17 |
* |
|
18 |
* @return error message |
|
19 |
*/ |
|
20 |
String message() default "Email is not valid - example \"x@x.xx\" (at least one character before and after \"@\" and two characters after \".\")"; |
|
21 |
|
|
22 |
Class<?>[] groups() default {}; |
|
23 |
|
|
24 |
Class<? extends Payload>[] payload() default {}; |
|
25 |
} |
Také k dispozici: Unified diff
Added OpenAPI, user DTO and some basic user controller endpoints
re #9204
re #9215