Skip to content

Commit

Permalink
Merge branch 'feature/sse_java_17' into 'develop'
Browse files Browse the repository at this point in the history
Feature/sse java 17

See merge request adorsys/xs2a/ledgers!604
  • Loading branch information
Serhii Semenykhin committed Oct 10, 2023
2 parents 236f658 + f032287 commit e1fdd19
Show file tree
Hide file tree
Showing 132 changed files with 676 additions and 547 deletions.
10 changes: 7 additions & 3 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ variables: &variables
# Build variables #
###########################

BUILD_JAVA_VERSION: "system@1.11"
BUILD_JAVA_VERSION: "system@1.17"
JAVA_TOOL_OPTIONS: "-XX:+UnlockExperimentalVMOptions -XX:MaxRAM=3G -XX:MaxRAMFraction=3"
MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository"

Expand Down Expand Up @@ -95,7 +95,7 @@ variables: &variables
#-------------------------------------------------------------------

Lint (Docker compose):
image: maven:3.6-jdk-11-slim
image: maven:3.6.3-openjdk-17-slim
stage: "Lint"
except:
- schedules
Expand Down Expand Up @@ -157,7 +157,7 @@ Lint (Dockerfiles):


Lint (PMD check):
image: maven:3.6-jdk-11-slim
image: maven:3.6.3-openjdk-17-slim
stage: "Lint"
except:
- schedules
Expand All @@ -182,6 +182,8 @@ Lint (PMD check):

Build Java (Ledgers):
stage: "Compile"
tags:
- aws
except:
- schedules
<<: *build_java
Expand Down Expand Up @@ -232,6 +234,8 @@ Test (Javadoc Check):

Test (Unit Tests):
stage: "Test"
tags:
- aws
except:
- schedules
script:
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM adorsys/java:11
FROM adorsys/java:17
LABEL maintainer=https://git.adorsys.de/adorsys/xs2a/ledgers

ENV SERVER_PORT 8088
Expand Down
3 changes: 2 additions & 1 deletion doc/Configuration properties.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
|===
|Property |Description |Default value

|keycloak.auth-server-url | Link to IDP | http://localhost:8080
|keycloak.auth-server-url | Link to IDP (Keycloak) | http://localhost:8080
|keycloak.realm | Realm in IDP | ledgers
|keycloak.resource | Client name | ledgers-client
|keycloak.public-client | Flag describes if client public | FALSE
Expand All @@ -19,6 +19,7 @@
|db.user | Ledgers datasource connection username | cms
|db.password | Ledgers datasource connection user password | cms

|spring.security.oauth2.resourceserver.jwt.issuer-uri | IDP URL to a configured realm | http://localhost:8080/realms/ledgers
|spring.mail.host | Mail server host |
|spring.mail.port | Mail server port |
|spring.mail.username | Mail server login |
Expand Down
14 changes: 0 additions & 14 deletions doc/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,3 @@ This will start the ledgers app with the embedded h2 database.
### Visiting the API

