Projekt

Obecné

Profil

« Předchozí | Další » 

Revize 381df0b2

Přidáno uživatelem Jakub Šmíd před asi 3 roky(ů)

Added OpenAPI, user DTO and some basic user controller endpoints
re #9204
re #9215

Zobrazit rozdíly:

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