Initial commit

This commit is contained in:
2022-09-05 04:13:18 -03:00
commit c0580118c1
59 changed files with 3122 additions and 0 deletions

26
.github/workflows/maven.yml vendored Normal file
View File

@@ -0,0 +1,26 @@
# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
name: Java CI with Maven
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
cache: maven
- name: Build with Maven
run: mvn -B package --file pom.xml

35
.gitignore vendored Normal file
View File

@@ -0,0 +1,35 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
src/main/resources/application-devel.yml

BIN
.mvn/wrapper/maven-wrapper.jar vendored Normal file

Binary file not shown.

2
.mvn/wrapper/maven-wrapper.properties vendored Normal file
View File

@@ -0,0 +1,2 @@
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.5/apache-maven-3.8.5-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar

16
Dockerfile Normal file
View File

@@ -0,0 +1,16 @@
#
# Build stage
#
FROM maven:3.8-jdk-11 AS build
COPY src /home/app/src
COPY pom.xml /home/app
RUN mvn -Dmaven.test.skip -f /home/app/pom.xml clean package
#
# Package stage
#
FROM openjdk:17-jdk
COPY --from=build /home/app/target/*.jar app.jar
COPY src/main/resources/* credentials/
ENTRYPOINT ["java","-jar","/app.jar"]

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Vitor Hideyoshi Nakazone Batista
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

316
mvnw vendored Executable file
View File

@@ -0,0 +1,316 @@
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Maven Start Up Batch script
#
# Required ENV vars:
# ------------------
# JAVA_HOME - location of a JDK home dir
#
# Optional ENV vars
# -----------------
# M2_HOME - location of maven2's installed home dir
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
# e.g. to debug Maven itself, use
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
# ----------------------------------------------------------------------------
if [ -z "$MAVEN_SKIP_RC" ] ; then
if [ -f /usr/local/etc/mavenrc ] ; then
. /usr/local/etc/mavenrc
fi
if [ -f /etc/mavenrc ] ; then
. /etc/mavenrc
fi
if [ -f "$HOME/.mavenrc" ] ; then
. "$HOME/.mavenrc"
fi
fi
# OS specific support. $var _must_ be set to either true or false.
cygwin=false;
darwin=false;
mingw=false
case "`uname`" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true;;
Darwin*) darwin=true
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
if [ -z "$JAVA_HOME" ]; then
if [ -x "/usr/libexec/java_home" ]; then
export JAVA_HOME="`/usr/libexec/java_home`"
else
export JAVA_HOME="/Library/Java/Home"
fi
fi
;;
esac
if [ -z "$JAVA_HOME" ] ; then
if [ -r /etc/gentoo-release ] ; then
JAVA_HOME=`java-config --jre-home`
fi
fi
if [ -z "$M2_HOME" ] ; then
## resolve links - $0 may be a link to maven's home
PRG="$0"
# need this for relative symlinks
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG="`dirname "$PRG"`/$link"
fi
done
saveddir=`pwd`
M2_HOME=`dirname "$PRG"`/..
# make it fully qualified
M2_HOME=`cd "$M2_HOME" && pwd`
cd "$saveddir"
# echo Using m2 at $M2_HOME
fi
# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin ; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --unix "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
fi
# For Mingw, ensure paths are in UNIX format before anything is touched
if $mingw ; then
[ -n "$M2_HOME" ] &&
M2_HOME="`(cd "$M2_HOME"; pwd)`"
[ -n "$JAVA_HOME" ] &&
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
fi
if [ -z "$JAVA_HOME" ]; then
javaExecutable="`which javac`"
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
# readlink(1) is not available as standard on Solaris 10.
readLink=`which readlink`
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
if $darwin ; then
javaHome="`dirname \"$javaExecutable\"`"
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
else
javaExecutable="`readlink -f \"$javaExecutable\"`"
fi
javaHome="`dirname \"$javaExecutable\"`"
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
JAVA_HOME="$javaHome"
export JAVA_HOME
fi
fi
fi
if [ -z "$JAVACMD" ] ; then
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
else
JAVACMD="`\\unset -f command; \\command -v java`"
fi
fi
if [ ! -x "$JAVACMD" ] ; then
echo "Error: JAVA_HOME is not defined correctly." >&2
echo " We cannot execute $JAVACMD" >&2
exit 1
fi
if [ -z "$JAVA_HOME" ] ; then
echo "Warning: JAVA_HOME environment variable is not set."
fi
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
# traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() {
if [ -z "$1" ]
then
echo "Path not specified to find_maven_basedir"
return 1
fi
basedir="$1"
wdir="$1"
while [ "$wdir" != '/' ] ; do
if [ -d "$wdir"/.mvn ] ; then
basedir=$wdir
break
fi
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
if [ -d "${wdir}" ]; then
wdir=`cd "$wdir/.."; pwd`
fi
# end of workaround
done
echo "${basedir}"
}
# concatenates all lines of a file
concat_lines() {
if [ -f "$1" ]; then
echo "$(tr -s '\n' ' ' < "$1")"
fi
}
BASE_DIR=`find_maven_basedir "$(pwd)"`
if [ -z "$BASE_DIR" ]; then
exit 1;
fi
##########################################################################################
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
# This allows using the maven wrapper in projects that prohibit checking in binary data.
##########################################################################################
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found .mvn/wrapper/maven-wrapper.jar"
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
fi
if [ -n "$MVNW_REPOURL" ]; then
jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
else
jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
fi
while IFS="=" read key value; do
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
esac
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
if [ "$MVNW_VERBOSE" = true ]; then
echo "Downloading from: $jarUrl"
fi
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
if $cygwin; then
wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
fi
if command -v wget > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found wget ... using wget"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
else
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
fi
elif command -v curl > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found curl ... using curl"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
curl -o "$wrapperJarPath" "$jarUrl" -f
else
curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Falling back to using Java to download"
fi
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
# For Cygwin, switch paths to Windows format before running javac
if $cygwin; then
javaClass=`cygpath --path --windows "$javaClass"`
fi
if [ -e "$javaClass" ]; then
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Compiling MavenWrapperDownloader.java ..."
fi
# Compiling the Java class
("$JAVA_HOME/bin/javac" "$javaClass")
fi
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
# Running the downloader
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Running MavenWrapperDownloader.java ..."
fi
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
fi
fi
fi
fi
##########################################################################################
# End of extension
##########################################################################################
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
if [ "$MVNW_VERBOSE" = true ]; then
echo $MAVEN_PROJECTBASEDIR
fi
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --path --windows "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
fi
# Provide a "standardized" way to retrieve the CLI args that will
# work with both Windows and non-Windows executions.
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
export MAVEN_CMD_LINE_ARGS
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
exec "$JAVACMD" \
$MAVEN_OPTS \
$MAVEN_DEBUG_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.home=${M2_HOME}" \
"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"

188
mvnw.cmd vendored Normal file
View File

@@ -0,0 +1,188 @@
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM https://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Maven Start Up Batch script
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
@REM M2_HOME - location of maven2's installed home dir
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
@REM ----------------------------------------------------------------------------
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
@echo off
@REM set title of command window
title %0
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
@REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
:skipRcPre
@setlocal
set ERROR_CODE=0
@REM To isolate internal variables from possible post scripts, we use another setlocal
@setlocal
@REM ==== START VALIDATION ====
if not "%JAVA_HOME%" == "" goto OkJHome
echo.
echo Error: JAVA_HOME not found in your environment. >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
:OkJHome
if exist "%JAVA_HOME%\bin\java.exe" goto init
echo.
echo Error: JAVA_HOME is set to an invalid directory. >&2
echo JAVA_HOME = "%JAVA_HOME%" >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
@REM ==== END VALIDATION ====
:init
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
@REM Fallback to current working directory if not found.
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
set EXEC_DIR=%CD%
set WDIR=%EXEC_DIR%
:findBaseDir
IF EXIST "%WDIR%"\.mvn goto baseDirFound
cd ..
IF "%WDIR%"=="%CD%" goto baseDirNotFound
set WDIR=%CD%
goto findBaseDir
:baseDirFound
set MAVEN_PROJECTBASEDIR=%WDIR%
cd "%EXEC_DIR%"
goto endDetectBaseDir
:baseDirNotFound
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
cd "%EXEC_DIR%"
:endDetectBaseDir
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
@setlocal EnableExtensions EnableDelayedExpansion
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
)
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
if exist %WRAPPER_JAR% (
if "%MVNW_VERBOSE%" == "true" (
echo Found %WRAPPER_JAR%
)
) else (
if not "%MVNW_REPOURL%" == "" (
SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
)
if "%MVNW_VERBOSE%" == "true" (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %DOWNLOAD_URL%
)
powershell -Command "&{"^
"$webclient = new-object System.Net.WebClient;"^
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
"}"^
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
"}"
if "%MVNW_VERBOSE%" == "true" (
echo Finished downloading %WRAPPER_JAR%
)
)
@REM End of extension
@REM Provide a "standardized" way to retrieve the CLI args that will
@REM work with both Windows and non-Windows executions.
set MAVEN_CMD_LINE_ARGS=%*
%MAVEN_JAVA_EXE% ^
%JVM_CONFIG_MAVEN_PROPS% ^
%MAVEN_OPTS% ^
%MAVEN_DEBUG_OPTS% ^
-classpath %WRAPPER_JAR% ^
"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
%WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
:error
set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%"=="on" pause
if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
cmd /C exit /B %ERROR_CODE%

120
pom.xml Normal file
View File

@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.hideyoshi</groupId>
<artifactId>backend-template</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>backend-template</name>
<description>Backend Template</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>2.7.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.5.0</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.project-lombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,21 @@
package com.hideyoshi.backendportfolio;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@SpringBootApplication
public class BackendPortfolioApplication {
public static void main(String[] args) {
SpringApplication.run(BackendPortfolioApplication.class, args);
}
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}

View File

@@ -0,0 +1,46 @@
package com.hideyoshi.backendportfolio.base.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.Arrays;
import java.util.List;
@Configuration
public class CorsConfig {
@Value("${com.hideyoshi.frontendPath}")
private String FRONTEND_PATH;
@Value("${com.hideyoshi.frontendConnectionType}")
private String CONNECTION_TYPE;
private final String HTTP = "http://";
private final String HTTPS = "https://";
@Bean
public CorsConfigurationSource corsConfigurationSource() {
String connectionProtocol = CONNECTION_TYPE.equals("secure")
? HTTPS
: HTTP;
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(List.of(connectionProtocol + FRONTEND_PATH));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(Arrays.asList("authorization", "content-type", "x-auth-token"));
configuration.setAllowCredentials(true);
configuration.setExposedHeaders(List.of("x-auth-token"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}

View File

@@ -0,0 +1,54 @@
package com.hideyoshi.backendportfolio.base.config;
import com.hideyoshi.backendportfolio.base.user.entity.Role;
import com.hideyoshi.backendportfolio.base.user.model.UserDTO;
import com.hideyoshi.backendportfolio.base.user.repo.UserRepository;
import com.hideyoshi.backendportfolio.base.user.service.UserService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
@Configuration
public class DefaultUserConfig {
@Value("${com.hideyoshi.defaultUser.fullName}")
private String ADMIN_NAME;
@Value("${com.hideyoshi.defaultUser.email}")
private String ADMIN_EMAIL;
@Value("${com.hideyoshi.defaultUser.username}")
private String ADMIN_USERNAME;
@Value("${com.hideyoshi.defaultUser.password}")
private String ADMIN_PASSWORD;
@Bean
CommandLineRunner run(UserService userService, UserRepository userRepo) {
return args -> {
UserDTO defaultUser = UserDTO.builder()
.fullname(ADMIN_NAME)
.email(ADMIN_EMAIL)
.username(ADMIN_USERNAME)
.password(ADMIN_PASSWORD)
.roles(new ArrayList<>())
.build();
if (!userRepo.findByUsername(defaultUser.getUsername()).isPresent()) {
defaultUser = userService.saveUser(defaultUser);
userService.addRoleToUser(
defaultUser.getId(),
Role.ADMIN.getDescription()
);
userService.addRoleToUser(
defaultUser.getId(),
Role.USER.getDescription()
);
}
};
}
}

View File

@@ -0,0 +1,34 @@
package com.hideyoshi.backendportfolio.base.config;
import com.hideyoshi.backendportfolio.util.exception.AuthenticationInvalidException;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerExceptionResolver;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Log4j2
@Component("restAuthenticationEntryPoint")
public class RestAuthenticationEntryPointConfig implements AuthenticationEntryPoint{
@Autowired
@Qualifier("handlerExceptionResolver")
private HandlerExceptionResolver resolver;
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authenticationException) {
resolver.resolveException(
request,
response,
null,
new AuthenticationInvalidException("Authentication Failed. Check your credentials.")
);
}
}

View File

@@ -0,0 +1,22 @@
package com.hideyoshi.backendportfolio.base.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.session.web.http.CookieSerializer;
import org.springframework.session.web.http.DefaultCookieSerializer;
public class SessionConfig {
@Value("${com.hideyoshi.frontEndPath}")
private String frontEndPath;
@Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setCookieName("SESSION");
serializer.setCookiePath("/");
serializer.setDomainNamePattern("(^.+)?(\\.)?(" + frontEndPath + ")((/#!)?(/\\w+)+)?");
return serializer;
}
}

View File

@@ -0,0 +1,72 @@
package com.hideyoshi.backendportfolio.base.security;
import com.hideyoshi.backendportfolio.base.config.RestAuthenticationEntryPointConfig;
import com.hideyoshi.backendportfolio.base.security.filter.CustomAuthenticationFilter;
import com.hideyoshi.backendportfolio.base.security.filter.CustomAuthorizationFilter;
import com.hideyoshi.backendportfolio.base.security.service.AuthService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.servlet.HandlerExceptionResolver;
import java.util.Arrays;
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final AuthService authService;
private final UserDetailsService userDetailsService;
private final BCryptPasswordEncoder passwordEncoder;
private final RestAuthenticationEntryPointConfig restAuthenticationEntryPointConfig;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
CustomAuthenticationFilter customAuthenticationFilter =
new CustomAuthenticationFilter(this.authenticationManager(), this.authService, this.restAuthenticationEntryPointConfig);
customAuthenticationFilter.setFilterProcessesUrl("/user/login");
http.cors().and().csrf().disable()
.authorizeRequests().antMatchers("/session/**").permitAll()
.and().authorizeRequests().antMatchers("/user/signup").permitAll()
.and().authorizeRequests().antMatchers("/user/login/refresh").permitAll()
.and().authorizeRequests().antMatchers("/**").hasAnyAuthority("ROLE_USER", "ROLE_ADMIN")
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and().addFilter(customAuthenticationFilter)
.addFilterBefore(new CustomAuthorizationFilter(this.authService), UsernamePasswordAuthenticationFilter.class);
}
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}

View File

@@ -0,0 +1,74 @@
package com.hideyoshi.backendportfolio.base.security.filter;
import com.auth0.jwt.algorithms.Algorithm;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.hideyoshi.backendportfolio.base.config.RestAuthenticationEntryPointConfig;
import com.hideyoshi.backendportfolio.base.security.service.AuthService;
import com.hideyoshi.backendportfolio.base.user.model.TokenDTO;
import com.hideyoshi.backendportfolio.base.user.model.UserDTO;
import lombok.extern.log4j.Log4j2;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.HashMap;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
@Log4j2
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final AuthService authService;
private final AuthenticationManager authenticationManager;
private final RestAuthenticationEntryPointConfig restAuthenticationEntryPointConfig;
public CustomAuthenticationFilter(AuthenticationManager authenticationManager, AuthService authService, RestAuthenticationEntryPointConfig restAuthenticationEntryPointConfig) {
this.authService = authService;
this.authenticationManager = authenticationManager;
this.restAuthenticationEntryPointConfig = restAuthenticationEntryPointConfig;
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
String username = request.getParameter("username");
String password = request.getParameter("password");
Authentication userAuthentication = null;
try {
userAuthentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(username, password)
);
} catch (AuthenticationException e) {
restAuthenticationEntryPointConfig.commence(request, response, e);
}
return userAuthentication;
}
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException {
UserDTO user = (UserDTO) authentication.getPrincipal();
Algorithm algorithm = Algorithm.HMAC256("secret".getBytes());
HashMap<String,TokenDTO> tokens = this.authService.generateTokens(user, algorithm, request);
HttpSession httpSession = request.getSession();
UserDTO authenticatedUser = user.toResponse(tokens.get("accessToken"), tokens.get("refreshToken"));
httpSession.setAttribute("user", authenticatedUser);
response.setContentType(APPLICATION_JSON_VALUE);
new ObjectMapper()
.writeValue(response.getOutputStream(), authenticatedUser);
}
}

View File

@@ -0,0 +1,72 @@
package com.hideyoshi.backendportfolio.base.security.filter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.hideyoshi.backendportfolio.base.security.service.AuthService;
import com.hideyoshi.backendportfolio.util.exception.BadRequestException;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.servlet.HandlerExceptionResolver;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import static org.springframework.http.HttpHeaders.AUTHORIZATION;
import static org.springframework.http.HttpStatus.FORBIDDEN;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
public class CustomAuthorizationFilter extends OncePerRequestFilter {
public static String AUTHORIZATION_TYPE_STRING = "Bearer ";
private final AuthService authService;
public CustomAuthorizationFilter(AuthService authService) {
this.authService = authService;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
if (request.getServletPath().equals("/user/login")) {
filterChain.doFilter(request, response);
} else {
String authorizationHeader = request.getHeader(AUTHORIZATION);
if (Objects.nonNull(authorizationHeader) && authorizationHeader.startsWith(AUTHORIZATION_TYPE_STRING)) {
try {
UsernamePasswordAuthenticationToken authenticationToken =
this.authService.verifyAccessToken(authorizationHeader);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain.doFilter(request, response);
} catch (Exception e) {
response.setHeader("error", e.getMessage());
response.setStatus(FORBIDDEN.value());
Map<String, String> error = new HashMap<>();
error.put("error_message", e.getMessage());
response.setContentType(APPLICATION_JSON_VALUE);
new ObjectMapper()
.writeValue(response.getOutputStream(), error);
}
} else {
filterChain.doFilter(request, response);
}
}
}
}

View File

@@ -0,0 +1,18 @@
package com.hideyoshi.backendportfolio.base.security.interceptor;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Component
@RequiredArgsConstructor
public class ConfigInterceptor implements WebMvcConfigurer {
private final UserResourceAccessInterceptor userResourceAccessInterceptor;
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(userResourceAccessInterceptor);
}
}

View File

@@ -0,0 +1,45 @@
package com.hideyoshi.backendportfolio.base.security.interceptor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.hideyoshi.backendportfolio.base.user.service.UserService;
import com.hideyoshi.backendportfolio.util.exception.BadRequestException;
import com.hideyoshi.backendportfolio.util.guard.UserResourceGuard;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Objects;
@Log4j2
@Component
@RequiredArgsConstructor
public class UserResourceAccessInterceptor implements HandlerInterceptor {
private final UserService userService;
private final ObjectMapper objectMapper;
public boolean preHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler) {
if (!(handler instanceof HandlerMethod)) {
return true;
}
final UserResourceGuard annotation = ((HandlerMethod)handler)
.getMethodAnnotation(UserResourceGuard.class);
if (Objects.nonNull(annotation)) {
Boolean accessPermission =
annotation.accessType().hasAccess(this.userService, this.objectMapper, request);
if (!accessPermission) {
throw new BadRequestException(annotation.denialMessage());
}
}
return true;
}
}

View File

@@ -0,0 +1,27 @@
package com.hideyoshi.backendportfolio.base.security.service;
import com.auth0.jwt.algorithms.Algorithm;
import com.hideyoshi.backendportfolio.base.user.model.TokenDTO;
import com.hideyoshi.backendportfolio.base.user.model.UserDTO;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.util.HashMap;
public interface AuthService {
TokenDTO generateAccessToken(@Valid UserDTO user, Algorithm algorithm, HttpServletRequest request);
TokenDTO generateRefreshToken(@Valid UserDTO user, Algorithm algorithm, HttpServletRequest request);
HashMap<String,TokenDTO> generateTokens(@Valid UserDTO user, Algorithm algorithm, HttpServletRequest request);
UsernamePasswordAuthenticationToken verifyAccessToken(String authorizationHeader);
UserDTO refreshAccessToken(String refreshToken, HttpServletRequest request, HttpServletResponse response);
UserDTO signupUser(@Valid UserDTO user, HttpServletRequest request);
}

View File

@@ -0,0 +1,175 @@
package com.hideyoshi.backendportfolio.base.security.service;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.hideyoshi.backendportfolio.base.user.model.TokenDTO;
import com.hideyoshi.backendportfolio.base.user.model.UserDTO;
import com.hideyoshi.backendportfolio.base.user.service.UserService;
import com.hideyoshi.backendportfolio.util.exception.BadRequestException;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.HandlerExceptionResolver;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.validation.Valid;
import java.util.*;
import java.util.stream.Collectors;
import static java.util.Arrays.stream;
@Log4j2
@Service
@RequiredArgsConstructor
public class AuthServiceImpl implements AuthService {
@Value("${com.hideyoshi.tokenSecret}")
private String TOKEN_SECRET;
@Value("${com.hideyoshi.accessTokenDuration}")
private Integer ACCESS_TOKEN_DURATION;
@Value("${com.hideyoshi.refreshTokenDuration}")
private Integer REFRESH_TOKEN_DURATION;
private static final String AUTHORIZATION_TYPE_STRING = "Bearer ";
private final UserService userService;
@Autowired
@Qualifier("handlerExceptionResolver")
private HandlerExceptionResolver resolver;
@Override
public TokenDTO generateAccessToken(@Valid UserDTO user, Algorithm algorithm, HttpServletRequest request) {
Date expirationDate = new Date(System.currentTimeMillis() + ACCESS_TOKEN_DURATION);
String accessToken = JWT.create()
.withSubject(user.getUsername())
.withExpiresAt(expirationDate)
.withIssuer(request.getRequestURL().toString())
.withClaim("roles", user.getAuthorities()
.stream().map(GrantedAuthority::getAuthority)
.collect(Collectors.toList()))
.sign(algorithm);
return new TokenDTO(accessToken, expirationDate);
}
@Override
public TokenDTO generateRefreshToken(@Valid UserDTO user, Algorithm algorithm, HttpServletRequest request) {
Date expirationDate = new Date(System.currentTimeMillis() + REFRESH_TOKEN_DURATION);
String refreshToken = JWT.create()
.withSubject(user.getUsername())
.withExpiresAt(expirationDate)
.withIssuer(request.getRequestURL().toString())
.sign(algorithm);
return new TokenDTO(refreshToken, expirationDate);
}
@Override
public HashMap<String, TokenDTO> generateTokens(@Valid UserDTO user, Algorithm algorithm, HttpServletRequest request) {
TokenDTO accessToken = generateAccessToken(user, algorithm, request);
TokenDTO refreshToken = generateRefreshToken(user, algorithm, request);
HashMap<String,TokenDTO> tokens = new HashMap<>();
tokens.put("accessToken", accessToken);
tokens.put("refreshToken", refreshToken);
return tokens;
}
@Override
public UsernamePasswordAuthenticationToken verifyAccessToken(String authorizationHeader) {
if (authorizationHeader.startsWith(AUTHORIZATION_TYPE_STRING)) {
String authorizationToken = authorizationHeader.substring(AUTHORIZATION_TYPE_STRING.length());
Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET.getBytes());
JWTVerifier verifier = JWT.require(algorithm).build();
DecodedJWT decodedJWT = verifier.verify(authorizationToken);
String username = decodedJWT.getSubject();
String[] roles = decodedJWT.getClaim("roles").asArray(String.class);
Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
stream(roles).forEach(role -> {
authorities.add(new SimpleGrantedAuthority(role));
});
return new UsernamePasswordAuthenticationToken(username, null, authorities);
}
return null;
}
@Override
public UserDTO refreshAccessToken(String refreshToken, HttpServletRequest request, HttpServletResponse response) {
if (Objects.nonNull(refreshToken)) {
Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET.getBytes());
JWTVerifier verifier = JWT.require(algorithm).build();
DecodedJWT decodedJWT = verifier.verify(refreshToken);
UserDTO user = this.userService.getUser(decodedJWT.getSubject());
if (Objects.nonNull(user)) {
HttpSession httpSession = request.getSession();
UserDTO authenticatedUser = user.toResponse(
this.generateAccessToken(user, algorithm, request),
new TokenDTO(
refreshToken,
decodedJWT.getExpiresAt()
)
);
httpSession.setAttribute("user", authenticatedUser);
return authenticatedUser;
}
} else {
resolver.resolveException(
request,
response,
null,
new BadRequestException("Invalid Refresh Token. Please authenticate first.")
);
}
return null;
}
@Override
public UserDTO signupUser(@Valid UserDTO user, HttpServletRequest request) {
Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET.getBytes());
UserDTO userSaved = this.userService.saveUser(user);
HashMap<String, TokenDTO> tokens = this.generateTokens(userSaved, algorithm, request);
HttpSession httpSession = request.getSession();
UserDTO userAuthenticated = userSaved.toResponse(tokens.get("accessToken"), tokens.get("refreshToken"));
httpSession.setAttribute("user", userAuthenticated);
return userAuthenticated;
}
}

View File

@@ -0,0 +1,35 @@
package com.hideyoshi.backendportfolio.base.session.api;
import com.hideyoshi.backendportfolio.base.session.service.SessionManagerService;
import com.hideyoshi.backendportfolio.base.user.model.UserDTO;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpSession;
@Controller
@RestController
@RequiredArgsConstructor
@RequestMapping(path = "/session")
public class SessionController {
private final SessionManagerService sessionManagerService;
@GetMapping(path = "/validate")
public ResponseEntity<UserDTO> validateCurrentSession(HttpSession session) {
return ResponseEntity.ok(this.sessionManagerService.validateSession(session));
}
@PostMapping(path="/destroy")
public ResponseEntity<Void> destroyCurrentSession(HttpSession session) {
this.sessionManagerService.destroySession(session);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
}

View File

@@ -0,0 +1,13 @@
package com.hideyoshi.backendportfolio.base.session.service;
import com.hideyoshi.backendportfolio.base.user.model.UserDTO;
import javax.servlet.http.HttpSession;
public interface SessionManagerService {
UserDTO validateSession(HttpSession session);
void destroySession(HttpSession session);
}

View File

@@ -0,0 +1,35 @@
package com.hideyoshi.backendportfolio.base.session.service;
import com.hideyoshi.backendportfolio.base.user.model.UserDTO;
import com.hideyoshi.backendportfolio.base.user.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpSession;
import java.util.Objects;
@Service
@RequiredArgsConstructor
public class SessionManagerServiceImpl implements SessionManagerService {
private final UserService userService;
@Override
public UserDTO validateSession(HttpSession session) {
UserDTO sessionObjects = (UserDTO) session.getAttribute("user");
if (Objects.nonNull(sessionObjects)) {
return this.userService.getUser(sessionObjects.getUsername())
.toResponse(sessionObjects.getAccessToken(), sessionObjects.getRefreshToken());
}
return null;
}
@Override
public void destroySession(HttpSession session) {
session.invalidate();
}
}

View File

@@ -0,0 +1,89 @@
package com.hideyoshi.backendportfolio.base.user.api;
import com.hideyoshi.backendportfolio.base.security.service.AuthService;
import com.hideyoshi.backendportfolio.base.user.model.TokenDTO;
import com.hideyoshi.backendportfolio.base.user.model.UserDTO;
import com.hideyoshi.backendportfolio.base.user.service.UserService;
import com.hideyoshi.backendportfolio.util.guard.UserResourceGuard;
import com.hideyoshi.backendportfolio.util.guard.UserResourceGuardEnum;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.net.URI;
import java.util.List;
@Log4j2
@Controller
@RestController
@RequestMapping("/user")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
private final AuthService authService;
@GetMapping
@UserResourceGuard(accessType = UserResourceGuardEnum.ADMIN_USER)
public ResponseEntity<List<UserDTO>> getUsers() {
return ResponseEntity.ok(this.userService.getUsers());
}
@PostMapping("/signup")
@UserResourceGuard(accessType = UserResourceGuardEnum.OPEN)
public ResponseEntity<UserDTO> signupUser(@RequestBody @Valid UserDTO user, HttpServletRequest request) {
URI uri = URI.create(
ServletUriComponentsBuilder
.fromCurrentContextPath()
.path("/user/signup").toUriString()
);
return ResponseEntity.created(uri).body(this.authService.signupUser(user, request));
}
@PostMapping("/delete/{id}")
@UserResourceGuard(accessType = UserResourceGuardEnum.SAME_USER)
public ResponseEntity<Void> deleteUser(@PathVariable("id") Long id) {
this.userService.deleteUser(id);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
//
// @PostMapping("/alter/{id}")
// @UserResourceGuard(accessType = UserResourceGuardEnum.SAME_USER)
// public ResponseEntity<Void> alterUser(@PathVariable("id") Long id, @RequestBody @Valid UserDTO user) {
// this.userService.alterUser(id, user);
// return new ResponseEntity<>(HttpStatus.NO_CONTENT);
// }
//
// @PostMapping("/alter/{id}/role/add")
// @UserResourceGuard(accessType = UserResourceGuardEnum.SAME_USER)
// public ResponseEntity<?> addRoleToUser(@PathVariable("id") Long id, @RequestBody RoleToUserDTO filter) {
// userService.addRoleToUser(id, filter.getRoleName());
// return ResponseEntity.ok().build();
// }
//
// @PostMapping("/alter/{id}/role/delete")
// @UserResourceGuard(accessType = UserResourceGuardEnum.SAME_USER)
// public ResponseEntity<?> deleteRoleToUser(@PathVariable("id") Long id, @RequestBody RoleToUserDTO filter) {
// userService.removeRoleFromUser(id, filter.getRoleName());
// return ResponseEntity.ok().build();
// }
@PostMapping("/login/refresh")
@UserResourceGuard(accessType = UserResourceGuardEnum.OPEN)
public ResponseEntity<UserDTO> refreshAccessToken(
@RequestBody @Valid TokenDTO refreshToken,
HttpServletRequest request,
HttpServletResponse response) {
return ResponseEntity.ok(this.authService.refreshAccessToken(refreshToken.getToken(), request, response));
}
}

View File

@@ -0,0 +1,18 @@
package com.hideyoshi.backendportfolio.base.user.entity;
public enum Provider {
GOOGLE("google"),
LOCAL("local");
private String name;
Provider(String name) {
this.name = name;
}
public String getName() {
return name;
}
}

View File

@@ -0,0 +1,29 @@
package com.hideyoshi.backendportfolio.base.user.entity;
import com.fasterxml.jackson.annotation.JsonValue;
public enum Role {
USER("ROLE_USER"),
ADMIN("ROLE_ADMIN");
@JsonValue
private final String description;
Role(String description) {
this.description = description;
}
public String getDescription() {
return this.description;
}
public static Role byValue(String description) {
for (Role r : values()) {
if (r.getDescription().equals(description)) {
return r;
}
}
throw new IllegalArgumentException("Argument not valid.");
}
}

View File

@@ -0,0 +1,77 @@
package com.hideyoshi.backendportfolio.base.user.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import static java.util.Arrays.stream;
@Data
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "`user`", schema = "auth")
public class User {
@Id
@SequenceGenerator(name = "seq_user", sequenceName = "auth.user_seq", allocationSize = 1)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq_user")
private Long id;
@Column(
name = "full_name",
nullable = false
)
private String fullname;
@Column(
name = "email",
unique = true,
nullable = false
)
private String email;
@Column(
name = "username",
unique = true,
nullable = false
)
private String username;
@Column(
name = "password",
nullable = false
)
private String password;
@Column(
name = "roles",
nullable = false
)
private String roles;
public void setRoles(List<Role> roles) {
this.roles = roles.stream()
.map(role -> role.getDescription())
.collect(Collectors.joining("&"));
}
public List<Role> getRoles() {
List<Role> roles = new ArrayList<>();
if (Objects.nonNull(this.roles) && !this.roles.isEmpty()) {
roles = stream(this.roles.split("&"))
.map(description -> Role.byValue(description))
.collect(Collectors.toList());
}
return roles;
}
}

View File

@@ -0,0 +1,10 @@
package com.hideyoshi.backendportfolio.base.user.model;
import lombok.Data;
@Data
public
class RoleToUserDTO {
private String username;
private String roleName;
}

View File

@@ -0,0 +1,21 @@
package com.hideyoshi.backendportfolio.base.user.model;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.*;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.Date;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TokenDTO implements Serializable {
@NotNull(message = "Invalid AccessToken. Please Authenticate first.")
private String token;
@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss")
private Date expirationDate;
}

View File

@@ -0,0 +1,162 @@
package com.hideyoshi.backendportfolio.base.user.model;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.hideyoshi.backendportfolio.base.user.entity.Provider;
import com.hideyoshi.backendportfolio.base.user.entity.Role;
import com.hideyoshi.backendportfolio.base.user.entity.User;
import com.hideyoshi.backendportfolio.util.validator.email.unique.UniqueEmail;
import com.hideyoshi.backendportfolio.util.validator.email.valid.ValidEmail;
import com.hideyoshi.backendportfolio.util.validator.password.ValidPassword;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.lang.Nullable;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Size;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public class UserDTO implements UserDetails {
private Long id;
@NotEmpty
private String fullname;
@NotEmpty
@ValidEmail
@UniqueEmail
private String email;
@NotEmpty
private String username;
@Nullable
@ValidPassword
private String password;
@Size(min=1)
private List<Role> roles;
private TokenDTO accessToken;
private TokenDTO refreshToken;
private Provider provider;
public UserDTO(
String fullname,
String email,
String username,
String password
) {
this.fullname = fullname;
this.email = email;
this.username = username;
this.password = password;
this.roles = List.of(Role.USER);
}
public UserDTO(
String fullname,
String email,
String username,
String password,
List<Role> roles
) {
this.fullname = fullname;
this.email = email;
this.username = username;
this.password = password;
this.roles = roles;
}
public UserDTO(User entity) {
this.id = entity.getId();
this.fullname = entity.getFullname();
this.email = entity.getEmail();
this.username = entity.getUsername();
this.password = entity.getPassword();
this.roles = entity.getRoles();
}
public User toEntity() {
return new User(
this.id,
this.fullname,
this.email,
this.username,
this.password,
Objects.nonNull(this.roles) ? this.roles.stream()
.map(role -> role.getDescription())
.collect(Collectors.joining("&")) : Role.USER.getDescription()
);
}
@JsonIgnore
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.roles.stream()
.map(role -> new SimpleGrantedAuthority(role.getDescription()))
.collect(Collectors.toList());
}
@JsonIgnore
@Override
public boolean isAccountNonExpired() {
return true;
}
@JsonIgnore
@Override
public boolean isAccountNonLocked() {
return true;
}
@JsonIgnore
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@JsonIgnore
@Override
public boolean isEnabled() {
return true;
}
public UserDTO toResponse() {
return UserDTO.builder()
.fullname(this.fullname)
.email(this.email)
.username(this.username)
.build();
}
public UserDTO toResponse(TokenDTO accessToken, TokenDTO refreshToken) {
return UserDTO.builder()
.id(this.id)
.fullname(this.fullname)
.email(this.email)
.username(this.username)
.roles(this.roles)
.accessToken(accessToken)
.refreshToken(refreshToken)
.build();
}
}

View File

@@ -0,0 +1,13 @@
package com.hideyoshi.backendportfolio.base.user.repo;
import com.hideyoshi.backendportfolio.base.user.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
Optional<User> findByEmail(String email);
}

View File

@@ -0,0 +1,26 @@
package com.hideyoshi.backendportfolio.base.user.service;
import com.hideyoshi.backendportfolio.base.user.model.UserDTO;
import org.springframework.security.core.userdetails.UserDetailsService;
import javax.validation.Valid;
import java.util.List;
public interface UserService extends UserDetailsService {
UserDTO saveUser(@Valid UserDTO user);
void alterUser(Long id, @Valid UserDTO user);
void deleteUser(Long id);
void addRoleToUser(Long id, String roleName);
void removeRoleFromUser(Long id, String roleName);
UserDTO getUser(Long id);
UserDTO getUser(String username);
List<UserDTO> getUsers();
}

View File

@@ -0,0 +1,146 @@
package com.hideyoshi.backendportfolio.base.user.service;
import com.hideyoshi.backendportfolio.base.user.entity.Role;
import com.hideyoshi.backendportfolio.base.user.entity.User;
import com.hideyoshi.backendportfolio.base.user.model.UserDTO;
import com.hideyoshi.backendportfolio.base.user.repo.UserRepository;
import com.hideyoshi.backendportfolio.util.exception.BadRequestException;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import javax.transaction.Transactional;
import javax.validation.Valid;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@Log4j2
@Service
@Transactional
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {
private final UserRepository userRepo;
private final PasswordEncoder passwordEncoder;
@Override
public UserDTO saveUser(@Valid UserDTO user) {
this.userRepo.findByUsername(user.getUsername()).ifPresent( userOnDB -> {
throw new BadRequestException(String.format("User %s already exists. Try another UserName.", userOnDB.getUsername()));
});
log.info(String.format("Saving to the database user of name: %s", user.getFullname()));
user.setPassword(passwordEncoder.encode(user.getPassword()));
UserDTO userSaved = new UserDTO(userRepo.save(user.toEntity()));
if (!userSaved.getRoles().contains(Role.USER)) {
userSaved.getRoles().add(Role.USER);
}
return userSaved;
}
@Override
public void alterUser(Long id, @Valid UserDTO user) {
this.userRepo.findById(id).ifPresentOrElse( userOnDB -> {
User userToSave = user.toEntity();
userToSave.setId(userOnDB.getId());
userRepo.save(userToSave);
}, () -> {
throw new BadRequestException(String.format("User {} doesn't exist.", user.getUsername()));
});
}
@Override
public void deleteUser(Long id) {
this.userRepo.findById(id).ifPresentOrElse( userOnDB -> {
this.userRepo.delete(userOnDB);
}, () -> {
throw new BadRequestException("User doesn't exist.");
});
}
@Override
public void addRoleToUser(Long id, String roleName) {
UserDTO userOnDB = this.getUser(id);
Role newAuthority = Role.byValue(roleName);
List<Role> roles = userOnDB.getRoles();
if (Objects.nonNull(newAuthority) && !roles.contains(newAuthority)) {
log.info(String.format("Adding to user %s the role %s",
userOnDB.getUsername(), newAuthority.getDescription()));
if (roles.add(newAuthority)) {
userOnDB.setRoles(roles);
this.alterUser(userOnDB.getId(), userOnDB);
}
}
}
@Override
public void removeRoleFromUser(Long id, String roleName) {
UserDTO userOnDB = this.getUser(id);
Role toDeleteAuthority = Role.byValue(roleName);
List<Role> roles = userOnDB.getRoles();
if (!roles.isEmpty()) {
log.info(String.format("Removing from user %s the role %s",
userOnDB.getUsername(), toDeleteAuthority.getDescription()));
roles = roles.stream()
.filter(role -> !role.equals(toDeleteAuthority))
.collect(Collectors.toList());
userOnDB.setRoles(roles);
this.alterUser(userOnDB.getId(), userOnDB);
}
}
@Override
public UserDTO getUser(Long id) {
log.info(String.format("Fetching user with id: %o", id));
return new UserDTO(
userRepo.findById(id)
.orElseThrow(() -> new BadRequestException("User Not Found. Please create an Account."))
);
}
@Override
public UserDTO getUser(String username) {
log.info(String.format("Fetching user: %s", username));
return new UserDTO(
userRepo.findByUsername(username)
.orElseThrow(() -> new BadRequestException("User Not Found. Please create an Account."))
);
}
@Override
public List<UserDTO> getUsers() {
log.info("Fetching all users.");
return userRepo.findAll().stream()
.map(user -> (new UserDTO(user)).toResponse())
.collect(Collectors.toList());
}
@Override
public UserDetails loadUserByUsername(String username) {
return this.getUser(username);
}
}

View File

@@ -0,0 +1,13 @@
package com.hideyoshi.backendportfolio.util.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.FORBIDDEN)
public class AuthenticationInvalidException extends RuntimeException {
public AuthenticationInvalidException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,15 @@
package com.hideyoshi.backendportfolio.util.exception;
import java.time.LocalDateTime;
public class AuthenticationInvalidExceptionDetails extends ExceptionDetails{
public AuthenticationInvalidExceptionDetails(
String title,
Integer status,
String details,
String developerMessage,
LocalDateTime timestamp) {
super(title, status, details, developerMessage, timestamp);
}
}

View File

@@ -0,0 +1,11 @@
package com.hideyoshi.backendportfolio.util.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.BAD_REQUEST)
public class BadRequestException extends RuntimeException{
public BadRequestException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,13 @@
package com.hideyoshi.backendportfolio.util.exception;
import java.time.LocalDateTime;
public class BadRequestExceptionDetails extends ExceptionDetails {
public BadRequestExceptionDetails(final String title, final Integer status,
final String details, final String developerMessage,
final LocalDateTime timestamp) {
super(title, status, details, developerMessage, timestamp);
}
}

View File

@@ -0,0 +1,30 @@
package com.hideyoshi.backendportfolio.util.exception;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime;
@Getter
@Setter
public class ExceptionDetails {
protected String title;
protected Integer status;
protected String details;
protected String developerMessage;
protected LocalDateTime timestamp;
public ExceptionDetails(final String title, final Integer status, final String details, final String developerMessage, final LocalDateTime timestamp) {
this.title = title;
this.status = status;
this.details = details;
this.developerMessage = developerMessage;
this.timestamp = timestamp;
}
}

View File

@@ -0,0 +1,25 @@
package com.hideyoshi.backendportfolio.util.exception;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime;
@Getter
@Setter
public class ValidationExceptionDetails extends ExceptionDetails {
private final String fields;
private final String fieldsMessage;
public ValidationExceptionDetails(final String title, final int status,
final String details, final String developerMessage,
final LocalDateTime timestamp, final String fields,
final String fieldsMessage) {
super(title, status, details, developerMessage, timestamp);
this.fields = fields;
this.fieldsMessage = fieldsMessage;
}
}

View File

@@ -0,0 +1,14 @@
package com.hideyoshi.backendportfolio.util.guard;
import java.lang.annotation.*;
@Target( ElementType.METHOD )
@Retention( RetentionPolicy.RUNTIME )
@Documented
public @interface UserResourceGuard {
String denialMessage() default "Operation not permitted. You don't have access to this Resource.";
UserResourceGuardEnum accessType();
}

View File

@@ -0,0 +1,111 @@
package com.hideyoshi.backendportfolio.util.guard;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.hideyoshi.backendportfolio.base.user.entity.Role;
import com.hideyoshi.backendportfolio.base.user.model.UserDTO;
import com.hideyoshi.backendportfolio.base.user.service.UserService;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.servlet.HandlerMapping;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
public enum UserResourceGuardEnum {
USER("user") {
@Override
public Boolean hasAccess(
UserService userService,
ObjectMapper objectMapper,
HttpServletRequest request) {
return justUser(userService, objectMapper, request);
}
},
SAME_USER("same_user") {
@Override
public Boolean hasAccess(
UserService userService,
ObjectMapper objectMapper,
HttpServletRequest request) {
return sameUser(userService, objectMapper, request);
}
},
ADMIN_USER("admin_user") {
@Override
public Boolean hasAccess(
UserService userService,
ObjectMapper objectMapper,
HttpServletRequest request) {
return adminUser(userService, objectMapper, request);
}
},
OPEN("open") {
@Override
public Boolean hasAccess(
UserService userService,
ObjectMapper objectMapper,
HttpServletRequest request) {
return openAccess(userService, objectMapper, request);
}
};
private final String accessType;
UserResourceGuardEnum(String accessType) {
this.accessType = accessType;
}
public abstract Boolean hasAccess(
UserService userService,
ObjectMapper objectMapper,
HttpServletRequest request);
public String getAccessType() {
return this.accessType;
}
public static UserResourceGuardEnum byValue(String accessType) {
for (UserResourceGuardEnum o : values()) {
if (o.getAccessType().equals(accessType)) {
return o;
}
}
throw new IllegalArgumentException("Argument not valid.");
}
private static boolean justUser(UserService userService, ObjectMapper objectMapper, HttpServletRequest request) {
String username = (String) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
UserDTO userLogged = userService.getUser(username);
return userLogged.getAuthorities().contains(new SimpleGrantedAuthority(Role.USER.getDescription()));
}
private static boolean sameUser(UserService userService, ObjectMapper objectMapper, HttpServletRequest request) {
String username = (String) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
UserDTO userLogged = userService.getUser(username);
Object requestPathVariable = request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
HashMap<String, String> pathVariable = objectMapper.convertValue(requestPathVariable, HashMap.class);
UserDTO userInfo = userService.getUser(Long.parseLong(pathVariable.get("id")));
return userLogged.getUsername().equals(userInfo.getUsername());
}
private static boolean adminUser(UserService userService, ObjectMapper objectMapper, HttpServletRequest request) {
String username = (String) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
UserDTO userLogged = userService.getUser(username);
return userLogged.getAuthorities().contains(new SimpleGrantedAuthority(Role.ADMIN.getDescription()));
}
private static Boolean openAccess(UserService userService, ObjectMapper objectMapper, HttpServletRequest request) {
return true;
}
}

View File

@@ -0,0 +1,22 @@
package com.hideyoshi.backendportfolio.util.guard;
import com.hideyoshi.backendportfolio.base.user.model.UserDTO;
import org.springframework.security.core.context.SecurityContextHolder;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class UserResourceValidator implements ConstraintValidator<UserResourceGuard, UserDTO> {
@Override
public void initialize(UserResourceGuard constraintAnnotation) {
ConstraintValidator.super.initialize(constraintAnnotation);
}
@Override
public boolean isValid(UserDTO userDTO, ConstraintValidatorContext constraintValidatorContext) {
System.out.println(SecurityContextHolder.getContext().getAuthentication());
return false;
}
}

View File

@@ -0,0 +1,84 @@
package com.hideyoshi.backendportfolio.util.handler;
import com.hideyoshi.backendportfolio.util.exception.*;
import lombok.extern.log4j.Log4j2;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.lang.Nullable;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
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;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@Log4j2
@ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(BadRequestException.class)
public ResponseEntity<BadRequestExceptionDetails> handleBadRequest(final BadRequestException exception) {
return new ResponseEntity<>(
new BadRequestExceptionDetails("Bad Request Exception, Check the Documentation",
HttpStatus.BAD_REQUEST.value(), exception.getMessage(),
exception.getClass().getName(), LocalDateTime.now()),
HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(AuthenticationInvalidException.class)
public ResponseEntity<AuthenticationInvalidExceptionDetails> handleBadRequest(final AuthenticationInvalidException exception) {
return new ResponseEntity<>(
new AuthenticationInvalidExceptionDetails("Authentication Failed. Check your credentials.",
HttpStatus.FORBIDDEN.value(), exception.getMessage(),
exception.getClass().getName(), LocalDateTime.now()),
HttpStatus.FORBIDDEN);
}
@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(
final MethodArgumentNotValidException exception, final HttpHeaders headers, final HttpStatus status, final WebRequest request) {
final List<FieldError> fieldErrors = exception.getBindingResult().getFieldErrors();
final String fields = fieldErrors.stream()
.map(FieldError::getField)
.collect(Collectors.joining(", "));
final String fieldsMessage = fieldErrors.stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining(", "));
return new ResponseEntity<>(
new ValidationExceptionDetails("Bad Request Exception, Invalid Fields",
HttpStatus.BAD_REQUEST.value(), "Check the field(s)",
exception.getClass().getName(), LocalDateTime.now(),
fields, fieldsMessage),
HttpStatus.BAD_REQUEST);
}
@Override
protected ResponseEntity<Object> handleExceptionInternal(final Exception exception, @Nullable final Object body, final HttpHeaders headers, final HttpStatus status, final WebRequest request) {
String errorMessage;
if (Objects.nonNull(exception.getCause())) {
errorMessage = exception.getCause().getMessage();
} else {
errorMessage = exception.getMessage();
}
final ExceptionDetails exceptionDetails = new ExceptionDetails(
errorMessage,
status.value(),
exception.getMessage(),
exception.getClass().getName(),
LocalDateTime.now()
);
return new ResponseEntity<>(exceptionDetails, headers, status);
}
}

View File

@@ -0,0 +1,34 @@
package com.hideyoshi.backendportfolio.util.validator.email.unique;
import com.hideyoshi.backendportfolio.base.user.repo.UserRepository;
import lombok.RequiredArgsConstructor;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.concurrent.atomic.AtomicReference;
@RequiredArgsConstructor
public class EmailUnique implements ConstraintValidator<UniqueEmail, String> {
private final UserRepository userRepository;
@Override
public void initialize(UniqueEmail constraintAnnotation) {
}
@Override
public boolean isValid(String email, ConstraintValidatorContext constraintValidatorContext) {
AtomicReference<Boolean> emailValid = new AtomicReference();
this.userRepository.findByEmail(email).ifPresentOrElse(
(value) -> {
emailValid.set(false);
},
() -> {
emailValid.set(true);
}
);
return emailValid.get();
}
}

View File

@@ -0,0 +1,24 @@
package com.hideyoshi.backendportfolio.util.validator.email.unique;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Target({TYPE, FIELD, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy = EmailUnique.class)
@Documented
public @interface UniqueEmail {
String message() default "Email taken, please choose another";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

View File

@@ -0,0 +1,32 @@
package com.hideyoshi.backendportfolio.util.validator.email.valid;
import lombok.RequiredArgsConstructor;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@RequiredArgsConstructor
public class EmailValidator implements ConstraintValidator<ValidEmail, String> {
private Pattern pattern;
private Matcher matcher;
private static final String EMAIL_PATTERN = "^[_A-Za-z\\d-+]+(.[_A-Za-z\\d-]+)*@+[A-Za-z\\d-]+(.[A-Za-z\\d]+)*(.[A-Za-z]{2,})$";
@Override
public void initialize(ValidEmail constraintAnnotation) {
}
@Override
public boolean isValid(String email, ConstraintValidatorContext context){
return (validateEmail(email));
}
private boolean validateEmail(String email) {
pattern = Pattern.compile(EMAIL_PATTERN);
matcher = pattern.matcher(email);
return matcher.matches();
}
}

View File

@@ -0,0 +1,25 @@
package com.hideyoshi.backendportfolio.util.validator.email.valid;
import com.hideyoshi.backendportfolio.util.validator.email.valid.EmailValidator;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Target({TYPE, FIELD, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy = EmailValidator.class)
@Documented
public @interface ValidEmail {
String message() default "Invalid email";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

View File

@@ -0,0 +1,25 @@
package com.hideyoshi.backendportfolio.util.validator.password;
import lombok.RequiredArgsConstructor;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.regex.Pattern;
@RequiredArgsConstructor
public class PasswordValidator implements ConstraintValidator<ValidPassword, String> {
private final String PASSWORD_PATTERN = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{8,}$";
@Override
public void initialize(ValidPassword constraintAnnotation) {}
@Override
public boolean isValid(String password, ConstraintValidatorContext context) {
return Pattern.compile(PASSWORD_PATTERN)
.matcher(password)
.matches();
}
}

View File

@@ -0,0 +1,23 @@
package com.hideyoshi.backendportfolio.util.validator.password;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Target({TYPE, FIELD, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy = PasswordValidator.class)
@Documented
public @interface ValidPassword {
String message() default "Invalid password";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

View File

@@ -0,0 +1,42 @@
com:
hideyoshi:
frontEndPath: ${FRONTEND_PATH}}
frontendConnectionType: ${FRONTEND_CONNECTION_TYPE}}
tokenSecret: ${TOKEN_SECRET}
accessTokenDuration: ${ACCESS_TOKEN_DURATION}
refreshTokenDuration: ${REFRESH_TOKEN_DURATION}
defaultUser:
fullName: ${DEFAULT_USER_FULLNAME}
email: ${DEFAULT_USER_EMAIL}
username: ${DEFAULT_USER_USERNAME}
password: ${DEFAULT_USER_PASSWORD}
server:
port: ${PORT}
spring:
datasource:
url: jdbc:${DATABASE_URL}
username: ${DATABASE_USERNAME}
password: ${DATABASE_PASSWORD}
session:
store:
type: redis
persistent: true
redis:
host: ${REDIS_URL}
port: ${REDIS_PORT}
password: ${REDIS_PASSWORD}
jpa:
open-in-view: false
hibernate:
ddl-auto: none
properties:
hibernate:
dialect: org.hibernate.dialect.PostgreSQLDialect
format_sql: true

View File

@@ -0,0 +1,11 @@
databaseChangeLog:
- changeSet:
id: db-table-model-client
author: vitor.h.n.batista@gmail.com
changes:
- sqlFile:
encoding: utf8
path: sqls/db-table-model-client.sql
relativeToChangelogFile: true
dbms: postgresql

View File

@@ -0,0 +1,21 @@
CREATE SCHEMA IF NOT EXISTS auth;
CREATE SEQUENCE IF NOT EXISTS auth.user_seq
INCREMENT 1
MINVALUE 1
MAXVALUE 9223372036854775807
START 1
CACHE 1;
CREATE TABLE IF NOT EXISTS auth.user (
id BIGINT NOT NULL DEFAULT NEXTVAL('auth.user_seq'),
full_name VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL,
username VARCHAR(20) NOT NULL,
password VARCHAR(100) NOT NULL,
roles VARCHAR(50) NOT NULL DEFAULT 'ROLE_USER',
CONSTRAINT client_primary_key PRIMARY KEY (id),
CONSTRAINT client_email_unique UNIQUE (email),
CONSTRAINT client_username_unique UNIQUE (username)
);

View File

@@ -0,0 +1,6 @@
databaseChangeLog:
- include:
file: db/changelog/client/db.changelog-client.yml

View File

@@ -0,0 +1,14 @@
package com.hideyoshi.backendportfolio;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.context.SpringBootTest;
@DataJpaTest
class BackendPortfolioApplicationTests {
@Test
void contextLoads() {
}
}

View File

@@ -0,0 +1,70 @@
package com.hideyoshi.backendportfolio.base.user.repo;
import com.hideyoshi.backendportfolio.base.user.entity.Role;
import com.hideyoshi.backendportfolio.base.user.entity.User;
import com.hideyoshi.backendportfolio.base.user.model.UserDTO;
import lombok.extern.log4j.Log4j2;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import java.util.List;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
@Log4j2
@DataJpaTest
class UserRepositoryTest {
@Autowired
private TestEntityManager entityManager;
@Autowired
private UserRepository underTest;
@Test
void savesUserToDataBase() {
// Given
User user = this.createEntity();
// When
User userSaved = this.underTest.save(user);
log.info(userSaved.getUsername());
// Then
assertThat(userSaved).isNotNull();
assertThat(userSaved).isEqualTo(user);
}
@Test
void canFindsUserByUsername() {
// Given
User userSaved = this.entityManager.persist(this.createEntity());
this.underTest.findAll();
// When
Optional<User> userOnDB =
this.underTest.findByUsername(userSaved.getUsername());
// Then
assertThat(userOnDB).isNotEmpty();
assertThat(userOnDB).hasValue(userSaved);
}
@Test
void cannotFindUserByUsername() {
// When
Optional<User> userOnDB = this.underTest.findByUsername("Batman");
// Then
assertThat(userOnDB).isEmpty();
}
private User createEntity() {
return new UserDTO(
"Clark Kent",
"superman@gmail.com",
"Superman",
"password",
List.of(Role.USER)
).toEntity();
}
}

View File

@@ -0,0 +1,363 @@
package com.hideyoshi.backendportfolio.base.user.service;
import com.hideyoshi.backendportfolio.base.security.service.AuthService;
import com.hideyoshi.backendportfolio.base.user.entity.Role;
import com.hideyoshi.backendportfolio.base.user.entity.User;
import com.hideyoshi.backendportfolio.base.user.model.UserDTO;
import com.hideyoshi.backendportfolio.base.user.repo.UserRepository;
import com.hideyoshi.backendportfolio.util.exception.BadRequestException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.*;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.test.annotation.DirtiesContext;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
@DataJpaTest
@ExtendWith(MockitoExtension.class)
@DirtiesContext(classMode= DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
class UserServiceImplTest {
@InjectMocks
private UserServiceImpl underTest;
@Mock
private UserRepository userRepository;
private PasswordEncoder passwordEncoder;
private AuthService authService;
@BeforeEach
void setUp() {
this.passwordEncoder = new BCryptPasswordEncoder();
this.underTest = new UserServiceImpl(userRepository,passwordEncoder);
}
@Test
void canSaveUser() {
BDDMockito.when(userRepository.findByUsername(ArgumentMatchers.any(String.class)))
.thenReturn(Optional.ofNullable(null));
BDDMockito.when(userRepository.save(ArgumentMatchers.any(User.class)))
.thenReturn(createUser().toEntity());
// Given
UserDTO user = this.createUser();
// When
UserDTO userSaved = this.underTest.saveUser(user);
//Then
ArgumentCaptor<User> userArgumentCaptor = ArgumentCaptor.forClass(User.class);
verify(userRepository).save(userArgumentCaptor.capture());
assertThat(userArgumentCaptor.getValue()).isEqualTo(user.toEntity());
assertThat(userSaved).isInstanceOf(UserDTO.class);
}
@Test
void cannotSaveUser() {
BDDMockito.when(userRepository.findByUsername(ArgumentMatchers.any(String.class)))
.thenReturn(Optional.ofNullable(createUser().toEntity()));
// Given
UserDTO user = this.createUser();
// When
//Then
assertThrows(
BadRequestException.class,
() -> {
this.underTest.saveUser(user);
},
"Excepts a BadRequestException to be thrown."
);
}
@Test
void canAlterUser() {
BDDMockito.when(userRepository.findById(ArgumentMatchers.any(Long.class)))
.thenReturn(Optional.ofNullable(createUser().toEntity()));
// Given
UserDTO user = this.createUser();
// When
this.underTest.alterUser(1L, user);
//Then
ArgumentCaptor<User> userArgumentCaptor = ArgumentCaptor.forClass(User.class);
verify(userRepository).save(userArgumentCaptor.capture());
assertThat(userArgumentCaptor.getValue()).isEqualTo(user.toEntity());
}
@Test
void cannotAlterUserDoesntExist() {
BDDMockito.when(userRepository.findById(ArgumentMatchers.any(Long.class)))
.thenReturn(Optional.ofNullable(null));
// Given
UserDTO user = this.createUser();
// When
//Then
assertThrows(
BadRequestException.class,
() -> {
this.underTest.alterUser(1L, user);
},
"User doesn't exist."
);
}
@Test
void canAddRoleToUser() {
UserDTO user = this.createUser();
BDDMockito.when(userRepository.findById(ArgumentMatchers.any(Long.class)))
.thenReturn(Optional.ofNullable(user.toEntity()));
BDDMockito.when(userRepository.findByUsername(ArgumentMatchers.any(String.class)))
.thenReturn(Optional.ofNullable(createUser().toEntity()));
// Given
UserDTO userSaved = this.underTest.getUser(user.getUsername());
if (!Objects.nonNull(userSaved)) {
userSaved = this.underTest.saveUser(user);
}
// When
this.underTest.addRoleToUser(userSaved.getId(), Role.USER.getDescription());
// Then
userSaved = this.underTest.getUser(userSaved.getUsername());
assertTrue(userSaved.getRoles().stream().anyMatch(e -> Role.USER.equals(e)));
}
@Test
void cannotAddRoleToUserDoesntExist() {
BDDMockito.when(userRepository.findById(ArgumentMatchers.any(Long.class)))
.thenReturn(Optional.ofNullable(null));
// Given
UserDTO user = this.createUser();
// When
// Then
UserDTO finalUserSaved = user;
assertThrows(
BadRequestException.class,
() -> {
this.underTest.addRoleToUser(finalUserSaved.getId(), Role.USER.getDescription());
},
"User not found. Error while adding role."
);
}
@Test
void cannotAddRoleToUserRoleDoesntExist() {
UserDTO user = this.createUser();
BDDMockito.when(userRepository.findById(ArgumentMatchers.any(Long.class)))
.thenReturn(Optional.ofNullable(user.toEntity()));
BDDMockito.when(userRepository.findByUsername(ArgumentMatchers.any(String.class)))
.thenReturn(Optional.ofNullable(createUser().toEntity()));
// Given
UserDTO userSaved = this.underTest.getUser(user.getUsername());
if (!Objects.nonNull(userSaved)) {
userSaved = this.underTest.saveUser(user);
}
// When
// Then
UserDTO finalUserSaved = userSaved;
assertThrows(
IllegalArgumentException.class,
() -> {
this.underTest.addRoleToUser(finalUserSaved.getId(), "BANANA");
},
"Argument not valid."
);
}
@Test
void canRemoveRoleFromUser() {
UserDTO user = this.createUser();
BDDMockito.when(userRepository.findByUsername(ArgumentMatchers.any(String.class)))
.thenReturn(Optional.ofNullable(user.toEntity()));
BDDMockito.when(userRepository.findById(ArgumentMatchers.any(Long.class)))
.thenReturn(Optional.ofNullable(user.toEntity()));
BDDMockito.when(userRepository.save(ArgumentMatchers.any(User.class)))
.thenReturn(createUser().toEntity());
// Given
UserDTO userSaved = this.underTest.getUser(user.getUsername());
if (!Objects.nonNull(userSaved)) {
userSaved = this.underTest.saveUser(user);
}
this.underTest.addRoleToUser(userSaved.getId(), Role.USER.getDescription());
// When
this.underTest.removeRoleFromUser(userSaved.getId(), Role.USER.getDescription());
// Then
ArgumentCaptor<User> userArgumentCaptor = ArgumentCaptor.forClass(User.class);
verify(userRepository).save(userArgumentCaptor.capture());
assertThat(userArgumentCaptor.getValue()).hasSameClassAs(user.toEntity());
assertFalse(userArgumentCaptor.getValue().getRoles().stream().anyMatch(e -> Role.USER.equals(e)));
}
@Test
void cannotRemoveRoleFromUserDoesntExist() {
UserDTO user = this.createUser();
BDDMockito.when(userRepository.findById(ArgumentMatchers.any(Long.class)))
.thenReturn(Optional.ofNullable(user.toEntity()));
BDDMockito.when(userRepository.findByUsername(ArgumentMatchers.any(String.class)))
.thenReturn(Optional.ofNullable(createUser().toEntity()));
// Given
UserDTO userSaved = this.underTest.getUser(user.getUsername());
if (!Objects.nonNull(userSaved)) {
userSaved = this.underTest.saveUser(user);
}
this.underTest.addRoleToUser(userSaved.getId(), Role.USER.getDescription());
// When
// Then
UserDTO finalUserSaved = userSaved;
assertThrows(
IllegalArgumentException.class,
() -> {
this.underTest.removeRoleFromUser(finalUserSaved.getId(), "BANANA");
},
"Argument not valid."
);
}
@Test
void cannotRemoveRoleFromUserRoleDoesntExist() {
// Given
UserDTO user = this.createUser();
// When
// Then
UserDTO finalUserSaved = user;
assertThrows(
BadRequestException.class,
() -> {
this.underTest.removeRoleFromUser(finalUserSaved.getId(), Role.USER.getDescription());
},
"User not found. Error while adding role."
);
}
@Test
void canGetUser() {
BDDMockito.when(userRepository.findByUsername(ArgumentMatchers.any(String.class)))
.thenReturn(Optional.ofNullable(createUser().toEntity()));
// Given
UserDTO user = this.createUser();
// When
UserDTO userOnDB = this.underTest.getUser(user.getUsername());
// Then
ArgumentCaptor<String> usernameArgumentCaptor = ArgumentCaptor.forClass(String.class);
verify(userRepository).findByUsername(usernameArgumentCaptor.capture());
assertThat(userOnDB).isNotNull();
assertThat(userOnDB).isInstanceOf(UserDTO.class);
assertThat(user.getUsername()).isEqualTo(usernameArgumentCaptor.getValue());
}
@Test
void cannotGetUser() {
BDDMockito.when(userRepository.findByUsername(ArgumentMatchers.any(String.class)))
.thenReturn(Optional.ofNullable(null));
// Given
UserDTO user = this.createUser();
// When
//Then
assertThrows(
BadRequestException.class,
() -> {
this.underTest.getUser(user.getUsername());
},
"Excepts a BadRequestException to be thrown."
);
}
@Test
void canGetUsers() {
List<UserDTO> users = this.underTest.getUsers();
assertThat(users).isNotNull();
}
@Test
void canLoadUserByUsername() {
BDDMockito.when(userRepository.findByUsername(ArgumentMatchers.any(String.class)))
.thenReturn(Optional.ofNullable(createUser().toEntity()));
// Given
UserDTO user = this.createUser();
// When
UserDTO userOnDB = (UserDTO) this.underTest.loadUserByUsername(user.getUsername());
// Then
ArgumentCaptor<String> usernameArgumentCaptor = ArgumentCaptor.forClass(String.class);
verify(userRepository).findByUsername(usernameArgumentCaptor.capture());
assertThat(userOnDB).isNotNull();
assertThat(userOnDB).isInstanceOf(UserDetails.class);
assertThat(user.getUsername()).isEqualTo(usernameArgumentCaptor.getValue());
}
@Test
void cannotLoadUserByUsername() {
BDDMockito.when(userRepository.findByUsername(ArgumentMatchers.any(String.class)))
.thenReturn(Optional.ofNullable(null));
// Given
UserDTO user = this.createUser();
// When
//Then
assertThrows(
BadRequestException.class,
() -> {
this.underTest.loadUserByUsername(user.getUsername());
},
"User Not Found. Please create an Account."
);
}
private UserDTO createUser() {
UserDTO userCreated = new UserDTO(
"Clark Kent",
"superman@gmail.com",
"Superman",
"password",
List.of(Role.USER)
);
userCreated.setId(1L);
return userCreated;
}
}

View File

@@ -0,0 +1,35 @@
com:
hideyoshi:
frontendPath: localhost:4200
frontendConnectionType: unsecure
tokenSecret: secret
accessTokenDuration: 1800000
refreshTokenDuration: 1314900000
defaultUser:
fullName: "Vitor Hideyoshi"
email: "vitor.h.n.batista@gmail.com"
username: "YoshiUnfriendly"
password: "passwd"
spring:
liquibase:
enabled: false
datasource:
jdbc:
url: jdbc:h2:mem:testdb
user: sa
password: sa
driver_class: org.h2.Driver
jpa:
open-in-view: false
hibernate:
ddl-auto: update
properties:
hibernate:
# dialect: org.hibernate.dialect.H2Dialect
format_sql: true

View File

@@ -0,0 +1 @@
CREATE SCHEMA IF NOT EXISTS auth;