[http://localhost:8088/swagger-ui.html](http://localhost:8088/swagger-ui.html#/)

### Testing API with Postman json collections

For testing API of xs2a it is used Postman https://www.getpostman.com/
Environment jsons with global parameter’s sets and Collections of jsons for imitation of processes flows are stored in /scripts/tests/postman folder.
To import Postman collections and environments follow next steps:
1. Download Postman jsons with collections and environments to your local machine.
2. Open Postman, press button “Import”.
3. Choose “Import file” to import one json or “Import folder” to import all jsons within the folder, then press button “Choose Files” or “Choose Folders” and open necessary files/folders.
4. To change settings of environments - go to “Manage Environments”, press the environment name and change variables.

To start testing with Postman collections it is necessary to have all services running.


6 changes: 4 additions & 2 deletions doc/release-notes/v5.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ v.5.0

#### Features:

* Ledgers was moved onto new Keycloak version 20.0.5.
* Version of Spring-Security was bumped to 5.7.8.
* Project moved to Java 17.
* New Keycloak version - 22.0.3.
* Version of Spring Security was bumped to 6.0.5.
* Version of Spring Boot was bumped to 3.0.7.
* Version of org.json was bumped to 20230227.
* Version of guava was bumped to 32.0.0-jre.
5 changes: 0 additions & 5 deletions doc/release-notes/v5.1.md

This file was deleted.

6 changes: 5 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ networks:

volumes:
ledgers-data:
keycloak-data:

services:
ledgers-keycloak:
Expand All @@ -14,11 +15,14 @@ services:
build:
context: keycloak
dockerfile: Dockerfile
volumes:
- keycloak-data:/opt/keycloak/data
networks:
- ledgers-net
ports:
- "8080:8080"
environment:
- KC_HOSTNAME=ledgers-keycloak:8080
- KEYCLOAK_ADMIN=admin
- KEYCLOAK_ADMIN_PASSWORD=admin
- KC_PROXY=edge
Expand Down Expand Up @@ -47,7 +51,7 @@ services:
- DB_USER=ledgers
- DB_PASSWORD=ledgers
- LIQUIBASE_ENABLED=true
- LEDGERS_SCA_MULTILEVEL_ENABLED=false
- LEDGERS_SCA_MULTILEVEL_ENABLED=true
- LEDGERS_SCA_AUTHORISATION_CONFIRMATION_ENABLED=false
- SPRING_MAIL_HOST=smtp.gmail.com
- SPRING_MAIL_PORT=465
Expand Down
2 changes: 1 addition & 1 deletion keycloak/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM quay.io/keycloak/keycloak:20.0.5
FROM keycloak/keycloak:22.0.3
LABEL maintainer=https://git.adorsys.de/adorsys/xs2a/ledgers

COPY ./keycloak-token-exchange/target/keycloak-token-exchange.jar /opt/keycloak/providers/keycloak-token-exchange.jar
Expand Down
12 changes: 11 additions & 1 deletion keycloak/keycloak-client/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<parent>
<artifactId>keycloak</artifactId>
<groupId>de.adorsys.ledgers</groupId>
<version>5.1-SNAPSHOT</version>
<version>5.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
Expand Down Expand Up @@ -76,6 +76,16 @@
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@

package de.adorsys.ledgers.keycloak.client.config;

import jakarta.annotation.PostConstruct;
import lombok.Data;
import lombok.Getter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import de.adorsys.ledgers.keycloak.client.model.KeycloakUser;
import de.adorsys.ledgers.keycloak.client.model.RequiredAction;
import de.adorsys.ledgers.keycloak.client.rest.KeycloakTokenRestClient;
import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.core.Response;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
Expand All @@ -24,8 +26,6 @@
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;

import javax.ws.rs.NotFoundException;
import javax.ws.rs.core.Response;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public BearerTokenTO login(String username, String password) {
formParams.add("client_id", clientId);
formParams.add("client_secret", clientSecret);
ResponseEntity<Map<String, ?>> resp = keycloakTokenRestClient.login(formParams);
HttpStatus statusCode = resp.getStatusCode();
HttpStatus statusCode = (HttpStatus) resp.getStatusCode();
if (HttpStatus.OK != statusCode) {
log.error("Could not obtain token by user credentials [{}]", username); //todo: throw specific exception
}
Expand All @@ -74,7 +74,7 @@ public BearerTokenTO validate(String token) {
formParams.add("client_id", clientId);
formParams.add("client_secret", clientSecret);
ResponseEntity<AccessToken> resp = keycloakTokenRestClient.validate(formParams);
HttpStatus statusCode = resp.getStatusCode();
HttpStatus statusCode = (HttpStatus) resp.getStatusCode();
if (HttpStatus.OK != statusCode) {
log.error("Could not validate token"); //todo: throw specific exception
}
Expand All @@ -95,7 +95,7 @@ public BearerTokenTO refreshToken(String refreshToken) {
formParams.add("client_secret", clientSecret);
formParams.add("refresh_token", refreshToken);
ResponseEntity<Map<String, ?>> resp = keycloakTokenRestClient.login(formParams);
HttpStatus statusCode = resp.getStatusCode();
HttpStatus statusCode = (HttpStatus) resp.getStatusCode();
if (HttpStatus.OK != statusCode) {
log.error("Could not obtain token by refresh token [{}]", refreshToken);
throw new AccessDeniedException("Invalid Refresh token");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,44 +7,42 @@

import de.adorsys.ledgers.middleware.api.domain.um.AccessTokenTO;
import de.adorsys.ledgers.middleware.api.domain.um.BearerTokenTO;
import de.adorsys.ledgers.middleware.api.domain.um.TokenUsageTO;
import de.adorsys.ledgers.middleware.api.domain.um.UserRoleTO;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessTokenResponse;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.springframework.security.oauth2.jwt.Jwt;

import java.util.*;
import java.util.stream.Collectors;

@Mapper(componentModel = "spring")
public interface KeycloakAuthMapper {

@Mapping(target = "act", ignore = true)
@Mapping(target = "scaId", ignore = true)
@Mapping(target = "consent", ignore = true)
@Mapping(target = "authorisationId", ignore = true)
@Mapping(target = "iat", source = "source.token.issuedAt")
@Mapping(target = "role", expression = "java(getLedgersUserRoles(source.getToken()))")
@Mapping(target = "sub", source = "source.token.subject")
@Mapping(target = "scopes", source = "source.token.scope")
@Mapping(target = "login", source = "source.token.name")
@Mapping(target = "exp", source = "source.token.exp")
@Mapping(target = "jti", source = "source.token.id")
@Mapping(target = "accessToken", source = "source.tokenString")
@Mapping(target = "tokenUsage", expression = "java(de.adorsys.ledgers.middleware.api.domain.um.TokenUsageTO.DIRECT_ACCESS)")
//TODO This is a stub!!!
AccessTokenTO toAccessToken(RefreshableKeycloakSecurityContext source);
default AccessTokenTO toAccessTokenFromJwt(Jwt source) {
AccessTokenTO token = new AccessTokenTO();
token.setIat(Date.from(source.getIssuedAt()));
token.setRole(getLedgersUserRolesFromJwt(source));
token.setSub(source.getClaimAsString("sub"));
token.setScopes(new HashSet(Arrays.asList(source.getClaimAsString("scope").split(" "))));
token.setLogin(source.getClaimAsString("name"));
token.setExp(Date.from(source.getExpiresAt()));
token.setJti(source.getClaimAsString("jti"));
token.setAccessToken(source.getTokenValue());
token.setTokenUsage(TokenUsageTO.DIRECT_ACCESS);

@Mapping(target = "accessTokenObject", ignore = true)
@Mapping(target = "scopes", source = "source.scope")
@Mapping(target = "access_token", source = "token")
@Mapping(target = "expires_in", source = "expiresIn")
@Mapping(target = "refresh_token", source = "refreshToken")
@Mapping(target = "token_type", source = "tokenType")
BearerTokenTO toBearerTokenTO(AccessTokenResponse source);
return token;
}

default BearerTokenTO toBearerTokenFromJwt(Jwt source) {
AccessTokenTO to = toAccessTokenFromJwt(source);
long ttl = (to.getExp().getTime() - new Date().getTime()) / DateUtils.MILLIS_PER_SECOND;

return new BearerTokenTO(source.getTokenValue(), "Bearer", (int) ttl, null, to, to.getScopes());
}

default Set<String> toScopes(String scope) {
return Optional.ofNullable(scope)
Expand Down Expand Up @@ -95,4 +93,22 @@ default UserRoleTO getLedgersUserRoles(AccessToken token) {
: UserRoleTO.getByValue(roles.iterator().next().toString()).orElse(null);

}

default UserRoleTO getLedgersUserRolesFromJwt(Jwt token) {
List<String> tokenizedRoles = (ArrayList) token.getClaimAsMap("realm_access").get("roles");

Collection<UserRoleTO> roles = CollectionUtils.intersection(
tokenizedRoles
.stream()
.map(UserRoleTO::getByValue)
.filter(Optional::isPresent)
.map(Optional::get)
.toList(), Arrays.asList(UserRoleTO.values())
);

return roles.isEmpty()
? null
: UserRoleTO.getByValue(roles.iterator().next().toString()).orElse(null);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import de.adorsys.ledgers.keycloak.client.mapper.KeycloakDataMapper;
import de.adorsys.ledgers.keycloak.client.model.KeycloakUser;
import de.adorsys.ledgers.keycloak.client.rest.KeycloakTokenRestClient;
import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.core.Response;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.keycloak.admin.client.Keycloak;
Expand All @@ -20,8 +22,6 @@
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import javax.ws.rs.NotFoundException;
import javax.ws.rs.core.Response;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
Expand Down Expand Up @@ -96,7 +96,7 @@ void getUser() {
when(keycloak.realm(any())).thenReturn(realmResource);
UsersResource usersResource = mock(UsersResource.class);
when(realmResource.users()).thenReturn(usersResource);
when(usersResource.search(any(),eq(true))).thenReturn(List.of(new UserRepresentation()));
when(usersResource.search(any(), eq(true))).thenReturn(List.of(new UserRepresentation()));

Optional<KeycloakUser> user = service.getUser("testRealm", "login");
assertTrue(user.isEmpty());
Expand Down Expand Up @@ -124,7 +124,7 @@ void updateUser() {
when(keycloak.realm(any())).thenReturn(realmResource);
UsersResource usersResource = mock(UsersResource.class);
when(realmResource.users()).thenReturn(usersResource);
when(usersResource.search(any(),eq(true))).thenReturn(Collections.singletonList(new UserRepresentation()));
when(usersResource.search(any(), eq(true))).thenReturn(Collections.singletonList(new UserRepresentation()));
UserResource userResource = mock(UserResource.class);
when(usersResource.get(any())).thenReturn(userResource);
when(userResource.toRepresentation()).thenReturn(new UserRepresentation());
Expand All @@ -141,7 +141,7 @@ void deleteUser() {
when(keycloak.realm(any())).thenReturn(realmResource);
UsersResource usersResource = mock(UsersResource.class);
when(realmResource.users()).thenReturn(usersResource);
when(usersResource.search(any(),eq(true))).thenReturn(Collections.singletonList(new UserRepresentation()));
when(usersResource.search(any(), eq(true))).thenReturn(Collections.singletonList(new UserRepresentation()));

service.deleteUser("login");
verify(usersResource, times(1)).delete(any());
Expand All @@ -154,7 +154,7 @@ void userExists() {
UsersResource usersResource = mock(UsersResource.class);
when(realmResource.users()).thenReturn(usersResource);
UserResource userResource = mock(UserResource.class);
when(usersResource.search(any(),eq(true))).thenReturn(Collections.singletonList(new UserRepresentation()));
when(usersResource.search(any(), eq(true))).thenReturn(Collections.singletonList(new UserRepresentation()));

boolean exists = service.userExists("login");
assertTrue(exists);
Expand All @@ -166,7 +166,7 @@ void resetPassword() {
when(keycloak.realm(any())).thenReturn(realmResource);
UsersResource usersResource = mock(UsersResource.class);
when(realmResource.users()).thenReturn(usersResource);
when(usersResource.search(any(),eq(true))).thenReturn(Collections.singletonList(new UserRepresentation()));
when(usersResource.search(any(), eq(true))).thenReturn(Collections.singletonList(new UserRepresentation()));
UserResource userResource = mock(UserResource.class);
when(usersResource.get(any())).thenReturn(userResource);

Expand Down Expand Up @@ -196,7 +196,7 @@ void assignRealmRoleToUser() {
when(keycloak.realm(any())).thenReturn(realmResource);
UsersResource usersResource = mock(UsersResource.class);
when(realmResource.users()).thenReturn(usersResource);
when(usersResource.search(any(),eq(true))).thenReturn(Collections.singletonList(new UserRepresentation()));
when(usersResource.search(any(), eq(true))).thenReturn(Collections.singletonList(new UserRepresentation()));
UserResource userResource = mock(UserResource.class);
when(usersResource.get(any())).thenReturn(userResource);
RoleMappingResource roleMappingResource = mock(RoleMappingResource.class);
Expand All @@ -218,7 +218,7 @@ void removeRealmRoleFromUser() {
when(keycloak.realm(any())).thenReturn(realmResource);
UsersResource usersResource = mock(UsersResource.class);
when(realmResource.users()).thenReturn(usersResource);
when(usersResource.search(any(),eq(true))).thenReturn(Collections.singletonList(new UserRepresentation()));
when(usersResource.search(any(), eq(true))).thenReturn(Collections.singletonList(new UserRepresentation()));
UserResource userResource = mock(UserResource.class);
when(usersResource.get(any())).thenReturn(userResource);
RoleMappingResource roleMappingResource = mock(RoleMappingResource.class);
Expand Down
Loading

0 comments on commit e1fdd19

Please sign in to comment.