From 87f588d5e8500dee366098a9bacf37524f6ec3f5 Mon Sep 17 00:00:00 2001 From: Oleg Levochkin Date: Sun, 8 Nov 2020 17:48:36 +0200 Subject: [PATCH 01/26] add basic dependencies (#3) * add dependency * fixes after review --- pom.xml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/pom.xml b/pom.xml index c3a79a4..5259ea5 100644 --- a/pom.xml +++ b/pom.xml @@ -24,6 +24,30 @@ org.springframework.boot spring-boot-starter-web + + org.apache.commons + commons-lang3 + 3.2.1 + + + org.projectlombok + lombok + 1.18.16 + provided + + + + org.mockito + mockito-all + 1.10.19 + test + + + org.junit.jupiter + junit-jupiter-api + 5.7.0 + test + From 22b10f066cb6f9a2d134d53b5d919726d61d3517 Mon Sep 17 00:00:00 2001 From: Serhii Nahornyi Date: Sun, 8 Nov 2020 22:07:55 +0200 Subject: [PATCH 02/26] Add compiler settings (#4) * Add compiler plugin * Add checkstyle plugin * Add git CI Co-authored-by: Serhii Nahornyi --- .github/workflows/pullrequest.yml | 16 +++ checkstyle.xml | 156 +++++++++++++++++++++++++ pom.xml | 42 +++++++ src/main/java/com/kpi/project/App.java | 2 + 4 files changed, 216 insertions(+) create mode 100644 .github/workflows/pullrequest.yml create mode 100644 checkstyle.xml diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml new file mode 100644 index 0000000..9330a2f --- /dev/null +++ b/.github/workflows/pullrequest.yml @@ -0,0 +1,16 @@ +name: Java CI + +on: [push] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 11 + uses: actions/setup-java@v1 + with: + java-version: 11 + - name: Build with Maven + run: mvn -B package --file pom.xml diff --git a/checkstyle.xml b/checkstyle.xml new file mode 100644 index 0000000..4f94986 --- /dev/null +++ b/checkstyle.xml @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pom.xml b/pom.xml index 5259ea5..b98070a 100644 --- a/pom.xml +++ b/pom.xml @@ -17,6 +17,10 @@ 11 11 + UTF-8 + 8.37 + 3.1.1 + 1.38.0 @@ -50,4 +54,42 @@ + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.0 + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.apache.maven.plugins + maven-checkstyle-plugin + 3.1.1 + + checkstyle.xml + UTF-8 + true + true + false + + + + validate + validate + + check + + + + + + + + diff --git a/src/main/java/com/kpi/project/App.java b/src/main/java/com/kpi/project/App.java index 2981ed9..949e52d 100644 --- a/src/main/java/com/kpi/project/App.java +++ b/src/main/java/com/kpi/project/App.java @@ -3,8 +3,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +@SuppressWarnings("checkstyle:hideutilityclassconstructor") @SpringBootApplication public class App { + public static void main(String[] args) { SpringApplication.run(App.class, args); } From ccd8518a335e1accee4aed031da5c99bf42727cc Mon Sep 17 00:00:00 2001 From: Serhii Nahornyi Date: Tue, 10 Nov 2020 21:47:10 +0200 Subject: [PATCH 03/26] Add JWT security on back-end part. (#10) Co-authored-by: Serhii Nahornyi --- pom.xml | 24 ++++++-- .../project/config/SecurityConfiguration.java | 52 ++++++++++++++++++ .../kpi/project/filter/JwtRequestFilter.java | 54 ++++++++++++++++++ .../project/model/AuthenticationRequest.java | 14 +++++ .../project/model/AuthenticationResponse.java | 13 +++++ .../kpi/project/resource/SystemResource.java | 42 ++++++++++++++ .../kpi/project/resource/TestController.java | 13 +++++ .../com/kpi/project/service/UserService.java | 18 ++++++ .../java/com/kpi/project/util/JwtUtil.java | 55 +++++++++++++++++++ 9 files changed, 279 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/kpi/project/config/SecurityConfiguration.java create mode 100644 src/main/java/com/kpi/project/filter/JwtRequestFilter.java create mode 100644 src/main/java/com/kpi/project/model/AuthenticationRequest.java create mode 100644 src/main/java/com/kpi/project/model/AuthenticationResponse.java create mode 100644 src/main/java/com/kpi/project/resource/SystemResource.java create mode 100644 src/main/java/com/kpi/project/resource/TestController.java create mode 100644 src/main/java/com/kpi/project/service/UserService.java create mode 100644 src/main/java/com/kpi/project/util/JwtUtil.java diff --git a/pom.xml b/pom.xml index b98070a..1616492 100644 --- a/pom.xml +++ b/pom.xml @@ -18,9 +18,7 @@ 11 11 UTF-8 - 8.37 3.1.1 - 1.38.0 @@ -28,6 +26,22 @@ org.springframework.boot spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-security + + + io.jsonwebtoken + jjwt + 0.9.1 + + + javax.xml.bind + jaxb-api + 2.3.0 + + org.apache.commons commons-lang3 @@ -70,7 +84,7 @@ org.apache.maven.plugins maven-checkstyle-plugin - 3.1.1 + ${checkstyle.plugin.version} checkstyle.xml UTF-8 @@ -83,13 +97,11 @@ validate validate - check + checkstyle - - diff --git a/src/main/java/com/kpi/project/config/SecurityConfiguration.java b/src/main/java/com/kpi/project/config/SecurityConfiguration.java new file mode 100644 index 0000000..c56653b --- /dev/null +++ b/src/main/java/com/kpi/project/config/SecurityConfiguration.java @@ -0,0 +1,52 @@ +package com.kpi.project.config; + +import com.kpi.project.filter.JwtRequestFilter; +import com.kpi.project.service.UserService; +import org.springframework.context.annotation.Bean; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.password.NoOpPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +@EnableWebSecurity +public class SecurityConfiguration extends WebSecurityConfigurerAdapter { + + private final UserService userService; + private final JwtRequestFilter jwtRequestFilter; + + public SecurityConfiguration(UserService userService, JwtRequestFilter jwtRequestFilter) { + this.userService = userService; + this.jwtRequestFilter = jwtRequestFilter; + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.userDetailsService(userService); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.csrf().disable() + .authorizeRequests().antMatchers("/authenticate").permitAll() + .anyRequest().authenticated() + .and().sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS); + http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class); + } + + @Override + @Bean + public AuthenticationManager authenticationManagerBean() throws Exception { + return super.authenticationManagerBean(); + } + + @Bean + public PasswordEncoder getPasswordEncoder() { + return NoOpPasswordEncoder.getInstance(); + } +} diff --git a/src/main/java/com/kpi/project/filter/JwtRequestFilter.java b/src/main/java/com/kpi/project/filter/JwtRequestFilter.java new file mode 100644 index 0000000..be7e44e --- /dev/null +++ b/src/main/java/com/kpi/project/filter/JwtRequestFilter.java @@ -0,0 +1,54 @@ +package com.kpi.project.filter; + +import com.kpi.project.service.UserService; +import com.kpi.project.util.JwtUtil; +import org.apache.commons.lang3.StringUtils; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@Component +public class JwtRequestFilter extends OncePerRequestFilter { + + private final JwtUtil jwtUtil; + private final UserService userService; + + public JwtRequestFilter(JwtUtil jwtUtil, UserService userService) { + this.jwtUtil = jwtUtil; + this.userService = userService; + } + + @Override + protected void doFilterInternal(HttpServletRequest httpServletRequest, + HttpServletResponse httpServletResponse, FilterChain filterChain) + throws ServletException, IOException { + final String authorizationHeader = httpServletRequest.getHeader("Authorization"); + String username = null; + String token = null; + if (StringUtils.isNotBlank(authorizationHeader) && authorizationHeader.startsWith("Bearer ")) { + token = authorizationHeader.substring(7); + username = jwtUtil.extractUsername(token); + } + if (StringUtils.isNotBlank(username) && SecurityContextHolder.getContext().getAuthentication() == null) { + final UserDetails userDetails = userService.loadUserByUsername(username); + if (jwtUtil.validateToken(token, userDetails)) { + final UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = + new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); + usernamePasswordAuthenticationToken + .setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest)); + SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken); + + } + } + filterChain.doFilter(httpServletRequest, httpServletResponse); + } +} diff --git a/src/main/java/com/kpi/project/model/AuthenticationRequest.java b/src/main/java/com/kpi/project/model/AuthenticationRequest.java new file mode 100644 index 0000000..fc5e9a9 --- /dev/null +++ b/src/main/java/com/kpi/project/model/AuthenticationRequest.java @@ -0,0 +1,14 @@ +package com.kpi.project.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class AuthenticationRequest { + + private String username; + private String password; +} diff --git a/src/main/java/com/kpi/project/model/AuthenticationResponse.java b/src/main/java/com/kpi/project/model/AuthenticationResponse.java new file mode 100644 index 0000000..478ac2e --- /dev/null +++ b/src/main/java/com/kpi/project/model/AuthenticationResponse.java @@ -0,0 +1,13 @@ +package com.kpi.project.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class AuthenticationResponse { + + private String token; +} diff --git a/src/main/java/com/kpi/project/resource/SystemResource.java b/src/main/java/com/kpi/project/resource/SystemResource.java new file mode 100644 index 0000000..05ef8e2 --- /dev/null +++ b/src/main/java/com/kpi/project/resource/SystemResource.java @@ -0,0 +1,42 @@ +package com.kpi.project.resource; + +import com.kpi.project.model.AuthenticationRequest; +import com.kpi.project.model.AuthenticationResponse; +import com.kpi.project.service.UserService; +import com.kpi.project.util.JwtUtil; +import org.springframework.http.ResponseEntity; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class SystemResource { + + private final AuthenticationManager authenticationManager; + private final UserService userService; + private final JwtUtil jwtUtil; + + public SystemResource(AuthenticationManager authenticationManager, UserService userService, JwtUtil jwtUtil) { + this.authenticationManager = authenticationManager; + this.userService = userService; + this.jwtUtil = jwtUtil; + } + + @PostMapping("/authenticate") + public ResponseEntity authenticate(@RequestBody AuthenticationRequest authenticationRequest) throws Exception { + try { + authenticationManager.authenticate( + new UsernamePasswordAuthenticationToken(authenticationRequest.getUsername(), + authenticationRequest.getPassword())); + } catch (BadCredentialsException e) { + throw new Exception("incorrect password or login", e); + } + final UserDetails userDetails = userService.loadUserByUsername(authenticationRequest.getUsername()); + final String jwt = jwtUtil.generateToken(userDetails); + return ResponseEntity.ok(new AuthenticationResponse(jwt)); + } +} diff --git a/src/main/java/com/kpi/project/resource/TestController.java b/src/main/java/com/kpi/project/resource/TestController.java new file mode 100644 index 0000000..ca916df --- /dev/null +++ b/src/main/java/com/kpi/project/resource/TestController.java @@ -0,0 +1,13 @@ +package com.kpi.project.resource; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class TestController { + + @RequestMapping({"/test", "/"}) + public String test() { + return "test.ok"; + } +} diff --git a/src/main/java/com/kpi/project/service/UserService.java b/src/main/java/com/kpi/project/service/UserService.java new file mode 100644 index 0000000..2c6f527 --- /dev/null +++ b/src/main/java/com/kpi/project/service/UserService.java @@ -0,0 +1,18 @@ +package com.kpi.project.service; + +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; + +@Service +public class UserService implements UserDetailsService { + + @Override + public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { + return new User("user", "pass", new ArrayList<>()); + } +} diff --git a/src/main/java/com/kpi/project/util/JwtUtil.java b/src/main/java/com/kpi/project/util/JwtUtil.java new file mode 100644 index 0000000..e6f6cdc --- /dev/null +++ b/src/main/java/com/kpi/project/util/JwtUtil.java @@ -0,0 +1,55 @@ +package com.kpi.project.util; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Service; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +@Service +public class JwtUtil { + + private static final String SIGNING_KEY = "secret"; + + public String extractUsername(String token) { + return extractClaim(token, Claims::getSubject); + } + + public Date extractExpiration(String token) { + return extractClaim(token, Claims::getExpiration); + } + + public T extractClaim(String token, Function claimsResolver) { + final Claims claims = extractAllClaims(token); + return claimsResolver.apply(claims); + } + + private Claims extractAllClaims(String token) { + return Jwts.parser().setSigningKey(SIGNING_KEY).parseClaimsJws(token).getBody(); + } + + private Boolean isTokenExpired(String token) { + return extractExpiration(token).before(new Date()); + } + + public String generateToken(UserDetails userDetails) { + Map claims = new HashMap<>(); + return createToken(claims, userDetails.getUsername()); + } + + private String createToken(Map claims, String subject) { + return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis())) + .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10)) + .signWith(SignatureAlgorithm.HS256, SIGNING_KEY).compact(); + } + + public Boolean validateToken(String token, UserDetails userDetails) { + final String username = extractUsername(token); + return (username.equals(userDetails.getUsername())) && !isTokenExpired(token); + } +} From a1612a91bba9fd6aa9f08648b65d8d78a7d17829 Mon Sep 17 00:00:00 2001 From: Oleg Levochkin Date: Fri, 13 Nov 2020 01:11:26 +0200 Subject: [PATCH 04/26] Change token expiration (#12) Change token expiration time to 30min --- src/main/java/com/kpi/project/util/JwtUtil.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/kpi/project/util/JwtUtil.java b/src/main/java/com/kpi/project/util/JwtUtil.java index e6f6cdc..c295a47 100644 --- a/src/main/java/com/kpi/project/util/JwtUtil.java +++ b/src/main/java/com/kpi/project/util/JwtUtil.java @@ -15,6 +15,7 @@ public class JwtUtil { private static final String SIGNING_KEY = "secret"; + private static final int THIRTY_MINUTES_IN_MILLISECONDS = 1000 * 60 * 30; public String extractUsername(String token) { return extractClaim(token, Claims::getSubject); @@ -38,13 +39,13 @@ private Boolean isTokenExpired(String token) { } public String generateToken(UserDetails userDetails) { - Map claims = new HashMap<>(); + final Map claims = new HashMap<>(); return createToken(claims, userDetails.getUsername()); } private String createToken(Map claims, String subject) { return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis())) - .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10)) + .setExpiration(new Date(System.currentTimeMillis() + THIRTY_MINUTES_IN_MILLISECONDS)) .signWith(SignatureAlgorithm.HS256, SIGNING_KEY).compact(); } From dba1eac5008f095359b66eb5311967f0d0a6d84d Mon Sep 17 00:00:00 2001 From: Oleg Levochkin Date: Fri, 13 Nov 2020 02:53:06 +0200 Subject: [PATCH 05/26] Create user model (#13) Create basic user model for authentication --- .../project/model/AuthenticationRequest.java | 1 + src/main/java/com/kpi/project/model/User.java | 50 +++++++++++++++++++ .../com/kpi/project/service/UserService.java | 9 ++-- 3 files changed, 56 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/kpi/project/model/User.java diff --git a/src/main/java/com/kpi/project/model/AuthenticationRequest.java b/src/main/java/com/kpi/project/model/AuthenticationRequest.java index fc5e9a9..52278f7 100644 --- a/src/main/java/com/kpi/project/model/AuthenticationRequest.java +++ b/src/main/java/com/kpi/project/model/AuthenticationRequest.java @@ -11,4 +11,5 @@ public class AuthenticationRequest { private String username; private String password; + } diff --git a/src/main/java/com/kpi/project/model/User.java b/src/main/java/com/kpi/project/model/User.java new file mode 100644 index 0000000..f949e25 --- /dev/null +++ b/src/main/java/com/kpi/project/model/User.java @@ -0,0 +1,50 @@ +package com.kpi.project.model; + +import lombok.Data; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; +import java.util.Collections; + +@Data +public class User implements UserDetails { + + private String email; + private String password; + + @Override + public Collection getAuthorities() { + return Collections.emptyList(); + } + + @Override + public String getPassword() { + return password; + } + + @Override + public String getUsername() { + return email; + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } +} diff --git a/src/main/java/com/kpi/project/service/UserService.java b/src/main/java/com/kpi/project/service/UserService.java index 2c6f527..dc577bb 100644 --- a/src/main/java/com/kpi/project/service/UserService.java +++ b/src/main/java/com/kpi/project/service/UserService.java @@ -1,18 +1,19 @@ package com.kpi.project.service; -import org.springframework.security.core.userdetails.User; +import com.kpi.project.model.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; -import java.util.ArrayList; - @Service public class UserService implements UserDetailsService { @Override public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { - return new User("user", "pass", new ArrayList<>()); + final User user = new User(); + user.setEmail("user"); + user.setPassword("pass"); + return user; } } From 8a2759d484ed5559aeba86bade5bfd845b899eb2 Mon Sep 17 00:00:00 2001 From: Oleg Levochkin Date: Fri, 13 Nov 2020 18:59:23 +0200 Subject: [PATCH 06/26] Create/client web (#17) Created client directory Added site name Added basic dependencies --- client-web/.gitignore | 22 +++++++++++++++++++ client-web/package.json | 42 ++++++++++++++++++++++++++++++++++++ client-web/public/index.html | 12 +++++++++++ client-web/src/App.js | 9 ++++++++ client-web/src/index.js | 10 +++++++++ 5 files changed, 95 insertions(+) create mode 100644 client-web/.gitignore create mode 100644 client-web/package.json create mode 100644 client-web/public/index.html create mode 100644 client-web/src/App.js create mode 100644 client-web/src/index.js diff --git a/client-web/.gitignore b/client-web/.gitignore new file mode 100644 index 0000000..cdd9008 --- /dev/null +++ b/client-web/.gitignore @@ -0,0 +1,22 @@ +# dependencies +/node_modules +/.pnp +.pnp.js +package-lock.json + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/client-web/package.json b/client-web/package.json new file mode 100644 index 0000000..38c19e8 --- /dev/null +++ b/client-web/package.json @@ -0,0 +1,42 @@ +{ + "name": "front-end", + "version": "0.1.0", + "private": true, + "dependencies": { + "@material-ui/core": "^4.11.0", + "@testing-library/jest-dom": "^5.11.5", + "@testing-library/react": "^11.1.2", + "@testing-library/user-event": "^12.2.2", + "bootstrap": "^4.5.3", + "node-sass": "^5.0.0", + "react": "^17.0.1", + "react-dom": "^17.0.1", + "react-redux": "^7.2.2", + "react-router-dom": "^5.2.0", + "react-scripts": "4.0.0", + "redux": "^4.0.5", + "web-vitals": "^0.2.4" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version" + ] + } +} diff --git a/client-web/public/index.html b/client-web/public/index.html new file mode 100644 index 0000000..54ff46a --- /dev/null +++ b/client-web/public/index.html @@ -0,0 +1,12 @@ + + + + + + YouFace + + + +
+ + diff --git a/client-web/src/App.js b/client-web/src/App.js new file mode 100644 index 0000000..667f886 --- /dev/null +++ b/client-web/src/App.js @@ -0,0 +1,9 @@ +import React from "react" + +function App() { + return ( +

test

+ ); +} + +export default App; diff --git a/client-web/src/index.js b/client-web/src/index.js new file mode 100644 index 0000000..c1f31c5 --- /dev/null +++ b/client-web/src/index.js @@ -0,0 +1,10 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import App from './App'; + +ReactDOM.render( + + + , + document.getElementById('root') +); From 87f8c4028813971708b8634d382bfd1ae9e8f449 Mon Sep 17 00:00:00 2001 From: Serhii Nahornyi Date: Fri, 13 Nov 2020 20:32:20 +0200 Subject: [PATCH 07/26] Add MySql DB to project (#20) Added MySql DB to project. Made User model as hibernate entity. --- pom.xml | 10 ++++++++++ src/main/java/com/kpi/project/model/User.java | 16 ++++++++++++++++ .../kpi/project/repository/UserRepository.java | 13 +++++++++++++ .../com/kpi/project/service/UserService.java | 10 +++++++++- src/main/resources/application.properties | 5 +++++ .../com/kpi/project/service/UserServiceTest.java | 11 +++++++++++ 6 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/kpi/project/repository/UserRepository.java create mode 100644 src/main/resources/application.properties create mode 100644 src/test/java/com/kpi/project/service/UserServiceTest.java diff --git a/pom.xml b/pom.xml index 1616492..ac07d81 100644 --- a/pom.xml +++ b/pom.xml @@ -27,6 +27,16 @@ spring-boot-starter-web
+ + org.springframework.boot + spring-boot-starter-data-jpa + + + mysql + mysql-connector-java + runtime + + org.springframework.boot spring-boot-starter-security diff --git a/src/main/java/com/kpi/project/model/User.java b/src/main/java/com/kpi/project/model/User.java index f949e25..eb9b216 100644 --- a/src/main/java/com/kpi/project/model/User.java +++ b/src/main/java/com/kpi/project/model/User.java @@ -4,13 +4,29 @@ import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Table; import java.util.Collection; import java.util.Collections; +import java.util.UUID; @Data +@Entity +@Table(name = "USERS") public class User implements UserDetails { + @Id + @GeneratedValue + @Column(name = "USER_ID") + private UUID id; + + @Column(name = "EMAIL") private String email; + + @Column(name = "PASSWORD") private String password; @Override diff --git a/src/main/java/com/kpi/project/repository/UserRepository.java b/src/main/java/com/kpi/project/repository/UserRepository.java new file mode 100644 index 0000000..4a3428f --- /dev/null +++ b/src/main/java/com/kpi/project/repository/UserRepository.java @@ -0,0 +1,13 @@ +package com.kpi.project.repository; + +import com.kpi.project.model.User; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.UUID; + +@Repository +public interface UserRepository extends JpaRepository { + + User findByEmail(String email); +} diff --git a/src/main/java/com/kpi/project/service/UserService.java b/src/main/java/com/kpi/project/service/UserService.java index dc577bb..9c0fcf6 100644 --- a/src/main/java/com/kpi/project/service/UserService.java +++ b/src/main/java/com/kpi/project/service/UserService.java @@ -1,6 +1,7 @@ package com.kpi.project.service; import com.kpi.project.model.User; +import com.kpi.project.repository.UserRepository; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; @@ -9,11 +10,18 @@ @Service public class UserService implements UserDetailsService { + private final UserRepository userRepository; + + public UserService(UserRepository userRepository) { + this.userRepository = userRepository; + } + @Override public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { final User user = new User(); user.setEmail("user"); user.setPassword("pass"); - return user; + userRepository.save(user); + return userRepository.findByEmail("user"); } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..9c7ec0d --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,5 @@ +# DB config +spring.jpa.hibernate.ddl-auto=update +spring.datasource.url=jdbc:mysql://localhost:3306/KpiSocialNetwork +spring.datasource.username=root +spring.datasource.password= diff --git a/src/test/java/com/kpi/project/service/UserServiceTest.java b/src/test/java/com/kpi/project/service/UserServiceTest.java new file mode 100644 index 0000000..d45ad9f --- /dev/null +++ b/src/test/java/com/kpi/project/service/UserServiceTest.java @@ -0,0 +1,11 @@ +package com.kpi.project.service; + +import org.junit.jupiter.api.Test; + +public class UserServiceTest { + + @Test + public void test(){ + + } +} From 28a4437156259e62a2f016ed8222a9ab72ba7bf8 Mon Sep 17 00:00:00 2001 From: Oleg Levochkin Date: Sat, 14 Nov 2020 14:03:40 +0200 Subject: [PATCH 08/26] Add custom TSlint (#37) --- client-web/package.json | 4 +++ client-web/src/App.js | 2 +- client-web/tslint.json | 40 +++++++++++++++++++++++ src/main/resources/application.properties | 2 +- 4 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 client-web/tslint.json diff --git a/client-web/package.json b/client-web/package.json index 38c19e8..0cb74e1 100644 --- a/client-web/package.json +++ b/client-web/package.json @@ -38,5 +38,9 @@ "development": [ "last 1 chrome version" ] + }, + "devDependencies": { + "tslint": "^6.1.3", + "typescript": "^4.0.5" } } diff --git a/client-web/src/App.js b/client-web/src/App.js index 667f886..5437f58 100644 --- a/client-web/src/App.js +++ b/client-web/src/App.js @@ -2,7 +2,7 @@ import React from "react" function App() { return ( -

test

+

test

); } diff --git a/client-web/tslint.json b/client-web/tslint.json new file mode 100644 index 0000000..4f3e611 --- /dev/null +++ b/client-web/tslint.json @@ -0,0 +1,40 @@ +{ + "extends": "tslint:recommended", + "rules": { + "max-line-length": { + "options": [120] + }, + "new-parens": true, + "no-arg": true, + "no-bitwise": true, + "no-conditional-assignment": true, + "no-consecutive-blank-lines": false, + "no-console": { + "severity": "warning", + "options": ["debug", "info", "log", "time", "timeEnd", "trace"] + } + }, + "jsRules": { + "max-line-length": { + "options": [120] + }, + "no-empty": true, + "member-ordering": [true, {"order": "fields-first"}], + "no-magic-numbers": [true, 1, 2, 3, 0], + "no-reference": true, + "ban-comma-operator": true, + "curly": [true, "ignore-same-line"], + "no-console": [true, "log", "error"], + "no-duplicate-super": true, + "no-duplicate-switch-case": true, + "no-duplicate-variable": [true, "check-parameters"], + "no-invalid-template-strings": true, + "switch-default": true, + "triple-equals": true, + "use-isnan": true, + "no-duplicate-imports": [true, {"allow-namespace-imports": true}], + "arrow-return-shorthand": true, + "ordered-imports": true, + "whitespace": [true, "check-branch", "check-operator", "check-typecast"] + } +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 9c7ec0d..dc35685 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -2,4 +2,4 @@ spring.jpa.hibernate.ddl-auto=update spring.datasource.url=jdbc:mysql://localhost:3306/KpiSocialNetwork spring.datasource.username=root -spring.datasource.password= +spring.datasource.password=root From 4e74c3a4e027dbae52daa31dd85277ed9d62bf08 Mon Sep 17 00:00:00 2001 From: Serhii Nahornyi Date: Sat, 14 Nov 2020 14:06:13 +0200 Subject: [PATCH 09/26] Make maven to work with jupiter tests (#38) --- pom.xml | 19 +++++++++++++++++-- .../kpi/project/service/UserServiceTest.java | 7 ++++++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index ac07d81..f3b0e93 100644 --- a/pom.xml +++ b/pom.xml @@ -18,7 +18,9 @@ 11 11 UTF-8 + 3.8.0 3.1.1 + 3.0.0-M5 @@ -72,10 +74,18 @@
org.junit.jupiter - junit-jupiter-api + junit-jupiter 5.7.0 test + + org.assertj + assertj-core + 3.18.1 + test + + +
@@ -83,7 +93,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.0 + ${compiler.plugin.version} @@ -112,6 +122,11 @@ + + org.apache.maven.plugins + maven-surefire-plugin + ${surefire.plugin.version} + diff --git a/src/test/java/com/kpi/project/service/UserServiceTest.java b/src/test/java/com/kpi/project/service/UserServiceTest.java index d45ad9f..3974754 100644 --- a/src/test/java/com/kpi/project/service/UserServiceTest.java +++ b/src/test/java/com/kpi/project/service/UserServiceTest.java @@ -1,11 +1,16 @@ package com.kpi.project.service; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; + + public class UserServiceTest { @Test + @DisplayName("some test") public void test(){ - + assertThat(true).isEqualTo(true); } } From 1a060651e7a1b39c8b04d55812f54a8f9bfe5cc7 Mon Sep 17 00:00:00 2001 From: Serhii Nahornyi Date: Sat, 14 Nov 2020 15:14:42 +0200 Subject: [PATCH 10/26] Change user id type and generation strategy (#43) --- src/main/java/com/kpi/project/model/User.java | 6 +++--- .../java/com/kpi/project/repository/UserRepository.java | 4 +--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/kpi/project/model/User.java b/src/main/java/com/kpi/project/model/User.java index eb9b216..4a51551 100644 --- a/src/main/java/com/kpi/project/model/User.java +++ b/src/main/java/com/kpi/project/model/User.java @@ -7,11 +7,11 @@ import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; import java.util.Collection; import java.util.Collections; -import java.util.UUID; @Data @Entity @@ -19,9 +19,9 @@ public class User implements UserDetails { @Id - @GeneratedValue + @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "USER_ID") - private UUID id; + private Long id; @Column(name = "EMAIL") private String email; diff --git a/src/main/java/com/kpi/project/repository/UserRepository.java b/src/main/java/com/kpi/project/repository/UserRepository.java index 4a3428f..32e01f9 100644 --- a/src/main/java/com/kpi/project/repository/UserRepository.java +++ b/src/main/java/com/kpi/project/repository/UserRepository.java @@ -4,10 +4,8 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import java.util.UUID; - @Repository -public interface UserRepository extends JpaRepository { +public interface UserRepository extends JpaRepository { User findByEmail(String email); } From bc3630fc890620dd73f8ce411193cbc8a68a9428 Mon Sep 17 00:00:00 2001 From: Oleg Levochkin Date: Sat, 14 Nov 2020 15:33:20 +0200 Subject: [PATCH 11/26] Move token expiration time to property file (#42) --- src/main/java/com/kpi/project/util/JwtUtil.java | 7 +++++-- src/main/resources/application.properties | 5 ----- src/main/resources/application.yaml | 15 +++++++++++++++ 3 files changed, 20 insertions(+), 7 deletions(-) delete mode 100644 src/main/resources/application.properties create mode 100644 src/main/resources/application.yaml diff --git a/src/main/java/com/kpi/project/util/JwtUtil.java b/src/main/java/com/kpi/project/util/JwtUtil.java index c295a47..717db56 100644 --- a/src/main/java/com/kpi/project/util/JwtUtil.java +++ b/src/main/java/com/kpi/project/util/JwtUtil.java @@ -3,6 +3,7 @@ import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; +import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Service; @@ -15,7 +16,9 @@ public class JwtUtil { private static final String SIGNING_KEY = "secret"; - private static final int THIRTY_MINUTES_IN_MILLISECONDS = 1000 * 60 * 30; + + @Value("${security.token.expiration.time}") + private int tokenExpirationTime; public String extractUsername(String token) { return extractClaim(token, Claims::getSubject); @@ -45,7 +48,7 @@ public String generateToken(UserDetails userDetails) { private String createToken(Map claims, String subject) { return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis())) - .setExpiration(new Date(System.currentTimeMillis() + THIRTY_MINUTES_IN_MILLISECONDS)) + .setExpiration(new Date(System.currentTimeMillis() + tokenExpirationTime)) .signWith(SignatureAlgorithm.HS256, SIGNING_KEY).compact(); } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties deleted file mode 100644 index dc35685..0000000 --- a/src/main/resources/application.properties +++ /dev/null @@ -1,5 +0,0 @@ -# DB config -spring.jpa.hibernate.ddl-auto=update -spring.datasource.url=jdbc:mysql://localhost:3306/KpiSocialNetwork -spring.datasource.username=root -spring.datasource.password=root diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml new file mode 100644 index 0000000..ef4fc9e --- /dev/null +++ b/src/main/resources/application.yaml @@ -0,0 +1,15 @@ +# Security config +security: + token: + expiration: + time: 1800000 + +# DB config +spring: + datasource: + password: root + url: jdbc:mysql://localhost:3306/KpiSocialNetwork + username: root + jpa: + hibernate: + ddl-auto: update From 125c676632c87e8b66a8337bc229c0d07caadf6e Mon Sep 17 00:00:00 2001 From: Oleg Levochkin Date: Sat, 14 Nov 2020 15:56:52 +0200 Subject: [PATCH 12/26] Change server port (#45) New port is 8092 --- src/main/resources/application.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index ef4fc9e..95dfa00 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -13,3 +13,7 @@ spring: jpa: hibernate: ddl-auto: update + +# Server properties +server: + port: 8092 From e198f6c98ae3fbcbfb695960923dca777e655256 Mon Sep 17 00:00:00 2001 From: Oleg Levochkin Date: Sat, 14 Nov 2020 15:57:18 +0200 Subject: [PATCH 13/26] Create authentication folder (#44) * Create authentication folder * Change server port --- .../model/{ => authentication}/AuthenticationRequest.java | 2 +- .../model/{ => authentication}/AuthenticationResponse.java | 2 +- src/main/java/com/kpi/project/resource/SystemResource.java | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) rename src/main/java/com/kpi/project/model/{ => authentication}/AuthenticationRequest.java (83%) rename src/main/java/com/kpi/project/model/{ => authentication}/AuthenticationResponse.java (81%) diff --git a/src/main/java/com/kpi/project/model/AuthenticationRequest.java b/src/main/java/com/kpi/project/model/authentication/AuthenticationRequest.java similarity index 83% rename from src/main/java/com/kpi/project/model/AuthenticationRequest.java rename to src/main/java/com/kpi/project/model/authentication/AuthenticationRequest.java index 52278f7..2f12d79 100644 --- a/src/main/java/com/kpi/project/model/AuthenticationRequest.java +++ b/src/main/java/com/kpi/project/model/authentication/AuthenticationRequest.java @@ -1,4 +1,4 @@ -package com.kpi.project.model; +package com.kpi.project.model.authentication; import lombok.AllArgsConstructor; import lombok.Data; diff --git a/src/main/java/com/kpi/project/model/AuthenticationResponse.java b/src/main/java/com/kpi/project/model/authentication/AuthenticationResponse.java similarity index 81% rename from src/main/java/com/kpi/project/model/AuthenticationResponse.java rename to src/main/java/com/kpi/project/model/authentication/AuthenticationResponse.java index 478ac2e..4785504 100644 --- a/src/main/java/com/kpi/project/model/AuthenticationResponse.java +++ b/src/main/java/com/kpi/project/model/authentication/AuthenticationResponse.java @@ -1,4 +1,4 @@ -package com.kpi.project.model; +package com.kpi.project.model.authentication; import lombok.AllArgsConstructor; import lombok.Data; diff --git a/src/main/java/com/kpi/project/resource/SystemResource.java b/src/main/java/com/kpi/project/resource/SystemResource.java index 05ef8e2..c28cb7d 100644 --- a/src/main/java/com/kpi/project/resource/SystemResource.java +++ b/src/main/java/com/kpi/project/resource/SystemResource.java @@ -1,7 +1,7 @@ package com.kpi.project.resource; -import com.kpi.project.model.AuthenticationRequest; -import com.kpi.project.model.AuthenticationResponse; +import com.kpi.project.model.authentication.AuthenticationRequest; +import com.kpi.project.model.authentication.AuthenticationResponse; import com.kpi.project.service.UserService; import com.kpi.project.util.JwtUtil; import org.springframework.http.ResponseEntity; From f2e8535dd14c7056b254b4f1875d294fad8bd0ed Mon Sep 17 00:00:00 2001 From: Serhii Nahornyi Date: Sat, 14 Nov 2020 18:17:51 +0200 Subject: [PATCH 14/26] Make model from Jwt properties. (#46) --- .../kpi/project/config/SecurityConfiguration.java | 11 ++++++++++- src/main/java/com/kpi/project/util/JwtUtil.java | 15 ++++++++------- .../com/kpi/project/util/model/JwtProperties.java | 14 ++++++++++++++ src/main/resources/application.yaml | 5 ++--- 4 files changed, 34 insertions(+), 11 deletions(-) create mode 100644 src/main/java/com/kpi/project/util/model/JwtProperties.java diff --git a/src/main/java/com/kpi/project/config/SecurityConfiguration.java b/src/main/java/com/kpi/project/config/SecurityConfiguration.java index c56653b..3e6905a 100644 --- a/src/main/java/com/kpi/project/config/SecurityConfiguration.java +++ b/src/main/java/com/kpi/project/config/SecurityConfiguration.java @@ -2,7 +2,10 @@ import com.kpi.project.filter.JwtRequestFilter; import com.kpi.project.service.UserService; +import com.kpi.project.util.model.JwtProperties; +import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Lazy; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; @@ -19,7 +22,7 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter { private final UserService userService; private final JwtRequestFilter jwtRequestFilter; - public SecurityConfiguration(UserService userService, JwtRequestFilter jwtRequestFilter) { + public SecurityConfiguration(UserService userService, @Lazy JwtRequestFilter jwtRequestFilter) { this.userService = userService; this.jwtRequestFilter = jwtRequestFilter; } @@ -45,6 +48,12 @@ public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } + @Bean + @ConfigurationProperties(prefix = "security") + public JwtProperties jwtProperties() { + return new JwtProperties(); + } + @Bean public PasswordEncoder getPasswordEncoder() { return NoOpPasswordEncoder.getInstance(); diff --git a/src/main/java/com/kpi/project/util/JwtUtil.java b/src/main/java/com/kpi/project/util/JwtUtil.java index 717db56..07b6818 100644 --- a/src/main/java/com/kpi/project/util/JwtUtil.java +++ b/src/main/java/com/kpi/project/util/JwtUtil.java @@ -1,9 +1,9 @@ package com.kpi.project.util; +import com.kpi.project.util.model.JwtProperties; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; -import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Service; @@ -15,10 +15,11 @@ @Service public class JwtUtil { - private static final String SIGNING_KEY = "secret"; + private final JwtProperties jwtProperties; - @Value("${security.token.expiration.time}") - private int tokenExpirationTime; + public JwtUtil(JwtProperties jwtProperties) { + this.jwtProperties = jwtProperties; + } public String extractUsername(String token) { return extractClaim(token, Claims::getSubject); @@ -34,7 +35,7 @@ public T extractClaim(String token, Function claimsResolver) { } private Claims extractAllClaims(String token) { - return Jwts.parser().setSigningKey(SIGNING_KEY).parseClaimsJws(token).getBody(); + return Jwts.parser().setSigningKey(jwtProperties.getSigningKey()).parseClaimsJws(token).getBody(); } private Boolean isTokenExpired(String token) { @@ -48,8 +49,8 @@ public String generateToken(UserDetails userDetails) { private String createToken(Map claims, String subject) { return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis())) - .setExpiration(new Date(System.currentTimeMillis() + tokenExpirationTime)) - .signWith(SignatureAlgorithm.HS256, SIGNING_KEY).compact(); + .setExpiration(new Date(System.currentTimeMillis() + jwtProperties.getTokenExpirationTime())) + .signWith(SignatureAlgorithm.HS256, jwtProperties.getSigningKey()).compact(); } public Boolean validateToken(String token, UserDetails userDetails) { diff --git a/src/main/java/com/kpi/project/util/model/JwtProperties.java b/src/main/java/com/kpi/project/util/model/JwtProperties.java new file mode 100644 index 0000000..3fbf037 --- /dev/null +++ b/src/main/java/com/kpi/project/util/model/JwtProperties.java @@ -0,0 +1,14 @@ +package com.kpi.project.util.model; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@NoArgsConstructor +@Getter +@Setter +public class JwtProperties { + + private String signingKey; + private int tokenExpirationTime; +} diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 95dfa00..09104e8 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -1,8 +1,7 @@ # Security config security: - token: - expiration: - time: 1800000 + token_expiration_time: 1800000 + signing_key: some-signing-key # DB config spring: From 7d872e7667cb637524f7d85526ad8d46f89678c6 Mon Sep 17 00:00:00 2001 From: Serhii Nahornyi Date: Sat, 14 Nov 2020 18:45:41 +0200 Subject: [PATCH 15/26] Update user login logic to work with username and email (#47) Update user login logic to work with username and email, Made both username and email, to be unique --- pom.xml | 12 +++--- src/main/java/com/kpi/project/model/User.java | 19 ++++----- .../project/repository/UserRepository.java | 5 ++- .../com/kpi/project/service/UserService.java | 10 ++--- .../kpi/project/service/UserServiceTest.java | 39 ++++++++++++++++--- 5 files changed, 55 insertions(+), 30 deletions(-) diff --git a/pom.xml b/pom.xml index f3b0e93..b4820a3 100644 --- a/pom.xml +++ b/pom.xml @@ -66,18 +66,18 @@ provided - - org.mockito - mockito-all - 1.10.19 - test - org.junit.jupiter junit-jupiter 5.7.0 test + + org.mockito + mockito-junit-jupiter + 3.6.0 + test + org.assertj assertj-core diff --git a/src/main/java/com/kpi/project/model/User.java b/src/main/java/com/kpi/project/model/User.java index 4a51551..274b7c0 100644 --- a/src/main/java/com/kpi/project/model/User.java +++ b/src/main/java/com/kpi/project/model/User.java @@ -1,6 +1,8 @@ package com.kpi.project.model; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; @@ -14,6 +16,8 @@ import java.util.Collections; @Data +@NoArgsConstructor +@AllArgsConstructor @Entity @Table(name = "USERS") public class User implements UserDetails { @@ -23,9 +27,12 @@ public class User implements UserDetails { @Column(name = "USER_ID") private Long id; - @Column(name = "EMAIL") + @Column(name = "EMAIL", unique = true) private String email; + @Column(name = "USERNAME", unique = true) + private String username; + @Column(name = "PASSWORD") private String password; @@ -34,16 +41,6 @@ public Collection getAuthorities() { return Collections.emptyList(); } - @Override - public String getPassword() { - return password; - } - - @Override - public String getUsername() { - return email; - } - @Override public boolean isAccountNonExpired() { return true; diff --git a/src/main/java/com/kpi/project/repository/UserRepository.java b/src/main/java/com/kpi/project/repository/UserRepository.java index 32e01f9..452a40b 100644 --- a/src/main/java/com/kpi/project/repository/UserRepository.java +++ b/src/main/java/com/kpi/project/repository/UserRepository.java @@ -2,10 +2,13 @@ import com.kpi.project.model.User; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; @Repository public interface UserRepository extends JpaRepository { - User findByEmail(String email); + @Query("SELECT u FROM User u WHERE u.email = :loginParam or u.username = :loginParam") + User loadByEmailOrUsername(@Param("loginParam") String loginParam); } diff --git a/src/main/java/com/kpi/project/service/UserService.java b/src/main/java/com/kpi/project/service/UserService.java index 9c0fcf6..72f2c5a 100644 --- a/src/main/java/com/kpi/project/service/UserService.java +++ b/src/main/java/com/kpi/project/service/UserService.java @@ -2,7 +2,6 @@ import com.kpi.project.model.User; import com.kpi.project.repository.UserRepository; -import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; @@ -17,11 +16,8 @@ public UserService(UserRepository userRepository) { } @Override - public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { - final User user = new User(); - user.setEmail("user"); - user.setPassword("pass"); - userRepository.save(user); - return userRepository.findByEmail("user"); + public User loadUserByUsername(String login) throws UsernameNotFoundException { + + return userRepository.loadByEmailOrUsername(login); } } diff --git a/src/test/java/com/kpi/project/service/UserServiceTest.java b/src/test/java/com/kpi/project/service/UserServiceTest.java index 3974754..dca705a 100644 --- a/src/test/java/com/kpi/project/service/UserServiceTest.java +++ b/src/test/java/com/kpi/project/service/UserServiceTest.java @@ -1,16 +1,45 @@ package com.kpi.project.service; -import org.junit.jupiter.api.DisplayName; +import com.kpi.project.model.User; +import com.kpi.project.repository.UserRepository; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.security.core.userdetails.UserDetails; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verify; - +@ExtendWith(MockitoExtension.class) public class UserServiceTest { + @Mock + private UserRepository userRepository; + + private User user; + + @InjectMocks + private UserService testingInstance; + + @BeforeEach + public void setUp() { + user = new User(1L, "mail@mail.com", "username", "password"); + } + @Test - @DisplayName("some test") - public void test(){ - assertThat(true).isEqualTo(true); + public void loadUserByUsernameShouldReturnUserFoundByEmailOrUsername() { + // given + given(userRepository.loadByEmailOrUsername("login")).willReturn(user); + + // when + final User actualUser = testingInstance.loadUserByUsername("login"); + + // then + verify(userRepository).loadByEmailOrUsername("login"); + assertThat(actualUser).isEqualTo(user); } } From cf792e088eec0ba4493cc4ff1d9af52f4ead939a Mon Sep 17 00:00:00 2001 From: Oleg Levochkin Date: Sat, 14 Nov 2020 23:46:04 +0200 Subject: [PATCH 16/26] Add user role (#48) Add user role Create registration endpoint --- .../project/config/SecurityConfiguration.java | 2 +- src/main/java/com/kpi/project/model/User.java | 13 +++ .../com/kpi/project/model/dto/UserDto.java | 12 ++ .../kpi/project/model/mapper/UserMapper.java | 27 +++++ .../com/kpi/project/model/userRole/Role.java | 26 +++++ .../project/repository/UserRepository.java | 2 + .../kpi/project/resource/UserResource.java | 23 ++++ .../com/kpi/project/service/UserService.java | 20 +++- .../kpi/project/validate/UserValidator.java | 41 +++++++ .../kpi/project/service/UserServiceTest.java | 39 ++++++- .../project/validator/UserValidatorTest.java | 107 ++++++++++++++++++ 11 files changed, 308 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/kpi/project/model/dto/UserDto.java create mode 100644 src/main/java/com/kpi/project/model/mapper/UserMapper.java create mode 100644 src/main/java/com/kpi/project/model/userRole/Role.java create mode 100644 src/main/java/com/kpi/project/resource/UserResource.java create mode 100644 src/main/java/com/kpi/project/validate/UserValidator.java create mode 100644 src/test/java/com/kpi/project/validator/UserValidatorTest.java diff --git a/src/main/java/com/kpi/project/config/SecurityConfiguration.java b/src/main/java/com/kpi/project/config/SecurityConfiguration.java index 3e6905a..2798f26 100644 --- a/src/main/java/com/kpi/project/config/SecurityConfiguration.java +++ b/src/main/java/com/kpi/project/config/SecurityConfiguration.java @@ -35,7 +35,7 @@ protected void configure(AuthenticationManagerBuilder auth) throws Exception { @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() - .authorizeRequests().antMatchers("/authenticate").permitAll() + .authorizeRequests().antMatchers("/authenticate", "/user/registration").permitAll() .anyRequest().authenticated() .and().sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS); diff --git a/src/main/java/com/kpi/project/model/User.java b/src/main/java/com/kpi/project/model/User.java index 274b7c0..94ed96a 100644 --- a/src/main/java/com/kpi/project/model/User.java +++ b/src/main/java/com/kpi/project/model/User.java @@ -1,19 +1,27 @@ package com.kpi.project.model; +import com.kpi.project.model.userRole.Role; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; +import javax.persistence.CollectionTable; import javax.persistence.Column; +import javax.persistence.ElementCollection; import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; +import javax.persistence.JoinColumn; import javax.persistence.Table; import java.util.Collection; import java.util.Collections; +import java.util.List; @Data @NoArgsConstructor @@ -36,6 +44,11 @@ public class User implements UserDetails { @Column(name = "PASSWORD") private String password; + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable(name = "USER_ROLES", joinColumns = @JoinColumn(name = "USER_ID")) + @Enumerated(EnumType.STRING) + private List roles; + @Override public Collection getAuthorities() { return Collections.emptyList(); diff --git a/src/main/java/com/kpi/project/model/dto/UserDto.java b/src/main/java/com/kpi/project/model/dto/UserDto.java new file mode 100644 index 0000000..8c7af2e --- /dev/null +++ b/src/main/java/com/kpi/project/model/dto/UserDto.java @@ -0,0 +1,12 @@ +package com.kpi.project.model.dto; + +import lombok.Data; + +@Data +public class UserDto { + + private String username; + private String email; + private String password; + private String matchingPassword; +} diff --git a/src/main/java/com/kpi/project/model/mapper/UserMapper.java b/src/main/java/com/kpi/project/model/mapper/UserMapper.java new file mode 100644 index 0000000..d326c60 --- /dev/null +++ b/src/main/java/com/kpi/project/model/mapper/UserMapper.java @@ -0,0 +1,27 @@ +package com.kpi.project.model.mapper; + +import com.kpi.project.model.User; +import com.kpi.project.model.dto.UserDto; +import org.springframework.stereotype.Component; + +@Component +public class UserMapper { + + public User dtoToUser(UserDto userDto) { + final User user = new User(); + user.setEmail(userDto.getEmail()); + user.setPassword(userDto.getPassword()); + user.setUsername(userDto.getUsername()); + + return user; + } + + public UserDto userToDto(User user) { + final UserDto userDto = new UserDto(); + userDto.setEmail(user.getEmail()); + userDto.setPassword(user.getPassword()); + userDto.setUsername(user.getUsername()); + + return userDto; + } +} diff --git a/src/main/java/com/kpi/project/model/userRole/Role.java b/src/main/java/com/kpi/project/model/userRole/Role.java new file mode 100644 index 0000000..ba4bfaf --- /dev/null +++ b/src/main/java/com/kpi/project/model/userRole/Role.java @@ -0,0 +1,26 @@ +package com.kpi.project.model.userRole; + +import org.springframework.security.core.GrantedAuthority; + +@SuppressWarnings("checkstyle:hideutilityclassconstructor") +public enum Role implements GrantedAuthority { + + ADMIN(Code.ADMIN), + USER(Code.USER); + + private final String authority; + + Role(String authority) { + this.authority = authority; + } + + @Override + public String getAuthority() { + return authority; + } + + public class Code { + public static final String ADMIN = "ROLE_ADMIN"; + public static final String USER = "ROLE_USER"; + } +} diff --git a/src/main/java/com/kpi/project/repository/UserRepository.java b/src/main/java/com/kpi/project/repository/UserRepository.java index 452a40b..4c47329 100644 --- a/src/main/java/com/kpi/project/repository/UserRepository.java +++ b/src/main/java/com/kpi/project/repository/UserRepository.java @@ -11,4 +11,6 @@ public interface UserRepository extends JpaRepository { @Query("SELECT u FROM User u WHERE u.email = :loginParam or u.username = :loginParam") User loadByEmailOrUsername(@Param("loginParam") String loginParam); + + User findByEmailOrUsername(String email, String username); } diff --git a/src/main/java/com/kpi/project/resource/UserResource.java b/src/main/java/com/kpi/project/resource/UserResource.java new file mode 100644 index 0000000..a9d5c2c --- /dev/null +++ b/src/main/java/com/kpi/project/resource/UserResource.java @@ -0,0 +1,23 @@ +package com.kpi.project.resource; + +import com.kpi.project.model.dto.UserDto; +import com.kpi.project.service.UserService; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class UserResource { + + private final UserService userService; + + public UserResource(UserService userService) { + this.userService = userService; + } + + @PostMapping("/user/registration") + public ResponseEntity showRegistrationForm(@RequestBody UserDto userDto) { + return ResponseEntity.ok(userService.saveUser(userDto)); + } +} diff --git a/src/main/java/com/kpi/project/service/UserService.java b/src/main/java/com/kpi/project/service/UserService.java index 72f2c5a..83e04ba 100644 --- a/src/main/java/com/kpi/project/service/UserService.java +++ b/src/main/java/com/kpi/project/service/UserService.java @@ -1,18 +1,28 @@ package com.kpi.project.service; import com.kpi.project.model.User; +import com.kpi.project.model.dto.UserDto; +import com.kpi.project.model.mapper.UserMapper; +import com.kpi.project.model.userRole.Role; import com.kpi.project.repository.UserRepository; +import com.kpi.project.validate.UserValidator; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; +import java.util.Collections; + @Service public class UserService implements UserDetailsService { private final UserRepository userRepository; + private final UserValidator userValidator; + private final UserMapper userMapper; - public UserService(UserRepository userRepository) { + public UserService(UserRepository userRepository, UserValidator userValidator, UserMapper userMapper) { this.userRepository = userRepository; + this.userValidator = userValidator; + this.userMapper = userMapper; } @Override @@ -20,4 +30,12 @@ public User loadUserByUsername(String login) throws UsernameNotFoundException { return userRepository.loadByEmailOrUsername(login); } + + public UserDto saveUser(UserDto userDto) { + userValidator.validateUser(userDto); + final User user = userMapper.dtoToUser(userDto); + user.setRoles(Collections.singletonList(Role.USER)); + + return userMapper.userToDto(userRepository.save(user)); + } } diff --git a/src/main/java/com/kpi/project/validate/UserValidator.java b/src/main/java/com/kpi/project/validate/UserValidator.java new file mode 100644 index 0000000..337c091 --- /dev/null +++ b/src/main/java/com/kpi/project/validate/UserValidator.java @@ -0,0 +1,41 @@ +package com.kpi.project.validate; + +import com.kpi.project.model.User; +import com.kpi.project.model.dto.UserDto; +import com.kpi.project.repository.UserRepository; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; + +import java.util.Objects; + +@Component +public class UserValidator { + + private final UserRepository userRepository; + + public UserValidator(UserRepository userRepository) { + this.userRepository = userRepository; + } + + public void validateUser(UserDto userToValidate) { + if (!Objects.equals(userToValidate.getPassword(), userToValidate.getMatchingPassword())) { + throw new IllegalArgumentException("Passwords does not match"); + } + if (userToValidate.getPassword().length() < 4) { + throw new IllegalArgumentException("Password length must be minimum of 4 symbols"); + } + if (StringUtils.isBlank(userToValidate.getEmail())) { + throw new IllegalArgumentException("Email should be present"); + } + if (StringUtils.isBlank(userToValidate.getUsername())) { + throw new IllegalArgumentException("Username should be present"); + } + final User user = userRepository.findByEmailOrUsername(userToValidate.getEmail(), userToValidate.getUsername()); + if (Objects.nonNull(user)) { + if (Objects.equals(user.getEmail(), userToValidate.getEmail())) { + throw new IllegalArgumentException("Email already exists"); + } + throw new IllegalArgumentException("Username already exists"); + } + } +} diff --git a/src/test/java/com/kpi/project/service/UserServiceTest.java b/src/test/java/com/kpi/project/service/UserServiceTest.java index dca705a..8c078bb 100644 --- a/src/test/java/com/kpi/project/service/UserServiceTest.java +++ b/src/test/java/com/kpi/project/service/UserServiceTest.java @@ -1,14 +1,19 @@ package com.kpi.project.service; import com.kpi.project.model.User; +import com.kpi.project.model.dto.UserDto; +import com.kpi.project.model.mapper.UserMapper; +import com.kpi.project.model.userRole.Role; import com.kpi.project.repository.UserRepository; +import com.kpi.project.validate.UserValidator; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collections; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; @@ -20,6 +25,12 @@ public class UserServiceTest { @Mock private UserRepository userRepository; + @Mock + private UserMapper userMapper; + + @Mock + private UserValidator userValidator; + private User user; @InjectMocks @@ -27,7 +38,8 @@ public class UserServiceTest { @BeforeEach public void setUp() { - user = new User(1L, "mail@mail.com", "username", "password"); + user = new User(1L, "mail@mail.com", "username", + "password", Collections.singletonList(Role.ADMIN)); } @Test @@ -42,4 +54,27 @@ public void loadUserByUsernameShouldReturnUserFoundByEmailOrUsername() { verify(userRepository).loadByEmailOrUsername("login"); assertThat(actualUser).isEqualTo(user); } + + @Test + public void loadUserByUsernameShouldReturnSavedUser() { + // given + final UserDto userDto = new UserDto(); + userDto.setPassword("password"); + userDto.setMatchingPassword("password"); + userDto.setUsername("username"); + userDto.setEmail("mail@mail.com"); + + given(userMapper.dtoToUser(userDto)).willReturn(user); + given(userMapper.userToDto(user)).willReturn(userDto); + given(userRepository.save(user)).willReturn(user); + + // when + final UserDto actualUser = testingInstance.saveUser(userDto); + + // then + verify(userMapper).dtoToUser(userDto); + verify(userMapper).userToDto(user); + verify(userRepository).save(user); + assertThat(actualUser).isEqualTo(userDto); + } } diff --git a/src/test/java/com/kpi/project/validator/UserValidatorTest.java b/src/test/java/com/kpi/project/validator/UserValidatorTest.java new file mode 100644 index 0000000..17f6084 --- /dev/null +++ b/src/test/java/com/kpi/project/validator/UserValidatorTest.java @@ -0,0 +1,107 @@ +package com.kpi.project.validator; + +import com.kpi.project.model.User; +import com.kpi.project.model.dto.UserDto; +import com.kpi.project.repository.UserRepository; +import com.kpi.project.validate.UserValidator; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.mockito.BDDMockito.given; + +@ExtendWith(MockitoExtension.class) +public class UserValidatorTest { + + @Mock + private UserRepository userRepository; + + @InjectMocks + private UserValidator testingInstance; + + private UserDto user; + + @BeforeEach + public void setUp() { + user = new UserDto(); + user.setPassword("password"); + user.setMatchingPassword("password"); + user.setUsername("username"); + user.setEmail("email@mail.com"); + } + + @Test + public void validateUserShouldThrowExceptionIfPasswordDoesNotMatch() { + // given + user.setPassword(""); + + // expected + assertThatIllegalArgumentException() + .isThrownBy(() -> testingInstance.validateUser(user)) + .withMessage("Passwords does not match"); + } + + @Test + public void validateUserShouldThrowExceptionIfPasswordHasIncorrectLength() { + // given + user.setPassword("pas"); + user.setMatchingPassword("pas"); + + // expected + assertThatIllegalArgumentException() + .isThrownBy(() -> testingInstance.validateUser(user)) + .withMessage("Password length must be minimum of 4 symbols"); + } + + @Test + public void validateUserShouldThrowExceptionIfEmailIsNotPresent() { + // given + user.setEmail(""); + + // expected + assertThatIllegalArgumentException() + .isThrownBy(() -> testingInstance.validateUser(user)) + .withMessage("Email should be present"); + } + + @Test + public void validateUserShouldThrowExceptionIfUserNameIsNotPresent() { + // given + user.setUsername(""); + + // expected + assertThatIllegalArgumentException() + .isThrownBy(() -> testingInstance.validateUser(user)) + .withMessage("Username should be present"); + } + + @Test + public void validateUserShouldThrowExceptionIfEmailAlreadyExists() { + // given + final User userModel = new User(); + userModel.setEmail("email@mail.com"); + given(userRepository.findByEmailOrUsername("email@mail.com", "username")).willReturn(userModel); + + // expected + assertThatIllegalArgumentException() + .isThrownBy(() -> testingInstance.validateUser(user)) + .withMessage("Email already exists"); + } + + @Test + public void validateUserShouldThrowExceptionIfUserNameAlreadyExists() { + // given + final User userModel = new User(); + userModel.setUsername("username"); + given(userRepository.findByEmailOrUsername("email@mail.com", "username")).willReturn(userModel); + + // expected + assertThatIllegalArgumentException() + .isThrownBy(() -> testingInstance.validateUser(user)) + .withMessage("Username already exists"); + } +} From 99cc4f8e8dc586ad1902a07e0ec63afc762e393e Mon Sep 17 00:00:00 2001 From: Serhii Nahornyi Date: Sun, 15 Nov 2020 01:10:56 +0200 Subject: [PATCH 17/26] Create one type exception response (#50) --- .../project/config/SecurityConfiguration.java | 9 ++++- .../com/kpi/project/model/ErrorResponse.java | 11 ++++++ src/main/java/com/kpi/project/model/User.java | 2 +- .../kpi/project/model/enums/ErrorTypes.java | 7 ++++ .../model/{userRole => enums}/Role.java | 2 +- .../kpi/project/resource/ErrorsResource.java | 35 +++++++++++++++++++ .../kpi/project/resource/TestController.java | 14 ++++++-- .../com/kpi/project/service/UserService.java | 2 +- .../kpi/project/service/UserServiceTest.java | 2 +- 9 files changed, 77 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/kpi/project/model/ErrorResponse.java create mode 100644 src/main/java/com/kpi/project/model/enums/ErrorTypes.java rename src/main/java/com/kpi/project/model/{userRole => enums}/Role.java (93%) create mode 100644 src/main/java/com/kpi/project/resource/ErrorsResource.java diff --git a/src/main/java/com/kpi/project/config/SecurityConfiguration.java b/src/main/java/com/kpi/project/config/SecurityConfiguration.java index 2798f26..de820e3 100644 --- a/src/main/java/com/kpi/project/config/SecurityConfiguration.java +++ b/src/main/java/com/kpi/project/config/SecurityConfiguration.java @@ -3,6 +3,7 @@ import com.kpi.project.filter.JwtRequestFilter; import com.kpi.project.service.UserService; import com.kpi.project.util.model.JwtProperties; +import org.apache.commons.lang3.ArrayUtils; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Lazy; @@ -34,8 +35,14 @@ protected void configure(AuthenticationManagerBuilder auth) throws Exception { @Override protected void configure(HttpSecurity http) throws Exception { + final String[] testEndpoints = {"test/string", "/test/error", "/test/error2"}; + http.csrf().disable() - .authorizeRequests().antMatchers("/authenticate", "/user/registration").permitAll() + .authorizeRequests() + .antMatchers( + (String[]) ArrayUtils.addAll(testEndpoints, + "/authenticate", "/user/registration")) + .permitAll() .anyRequest().authenticated() .and().sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS); diff --git a/src/main/java/com/kpi/project/model/ErrorResponse.java b/src/main/java/com/kpi/project/model/ErrorResponse.java new file mode 100644 index 0000000..de1d92f --- /dev/null +++ b/src/main/java/com/kpi/project/model/ErrorResponse.java @@ -0,0 +1,11 @@ +package com.kpi.project.model; + +import com.kpi.project.model.enums.ErrorTypes; +import lombok.Data; + +@Data +public class ErrorResponse { + + ErrorTypes errorType; + String message; +} diff --git a/src/main/java/com/kpi/project/model/User.java b/src/main/java/com/kpi/project/model/User.java index 94ed96a..43bbab5 100644 --- a/src/main/java/com/kpi/project/model/User.java +++ b/src/main/java/com/kpi/project/model/User.java @@ -1,6 +1,6 @@ package com.kpi.project.model; -import com.kpi.project.model.userRole.Role; +import com.kpi.project.model.enums.Role; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; diff --git a/src/main/java/com/kpi/project/model/enums/ErrorTypes.java b/src/main/java/com/kpi/project/model/enums/ErrorTypes.java new file mode 100644 index 0000000..40d7d18 --- /dev/null +++ b/src/main/java/com/kpi/project/model/enums/ErrorTypes.java @@ -0,0 +1,7 @@ +package com.kpi.project.model.enums; + +public enum ErrorTypes { + + server_error, + validation_error +} diff --git a/src/main/java/com/kpi/project/model/userRole/Role.java b/src/main/java/com/kpi/project/model/enums/Role.java similarity index 93% rename from src/main/java/com/kpi/project/model/userRole/Role.java rename to src/main/java/com/kpi/project/model/enums/Role.java index ba4bfaf..1bee412 100644 --- a/src/main/java/com/kpi/project/model/userRole/Role.java +++ b/src/main/java/com/kpi/project/model/enums/Role.java @@ -1,4 +1,4 @@ -package com.kpi.project.model.userRole; +package com.kpi.project.model.enums; import org.springframework.security.core.GrantedAuthority; diff --git a/src/main/java/com/kpi/project/resource/ErrorsResource.java b/src/main/java/com/kpi/project/resource/ErrorsResource.java new file mode 100644 index 0000000..d73b820 --- /dev/null +++ b/src/main/java/com/kpi/project/resource/ErrorsResource.java @@ -0,0 +1,35 @@ +package com.kpi.project.resource; + +import com.kpi.project.model.ErrorResponse; +import com.kpi.project.model.enums.ErrorTypes; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.context.request.WebRequest; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; + +@ControllerAdvice +public class ErrorsResource extends ResponseEntityExceptionHandler { + + @ExceptionHandler(value = {IllegalArgumentException.class}) + protected ResponseEntity handleValidationExceptions(IllegalArgumentException ex, WebRequest request) { + + final ErrorResponse errorResponse = new ErrorResponse(); + errorResponse.setErrorType(ErrorTypes.validation_error); + errorResponse.setMessage(ex.getMessage()); + + return handleExceptionInternal(ex, errorResponse, new HttpHeaders(), HttpStatus.BAD_REQUEST, request); + } + + @ExceptionHandler(value = {Exception.class}) + protected ResponseEntity handleUncaughtException(Exception ex, WebRequest request) { + + final ErrorResponse errorResponse = new ErrorResponse(); + errorResponse.setErrorType(ErrorTypes.server_error); + errorResponse.setMessage(ex.getMessage()); + + return handleExceptionInternal(ex, errorResponse, new HttpHeaders(), HttpStatus.BAD_REQUEST, request); + } +} diff --git a/src/main/java/com/kpi/project/resource/TestController.java b/src/main/java/com/kpi/project/resource/TestController.java index ca916df..5d5aa42 100644 --- a/src/main/java/com/kpi/project/resource/TestController.java +++ b/src/main/java/com/kpi/project/resource/TestController.java @@ -6,8 +6,18 @@ @RestController public class TestController { - @RequestMapping({"/test", "/"}) + @RequestMapping("test/string") public String test() { - return "test.ok"; + return "string.ok"; + } + + @RequestMapping("/test/error") + public Object illegalArgument() { + throw new IllegalArgumentException("some exception"); + } + + @RequestMapping("/test/error2") + public Object exception() throws Exception { + throw new Exception("some exception"); } } diff --git a/src/main/java/com/kpi/project/service/UserService.java b/src/main/java/com/kpi/project/service/UserService.java index 83e04ba..e62832a 100644 --- a/src/main/java/com/kpi/project/service/UserService.java +++ b/src/main/java/com/kpi/project/service/UserService.java @@ -3,7 +3,7 @@ import com.kpi.project.model.User; import com.kpi.project.model.dto.UserDto; import com.kpi.project.model.mapper.UserMapper; -import com.kpi.project.model.userRole.Role; +import com.kpi.project.model.enums.Role; import com.kpi.project.repository.UserRepository; import com.kpi.project.validate.UserValidator; import org.springframework.security.core.userdetails.UserDetailsService; diff --git a/src/test/java/com/kpi/project/service/UserServiceTest.java b/src/test/java/com/kpi/project/service/UserServiceTest.java index 8c078bb..19bc48d 100644 --- a/src/test/java/com/kpi/project/service/UserServiceTest.java +++ b/src/test/java/com/kpi/project/service/UserServiceTest.java @@ -3,7 +3,7 @@ import com.kpi.project.model.User; import com.kpi.project.model.dto.UserDto; import com.kpi.project.model.mapper.UserMapper; -import com.kpi.project.model.userRole.Role; +import com.kpi.project.model.enums.Role; import com.kpi.project.repository.UserRepository; import com.kpi.project.validate.UserValidator; import org.junit.jupiter.api.BeforeEach; From 1b53e407c144b619c340ed54a7eab4789b7c116c Mon Sep 17 00:00:00 2001 From: Serhii Nahornyi Date: Sun, 15 Nov 2020 02:09:12 +0200 Subject: [PATCH 18/26] Add BCrypt password encoder (#52) --- .../java/com/kpi/project/config/SecurityConfiguration.java | 4 ++-- src/main/java/com/kpi/project/service/UserService.java | 7 ++++++- src/test/java/com/kpi/project/service/UserServiceTest.java | 7 ++++++- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/kpi/project/config/SecurityConfiguration.java b/src/main/java/com/kpi/project/config/SecurityConfiguration.java index de820e3..16f8df1 100644 --- a/src/main/java/com/kpi/project/config/SecurityConfiguration.java +++ b/src/main/java/com/kpi/project/config/SecurityConfiguration.java @@ -13,7 +13,7 @@ import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; -import org.springframework.security.crypto.password.NoOpPasswordEncoder; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @@ -63,6 +63,6 @@ public JwtProperties jwtProperties() { @Bean public PasswordEncoder getPasswordEncoder() { - return NoOpPasswordEncoder.getInstance(); + return new BCryptPasswordEncoder(); } } diff --git a/src/main/java/com/kpi/project/service/UserService.java b/src/main/java/com/kpi/project/service/UserService.java index e62832a..17704d5 100644 --- a/src/main/java/com/kpi/project/service/UserService.java +++ b/src/main/java/com/kpi/project/service/UserService.java @@ -8,6 +8,7 @@ import com.kpi.project.validate.UserValidator; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import java.util.Collections; @@ -18,11 +19,14 @@ public class UserService implements UserDetailsService { private final UserRepository userRepository; private final UserValidator userValidator; private final UserMapper userMapper; + private final PasswordEncoder passwordEncoder; - public UserService(UserRepository userRepository, UserValidator userValidator, UserMapper userMapper) { + public UserService(UserRepository userRepository, UserValidator userValidator, + UserMapper userMapper, PasswordEncoder passwordEncoder) { this.userRepository = userRepository; this.userValidator = userValidator; this.userMapper = userMapper; + this.passwordEncoder = passwordEncoder; } @Override @@ -34,6 +38,7 @@ public User loadUserByUsername(String login) throws UsernameNotFoundException { public UserDto saveUser(UserDto userDto) { userValidator.validateUser(userDto); final User user = userMapper.dtoToUser(userDto); + user.setPassword(passwordEncoder.encode(user.getPassword())); user.setRoles(Collections.singletonList(Role.USER)); return userMapper.userToDto(userRepository.save(user)); diff --git a/src/test/java/com/kpi/project/service/UserServiceTest.java b/src/test/java/com/kpi/project/service/UserServiceTest.java index 19bc48d..7644010 100644 --- a/src/test/java/com/kpi/project/service/UserServiceTest.java +++ b/src/test/java/com/kpi/project/service/UserServiceTest.java @@ -12,6 +12,7 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.security.crypto.password.PasswordEncoder; import java.util.Collections; @@ -31,6 +32,9 @@ public class UserServiceTest { @Mock private UserValidator userValidator; + @Mock + private PasswordEncoder passwordEncoder; + private User user; @InjectMocks @@ -56,7 +60,7 @@ public void loadUserByUsernameShouldReturnUserFoundByEmailOrUsername() { } @Test - public void loadUserByUsernameShouldReturnSavedUser() { + public void saveUserShouldReturnSavedUser() { // given final UserDto userDto = new UserDto(); userDto.setPassword("password"); @@ -67,6 +71,7 @@ public void loadUserByUsernameShouldReturnSavedUser() { given(userMapper.dtoToUser(userDto)).willReturn(user); given(userMapper.userToDto(user)).willReturn(userDto); given(userRepository.save(user)).willReturn(user); + given(passwordEncoder.encode("password")).willReturn("hashedPassword"); // when final UserDto actualUser = testingInstance.saveUser(userDto); From 7110ee338b8080a7bacc3fa51078f36a944fcf8c Mon Sep 17 00:00:00 2001 From: Serhii Nahornyi Date: Sun, 15 Nov 2020 02:37:23 +0200 Subject: [PATCH 19/26] Remove null fields from json response (#54) --- pom.xml | 5 +++++ .../project/config/JacksonConfiguration.java | 17 +++++++++++++++++ .../project/config/SecurityConfiguration.java | 2 +- 3 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/kpi/project/config/JacksonConfiguration.java diff --git a/pom.xml b/pom.xml index b4820a3..9f08ba9 100644 --- a/pom.xml +++ b/pom.xml @@ -53,6 +53,11 @@ jaxb-api 2.3.0 + + com.fasterxml.jackson.core + jackson-databind + 2.11.3 + org.apache.commons diff --git a/src/main/java/com/kpi/project/config/JacksonConfiguration.java b/src/main/java/com/kpi/project/config/JacksonConfiguration.java new file mode 100644 index 0000000..9ac35ec --- /dev/null +++ b/src/main/java/com/kpi/project/config/JacksonConfiguration.java @@ -0,0 +1,17 @@ +package com.kpi.project.config; + +import com.fasterxml.jackson.annotation.JsonInclude; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; + +@Configuration +public class JacksonConfiguration { + + @Bean + public Jackson2ObjectMapperBuilder objectMapperBuilder() { + Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder(); + builder.serializationInclusion(JsonInclude.Include.NON_NULL); + return builder; + } +} diff --git a/src/main/java/com/kpi/project/config/SecurityConfiguration.java b/src/main/java/com/kpi/project/config/SecurityConfiguration.java index 16f8df1..fc776ba 100644 --- a/src/main/java/com/kpi/project/config/SecurityConfiguration.java +++ b/src/main/java/com/kpi/project/config/SecurityConfiguration.java @@ -23,7 +23,7 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter { private final UserService userService; private final JwtRequestFilter jwtRequestFilter; - public SecurityConfiguration(UserService userService, @Lazy JwtRequestFilter jwtRequestFilter) { + public SecurityConfiguration(@Lazy UserService userService, @Lazy JwtRequestFilter jwtRequestFilter) { this.userService = userService; this.jwtRequestFilter = jwtRequestFilter; } From f117e6fb6e8672ebe59d602d30f6d4e550bbba5c Mon Sep 17 00:00:00 2001 From: Oleg Levochkin Date: Sun, 15 Nov 2020 16:00:46 +0200 Subject: [PATCH 20/26] Create/udate/endpoint (#71) --- .../project/config/SecurityConfiguration.java | 2 +- src/main/java/com/kpi/project/model/User.java | 4 +- .../com/kpi/project/model/dto/UserDto.java | 4 ++ .../model/exception/ValidatorException.java | 12 ++++++ .../kpi/project/model/mapper/UserMapper.java | 8 ++++ .../project/repository/UserRepository.java | 3 ++ .../kpi/project/resource/ErrorsResource.java | 5 ++- .../kpi/project/resource/UserResource.java | 9 ++++ .../com/kpi/project/service/UserService.java | 18 +++++++- .../kpi/project/validate/UserValidator.java | 28 +++++++++--- .../kpi/project/service/UserServiceTest.java | 43 +++++++++++++++---- .../project/validator/UserValidatorTest.java | 28 ++++++++++++ 12 files changed, 143 insertions(+), 21 deletions(-) create mode 100644 src/main/java/com/kpi/project/model/exception/ValidatorException.java diff --git a/src/main/java/com/kpi/project/config/SecurityConfiguration.java b/src/main/java/com/kpi/project/config/SecurityConfiguration.java index fc776ba..a0bb182 100644 --- a/src/main/java/com/kpi/project/config/SecurityConfiguration.java +++ b/src/main/java/com/kpi/project/config/SecurityConfiguration.java @@ -40,7 +40,7 @@ protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .antMatchers( - (String[]) ArrayUtils.addAll(testEndpoints, + ArrayUtils.addAll(testEndpoints, "/authenticate", "/user/registration")) .permitAll() .anyRequest().authenticated() diff --git a/src/main/java/com/kpi/project/model/User.java b/src/main/java/com/kpi/project/model/User.java index 43bbab5..574abe3 100644 --- a/src/main/java/com/kpi/project/model/User.java +++ b/src/main/java/com/kpi/project/model/User.java @@ -21,7 +21,7 @@ import javax.persistence.Table; import java.util.Collection; import java.util.Collections; -import java.util.List; +import java.util.Set; @Data @NoArgsConstructor @@ -47,7 +47,7 @@ public class User implements UserDetails { @ElementCollection(fetch = FetchType.EAGER) @CollectionTable(name = "USER_ROLES", joinColumns = @JoinColumn(name = "USER_ID")) @Enumerated(EnumType.STRING) - private List roles; + private Set roles; @Override public Collection getAuthorities() { diff --git a/src/main/java/com/kpi/project/model/dto/UserDto.java b/src/main/java/com/kpi/project/model/dto/UserDto.java index 8c7af2e..c0d8898 100644 --- a/src/main/java/com/kpi/project/model/dto/UserDto.java +++ b/src/main/java/com/kpi/project/model/dto/UserDto.java @@ -2,11 +2,15 @@ import lombok.Data; +import java.util.Set; + @Data public class UserDto { + private Long id; private String username; private String email; private String password; private String matchingPassword; + private Set roles; } diff --git a/src/main/java/com/kpi/project/model/exception/ValidatorException.java b/src/main/java/com/kpi/project/model/exception/ValidatorException.java new file mode 100644 index 0000000..ff83bb1 --- /dev/null +++ b/src/main/java/com/kpi/project/model/exception/ValidatorException.java @@ -0,0 +1,12 @@ +package com.kpi.project.model.exception; + +public class ValidatorException extends IllegalArgumentException { + + public ValidatorException(String errorMessage) { + super(errorMessage); + } + + public ValidatorException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/com/kpi/project/model/mapper/UserMapper.java b/src/main/java/com/kpi/project/model/mapper/UserMapper.java index d326c60..830b6a4 100644 --- a/src/main/java/com/kpi/project/model/mapper/UserMapper.java +++ b/src/main/java/com/kpi/project/model/mapper/UserMapper.java @@ -4,6 +4,9 @@ import com.kpi.project.model.dto.UserDto; import org.springframework.stereotype.Component; +import java.util.Set; +import java.util.stream.Collectors; + @Component public class UserMapper { @@ -22,6 +25,11 @@ public UserDto userToDto(User user) { userDto.setPassword(user.getPassword()); userDto.setUsername(user.getUsername()); + final Set newRoles = user.getRoles().stream() + .map(Enum::toString) + .collect(Collectors.toSet()); + userDto.setRoles(newRoles); + return userDto; } } diff --git a/src/main/java/com/kpi/project/repository/UserRepository.java b/src/main/java/com/kpi/project/repository/UserRepository.java index 4c47329..5ce8a8e 100644 --- a/src/main/java/com/kpi/project/repository/UserRepository.java +++ b/src/main/java/com/kpi/project/repository/UserRepository.java @@ -13,4 +13,7 @@ public interface UserRepository extends JpaRepository { User loadByEmailOrUsername(@Param("loginParam") String loginParam); User findByEmailOrUsername(String email, String username); + + @Query("SELECT u FROM User u WHERE u.id = :idParam") + User findByIdIdentifier(@Param("idParam") Long id); } diff --git a/src/main/java/com/kpi/project/resource/ErrorsResource.java b/src/main/java/com/kpi/project/resource/ErrorsResource.java index d73b820..8945a54 100644 --- a/src/main/java/com/kpi/project/resource/ErrorsResource.java +++ b/src/main/java/com/kpi/project/resource/ErrorsResource.java @@ -2,6 +2,7 @@ import com.kpi.project.model.ErrorResponse; import com.kpi.project.model.enums.ErrorTypes; +import com.kpi.project.model.exception.ValidatorException; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -13,8 +14,8 @@ @ControllerAdvice public class ErrorsResource extends ResponseEntityExceptionHandler { - @ExceptionHandler(value = {IllegalArgumentException.class}) - protected ResponseEntity handleValidationExceptions(IllegalArgumentException ex, WebRequest request) { + @ExceptionHandler(value = {ValidatorException.class}) + protected ResponseEntity handleValidationExceptions(ValidatorException ex, WebRequest request) { final ErrorResponse errorResponse = new ErrorResponse(); errorResponse.setErrorType(ErrorTypes.validation_error); diff --git a/src/main/java/com/kpi/project/resource/UserResource.java b/src/main/java/com/kpi/project/resource/UserResource.java index a9d5c2c..b0b715e 100644 --- a/src/main/java/com/kpi/project/resource/UserResource.java +++ b/src/main/java/com/kpi/project/resource/UserResource.java @@ -4,6 +4,7 @@ import com.kpi.project.service.UserService; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; @@ -18,6 +19,14 @@ public UserResource(UserService userService) { @PostMapping("/user/registration") public ResponseEntity showRegistrationForm(@RequestBody UserDto userDto) { + return ResponseEntity.ok(userService.saveUser(userDto)); } + + @PutMapping("/user/update/roles") + public ResponseEntity updateUsersRole(@RequestBody UserDto userDto) { + final UserDto user = userService.updateUserRoles(userDto); + + return ResponseEntity.ok(user); + } } diff --git a/src/main/java/com/kpi/project/service/UserService.java b/src/main/java/com/kpi/project/service/UserService.java index 17704d5..5842519 100644 --- a/src/main/java/com/kpi/project/service/UserService.java +++ b/src/main/java/com/kpi/project/service/UserService.java @@ -2,8 +2,8 @@ import com.kpi.project.model.User; import com.kpi.project.model.dto.UserDto; -import com.kpi.project.model.mapper.UserMapper; import com.kpi.project.model.enums.Role; +import com.kpi.project.model.mapper.UserMapper; import com.kpi.project.repository.UserRepository; import com.kpi.project.validate.UserValidator; import org.springframework.security.core.userdetails.UserDetailsService; @@ -12,6 +12,8 @@ import org.springframework.stereotype.Service; import java.util.Collections; +import java.util.Set; +import java.util.stream.Collectors; @Service public class UserService implements UserDetailsService { @@ -29,6 +31,18 @@ public UserService(UserRepository userRepository, UserValidator userValidator, this.passwordEncoder = passwordEncoder; } + public UserDto updateUserRoles(UserDto userDto) throws UsernameNotFoundException { + userValidator.userRolesUpdateValidator(userDto.getId(), userDto.getRoles()); + + final User userWithNewRoles = userRepository.findByIdIdentifier(userDto.getId()); + final Set newRoles = userDto.getRoles().stream() + .map(Role::valueOf) + .collect(Collectors.toSet()); + userWithNewRoles.setRoles(newRoles); + + return userMapper.userToDto(userRepository.save(userWithNewRoles)); + } + @Override public User loadUserByUsername(String login) throws UsernameNotFoundException { @@ -38,8 +52,8 @@ public User loadUserByUsername(String login) throws UsernameNotFoundException { public UserDto saveUser(UserDto userDto) { userValidator.validateUser(userDto); final User user = userMapper.dtoToUser(userDto); + user.setRoles(Collections.singleton(Role.USER)); user.setPassword(passwordEncoder.encode(user.getPassword())); - user.setRoles(Collections.singletonList(Role.USER)); return userMapper.userToDto(userRepository.save(user)); } diff --git a/src/main/java/com/kpi/project/validate/UserValidator.java b/src/main/java/com/kpi/project/validate/UserValidator.java index 337c091..54820eb 100644 --- a/src/main/java/com/kpi/project/validate/UserValidator.java +++ b/src/main/java/com/kpi/project/validate/UserValidator.java @@ -2,11 +2,14 @@ import com.kpi.project.model.User; import com.kpi.project.model.dto.UserDto; +import com.kpi.project.model.enums.Role; +import com.kpi.project.model.exception.ValidatorException; import com.kpi.project.repository.UserRepository; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; import java.util.Objects; +import java.util.Set; @Component public class UserValidator { @@ -17,25 +20,38 @@ public UserValidator(UserRepository userRepository) { this.userRepository = userRepository; } + public void userRolesUpdateValidator(Long userId, Set roles) { + for (String role : roles) { + try { + Role.valueOf(role); + } catch (Exception e) { + throw new ValidatorException(String.format("Not existing role: %s", role)); + } + } + if (Objects.isNull(userRepository.findByIdIdentifier(userId))) { + throw new ValidatorException(String.format("User with id : %s, not exists", userId)); + } + } + public void validateUser(UserDto userToValidate) { if (!Objects.equals(userToValidate.getPassword(), userToValidate.getMatchingPassword())) { - throw new IllegalArgumentException("Passwords does not match"); + throw new ValidatorException("Passwords does not match"); } if (userToValidate.getPassword().length() < 4) { - throw new IllegalArgumentException("Password length must be minimum of 4 symbols"); + throw new ValidatorException("Password length must be minimum of 4 symbols"); } if (StringUtils.isBlank(userToValidate.getEmail())) { - throw new IllegalArgumentException("Email should be present"); + throw new ValidatorException("Email should be present"); } if (StringUtils.isBlank(userToValidate.getUsername())) { - throw new IllegalArgumentException("Username should be present"); + throw new ValidatorException("Username should be present"); } final User user = userRepository.findByEmailOrUsername(userToValidate.getEmail(), userToValidate.getUsername()); if (Objects.nonNull(user)) { if (Objects.equals(user.getEmail(), userToValidate.getEmail())) { - throw new IllegalArgumentException("Email already exists"); + throw new ValidatorException("Email already exists"); } - throw new IllegalArgumentException("Username already exists"); + throw new ValidatorException("Username already exists"); } } } diff --git a/src/test/java/com/kpi/project/service/UserServiceTest.java b/src/test/java/com/kpi/project/service/UserServiceTest.java index 7644010..e1e6dcb 100644 --- a/src/test/java/com/kpi/project/service/UserServiceTest.java +++ b/src/test/java/com/kpi/project/service/UserServiceTest.java @@ -2,21 +2,28 @@ import com.kpi.project.model.User; import com.kpi.project.model.dto.UserDto; -import com.kpi.project.model.mapper.UserMapper; import com.kpi.project.model.enums.Role; +import com.kpi.project.model.mapper.UserMapper; import com.kpi.project.repository.UserRepository; import com.kpi.project.validate.UserValidator; +import org.assertj.core.api.IterableAssert; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.security.core.GrantedAuthority; import org.springframework.security.crypto.password.PasswordEncoder; import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.verify; @@ -37,13 +44,21 @@ public class UserServiceTest { private User user; + private UserDto userDto; + @InjectMocks private UserService testingInstance; @BeforeEach public void setUp() { user = new User(1L, "mail@mail.com", "username", - "password", Collections.singletonList(Role.ADMIN)); + "password", Collections.singleton(Role.ADMIN)); + userDto = new UserDto(); + userDto.setPassword("password"); + userDto.setMatchingPassword("password"); + userDto.setUsername("username"); + userDto.setEmail("mail@mail.com"); + userDto.setId(1L); } @Test @@ -62,12 +77,6 @@ public void loadUserByUsernameShouldReturnUserFoundByEmailOrUsername() { @Test public void saveUserShouldReturnSavedUser() { // given - final UserDto userDto = new UserDto(); - userDto.setPassword("password"); - userDto.setMatchingPassword("password"); - userDto.setUsername("username"); - userDto.setEmail("mail@mail.com"); - given(userMapper.dtoToUser(userDto)).willReturn(user); given(userMapper.userToDto(user)).willReturn(userDto); given(userRepository.save(user)).willReturn(user); @@ -82,4 +91,22 @@ public void saveUserShouldReturnSavedUser() { verify(userRepository).save(user); assertThat(actualUser).isEqualTo(userDto); } + + @Test + public void updateUserRolesShouldReturnUpdatedUser() { + // given + final Set updatedRoles = Stream.of("ADMIN", "USER") + .collect(Collectors.toCollection(HashSet::new)); + userDto.setRoles(updatedRoles); + given(userRepository.save(any())).willReturn(user); + given(userMapper.userToDto(user)).willReturn(userDto); + given(userRepository.findByIdIdentifier(1L)).willReturn(user); + + // when + final UserDto actualUser = testingInstance.updateUserRoles(userDto); + + // then + assertThat(actualUser).isNotNull(); + assertThat(actualUser.getRoles()).containsExactly("ADMIN", "USER"); + } } diff --git a/src/test/java/com/kpi/project/validator/UserValidatorTest.java b/src/test/java/com/kpi/project/validator/UserValidatorTest.java index 17f6084..bf1660d 100644 --- a/src/test/java/com/kpi/project/validator/UserValidatorTest.java +++ b/src/test/java/com/kpi/project/validator/UserValidatorTest.java @@ -2,6 +2,7 @@ import com.kpi.project.model.User; import com.kpi.project.model.dto.UserDto; +import com.kpi.project.model.exception.ValidatorException; import com.kpi.project.repository.UserRepository; import com.kpi.project.validate.UserValidator; import org.junit.jupiter.api.BeforeEach; @@ -11,6 +12,11 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.mockito.BDDMockito.given; @@ -104,4 +110,26 @@ public void validateUserShouldThrowExceptionIfUserNameAlreadyExists() { .isThrownBy(() -> testingInstance.validateUser(user)) .withMessage("Username already exists"); } + + @Test + public void validateUserShouldThrowExceptionIfUserIsNotExist() { + // given + final Set roles = new HashSet(Arrays.asList("ADMIN", "USER")); + + // expected + assertThatExceptionOfType(ValidatorException.class) + .isThrownBy(() -> testingInstance.userRolesUpdateValidator(1L, roles)) + .withMessage("User with id : 1, not exists"); + } + + @Test + public void validateUserShouldThrowExceptionIfRolesIsNotExist() { + // given + final Set roles = new HashSet(Arrays.asList("NOT_EXISTED_ROLE")); + + // expected + assertThatExceptionOfType(ValidatorException.class) + .isThrownBy(() -> testingInstance.userRolesUpdateValidator(1L, roles)) + .withMessage("Not existing role: NOT_EXISTED_ROLE"); + } } From a2c12ce7580e6822da1a2e1f17501c2d9d83dbb5 Mon Sep 17 00:00:00 2001 From: Oleg Levochkin Date: Sun, 15 Nov 2020 16:27:57 +0200 Subject: [PATCH 21/26] Remove password field in json (#72) --- src/main/java/com/kpi/project/model/dto/UserDto.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/com/kpi/project/model/dto/UserDto.java b/src/main/java/com/kpi/project/model/dto/UserDto.java index c0d8898..86c0180 100644 --- a/src/main/java/com/kpi/project/model/dto/UserDto.java +++ b/src/main/java/com/kpi/project/model/dto/UserDto.java @@ -1,5 +1,6 @@ package com.kpi.project.model.dto; +import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; import java.util.Set; @@ -8,9 +9,16 @@ public class UserDto { private Long id; + private String username; + private String email; + + @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) private String password; + private String matchingPassword; + private Set roles; + } From 0440b07a7295884b7d58b04c967780f12bb1c945 Mon Sep 17 00:00:00 2001 From: Oleg Levochkin Date: Tue, 17 Nov 2020 19:54:21 +0200 Subject: [PATCH 22/26] Update validation test (#75) --- .../com/kpi/project/service/UserServiceTest.java | 2 -- .../kpi/project/validator/UserValidatorTest.java | 13 ++++++------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/test/java/com/kpi/project/service/UserServiceTest.java b/src/test/java/com/kpi/project/service/UserServiceTest.java index e1e6dcb..a660126 100644 --- a/src/test/java/com/kpi/project/service/UserServiceTest.java +++ b/src/test/java/com/kpi/project/service/UserServiceTest.java @@ -6,14 +6,12 @@ import com.kpi.project.model.mapper.UserMapper; import com.kpi.project.repository.UserRepository; import com.kpi.project.validate.UserValidator; -import org.assertj.core.api.IterableAssert; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.security.core.GrantedAuthority; import org.springframework.security.crypto.password.PasswordEncoder; import java.util.Collections; diff --git a/src/test/java/com/kpi/project/validator/UserValidatorTest.java b/src/test/java/com/kpi/project/validator/UserValidatorTest.java index bf1660d..77c24b2 100644 --- a/src/test/java/com/kpi/project/validator/UserValidatorTest.java +++ b/src/test/java/com/kpi/project/validator/UserValidatorTest.java @@ -17,7 +17,6 @@ import java.util.Set; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.mockito.BDDMockito.given; @ExtendWith(MockitoExtension.class) @@ -46,7 +45,7 @@ public void validateUserShouldThrowExceptionIfPasswordDoesNotMatch() { user.setPassword(""); // expected - assertThatIllegalArgumentException() + assertThatExceptionOfType(ValidatorException.class) .isThrownBy(() -> testingInstance.validateUser(user)) .withMessage("Passwords does not match"); } @@ -58,7 +57,7 @@ public void validateUserShouldThrowExceptionIfPasswordHasIncorrectLength() { user.setMatchingPassword("pas"); // expected - assertThatIllegalArgumentException() + assertThatExceptionOfType(ValidatorException.class) .isThrownBy(() -> testingInstance.validateUser(user)) .withMessage("Password length must be minimum of 4 symbols"); } @@ -69,7 +68,7 @@ public void validateUserShouldThrowExceptionIfEmailIsNotPresent() { user.setEmail(""); // expected - assertThatIllegalArgumentException() + assertThatExceptionOfType(ValidatorException.class) .isThrownBy(() -> testingInstance.validateUser(user)) .withMessage("Email should be present"); } @@ -80,7 +79,7 @@ public void validateUserShouldThrowExceptionIfUserNameIsNotPresent() { user.setUsername(""); // expected - assertThatIllegalArgumentException() + assertThatExceptionOfType(ValidatorException.class) .isThrownBy(() -> testingInstance.validateUser(user)) .withMessage("Username should be present"); } @@ -93,7 +92,7 @@ public void validateUserShouldThrowExceptionIfEmailAlreadyExists() { given(userRepository.findByEmailOrUsername("email@mail.com", "username")).willReturn(userModel); // expected - assertThatIllegalArgumentException() + assertThatExceptionOfType(ValidatorException.class) .isThrownBy(() -> testingInstance.validateUser(user)) .withMessage("Email already exists"); } @@ -106,7 +105,7 @@ public void validateUserShouldThrowExceptionIfUserNameAlreadyExists() { given(userRepository.findByEmailOrUsername("email@mail.com", "username")).willReturn(userModel); // expected - assertThatIllegalArgumentException() + assertThatExceptionOfType(ValidatorException.class) .isThrownBy(() -> testingInstance.validateUser(user)) .withMessage("Username already exists"); } From 79f5a8964e7664d3d57016718a9fa0c27cdc0b70 Mon Sep 17 00:00:00 2001 From: Oleg Levochkin Date: Tue, 17 Nov 2020 22:31:57 +0200 Subject: [PATCH 23/26] Created change password endpoint and fixed bag (#77) --- src/main/java/com/kpi/project/model/User.java | 6 +- .../project/repository/UserRepository.java | 4 + .../kpi/project/resource/UserResource.java | 7 ++ .../com/kpi/project/service/UserService.java | 18 +++- .../kpi/project/validate/UserValidator.java | 46 +++++++--- .../kpi/project/service/UserServiceTest.java | 24 +++++- .../project/validator/UserValidatorTest.java | 86 +++++++++++-------- 7 files changed, 139 insertions(+), 52 deletions(-) diff --git a/src/main/java/com/kpi/project/model/User.java b/src/main/java/com/kpi/project/model/User.java index 574abe3..ac7ce51 100644 --- a/src/main/java/com/kpi/project/model/User.java +++ b/src/main/java/com/kpi/project/model/User.java @@ -35,13 +35,13 @@ public class User implements UserDetails { @Column(name = "USER_ID") private Long id; - @Column(name = "EMAIL", unique = true) + @Column(name = "EMAIL", unique = true, nullable = false) private String email; - @Column(name = "USERNAME", unique = true) + @Column(name = "USERNAME", unique = true, nullable = false) private String username; - @Column(name = "PASSWORD") + @Column(name = "PASSWORD", nullable = false) private String password; @ElementCollection(fetch = FetchType.EAGER) diff --git a/src/main/java/com/kpi/project/repository/UserRepository.java b/src/main/java/com/kpi/project/repository/UserRepository.java index 5ce8a8e..f027b11 100644 --- a/src/main/java/com/kpi/project/repository/UserRepository.java +++ b/src/main/java/com/kpi/project/repository/UserRepository.java @@ -14,6 +14,10 @@ public interface UserRepository extends JpaRepository { User findByEmailOrUsername(String email, String username); + User findByEmail(String email); + + User findByUsername(String username); + @Query("SELECT u FROM User u WHERE u.id = :idParam") User findByIdIdentifier(@Param("idParam") Long id); } diff --git a/src/main/java/com/kpi/project/resource/UserResource.java b/src/main/java/com/kpi/project/resource/UserResource.java index b0b715e..0d59162 100644 --- a/src/main/java/com/kpi/project/resource/UserResource.java +++ b/src/main/java/com/kpi/project/resource/UserResource.java @@ -29,4 +29,11 @@ public ResponseEntity updateUsersRole(@RequestBody UserDto userDto) { return ResponseEntity.ok(user); } + + @PutMapping("/user/change/password") + public ResponseEntity changeUsersPassword(@RequestBody UserDto userDto) { + final UserDto user = userService.changeUserPassword(userDto); + + return ResponseEntity.ok(user); + } } diff --git a/src/main/java/com/kpi/project/service/UserService.java b/src/main/java/com/kpi/project/service/UserService.java index 5842519..e0242d0 100644 --- a/src/main/java/com/kpi/project/service/UserService.java +++ b/src/main/java/com/kpi/project/service/UserService.java @@ -31,6 +31,18 @@ public UserService(UserRepository userRepository, UserValidator userValidator, this.passwordEncoder = passwordEncoder; } + public UserDto changeUserPassword(UserDto userDto) throws UsernameNotFoundException { + final String password = userDto.getPassword(); + userValidator.validatePassword(password, userDto.getMatchingPassword()); + final Long userId = userDto.getId(); + userValidator.validateUserExistence(userId); + userValidator.validateUserPermissions(userId); + final User updatedUser = userRepository.findByIdIdentifier(userId); + updatedUser.setPassword(passwordEncoder.encode(password)); + + return userMapper.userToDto(userRepository.save(updatedUser)); + } + public UserDto updateUserRoles(UserDto userDto) throws UsernameNotFoundException { userValidator.userRolesUpdateValidator(userDto.getId(), userDto.getRoles()); @@ -50,10 +62,12 @@ public User loadUserByUsername(String login) throws UsernameNotFoundException { } public UserDto saveUser(UserDto userDto) { - userValidator.validateUser(userDto); + final String userPassword = userDto.getPassword(); + userValidator.validatePassword(userPassword, userDto.getMatchingPassword()); + userValidator.validateUser(userDto.getEmail(), userDto.getUsername()); final User user = userMapper.dtoToUser(userDto); user.setRoles(Collections.singleton(Role.USER)); - user.setPassword(passwordEncoder.encode(user.getPassword())); + user.setPassword(passwordEncoder.encode(userPassword)); return userMapper.userToDto(userRepository.save(user)); } diff --git a/src/main/java/com/kpi/project/validate/UserValidator.java b/src/main/java/com/kpi/project/validate/UserValidator.java index 54820eb..145a6e1 100644 --- a/src/main/java/com/kpi/project/validate/UserValidator.java +++ b/src/main/java/com/kpi/project/validate/UserValidator.java @@ -1,11 +1,12 @@ package com.kpi.project.validate; import com.kpi.project.model.User; -import com.kpi.project.model.dto.UserDto; import com.kpi.project.model.enums.Role; import com.kpi.project.model.exception.ValidatorException; import com.kpi.project.repository.UserRepository; import org.apache.commons.lang3.StringUtils; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; import java.util.Objects; @@ -33,24 +34,47 @@ public void userRolesUpdateValidator(Long userId, Set roles) { } } - public void validateUser(UserDto userToValidate) { - if (!Objects.equals(userToValidate.getPassword(), userToValidate.getMatchingPassword())) { + public void validateUserPermissions(Long id) { + final Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + + final String userName = authentication != null ? authentication.getName() : null; + final User userInContext = userRepository.findByUsername(userName); + final boolean isAdmin = userInContext.getRoles().stream().anyMatch(role -> role == Role.ADMIN); + + if (!isAdmin && !id.equals(userInContext.getId())) { + throw new ValidatorException("You do not have permission to change password"); + } + } + + public void validateUserExistence(Long id) { + if (Objects.isNull(userRepository.findByIdIdentifier(id))) { + throw new ValidatorException(String.format("User with id : %s, not exists", id)); + } + } + + public void validatePassword(String password, String matchingPassword) { + if (!Objects.equals(password, matchingPassword)) { throw new ValidatorException("Passwords does not match"); } - if (userToValidate.getPassword().length() < 4) { + if (password.length() < 4) { throw new ValidatorException("Password length must be minimum of 4 symbols"); } - if (StringUtils.isBlank(userToValidate.getEmail())) { + } + + public void validateUser(String userEmail, String userName) { + if (StringUtils.isBlank(userEmail)) { throw new ValidatorException("Email should be present"); } - if (StringUtils.isBlank(userToValidate.getUsername())) { + if (StringUtils.isBlank(userName)) { throw new ValidatorException("Username should be present"); } - final User user = userRepository.findByEmailOrUsername(userToValidate.getEmail(), userToValidate.getUsername()); - if (Objects.nonNull(user)) { - if (Objects.equals(user.getEmail(), userToValidate.getEmail())) { - throw new ValidatorException("Email already exists"); - } + + final User userByEmail = userRepository.findByEmail(userEmail); + final User userByUsername = userRepository.findByUsername(userName); + if (Objects.nonNull(userByEmail)) { + throw new ValidatorException("Email already exists"); + } + if (Objects.nonNull(userByUsername)) { throw new ValidatorException("Username already exists"); } } diff --git a/src/test/java/com/kpi/project/service/UserServiceTest.java b/src/test/java/com/kpi/project/service/UserServiceTest.java index a660126..78aafda 100644 --- a/src/test/java/com/kpi/project/service/UserServiceTest.java +++ b/src/test/java/com/kpi/project/service/UserServiceTest.java @@ -104,7 +104,27 @@ public void updateUserRolesShouldReturnUpdatedUser() { final UserDto actualUser = testingInstance.updateUserRoles(userDto); // then - assertThat(actualUser).isNotNull(); - assertThat(actualUser.getRoles()).containsExactly("ADMIN", "USER"); + assertThat(actualUser) + .isNotNull() + .extracting(UserDto::getRoles) + .isEqualTo(updatedRoles); + } + + @Test + public void changeUserPasswordShouldUpdateUsersPassword() { + // given + userDto.setPassword("passwordChange"); + given(userRepository.save(any())).willReturn(user); + given(userMapper.userToDto(user)).willReturn(userDto); + given(userRepository.findByIdIdentifier(1L)).willReturn(user); + + // when + final UserDto actualUser = testingInstance.changeUserPassword(userDto); + + // then + assertThat(actualUser) + .isNotNull() + .extracting(UserDto::getPassword) + .isEqualTo("passwordChange"); } } diff --git a/src/test/java/com/kpi/project/validator/UserValidatorTest.java b/src/test/java/com/kpi/project/validator/UserValidatorTest.java index 77c24b2..556a615 100644 --- a/src/test/java/com/kpi/project/validator/UserValidatorTest.java +++ b/src/test/java/com/kpi/project/validator/UserValidatorTest.java @@ -1,11 +1,9 @@ package com.kpi.project.validator; import com.kpi.project.model.User; -import com.kpi.project.model.dto.UserDto; import com.kpi.project.model.exception.ValidatorException; import com.kpi.project.repository.UserRepository; import com.kpi.project.validate.UserValidator; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -13,10 +11,13 @@ import org.mockito.junit.jupiter.MockitoExtension; import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; import java.util.Set; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; @ExtendWith(MockitoExtension.class) @@ -28,59 +29,35 @@ public class UserValidatorTest { @InjectMocks private UserValidator testingInstance; - private UserDto user; - - @BeforeEach - public void setUp() { - user = new UserDto(); - user.setPassword("password"); - user.setMatchingPassword("password"); - user.setUsername("username"); - user.setEmail("email@mail.com"); - } - @Test public void validateUserShouldThrowExceptionIfPasswordDoesNotMatch() { - // given - user.setPassword(""); - // expected assertThatExceptionOfType(ValidatorException.class) - .isThrownBy(() -> testingInstance.validateUser(user)) + .isThrownBy(() -> testingInstance.validatePassword("password", "wrongPassword")) .withMessage("Passwords does not match"); } @Test public void validateUserShouldThrowExceptionIfPasswordHasIncorrectLength() { - // given - user.setPassword("pas"); - user.setMatchingPassword("pas"); - // expected assertThatExceptionOfType(ValidatorException.class) - .isThrownBy(() -> testingInstance.validateUser(user)) + .isThrownBy(() -> testingInstance.validatePassword("pa", "pa")) .withMessage("Password length must be minimum of 4 symbols"); } @Test public void validateUserShouldThrowExceptionIfEmailIsNotPresent() { - // given - user.setEmail(""); - // expected assertThatExceptionOfType(ValidatorException.class) - .isThrownBy(() -> testingInstance.validateUser(user)) + .isThrownBy(() -> testingInstance.validateUser("", "username")) .withMessage("Email should be present"); } @Test public void validateUserShouldThrowExceptionIfUserNameIsNotPresent() { - // given - user.setUsername(""); - // expected assertThatExceptionOfType(ValidatorException.class) - .isThrownBy(() -> testingInstance.validateUser(user)) + .isThrownBy(() -> testingInstance.validateUser("email@mail.com", "")) .withMessage("Username should be present"); } @@ -89,11 +66,13 @@ public void validateUserShouldThrowExceptionIfEmailAlreadyExists() { // given final User userModel = new User(); userModel.setEmail("email@mail.com"); - given(userRepository.findByEmailOrUsername("email@mail.com", "username")).willReturn(userModel); + userModel.setUsername("username2.0"); + given(userRepository.findByEmail("email@mail.com")).willReturn(userModel); + given(userRepository.findByUsername("username")).willReturn(null); // expected assertThatExceptionOfType(ValidatorException.class) - .isThrownBy(() -> testingInstance.validateUser(user)) + .isThrownBy(() -> testingInstance.validateUser("email@mail.com", "username")) .withMessage("Email already exists"); } @@ -101,12 +80,14 @@ public void validateUserShouldThrowExceptionIfEmailAlreadyExists() { public void validateUserShouldThrowExceptionIfUserNameAlreadyExists() { // given final User userModel = new User(); + userModel.setEmail("email2.0@mail.com"); userModel.setUsername("username"); - given(userRepository.findByEmailOrUsername("email@mail.com", "username")).willReturn(userModel); + given(userRepository.findByEmail("email@mail.com")).willReturn(null); + given(userRepository.findByUsername("username")).willReturn(userModel); // expected assertThatExceptionOfType(ValidatorException.class) - .isThrownBy(() -> testingInstance.validateUser(user)) + .isThrownBy(() -> testingInstance.validateUser("email@mail.com", "username")) .withMessage("Username already exists"); } @@ -131,4 +112,41 @@ public void validateUserShouldThrowExceptionIfRolesIsNotExist() { .isThrownBy(() -> testingInstance.userRolesUpdateValidator(1L, roles)) .withMessage("Not existing role: NOT_EXISTED_ROLE"); } + + @Test + public void validateUserHavePermissionShouldThrowExceptionYouDoNotHavePermission() { + // given + final User someUser = new User(); + someUser.setId(2L); + someUser.setRoles(Collections.emptySet()); + given(userRepository.findByUsername(any())).willReturn(someUser); + + // expected + assertThatExceptionOfType(ValidatorException.class) + .isThrownBy(() -> testingInstance.validateUserPermissions(1L)) + .withMessage("You do not have permission to change password"); + } + + @Test + public void validateUserHavePermissionShouldNotThrowException() { + // given + final User someUser = new User(); + someUser.setId(1L); + someUser.setRoles(Collections.emptySet()); + given(userRepository.findByUsername(any())).willReturn(someUser); + + // expected + assertDoesNotThrow(() -> testingInstance.validateUserPermissions(1L)); + } + + @Test + public void validateUserExistenceShouldThrowExceptionUserIsNotExist() { + //given + given(userRepository.findByIdIdentifier(any())).willReturn(null); + + //expected + assertThatExceptionOfType(ValidatorException.class) + .isThrownBy(() -> testingInstance.validateUserExistence(25L)) + .withMessage("User with id : 25, not exists"); + } } From 865c9adb855fa2b76d5e65b1be4a0eb995044e47 Mon Sep 17 00:00:00 2001 From: Oleg Levochkin Date: Tue, 17 Nov 2020 22:32:39 +0200 Subject: [PATCH 24/26] Add TSLint to build (#76) --- client-web/package.json | 4 +- client-web/src/index.js | 8 +-- client-web/tslint.json | 114 +++++++++++++++++++++++++++------------- 3 files changed, 85 insertions(+), 41 deletions(-) diff --git a/client-web/package.json b/client-web/package.json index 0cb74e1..31deb2c 100644 --- a/client-web/package.json +++ b/client-web/package.json @@ -21,7 +21,9 @@ "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", - "eject": "react-scripts eject" + "eject": "react-scripts eject", + "lint": "tslint src/* --fix", + "build:tslint": "npm run lint && npm run start" }, "eslintConfig": { "extends": [ diff --git a/client-web/src/index.js b/client-web/src/index.js index c1f31c5..7384bc7 100644 --- a/client-web/src/index.js +++ b/client-web/src/index.js @@ -3,8 +3,8 @@ import ReactDOM from 'react-dom'; import App from './App'; ReactDOM.render( - - - , - document.getElementById('root') + + + , + document.getElementById('root') ); diff --git a/client-web/tslint.json b/client-web/tslint.json index 4f3e611..848319f 100644 --- a/client-web/tslint.json +++ b/client-web/tslint.json @@ -1,40 +1,82 @@ { - "extends": "tslint:recommended", - "rules": { - "max-line-length": { - "options": [120] - }, - "new-parens": true, - "no-arg": true, - "no-bitwise": true, - "no-conditional-assignment": true, - "no-consecutive-blank-lines": false, - "no-console": { - "severity": "warning", - "options": ["debug", "info", "log", "time", "timeEnd", "trace"] - } + "extends": "tslint:recommended", + "rules": { + "max-line-length": { + "options": [ + 120 + ] }, - "jsRules": { - "max-line-length": { - "options": [120] - }, - "no-empty": true, - "member-ordering": [true, {"order": "fields-first"}], - "no-magic-numbers": [true, 1, 2, 3, 0], - "no-reference": true, - "ban-comma-operator": true, - "curly": [true, "ignore-same-line"], - "no-console": [true, "log", "error"], - "no-duplicate-super": true, - "no-duplicate-switch-case": true, - "no-duplicate-variable": [true, "check-parameters"], - "no-invalid-template-strings": true, - "switch-default": true, - "triple-equals": true, - "use-isnan": true, - "no-duplicate-imports": [true, {"allow-namespace-imports": true}], - "arrow-return-shorthand": true, - "ordered-imports": true, - "whitespace": [true, "check-branch", "check-operator", "check-typecast"] + "new-parens": true, + "no-arg": true, + "no-bitwise": true, + "no-conditional-assignment": true, + "no-consecutive-blank-lines": false, + "no-console": { + "severity": "warning", + "options": [ + "debug", + "info", + "log", + "time", + "timeEnd", + "trace" + ] } + }, + "jsRules": { + "max-line-length": { + "options": [ + 120 + ] + }, + "no-empty": true, + "member-ordering": [ + true, + { + "order": "fields-first" + } + ], + "no-magic-numbers": [ + true, + 1, + 2, + 3, + 0 + ], + "no-reference": true, + "ban-comma-operator": true, + "curly": [ + true, + "ignore-same-line" + ], + "no-console": [ + true, + "log", + "error" + ], + "no-duplicate-super": true, + "no-duplicate-switch-case": true, + "no-duplicate-variable": [ + true, + "check-parameters" + ], + "no-invalid-template-strings": true, + "switch-default": true, + "triple-equals": true, + "use-isnan": true, + "no-duplicate-imports": [ + true, + { + "allow-namespace-imports": true + } + ], + "arrow-return-shorthand": true, + "ordered-imports": true, + "whitespace": [ + true, + "check-branch", + "check-operator", + "check-typecast" + ] + } } \ No newline at end of file From ce29e471c50247903decdaa9bb9ff05a98f1358a Mon Sep 17 00:00:00 2001 From: Serhii Nahornyi Date: Tue, 17 Nov 2020 23:39:56 +0200 Subject: [PATCH 25/26] Build front-end stage with maven (#82) --- client-web/.gitignore | 1 + client-web/package.json | 4 ++-- pom.xml | 43 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/client-web/.gitignore b/client-web/.gitignore index cdd9008..6b6890a 100644 --- a/client-web/.gitignore +++ b/client-web/.gitignore @@ -9,6 +9,7 @@ package-lock.json # production /build +/node # misc .DS_Store diff --git a/client-web/package.json b/client-web/package.json index 31deb2c..8254acb 100644 --- a/client-web/package.json +++ b/client-web/package.json @@ -22,8 +22,8 @@ "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject", - "lint": "tslint src/* --fix", - "build:tslint": "npm run lint && npm run start" + "lint": "tslint src/*", + "build:tslint": "npm run lint && npm run build" }, "eslintConfig": { "extends": [ diff --git a/pom.xml b/pom.xml index 9f08ba9..c3c96e1 100644 --- a/pom.xml +++ b/pom.xml @@ -132,6 +132,49 @@ maven-surefire-plugin ${surefire.plugin.version} + + + com.github.eirslett + frontend-maven-plugin + 1.10.3 + + client-web + + + + + install node and npm + + install-node-and-npm + + + v14.15.0 + 6.14.8 + + + + + npm install + + npm + + + install + + + + + npm run build:tslint + + npm + + + run build:tslint + + + + + From acdcf71f17ac89efff35dd07c86f8ac99b8ed228 Mon Sep 17 00:00:00 2001 From: Serhii Nahornyi Date: Wed, 9 Dec 2020 10:47:36 +0200 Subject: [PATCH 26/26] Test commit, stash then force --- client-web/package.json | 2 +- client-web/public/index.html | 2 +- client-web/src/App.js | 3 ++- client-web/src/pages/Login/Login.tsx | 4 +++ client-web/src/react-app-env.d.ts | 1 + client-web/tsconfig.json | 26 +++++++++++++++++++ .../kpi/project/resource/TestController.java | 6 ++++- 7 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 client-web/src/pages/Login/Login.tsx create mode 100644 client-web/src/react-app-env.d.ts create mode 100644 client-web/tsconfig.json diff --git a/client-web/package.json b/client-web/package.json index 8254acb..38ade89 100644 --- a/client-web/package.json +++ b/client-web/package.json @@ -43,6 +43,6 @@ }, "devDependencies": { "tslint": "^6.1.3", - "typescript": "^4.0.5" + "typescript": "~3.7.2" } } diff --git a/client-web/public/index.html b/client-web/public/index.html index 54ff46a..7ceb623 100644 --- a/client-web/public/index.html +++ b/client-web/public/index.html @@ -3,7 +3,7 @@ - YouFace + social-network diff --git a/client-web/src/App.js b/client-web/src/App.js index 5437f58..401ee13 100644 --- a/client-web/src/App.js +++ b/client-web/src/App.js @@ -1,8 +1,9 @@ import React from "react" +import {Login} from "./pages/Login/Login"; function App() { return ( -

test

+ ); } diff --git a/client-web/src/pages/Login/Login.tsx b/client-web/src/pages/Login/Login.tsx new file mode 100644 index 0000000..ec29278 --- /dev/null +++ b/client-web/src/pages/Login/Login.tsx @@ -0,0 +1,4 @@ +import * as React from 'react'; + +export const Login = () =>

Login page

+ diff --git a/client-web/src/react-app-env.d.ts b/client-web/src/react-app-env.d.ts new file mode 100644 index 0000000..6431bc5 --- /dev/null +++ b/client-web/src/react-app-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/client-web/tsconfig.json b/client-web/tsconfig.json new file mode 100644 index 0000000..7b1d3c6 --- /dev/null +++ b/client-web/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react" + }, + "include": [ + "src" + ] +} diff --git a/src/main/java/com/kpi/project/resource/TestController.java b/src/main/java/com/kpi/project/resource/TestController.java index 5d5aa42..124085e 100644 --- a/src/main/java/com/kpi/project/resource/TestController.java +++ b/src/main/java/com/kpi/project/resource/TestController.java @@ -5,7 +5,6 @@ @RestController public class TestController { - @RequestMapping("test/string") public String test() { return "string.ok"; @@ -20,4 +19,9 @@ public Object illegalArgument() { public Object exception() throws Exception { throw new Exception("some exception"); } + + @RequestMapping("/test/secured") + public Object getSecured() throws Exception { + throw new Exception("secured point"); + } }