├── .gitignore ├── README.md ├── branches-overlay.png ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── src ├── main ├── java │ └── com │ │ └── tesobe │ │ └── obp │ │ ├── Application.java │ │ ├── WebAuthConfig.java │ │ ├── api │ │ └── BanksController.java │ │ ├── auth │ │ └── ObpAuthProvider.java │ │ ├── clientapi │ │ ├── AddAuthRequestInterceptor.java │ │ ├── DirectAuthenticationClient.java │ │ ├── EntitlementsApiClient.java │ │ ├── ObpApiClient.java │ │ └── ObpBankMetaApiClient.java │ │ └── domain │ │ ├── ATM.java │ │ ├── Account.java │ │ ├── AccountRouting.java │ │ ├── AccountView.java │ │ ├── Address.java │ │ ├── Bank.java │ │ ├── Branch.java │ │ ├── Location.java │ │ ├── MoneyJson.java │ │ ├── Token.java │ │ ├── Transaction.java │ │ ├── TransactionRequest.java │ │ ├── TransactionRequestType.java │ │ └── User.java └── resources │ ├── application.properties │ ├── config.properties │ └── static │ └── index.html └── test ├── java └── com │ └── tesobe │ └── obp │ ├── AbstractTestSupport.java │ ├── auth │ └── DirectAuthenticationServiceTest.java │ ├── bankmeta │ └── BankMetadataTest.java │ ├── domain │ └── AccountServiceTest.java │ ├── transaction │ ├── MonetaryTransactionsServiceTest.java │ └── TransactionAnnotationServiceTest.java │ └── user │ └── EntitlementsTest.java └── resources └── auth.properties /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | .idea 3 | .gradle 4 | classes 5 | .eclipse 6 | classes 7 | out 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Open Bank API client implemented with Spring Boot 2 | This is a technology demonstration of the OBP API `v3.1.0` capabilities. 3 | This project provides Java bindings to sveral OBP REST API endpoints, including banks, branches, accounts and transactions. 4 | Included is also a demo webpage that puts all available bank branches in the `https://apisandbox.openbankproject.com` sandbox on a map. 5 | 6 | 7 | 8 | ## Setup and run 9 | The OBP Client API is using Spring Boot 2.1.x and needs JDK 10. 10 | 11 | Sign-up at the OBP sandbox: https://apisandbox.openbankproject.com/ Note the consumer key, username and password. 12 | Then edit `/src/main/resources/application.properties` and paste the consumer key above as the `obp.consumerKey` value. 13 | 14 | The standard command ```./gradlew bootRun``` will start a Tomcat container running on port 8080. Open a browser and browse to http://localhost:8080 15 | 16 | To login, use the username and password you noted above. After a successful login, a map with all available branches present in the `apisandbox` will be displayed. 17 | 18 | Only a small subset of the full API capabilities are integrated in the webapp (banks, branches). 19 | 20 | # OBP Java API bindings (v3.1.0) 21 | The OBP REST API is made available as a native Java API by the com.tesobe.obp.clientapi.*ApiClient classes. Accounts and transactions are available as many other OBP API capabilities. See the integration test suite for example usage. 22 | Caveat: currently, the supported version is `v3.1.0`. Also, not all of the REST API implemented. 23 | 24 | ## Code organization 25 | 26 | The code is organized between main and test directories. The tests are covering the following functionality: 27 | - Authentication 28 | - Get account details 29 | - Get transactions for an account 30 | - Tag transactions 31 | - Add geolocation to transaction 32 | 33 | ## Dependencies 34 | 35 | The single external dependency is on a live OBP sandbox that needs to run in order for the tested functionality to be successful. This sample project is using the version 2.2.0 of the OBP API. It shouldn't be difficult to use a different API version with the caveat that some of the entities must be adapted to match the data in the target API version. 36 | 37 | The OBP client API is abstracted via [Feign](http://projects.spring.io/spring-cloud/spring-cloud.html#spring-cloud-feign), a declarative REST client. See [ObpApiClient](src/main/java/com/tesobe/obp/api/ObpApiClient.java) and [DirectAuthenticationClient](src/main/java/com/tesobe/obp/api/DirectAuthenticationClient.java) for implementation details. 38 | 39 | Internally, the project is also using Lombok to simplify the code and the Joda Money API for representing monetary values. 40 | 41 | ## Test and build 42 | 43 | Run all tests with: 44 | ```./gradlew clean build``` 45 | 46 | There is a fat jar build as well, available under `build/libs` that can be executed with `java -jar obp-api-client.jar` -------------------------------------------------------------------------------- /branches-overlay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenBankProject/Hello-OBP-SpringBoot/9088fbf02c056dba88617d3d3dca31072b53adf7/branches-overlay.png -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext { 3 | springBootVersion = '2.1.2.RELEASE' 4 | } 5 | repositories { 6 | mavenCentral() 7 | maven { url "https://plugins.gradle.org/m2/" } 8 | } 9 | dependencies { 10 | classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") 11 | } 12 | } 13 | 14 | plugins { 15 | id 'net.ltgt.apt' version '0.10' // https://projectlombok.org/setup/gradle 16 | } 17 | 18 | apply plugin: 'java' 19 | apply plugin: 'org.springframework.boot' 20 | apply plugin: 'io.spring.dependency-management' 21 | 22 | jar { 23 | baseName = 'obp-clientapi-client' 24 | version = '1.0.0' 25 | } 26 | 27 | repositories { 28 | mavenCentral() 29 | } 30 | 31 | ext { 32 | set('springCloudVersion', 'Finchley.SR2') 33 | } 34 | 35 | sourceCompatibility = JavaVersion.VERSION_1_10 36 | targetCompatibility = JavaVersion.VERSION_1_10 37 | 38 | dependencyManagement { 39 | imports { 40 | mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}" 41 | } 42 | } 43 | 44 | dependencies { 45 | compile("org.springframework.boot:spring-boot-starter-web") 46 | compile('org.joda:joda-money:1.0.1') 47 | compileOnly('org.projectlombok:lombok') 48 | apt('org.projectlombok:lombok') 49 | 50 | implementation('org.springframework.cloud:spring-cloud-starter-openfeign') 51 | compile('org.springframework.boot:spring-boot-starter-security') 52 | compile('org.joda:joda-money:1.0.1') 53 | compile('org.projectlombok:lombok') 54 | 55 | testCompile("junit:junit") 56 | testCompile('org.springframework.boot:spring-boot-starter-test') 57 | } 58 | 59 | test { 60 | systemProperties = System.properties 61 | testLogging { 62 | events "passed", "skipped", "failed", "standardError" 63 | } 64 | } 65 | 66 | task copyStaticResources(type: Copy) { 67 | from "${sourceSets.main.resources}/static" 68 | into "${sourceSets.main.output.resourcesDir}/static" 69 | } 70 | 71 | bootRun { 72 | dependsOn copyStaticResources 73 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenBankProject/Hello-OBP-SpringBoot/9088fbf02c056dba88617d3d3dca31072b53adf7/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Jan 20 10:26:50 CET 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.3-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /src/main/java/com/tesobe/obp/Application.java: -------------------------------------------------------------------------------- 1 | package com.tesobe.obp; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.openfeign.EnableFeignClients; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.web.cors.CorsConfiguration; 8 | import org.springframework.web.cors.UrlBasedCorsConfigurationSource; 9 | import org.springframework.web.filter.CorsFilter; 10 | 11 | @SpringBootApplication 12 | @EnableFeignClients 13 | public class Application { 14 | public static final String ISO8601_TIMESTAMP_FORMAT = "yyyy-MM-dd\'T\'HH:mm:ss\'Z\'"; 15 | 16 | public static void main(String[] args) { 17 | SpringApplication.run(Application.class, args); 18 | } 19 | 20 | 21 | //http://stackoverflow.com/questions/31724994/spring-data-rest-and-cors/31748398#31748398 22 | //This method for enabling CORS is compatible to Spring Data REST and Spring MVC. 23 | @Bean 24 | public CorsFilter corsFilter() { 25 | UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); 26 | CorsConfiguration config = new CorsConfiguration(); 27 | config.setAllowCredentials(true); // you USUALLY want this 28 | config.addAllowedOrigin("*"); 29 | config.addAllowedHeader("*"); 30 | config.addAllowedMethod("GET"); 31 | config.addAllowedMethod("POST"); 32 | config.addAllowedMethod("PUT"); 33 | config.addAllowedMethod("PATCH"); 34 | config.addAllowedMethod("DELETE"); 35 | source.registerCorsConfiguration("/**", config); 36 | return new CorsFilter(source); 37 | } 38 | } -------------------------------------------------------------------------------- /src/main/java/com/tesobe/obp/WebAuthConfig.java: -------------------------------------------------------------------------------- 1 | package com.tesobe.obp; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.security.authentication.AuthenticationProvider; 5 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 6 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 7 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 8 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 9 | import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; 10 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 11 | 12 | // https://stackoverflow.com/questions/47273691/spring-boot-2-0-disable-default-security 13 | @Configuration 14 | @EnableWebSecurity 15 | public class WebAuthConfig extends WebSecurityConfigurerAdapter { 16 | 17 | private AuthenticationProvider authenticationProvider; 18 | 19 | public WebAuthConfig(AuthenticationProvider authProvider) { 20 | this.authenticationProvider = authProvider; 21 | } 22 | 23 | @Override 24 | protected void configure(AuthenticationManagerBuilder auth) throws Exception { 25 | auth.authenticationProvider(authenticationProvider); 26 | auth.eraseCredentials(false); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/tesobe/obp/api/BanksController.java: -------------------------------------------------------------------------------- 1 | package com.tesobe.obp.api; 2 | 3 | import com.tesobe.obp.clientapi.ObpBankMetaApiClient; 4 | import com.tesobe.obp.domain.ATM; 5 | import com.tesobe.obp.domain.Bank; 6 | import com.tesobe.obp.domain.Branch; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.web.bind.annotation.GetMapping; 10 | import org.springframework.web.bind.annotation.RestController; 11 | 12 | import java.util.Collection; 13 | import java.util.Collections; 14 | import java.util.List; 15 | import java.util.stream.Collectors; 16 | 17 | @RestController 18 | @Slf4j 19 | public class BanksController { 20 | 21 | @Autowired 22 | private ObpBankMetaApiClient obpBankMetaApiClient; 23 | 24 | @GetMapping("/branches") 25 | public List allBranches() { 26 | List allBanks = obpBankMetaApiClient.getBanks().getBanks(); 27 | log.info("Fetching branches for " + allBanks); 28 | return allBanks.stream().map(bank -> { 29 | try { 30 | List branches = obpBankMetaApiClient.getBranches(bank.getId()).getBranches(); 31 | return branches; 32 | } catch (Exception e) { 33 | //TODO: fix API not to return 400 if no branches are found for a bank 34 | return Collections.emptyList(); 35 | } 36 | }).filter(branches -> branches.size() > 0) //exclude empty branch lists 37 | .flatMap(Collection::stream).collect(Collectors.toList()); 38 | } 39 | 40 | @GetMapping("/atms") 41 | public List allAtms() { 42 | List allBanks = obpBankMetaApiClient.getBanks().getBanks(); 43 | return allBanks.stream().map(bank -> obpBankMetaApiClient.getAtms(bank.getId()).getAtms()) 44 | .filter(branches -> branches.size() > 0) //exclude empty branch lists 45 | .flatMap(Collection::stream).collect(Collectors.toList()); 46 | 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/tesobe/obp/auth/ObpAuthProvider.java: -------------------------------------------------------------------------------- 1 | package com.tesobe.obp.auth; 2 | 3 | import com.tesobe.obp.clientapi.DirectAuthenticationClient; 4 | import org.springframework.beans.factory.annotation.Value; 5 | import org.springframework.security.authentication.AuthenticationProvider; 6 | import org.springframework.security.authentication.BadCredentialsException; 7 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 8 | import org.springframework.security.core.Authentication; 9 | import org.springframework.security.core.AuthenticationException; 10 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 11 | import org.springframework.stereotype.Component; 12 | 13 | import java.util.Collections; 14 | 15 | @Component 16 | public class ObpAuthProvider implements AuthenticationProvider { 17 | 18 | private DirectAuthenticationClient directAuthenticationClient; 19 | private String authToken; 20 | 21 | public ObpAuthProvider(DirectAuthenticationClient directAuthenticationClient, 22 | @Value("${obp.consumerKey}") String authToken) { 23 | this.directAuthenticationClient = directAuthenticationClient; 24 | this.authToken = authToken; 25 | } 26 | 27 | @Override 28 | public Authentication authenticate(Authentication authentication) throws AuthenticationException { 29 | if(authentication.isAuthenticated()) { 30 | return authentication; 31 | } 32 | String name = authentication.getName(); 33 | String password = authentication.getCredentials().toString(); 34 | try { 35 | String token = directAuthenticationClient.login(name, password, authToken); 36 | return new UsernamePasswordAuthenticationToken(name, token, Collections.singleton(new SimpleGrantedAuthority("USER"))); 37 | } catch (Exception e) { 38 | throw new BadCredentialsException(e.getMessage()); 39 | } 40 | } 41 | 42 | @Override 43 | public boolean supports(Class authentication) { 44 | return authentication.equals( 45 | UsernamePasswordAuthenticationToken.class); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/tesobe/obp/clientapi/AddAuthRequestInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.tesobe.obp.clientapi; 2 | 3 | import feign.RequestInterceptor; 4 | import feign.RequestTemplate; 5 | import org.springframework.beans.factory.annotation.Value; 6 | import org.springframework.http.HttpHeaders; 7 | import org.springframework.security.core.context.SecurityContextHolder; 8 | import org.springframework.stereotype.Component; 9 | 10 | @Component 11 | public class AddAuthRequestInterceptor implements RequestInterceptor { 12 | private String directLoginPath; 13 | 14 | public AddAuthRequestInterceptor(@Value("${obp.api.directLoginPath}") String directLoginPath) { 15 | 16 | this.directLoginPath = directLoginPath; 17 | } 18 | 19 | @Override 20 | public void apply(RequestTemplate template) { 21 | //skip login request, no auth context to add. 22 | if(directLoginPath.equals(template.url())) { 23 | return; 24 | } 25 | if(SecurityContextHolder.getContext().getAuthentication().isAuthenticated()) return; 26 | 27 | String authToken = (String) SecurityContextHolder.getContext() 28 | .getAuthentication().getCredentials(); 29 | String dlHeader = String.format("DirectLogin token=%s", authToken); 30 | template.header(HttpHeaders.AUTHORIZATION, dlHeader); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/tesobe/obp/clientapi/DirectAuthenticationClient.java: -------------------------------------------------------------------------------- 1 | package com.tesobe.obp.clientapi; 2 | 3 | import com.tesobe.obp.domain.Token; 4 | import lombok.val; 5 | import org.springframework.cloud.openfeign.FeignClient; 6 | import org.springframework.web.bind.annotation.PostMapping; 7 | import org.springframework.web.bind.annotation.RequestHeader; 8 | 9 | @FeignClient(name="account", url="${obp.api.rootUrl}") 10 | public interface DirectAuthenticationClient { 11 | 12 | @PostMapping(value = "${obp.api.directLoginPath}") 13 | Token loginInternal(@RequestHeader("Authorization") String authHeader); 14 | 15 | default String login(String username, String password, String consumerKey) { 16 | val dlData = String.format("DirectLogin username=%s,password=%s,consumer_key=%s", username, password, consumerKey); 17 | val token = loginInternal(dlData).getToken(); 18 | return token; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/tesobe/obp/clientapi/EntitlementsApiClient.java: -------------------------------------------------------------------------------- 1 | package com.tesobe.obp.clientapi; 2 | 3 | import org.springframework.cloud.openfeign.FeignClient; 4 | import org.springframework.web.bind.annotation.PathVariable; 5 | import org.springframework.web.bind.annotation.RequestMapping; 6 | import org.springframework.web.bind.annotation.RequestMethod; 7 | 8 | @FeignClient(name="entitlement", url="${obp.api.versionedUrl}") 9 | public interface EntitlementsApiClient { 10 | 11 | @RequestMapping(method = RequestMethod.GET, value = "users/{userId}/entitlements") 12 | String getEntitlements(@PathVariable("userId") String userId); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/tesobe/obp/clientapi/ObpApiClient.java: -------------------------------------------------------------------------------- 1 | package com.tesobe.obp.clientapi; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import com.tesobe.obp.domain.*; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | import org.springframework.cloud.openfeign.FeignClient; 9 | import org.springframework.web.bind.annotation.DeleteMapping; 10 | import org.springframework.web.bind.annotation.GetMapping; 11 | import org.springframework.web.bind.annotation.PathVariable; 12 | import org.springframework.web.bind.annotation.PostMapping; 13 | import org.springframework.web.bind.annotation.PutMapping; 14 | import org.springframework.web.bind.annotation.RequestBody; 15 | import org.springframework.web.bind.annotation.RequestMapping; 16 | import org.springframework.web.bind.annotation.RequestMethod; 17 | 18 | import java.util.List; 19 | import java.util.stream.Collectors; 20 | 21 | @FeignClient(name="account", url="${obp.api.versionedUrl}") 22 | public interface ObpApiClient { 23 | 24 | //tag::my-account[] 25 | @GetMapping(value = "my/accounts") 26 | List getPrivateAccountsNoDetails(); 27 | 28 | default List getPrivateAccountsWithDetails() { 29 | List accountsNoDetails = getPrivateAccountsNoDetails(); 30 | return accountsNoDetails.stream().map(account -> getAccount(account.getBankId(), account.getId())).collect(Collectors.toList()); 31 | } 32 | 33 | @GetMapping(value = "my/banks/{bankId}/accounts/{accountId}/account") 34 | Account getAccount(@PathVariable("bankId") String bankId, @PathVariable("accountId") String accountId); 35 | 36 | @GetMapping(value = "banks/{bankId}/accounts/{accountId}/views") 37 | AccountViews getViewsForAccount(@PathVariable("bankId") String bankId, @PathVariable("accountId") String accountId); 38 | 39 | @GetMapping(value = "banks/{bankId}/accounts/{accountId}/owner/transactions") 40 | Transactions getTransactionsForAccount(@PathVariable("bankId") String bankId, 41 | @PathVariable("accountId") String accountId); 42 | 43 | @GetMapping(value = "banks/{bankId}/accounts/{accountId}/owner/transactions/{transactionId}/transaction") 44 | Transaction getTransactionById(@PathVariable("bankId") String bankId, @PathVariable("accountId") String accountId, 45 | @PathVariable("transactionId") String transactionId); 46 | 47 | @GetMapping(value = "banks/{bankId}/accounts/{accountId}/owner/transactions") 48 | String transferMoney(@PathVariable("bankId") String bankId, @PathVariable("accountId") String accountId, @RequestBody TransactionRequest transfer); 49 | //end::my-account[] 50 | 51 | @GetMapping(value = "banks/{bankId}/accounts/{accountId}/owner/transaction-request-types") 52 | TransactionRequestTypes getTransactionTypes(@PathVariable("bankId") String bankId, @PathVariable("accountId") String accountId); 53 | 54 | @PostMapping(value = "banks/{bankId}/accounts/{accountId}/owner/transaction-request-types/{transactionReqType}/transaction-requests") 55 | String initiateTransaction(@PathVariable("bankId") String bankId, @PathVariable("accountId") String accountId, 56 | @PathVariable("transactionReqType") String transactionReqType, @RequestBody TransactionRequest txRequest); 57 | 58 | //tag::public-accounts[] 59 | @GetMapping(value = "accounts") 60 | List getAllPublicAccountsAtAllBanks(); 61 | 62 | @PutMapping("/banks/{bankId}/accounts/{accountId}") 63 | Account createAccount(@PathVariable("bankId") String bankId, @PathVariable("accountId") String accountId, @RequestBody Account accountRequest); 64 | 65 | @GetMapping("users/current") 66 | User getCurrentUser(); 67 | 68 | //end::public-accounts[] 69 | 70 | //tag::tx-metadata[] 71 | @PostMapping(value = "banks/{bankId}/accounts/{accountId}/owner/transactions/{transactionId}/metadata/tags") 72 | Transaction.Tag tagTransaction(@PathVariable("bankId") String bankId, @PathVariable("accountId") String accountId, 73 | @PathVariable("transactionId") String transactionId, @RequestBody Transaction.Tag tag); 74 | 75 | @DeleteMapping(value = "banks/{bankId}/accounts/{accountId}/owner/transactions/{transactionId}/metadata/tags/{tagId}") 76 | void deleteTransactionTag(@PathVariable("bankId") String bankId, @PathVariable("accountId") String accountId, 77 | @PathVariable("transactionId") String transactionId, @PathVariable("tagId") String tagId); 78 | 79 | @PostMapping(value = "banks/{bankId}/accounts/{accountId}/owner/transactions/{transactionId}/metadata/where") 80 | void addLocation(@PathVariable("bankId") String bankId, @PathVariable("accountId") String accountId, 81 | @PathVariable("transactionId") String transactionId, @RequestBody Where location); 82 | 83 | @DeleteMapping(value = "banks/{bankId}/accounts/{accountId}/owner/transactions/{transactionId}/metadata/where") 84 | void deleteLocation(@PathVariable("bankId") String bankId, @PathVariable("accountId") String accountId, 85 | @PathVariable("transactionId") String transactionId); 86 | //end::tx-metadata[] 87 | 88 | @Data 89 | class Transactions { 90 | private List transactions; 91 | } 92 | 93 | @Data 94 | @NoArgsConstructor @AllArgsConstructor 95 | class Where { 96 | @JsonProperty("where") 97 | private Location location; 98 | } 99 | 100 | @Data 101 | class TransactionRequestTypes { 102 | @JsonProperty("transaction_request_types") 103 | private List transactionRequests; 104 | } 105 | 106 | @Data 107 | class AccountViews { 108 | private List views; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/com/tesobe/obp/clientapi/ObpBankMetaApiClient.java: -------------------------------------------------------------------------------- 1 | package com.tesobe.obp.clientapi; 2 | 3 | import com.tesobe.obp.domain.ATM; 4 | import com.tesobe.obp.domain.Bank; 5 | import com.tesobe.obp.domain.Branch; 6 | import lombok.Data; 7 | import org.springframework.cloud.openfeign.FeignClient; 8 | import org.springframework.http.MediaType; 9 | import org.springframework.web.bind.annotation.GetMapping; 10 | import org.springframework.web.bind.annotation.PathVariable; 11 | import org.springframework.web.bind.annotation.RequestMapping; 12 | import org.springframework.web.bind.annotation.RequestMethod; 13 | 14 | import java.util.List; 15 | 16 | @FeignClient(name="bank", url="${obp.api.versionedUrl}") 17 | public interface ObpBankMetaApiClient { 18 | 19 | @GetMapping(value = "banks", consumes = MediaType.APPLICATION_JSON_VALUE) 20 | Banks getBanks(); 21 | 22 | @RequestMapping(method = RequestMethod.GET, value = "banks/{bankId}/branches") 23 | Branches getBranches(@PathVariable("bankId") String bankId); 24 | 25 | @RequestMapping(method = RequestMethod.GET, value = "banks/{bankId}/branches/{branchId}") 26 | Branch getBranch(@PathVariable("bankId") String bankId, @PathVariable("branchId") String branchId); 27 | 28 | @RequestMapping(method = RequestMethod.GET, value = "banks/{bankId}/atms") 29 | ATMs getAtms(@PathVariable("bankId") String bankId); 30 | 31 | @RequestMapping(method = RequestMethod.GET, value = "banks/{bankId}/branches/{branchId}/atms/{atmId}") 32 | Branch getAtm(@PathVariable("bankId") String bankId, @PathVariable("branchId") String branchId, @PathVariable("atmId") String atmId); 33 | 34 | @Data 35 | class Banks { 36 | private List banks; 37 | } 38 | 39 | @Data 40 | class ATMs { 41 | private List atms; 42 | } 43 | 44 | @Data 45 | class Branches { 46 | private List branches; 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/tesobe/obp/domain/ATM.java: -------------------------------------------------------------------------------- 1 | package com.tesobe.obp.domain; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class ATM { 7 | private String id; 8 | private Location location; 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/tesobe/obp/domain/Account.java: -------------------------------------------------------------------------------- 1 | package com.tesobe.obp.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 5 | import com.fasterxml.jackson.databind.annotation.JsonSerialize; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | import org.joda.money.Money; 10 | 11 | @Data 12 | public class Account { 13 | private String id; 14 | 15 | @JsonProperty("user_id") 16 | private String userId; 17 | 18 | private String label; 19 | 20 | @JsonProperty("bank_id") 21 | private String bankId; 22 | 23 | @JsonProperty("branch_id") 24 | private String branchId; 25 | 26 | @JsonDeserialize(using = MoneyJson.MoneyDeserializer.class) 27 | @JsonSerialize(using = MoneyJson.MoneySerializer.class) 28 | private Money balance; 29 | 30 | private String type; 31 | 32 | @JsonProperty("IBAN") 33 | private String iban; 34 | 35 | @JsonProperty("swist_bic") 36 | private String bic; 37 | 38 | @JsonProperty("account_routing") 39 | private AccountRouting accountRouting; 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/tesobe/obp/domain/AccountRouting.java: -------------------------------------------------------------------------------- 1 | package com.tesobe.obp.domain; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | public class AccountRouting { 11 | private String scheme; 12 | private String address; 13 | } -------------------------------------------------------------------------------- /src/main/java/com/tesobe/obp/domain/AccountView.java: -------------------------------------------------------------------------------- 1 | package com.tesobe.obp.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.Data; 5 | 6 | @Data 7 | public class AccountView { 8 | private String id; 9 | 10 | @JsonProperty("short_name") 11 | private String shortName; 12 | 13 | private String description; 14 | 15 | @JsonProperty("is_public") 16 | private Boolean isPublic; 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/tesobe/obp/domain/Address.java: -------------------------------------------------------------------------------- 1 | package com.tesobe.obp.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.Data; 5 | 6 | @Data 7 | public class Address { 8 | @JsonProperty("line_1") 9 | private String street; 10 | private String city; 11 | private String postcode; 12 | private String country; 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/tesobe/obp/domain/Bank.java: -------------------------------------------------------------------------------- 1 | package com.tesobe.obp.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import com.tesobe.obp.clientapi.ObpBankMetaApiClient; 5 | import lombok.Data; 6 | 7 | import java.util.List; 8 | 9 | @Data 10 | public class Bank { 11 | 12 | private String id; 13 | 14 | @JsonProperty("short_name") 15 | private String shortName; 16 | 17 | @JsonProperty("full_name") 18 | private String fullName; 19 | 20 | @JsonProperty("logo_URL") 21 | private String logoUrl; 22 | 23 | private String website; 24 | 25 | private List branches; 26 | 27 | private List atms; 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/tesobe/obp/domain/Branch.java: -------------------------------------------------------------------------------- 1 | package com.tesobe.obp.domain; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class Branch { 7 | private String id; 8 | private String name; 9 | private Location location; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/tesobe/obp/domain/Location.java: -------------------------------------------------------------------------------- 1 | package com.tesobe.obp.domain; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | public class Location { 11 | private double latitude; 12 | private double longitude; 13 | } 14 | 15 | -------------------------------------------------------------------------------- /src/main/java/com/tesobe/obp/domain/MoneyJson.java: -------------------------------------------------------------------------------- 1 | package com.tesobe.obp.domain; 2 | 3 | import com.fasterxml.jackson.core.JsonGenerator; 4 | import com.fasterxml.jackson.core.JsonParser; 5 | import com.fasterxml.jackson.core.ObjectCodec; 6 | import com.fasterxml.jackson.databind.*; 7 | import lombok.AllArgsConstructor; 8 | import lombok.Data; 9 | import lombok.NoArgsConstructor; 10 | import org.joda.money.CurrencyUnit; 11 | import org.joda.money.Money; 12 | import org.joda.money.format.MoneyFormatter; 13 | import org.joda.money.format.MoneyFormatterBuilder; 14 | import org.springframework.boot.jackson.JsonObjectDeserializer; 15 | import org.springframework.boot.jackson.JsonObjectSerializer; 16 | import org.springframework.context.i18n.LocaleContextHolder; 17 | 18 | import java.io.IOException; 19 | 20 | public class MoneyJson { 21 | 22 | public static class MoneyDeserializer extends JsonObjectDeserializer { 23 | @Override 24 | protected Money deserializeObject(JsonParser jsonParser, DeserializationContext context, ObjectCodec codec, JsonNode tree) throws IOException { 25 | return Money.of(CurrencyUnit.of(tree.get("currency").asText()), Double.parseDouble(tree.get("amount").textValue())); 26 | } 27 | } 28 | 29 | public static class MoneySerializer extends JsonObjectSerializer { 30 | @Override 31 | protected void serializeObject(Money money, JsonGenerator jgen, SerializerProvider provider) throws IOException { 32 | jgen.writeStringField("currency", money.getCurrencyUnit().getCode()); 33 | jgen.writeStringField("amount", String.format("%.2f", money.getAmount().floatValue())); 34 | } 35 | } 36 | 37 | @Data 38 | @NoArgsConstructor @AllArgsConstructor 39 | static class MonetaryVal { 40 | private String currency; 41 | private Double amount; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/tesobe/obp/domain/Token.java: -------------------------------------------------------------------------------- 1 | package com.tesobe.obp.domain; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class Token { 7 | private String token; 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/tesobe/obp/domain/Transaction.java: -------------------------------------------------------------------------------- 1 | package com.tesobe.obp.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 6 | import com.tesobe.obp.Application; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | import org.joda.money.Money; 10 | 11 | import java.util.Date; 12 | import java.util.List; 13 | 14 | @Data 15 | public class Transaction { 16 | 17 | private String id; 18 | 19 | @JsonProperty("other_account") 20 | private Account targetAccount; 21 | 22 | @JsonProperty("this_account") 23 | private Account ownAccount; 24 | 25 | private Details details; 26 | 27 | private Metadata metadata; 28 | 29 | @Data 30 | private class Details { 31 | private String type; 32 | private String description; 33 | 34 | @JsonProperty("posted") 35 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = Application.ISO8601_TIMESTAMP_FORMAT, timezone = "UTC") 36 | private Date postedDate; 37 | 38 | @JsonProperty("completed") 39 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = Application.ISO8601_TIMESTAMP_FORMAT, timezone = "UTC") 40 | private Date completedDate; 41 | 42 | @JsonProperty("new_balance") 43 | @JsonDeserialize(using = MoneyJson.MoneyDeserializer.class) 44 | private Money newBalance; 45 | 46 | @JsonProperty("value") 47 | @JsonDeserialize(using = MoneyJson.MoneyDeserializer.class) 48 | private Money value; 49 | } 50 | 51 | @Data 52 | public class Metadata { 53 | private String narrative; 54 | private List comments; 55 | private List tags; 56 | private List images; 57 | 58 | @JsonProperty("where") 59 | private Location location; 60 | } 61 | 62 | @Data 63 | @NoArgsConstructor 64 | public static class Tag { 65 | public Tag(String value) { 66 | this.value = value; 67 | } 68 | private String value; 69 | 70 | private String id; 71 | 72 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = Application.ISO8601_TIMESTAMP_FORMAT, timezone = "UTC") 73 | @JsonProperty("date") 74 | private Date createdAt; 75 | 76 | @Override 77 | public boolean equals(Object o) { 78 | if (this == o) return true; 79 | if (o == null || getClass() != o.getClass()) return false; 80 | Tag tag = (Tag) o; 81 | return id.equals(tag.id); 82 | } 83 | 84 | @Override 85 | public int hashCode() { 86 | int result = super.hashCode(); 87 | result = 31 * result + id.hashCode(); 88 | return result; 89 | } 90 | } 91 | 92 | @Data 93 | public static class Image { 94 | @JsonProperty("image_URL") 95 | private String imageUrl; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/com/tesobe/obp/domain/TransactionRequest.java: -------------------------------------------------------------------------------- 1 | package com.tesobe.obp.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 5 | import com.fasterxml.jackson.databind.annotation.JsonSerialize; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | import org.joda.money.Money; 10 | 11 | @NoArgsConstructor @AllArgsConstructor 12 | @Data 13 | public class TransactionRequest { 14 | 15 | private DestAccount to; 16 | 17 | @JsonDeserialize(using = MoneyJson.MoneyDeserializer.class) 18 | @JsonSerialize(using = MoneyJson.MoneySerializer.class) 19 | private Money value; 20 | 21 | private String description; 22 | 23 | @Data 24 | @NoArgsConstructor @AllArgsConstructor 25 | public static class DestAccount { 26 | @JsonProperty("bank_id") 27 | private String bankId; 28 | 29 | @JsonProperty("account_id") 30 | private String accountId; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/tesobe/obp/domain/TransactionRequestType.java: -------------------------------------------------------------------------------- 1 | package com.tesobe.obp.domain; 2 | 3 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 4 | import lombok.Data; 5 | import org.joda.money.Money; 6 | 7 | @Data 8 | public class TransactionRequestType { 9 | private String value; 10 | private Charge charge; 11 | 12 | @Data 13 | class Charge { 14 | private String summary; 15 | private ChargeValue value; 16 | } 17 | 18 | @Data 19 | static class ChargeValue { 20 | private String currency; 21 | 22 | @JsonDeserialize(using = MoneyJson.MoneyDeserializer.class) 23 | private Money value; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/tesobe/obp/domain/User.java: -------------------------------------------------------------------------------- 1 | package com.tesobe.obp.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.Data; 5 | 6 | @Data 7 | public class User { 8 | @JsonProperty("user_id") 9 | private String userId; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | obp.api.rootUrl=https://apisandbox.openbankproject.com 2 | 3 | obp.api.versionedUrl:${obp.api.rootUrl}/obp/v3.1.0 4 | obp.api.directloginUrl=${obp.api.rootUrl}/my/logins/direct 5 | obp.api.directLoginPath=/my/logins/direct 6 | 7 | obp.consumerKey=mohge1sdpcssyqwoygsaokfaycayqsrgqcg4o023 8 | 9 | //we are using multiple @FeignClient beans and without bean overriding the app fails to start. \ 10 | see https://stackoverflow.com/questions/53787550/jpaauditinghandler-defined-in-null-on-application-startup-using-spring-boot 11 | spring.main.allow-bean-definition-overriding=true 12 | 13 | security.basic.enabled=false -------------------------------------------------------------------------------- /src/main/resources/config.properties: -------------------------------------------------------------------------------- 1 | hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=15000 2 | hystrix.threadpool.default.queueSizeRejectionThreshold=8 3 | # http://stackoverflow.com/questions/29566777/does-spring-make-the-securitycontext-available-to-the-thread-executing-a-hystrix 4 | hystrix.command.default.execution.isolation.strategy=SEMAPHORE -------------------------------------------------------------------------------- /src/main/resources/static/index.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 |
6 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/test/java/com/tesobe/obp/AbstractTestSupport.java: -------------------------------------------------------------------------------- 1 | package com.tesobe.obp; 2 | 3 | import com.tesobe.obp.clientapi.DirectAuthenticationClient; 4 | import com.tesobe.obp.clientapi.ObpApiClient; 5 | import com.tesobe.obp.clientapi.ObpBankMetaApiClient; 6 | import com.tesobe.obp.domain.Account; 7 | import com.tesobe.obp.domain.AccountRouting; 8 | import com.tesobe.obp.domain.Branch; 9 | import com.tesobe.obp.domain.User; 10 | import org.joda.money.CurrencyUnit; 11 | import org.joda.money.Money; 12 | import org.junit.Assert; 13 | import org.junit.Before; 14 | import org.junit.runner.RunWith; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | import org.springframework.beans.factory.annotation.Value; 17 | import org.springframework.boot.test.context.SpringBootTest; 18 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 19 | import org.springframework.security.core.context.SecurityContextHolder; 20 | import org.springframework.security.core.context.SecurityContextImpl; 21 | import org.springframework.test.context.TestPropertySource; 22 | import org.springframework.test.context.junit4.SpringRunner; 23 | 24 | import java.util.Collections; 25 | import java.util.List; 26 | import java.util.UUID; 27 | 28 | @RunWith(SpringRunner.class) 29 | @TestPropertySource(locations = {"classpath:auth.properties"}) 30 | @SpringBootTest 31 | public abstract class AbstractTestSupport { 32 | @Value("${obp.username}") 33 | private String username; 34 | 35 | @Value("${obp.password}") 36 | private String password; 37 | 38 | @Value("${obp.consumerKey}") 39 | private String consumerKey; 40 | 41 | @Autowired private DirectAuthenticationClient authClient; 42 | @Autowired private ObpApiClient obpApiClient; 43 | @Autowired private ObpBankMetaApiClient obpBankMetaApiClient; 44 | 45 | @Before 46 | public void init() { 47 | String token = authClient.login(username, password, consumerKey); 48 | SecurityContextHolder.setContext(new SecurityContextImpl( 49 | new UsernamePasswordAuthenticationToken(username, token))); 50 | //a pre-requisite for tests is for the user to have an account 51 | createAccountIfNoneExists(); 52 | } 53 | 54 | private void createAccountIfNoneExists() { 55 | User currentUser = obpApiClient.getCurrentUser(); 56 | 57 | if(obpApiClient.getPrivateAccountsNoDetails().size() == 0) { 58 | //find a bank with at least a branch 59 | List bankBranchPair = obpBankMetaApiClient.getBanks().getBanks() 60 | .stream().map(bank -> { 61 | { 62 | try { 63 | List branches = obpBankMetaApiClient.getBranches(bank.getId()).getBranches(); 64 | return List.of(bank.getId(), branches.get(0).getId()); 65 | } catch (Exception e) { 66 | //TODO: fix API not to return 400 if no branches are found for a bank 67 | return Collections.emptyList(); 68 | } 69 | } 70 | }) 71 | .filter(v -> !v.isEmpty()) 72 | .findFirst().get(); 73 | 74 | String accountId = UUID.randomUUID().toString(); 75 | Account accountRequest = new Account(); 76 | accountRequest.setUserId(currentUser.getUserId()); 77 | accountRequest.setBranchId(bankBranchPair.get(1)); 78 | accountRequest.setAccountRouting(new AccountRouting("OBP", "UK123456")); 79 | accountRequest.setBalance(Money.zero(CurrencyUnit.EUR)); 80 | accountRequest.setType("CURRENT"); 81 | accountRequest.setLabel("Label1"); 82 | Account account = obpApiClient.createAccount(bankBranchPair.get(0), accountId, accountRequest); 83 | Assert.assertNotNull(account); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/test/java/com/tesobe/obp/auth/DirectAuthenticationServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.tesobe.obp.auth; 2 | 3 | import com.tesobe.obp.clientapi.DirectAuthenticationClient; 4 | import feign.FeignException; 5 | import org.junit.Assert; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.beans.factory.annotation.Value; 10 | import org.springframework.boot.test.context.SpringBootTest; 11 | import org.springframework.http.HttpStatus; 12 | import org.springframework.test.context.TestPropertySource; 13 | import org.springframework.test.context.junit4.SpringRunner; 14 | 15 | @RunWith(SpringRunner.class) 16 | @SpringBootTest 17 | @TestPropertySource(locations = {"classpath:auth.properties"}) 18 | public class DirectAuthenticationServiceTest { 19 | 20 | @Value("${obp.username}") 21 | private String username; 22 | 23 | @Value("${obp.password}") 24 | private String password; 25 | 26 | @Value("${obp.consumerKey}") 27 | private String consumerKey; 28 | 29 | @Autowired private DirectAuthenticationClient directAuthenticationClient; 30 | 31 | @Test 32 | public void loginOk() throws Exception { 33 | String token = directAuthenticationClient.login(username, password, consumerKey); 34 | Assert.assertNotNull(token); 35 | } 36 | 37 | @Test 38 | public void badCredentials() throws Exception { 39 | String username = "wrong"; 40 | String password = "garble"; 41 | try { 42 | directAuthenticationClient.login(username, password, "garble"); 43 | } catch (Exception ex) { 44 | Assert.assertEquals(HttpStatus.UNAUTHORIZED.value(), ((FeignException)ex).status()); 45 | return; 46 | } 47 | Assert.assertFalse("Should have gotten 401 exception", true); 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /src/test/java/com/tesobe/obp/bankmeta/BankMetadataTest.java: -------------------------------------------------------------------------------- 1 | package com.tesobe.obp.bankmeta; 2 | 3 | import com.tesobe.obp.AbstractTestSupport; 4 | import com.tesobe.obp.clientapi.ObpBankMetaApiClient; 5 | import com.tesobe.obp.domain.Bank; 6 | import com.tesobe.obp.domain.Branch; 7 | import org.junit.Assert; 8 | import org.junit.Test; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | 11 | import java.util.Collection; 12 | import java.util.List; 13 | import java.util.Map; 14 | import java.util.Objects; 15 | import java.util.stream.Collectors; 16 | 17 | public class BankMetadataTest extends AbstractTestSupport { 18 | 19 | @Autowired private ObpBankMetaApiClient obpBankMetaApiClient; 20 | 21 | @Test 22 | public void allBanksOk() { 23 | List allBanks = obpBankMetaApiClient.getBanks().getBanks(); 24 | Assert.assertTrue(allBanks.size() > 0); 25 | } 26 | 27 | @Test 28 | public void allBranchesOk() { 29 | List allBanks = obpBankMetaApiClient.getBanks().getBanks(); 30 | Map> banksWithBranches = allBanks.stream().map(bank -> { 31 | List branches = obpBankMetaApiClient.getBranches(bank.getId()).getBranches(); 32 | if(branches.size() == 0) return null; 33 | bank.setBranches(branches); 34 | return bank; 35 | }) 36 | .filter(Objects::nonNull) //exclude banks with no branches 37 | .collect(Collectors.toMap(b -> b, Bank::getBranches)); 38 | 39 | Assert.assertTrue(banksWithBranches.keySet().stream().map(Bank::getBranches).mapToLong(Collection::size).sum() > 0); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/com/tesobe/obp/domain/AccountServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.tesobe.obp.domain; 2 | 3 | import com.tesobe.obp.AbstractTestSupport; 4 | import com.tesobe.obp.clientapi.ObpApiClient; 5 | import org.junit.Assert; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.boot.test.context.SpringBootTest; 10 | import org.springframework.test.context.junit4.SpringRunner; 11 | 12 | import java.util.List; 13 | 14 | import static org.junit.Assert.assertNotNull; 15 | import static org.junit.Assert.assertTrue; 16 | 17 | @RunWith(SpringRunner.class) 18 | @SpringBootTest 19 | public class AccountServiceTest extends AbstractTestSupport { 20 | 21 | @Autowired private ObpApiClient obpApiClient; 22 | 23 | @Test 24 | public void fetchPrivateAccountsNoDetailsOk() { 25 | //fetch private accounts 26 | List privateAccounts = obpApiClient.getPrivateAccountsNoDetails(); 27 | assertTrue(privateAccounts.size() > 0); 28 | } 29 | 30 | @Test 31 | public void fetchPrivateAccountsWithDetailsOk() { 32 | //fetch private accounts 33 | List privateAccounts = obpApiClient.getPrivateAccountsWithDetails(); 34 | assertTrue(privateAccounts.size() > 0); 35 | privateAccounts.forEach(privateAccount -> assertNotNull(privateAccount.getBalance())); 36 | } 37 | 38 | @Test 39 | public void accountViewsOk() throws Exception { 40 | List privateAccounts = obpApiClient.getPrivateAccountsNoDetails(); 41 | Account firstAccount = privateAccounts.get(0); 42 | ObpApiClient.AccountViews views = obpApiClient.getViewsForAccount(firstAccount.getBankId(), firstAccount.getId()); 43 | Assert.assertNotNull(views); 44 | } 45 | } -------------------------------------------------------------------------------- /src/test/java/com/tesobe/obp/transaction/MonetaryTransactionsServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.tesobe.obp.transaction; 2 | 3 | import com.tesobe.obp.AbstractTestSupport; 4 | import com.tesobe.obp.clientapi.ObpApiClient; 5 | import com.tesobe.obp.domain.Account; 6 | import com.tesobe.obp.domain.TransactionRequest; 7 | import com.tesobe.obp.domain.Transaction; 8 | import org.joda.money.CurrencyUnit; 9 | import org.joda.money.Money; 10 | import org.junit.Assert; 11 | import org.junit.Test; 12 | import org.junit.runner.RunWith; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.boot.test.context.SpringBootTest; 15 | import org.springframework.test.context.junit4.SpringRunner; 16 | 17 | import java.math.BigDecimal; 18 | import java.util.List; 19 | 20 | @RunWith(SpringRunner.class) 21 | @SpringBootTest 22 | public class MonetaryTransactionsServiceTest extends AbstractTestSupport { 23 | 24 | @Autowired private ObpApiClient obpApiClient; 25 | 26 | @Test 27 | public void fetchTransactionListOk() throws Exception { 28 | List accounts = obpApiClient.getPrivateAccountsNoDetails(); 29 | Assert.assertTrue(accounts.size() > 0); 30 | 31 | String bankId = accounts.get(0).getBankId(); 32 | String accountIdOne = accounts.get(0).getId(); 33 | ObpApiClient.TransactionRequestTypes txTypes = obpApiClient.getTransactionTypes(bankId, accountIdOne); 34 | 35 | TransactionRequest transactionRequest = new TransactionRequest( 36 | new TransactionRequest.DestAccount(bankId, accountIdOne), Money.of(CurrencyUnit.EUR, 5), "some description"); 37 | 38 | String result = obpApiClient.initiateTransaction(bankId, accounts.get(1).getId(), "SANDBOX_TAN", transactionRequest); 39 | 40 | List transactions = obpApiClient.getTransactionsForAccount(bankId, accountIdOne).getTransactions(); 41 | Assert.assertTrue(transactions.size() > 0); 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /src/test/java/com/tesobe/obp/transaction/TransactionAnnotationServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.tesobe.obp.transaction; 2 | 3 | import com.tesobe.obp.AbstractTestSupport; 4 | import com.tesobe.obp.clientapi.ObpApiClient; 5 | import com.tesobe.obp.domain.Account; 6 | import com.tesobe.obp.domain.Location; 7 | import com.tesobe.obp.domain.Transaction; 8 | import org.junit.Assert; 9 | import org.junit.Test; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | 12 | import java.util.List; 13 | 14 | import static com.tesobe.obp.domain.Transaction.Tag; 15 | 16 | public class TransactionAnnotationServiceTest extends AbstractTestSupport { 17 | @Autowired private ObpApiClient obpApiClient; 18 | 19 | @Test 20 | public void addTagOk() { 21 | List accounts = obpApiClient.getPrivateAccountsWithDetails(); 22 | Account ownAccount = accounts.get(0); 23 | List transactions = obpApiClient.getTransactionsForAccount(ownAccount.getBankId(), ownAccount.getId()).getTransactions(); 24 | 25 | Transaction tx = transactions.get(0); 26 | 27 | String tagValue = "food"; 28 | Tag tag = obpApiClient.tagTransaction(ownAccount.getBankId(), ownAccount.getId(), tx.getId(), new Tag(tagValue)); 29 | Assert.assertNotNull(tag.getId()); 30 | Assert.assertEquals(tagValue, tag.getValue()); 31 | List newTags = obpApiClient.getTransactionById(ownAccount.getBankId(), ownAccount.getId(), tx.getId()).getMetadata().getTags(); 32 | Assert.assertTrue(newTags.contains(tag)); 33 | } 34 | 35 | @Test 36 | public void deleteTagOk() { 37 | List accounts = obpApiClient.getPrivateAccountsWithDetails(); 38 | Account ownAccount = accounts.get(0); 39 | List transactions = obpApiClient.getTransactionsForAccount(ownAccount.getBankId(), ownAccount.getId()).getTransactions(); 40 | 41 | Transaction tx = transactions.get(0); 42 | //tx.getMetadata().getTags().forEach(tag -> transactionAnnotationService.deleteTransactionTag(authToken, tx, tag)); 43 | Tag tag = obpApiClient.tagTransaction(ownAccount.getBankId(), ownAccount.getId(), tx.getId(), new Tag("food")); 44 | List txTags = obpApiClient.getTransactionById(ownAccount.getBankId(), ownAccount.getId(), tx.getId()).getMetadata().getTags(); 45 | Assert.assertTrue(txTags.contains(tag)); 46 | 47 | obpApiClient.deleteTransactionTag(ownAccount.getBankId(), ownAccount.getId(), tx.getId(), tag.getId()); 48 | txTags = obpApiClient.getTransactionById(ownAccount.getBankId(), ownAccount.getId(), tx.getId()).getMetadata().getTags(); 49 | Assert.assertTrue(!txTags.contains(tag)); 50 | } 51 | 52 | @Test 53 | public void addLocationOk() { 54 | List accounts = obpApiClient.getPrivateAccountsWithDetails(); 55 | Account ownAccount = accounts.get(0); 56 | List transactions = obpApiClient.getTransactionsForAccount(ownAccount.getBankId(), ownAccount.getId()).getTransactions(); 57 | 58 | Transaction tx = transactions.get(0); 59 | 60 | Location geoLocation = new Location(12.566331, 55.675313); 61 | obpApiClient.addLocation(ownAccount.getBankId(), ownAccount.getId(), tx.getId(), new ObpApiClient.Where(geoLocation)); 62 | Location txLocation = obpApiClient.getTransactionById(ownAccount.getBankId(), ownAccount.getId(), tx.getId()).getMetadata().getLocation(); 63 | Assert.assertEquals(geoLocation, txLocation); 64 | } 65 | 66 | @Test 67 | public void deleteLocationOk() { 68 | List accounts = obpApiClient.getPrivateAccountsWithDetails(); 69 | Account ownAccount = accounts.get(0); 70 | List transactions = obpApiClient.getTransactionsForAccount(ownAccount.getBankId(), ownAccount.getId()).getTransactions(); 71 | Transaction tx = transactions.get(0); 72 | 73 | //add location to transaction 74 | Location geoLocation = new Location(12.566331, 55.675313); 75 | obpApiClient.addLocation(ownAccount.getBankId(), ownAccount.getId(), tx.getId(), new ObpApiClient.Where(geoLocation)); 76 | Location txLocation = obpApiClient.getTransactionById(ownAccount.getBankId(), ownAccount.getId(), tx.getId()).getMetadata().getLocation(); 77 | Assert.assertEquals(geoLocation, txLocation); 78 | 79 | //delete location 80 | obpApiClient.deleteLocation(ownAccount.getBankId(), ownAccount.getId(), tx.getId()); 81 | //check null location 82 | txLocation = obpApiClient.getTransactionById(ownAccount.getBankId(), ownAccount.getId(), tx.getId()).getMetadata().getLocation(); 83 | Assert.assertNull(txLocation); 84 | } 85 | 86 | } -------------------------------------------------------------------------------- /src/test/java/com/tesobe/obp/user/EntitlementsTest.java: -------------------------------------------------------------------------------- 1 | package com.tesobe.obp.user; 2 | 3 | import com.tesobe.obp.AbstractTestSupport; 4 | import com.tesobe.obp.clientapi.EntitlementsApiClient; 5 | import org.junit.Assert; 6 | import org.junit.Test; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.beans.factory.annotation.Value; 9 | 10 | public class EntitlementsTest extends AbstractTestSupport { 11 | 12 | @Value("${obp.username}") 13 | private String user; 14 | @Autowired private EntitlementsApiClient entitlementsApiClient; 15 | 16 | @Test 17 | public void entitlementsUser() throws Exception { 18 | String entz = entitlementsApiClient.getEntitlements(user); 19 | Assert.assertNotNull(entz); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/test/resources/auth.properties: -------------------------------------------------------------------------------- 1 | obp.username=emmaf 2 | obp.password=xF6ZhTo5$E^S 3 | --------------------------------------------------------------------------------