diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index a654702..dbb1a95 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -7,4 +7,4 @@ on:
jobs:
build:
- uses: valitydev/java-workflow/.github/workflows/maven-service-build.yml@v1
+ uses: valitydev/java-workflow/.github/workflows/maven-service-build.yml@v3
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index 9162503..5cbf745 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -7,7 +7,7 @@ on:
jobs:
build-and-deploy:
- uses: valitydev/java-workflow/.github/workflows/maven-service-deploy.yml@v1
+ uses: valitydev/java-workflow/.github/workflows/maven-service-deploy.yml@v3
with:
ignore-coverage: true
secrets:
diff --git a/pom.xml b/pom.xml
index a0d1dbc..4487067 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
dev.vality
service-parent-pom
- 1.0.16
+ 3.1.9
fraudbusters-api
@@ -23,24 +23,18 @@
8023
${server.port} ${management.port}
${env.REGISTRY}
- 1.47-7bd0be7-server
+ 1.117-7e0ac25
1.108-0800fde
+ 0.12.6
+ 1.681-7a97267
-
- dev.vality.woody
- woody-thrift
-
-
- dev.vality
- shared-resources
-
dev.vality
- swag-fraudbusters
+ swag-fraudbusters-server
${swag-fraudbusters.version}
@@ -48,10 +42,25 @@
fraudbusters-proto
${fraudbusters-proto.version}
+
+ dev.vality
+ damsel
+ ${damsel.version}
+
dev.vality.geck
serializer
+
+ dev.vality.woody
+ libthrift
+ 2.0.9
+
+
+ dev.vality.woody
+ woody-thrift
+ 2.0.9
+
dev.vality.geck
common
@@ -85,15 +94,29 @@
org.springframework.boot
spring-boot-starter-validation
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+ org.springframework.boot
+ spring-boot-starter-oauth2-resource-server
+
- javax.servlet
- javax.servlet-api
+ jakarta.servlet
+ jakarta.servlet-api
+
+
+ jakarta.validation
+ jakarta.validation-api
+ provided
org.projectlombok
lombok
+ 1.18.36
provided
@@ -110,12 +133,36 @@
org.springframework.boot
spring-boot-starter-test
test
-
-
- org.junit.vintage
- junit-vintage-engine
-
-
+
+
+ io.jsonwebtoken
+ jjwt-api
+ ${jjwt-version}
+ test
+
+
+ io.jsonwebtoken
+ jjwt-impl
+ ${jjwt-version}
+ test
+
+
+ io.jsonwebtoken
+ jjwt-jackson
+ ${jjwt-version}
+ test
+
+
+ org.wiremock.integrations
+ wiremock-spring-boot
+ 3.8.2
+ test
+
+
+ org.bouncycastle
+ bcpkix-jdk18on
+ 1.79
+ test
@@ -148,6 +195,23 @@
org.springframework.boot
spring-boot-maven-plugin
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.13.0
+
+ 15
+ 15
+ true
+
+
+ org.projectlombok
+ lombok
+ 1.18.36
+
+
+
+
org.apache.maven.plugins
maven-remote-resources-plugin
diff --git a/src/main/java/dev/vality/fraudbusters/api/configuration/SecurityConfig.java b/src/main/java/dev/vality/fraudbusters/api/configuration/SecurityConfig.java
new file mode 100644
index 0000000..33a5955
--- /dev/null
+++ b/src/main/java/dev/vality/fraudbusters/api/configuration/SecurityConfig.java
@@ -0,0 +1,52 @@
+package dev.vality.fraudbusters.api.configuration;
+
+import dev.vality.fraudbusters.api.configuration.converter.JwtAuthConverter;
+import lombok.RequiredArgsConstructor;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.HttpMethod;
+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.configurers.AbstractHttpConfigurer;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.CorsConfigurationSource;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
+
+@Configuration
+@EnableWebSecurity
+@RequiredArgsConstructor
+@ConditionalOnProperty(value = "auth.enabled", havingValue = "true")
+public class SecurityConfig {
+
+ private final JwtAuthConverter jwtAuthConverter;
+
+ @Bean
+ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+ http.csrf(AbstractHttpConfigurer::disable);
+ http.authorizeHttpRequests(
+ (authorize) -> authorize
+ .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
+ .requestMatchers(HttpMethod.GET, "/**/health/liveness").permitAll()
+ .requestMatchers(HttpMethod.GET, "/**/health/readiness").permitAll()
+ .requestMatchers(HttpMethod.GET, "/**/actuator/prometheus").permitAll()
+ .anyRequest().authenticated());
+ http.oauth2ResourceServer(server -> server.jwt(token -> token.jwtAuthenticationConverter(jwtAuthConverter)));
+ http.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
+ http.cors(c -> c.configurationSource(corsConfigurationSource()));
+ return http.build();
+ }
+
+ public CorsConfigurationSource corsConfigurationSource() {
+ CorsConfiguration configuration = new CorsConfiguration();
+ configuration.applyPermitDefaultValues();
+ configuration.addAllowedMethod(HttpMethod.PUT);
+ configuration.addAllowedMethod(HttpMethod.DELETE);
+ configuration.addAllowedMethod(HttpMethod.OPTIONS);
+ UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
+ source.registerCorsConfiguration("/**", configuration);
+ return source;
+ }
+}
diff --git a/src/main/java/dev/vality/fraudbusters/api/configuration/converter/JwtAuthConverter.java b/src/main/java/dev/vality/fraudbusters/api/configuration/converter/JwtAuthConverter.java
new file mode 100644
index 0000000..3da5440
--- /dev/null
+++ b/src/main/java/dev/vality/fraudbusters/api/configuration/converter/JwtAuthConverter.java
@@ -0,0 +1,53 @@
+package dev.vality.fraudbusters.api.configuration.converter;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
+import org.springframework.stereotype.Component;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+@Slf4j
+@Component
+public class JwtAuthConverter implements Converter {
+
+ private static final String principleAttribute = "preferred_username";
+ private static final String resourceAttribute = "resource_access";
+
+ @Override
+ public AbstractAuthenticationToken convert(Jwt jwt) {
+ return new JwtAuthenticationToken(
+ jwt,
+ new HashSet<>(extractResourceRoles(jwt)),
+ getPrincipleClaimName(jwt)
+ );
+ }
+
+ private Collection extends GrantedAuthority> extractResourceRoles(Jwt token) {
+ if (token.getClaim(resourceAttribute) == null) {
+ return Set.of();
+ }
+ Map resourceAccess = token.getClaim(resourceAttribute);
+ if (resourceAccess.isEmpty()) {
+ return Set.of();
+ }
+
+ return resourceAccess.values().stream()
+ .map(resourceAccessInfo -> (Map) resourceAccessInfo)
+ .flatMap(resourceAccessInfo -> ((Collection) resourceAccessInfo.get("roles")).stream())
+ .map(SimpleGrantedAuthority::new)
+ .collect(Collectors.toSet());
+ }
+
+ private String getPrincipleClaimName(Jwt jwt) {
+ return jwt.getClaim(principleAttribute);
+ }
+}
diff --git a/src/main/java/dev/vality/fraudbusters/api/converter/PaymentInspectRequestToContextConverter.java b/src/main/java/dev/vality/fraudbusters/api/converter/PaymentInspectRequestToContextConverter.java
index 3ba923e..6a0e3d2 100644
--- a/src/main/java/dev/vality/fraudbusters/api/converter/PaymentInspectRequestToContextConverter.java
+++ b/src/main/java/dev/vality/fraudbusters/api/converter/PaymentInspectRequestToContextConverter.java
@@ -1,12 +1,11 @@
package dev.vality.fraudbusters.api.converter;
-import dev.vality.damsel.domain.BankCard;
import dev.vality.damsel.domain.*;
+import dev.vality.damsel.domain.BankCard;
+import dev.vality.damsel.proxy_inspector.*;
import dev.vality.damsel.proxy_inspector.Invoice;
import dev.vality.damsel.proxy_inspector.InvoicePayment;
-import dev.vality.damsel.proxy_inspector.Party;
import dev.vality.damsel.proxy_inspector.Shop;
-import dev.vality.damsel.proxy_inspector.*;
import dev.vality.swag.fraudbusters.model.*;
import lombok.RequiredArgsConstructor;
import org.springframework.core.convert.converter.Converter;
@@ -38,9 +37,9 @@ public Context convert(PaymentInspectRequest request) {
private Shop buildShop(Merchant merchant) {
var shop = merchant.getShop();
return new Shop()
- .setId(shop.getId())
- .setDetails(new ShopDetails()
- .setName(shop.getName()))
+ .setShopRef(new ShopConfigRef()
+ .setId(shop.getId()))
+ .setName(shop.getName())
.setCategory(new Category()
.setName(shop.getCategory())
.setDescription(MOCK_UNUSED_DATA))
@@ -49,7 +48,8 @@ private Shop buildShop(Merchant merchant) {
private Party buildParty(Merchant merchant) {
return new Party()
- .setPartyId(merchant.getId());
+ .setPartyRef(new PartyConfigRef()
+ .setId(merchant.getId()));
}
private Invoice buildInvoice(Payment payment) {
diff --git a/src/main/java/dev/vality/fraudbusters/api/converter/ShopContextToMerchantConverter.java b/src/main/java/dev/vality/fraudbusters/api/converter/ShopContextToMerchantConverter.java
new file mode 100644
index 0000000..c2f4ca1
--- /dev/null
+++ b/src/main/java/dev/vality/fraudbusters/api/converter/ShopContextToMerchantConverter.java
@@ -0,0 +1,29 @@
+package dev.vality.fraudbusters.api.converter;
+
+import dev.vality.damsel.proxy_inspector.ShopContext;
+import dev.vality.swag.fraudbusters.model.Merchant;
+import dev.vality.swag.fraudbusters.model.Shop;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.stereotype.Component;
+
+@Component
+public class ShopContextToMerchantConverter implements Converter {
+
+ @Override
+ public Merchant convert(ShopContext shopContext) {
+ var shop = shopContext.getShop();
+ Shop swaggerShop = new Shop();
+ if (shop != null) {
+ swaggerShop
+ .id(shop.getShopRef() != null ? shop.getShopRef().getId() : null)
+ .name(shop.getName())
+ .category(shop.getCategory() != null ? shop.getCategory().getName() : null)
+ .location(shop.getLocation() != null ? shop.getLocation().getUrl() : null);
+ }
+ var party = shopContext.getParty();
+ return new Merchant()
+ .id(party != null && party.getPartyRef() != null ? party.getPartyRef().getId() : null)
+ .shop(swaggerShop);
+ }
+}
+
diff --git a/src/main/java/dev/vality/fraudbusters/api/converter/UserInspectRequestToInspectUserContextConverter.java b/src/main/java/dev/vality/fraudbusters/api/converter/UserInspectRequestToInspectUserContextConverter.java
new file mode 100644
index 0000000..96b87d1
--- /dev/null
+++ b/src/main/java/dev/vality/fraudbusters/api/converter/UserInspectRequestToInspectUserContextConverter.java
@@ -0,0 +1,72 @@
+package dev.vality.fraudbusters.api.converter;
+
+import dev.vality.damsel.domain.*;
+import dev.vality.damsel.proxy_inspector.InspectUserContext;
+import dev.vality.damsel.proxy_inspector.Party;
+import dev.vality.damsel.proxy_inspector.ShopContext;
+import dev.vality.swag.fraudbusters.model.Contact;
+import dev.vality.swag.fraudbusters.model.Customer;
+import dev.vality.swag.fraudbusters.model.Merchant;
+import dev.vality.swag.fraudbusters.model.UserInspectRequest;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.stereotype.Component;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+@Component
+public class UserInspectRequestToInspectUserContextConverter
+ implements Converter {
+
+ private static final String MOCK_UNUSED_DATA = "MOCK_UNUSED_DATA";
+
+ @Override
+ public InspectUserContext convert(UserInspectRequest request) {
+ ContactInfo userInfo = buildUserInfo(request.getUser());
+ List shopContexts = Optional.ofNullable(request.getMerchants())
+ .orElse(Collections.emptyList())
+ .stream()
+ .map(this::buildShopContext)
+ .collect(Collectors.toList());
+ return new InspectUserContext()
+ .setUserInfo(userInfo)
+ .setShopList(shopContexts);
+ }
+
+ private ContactInfo buildUserInfo(Customer user) {
+ Contact contact = Optional.ofNullable(user)
+ .map(Customer::getContact)
+ .orElseGet(Contact::new);
+ return new ContactInfo()
+ .setEmail(contact.getEmail())
+ .setPhoneNumber(contact.getPhone());
+ }
+
+ private ShopContext buildShopContext(Merchant merchant) {
+ Party party = buildParty(merchant);
+ dev.vality.damsel.proxy_inspector.Shop shop = buildShop(merchant);
+ return new ShopContext()
+ .setParty(party)
+ .setShop(shop);
+ }
+
+ private Party buildParty(Merchant merchant) {
+ return new Party()
+ .setPartyRef(new PartyConfigRef()
+ .setId(merchant.getId()));
+ }
+
+ private dev.vality.damsel.proxy_inspector.Shop buildShop(Merchant merchant) {
+ dev.vality.swag.fraudbusters.model.Shop shop = merchant.getShop();
+ return new dev.vality.damsel.proxy_inspector.Shop()
+ .setShopRef(new ShopConfigRef()
+ .setId(shop.getId()))
+ .setName(shop.getName())
+ .setCategory(new Category()
+ .setName(shop.getCategory())
+ .setDescription(MOCK_UNUSED_DATA))
+ .setLocation(ShopLocation.url(shop.getLocation()));
+ }
+}
diff --git a/src/main/java/dev/vality/fraudbusters/api/resource/ChargebackResource.java b/src/main/java/dev/vality/fraudbusters/api/resource/ChargebackResource.java
index 7d8665a..5623f3e 100644
--- a/src/main/java/dev/vality/fraudbusters/api/resource/ChargebackResource.java
+++ b/src/main/java/dev/vality/fraudbusters/api/resource/ChargebackResource.java
@@ -12,7 +12,6 @@
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.RestController;
-import javax.validation.Valid;
import java.util.List;
@Slf4j
@@ -24,7 +23,7 @@ public class ChargebackResource implements ChargebacksApi {
private final Converter> chargebacksRequestToChargebacksConverter;
@Override
- public ResponseEntity insertChargebacks(@Valid ChargebacksRequest chargebacksRequest) {
+ public ResponseEntity insertChargebacks(ChargebacksRequest chargebacksRequest) {
log.debug("-> insertChargebacks request: {}", chargebacksRequest);
if (!CollectionUtils.isEmpty(chargebacksRequest.getChargebacks())) {
List chargebacks = chargebacksRequestToChargebacksConverter.convert(chargebacksRequest);
diff --git a/src/main/java/dev/vality/fraudbusters/api/resource/FraudPaymentsResource.java b/src/main/java/dev/vality/fraudbusters/api/resource/FraudPaymentsResource.java
index ad5bd6f..72766a6 100644
--- a/src/main/java/dev/vality/fraudbusters/api/resource/FraudPaymentsResource.java
+++ b/src/main/java/dev/vality/fraudbusters/api/resource/FraudPaymentsResource.java
@@ -12,7 +12,6 @@
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.RestController;
-import javax.validation.Valid;
import java.util.List;
@Slf4j
@@ -24,7 +23,7 @@ public class FraudPaymentsResource implements FraudPaymentsApi {
private final Converter> fraudPaymentsRequestToFraudPaymentsConverter;
@Override
- public ResponseEntity insertFraudPayments(@Valid FraudPaymentsRequest fraudPaymentsRequest) {
+ public ResponseEntity insertFraudPayments(FraudPaymentsRequest fraudPaymentsRequest) {
log.debug("-> insertFraudPayments request: {}", fraudPaymentsRequest);
if (!CollectionUtils.isEmpty(fraudPaymentsRequest.getFraudPayments())) {
List fraudPayments =
diff --git a/src/main/java/dev/vality/fraudbusters/api/resource/PaymentResource.java b/src/main/java/dev/vality/fraudbusters/api/resource/PaymentResource.java
index 152228f..71f2556 100644
--- a/src/main/java/dev/vality/fraudbusters/api/resource/PaymentResource.java
+++ b/src/main/java/dev/vality/fraudbusters/api/resource/PaymentResource.java
@@ -12,7 +12,6 @@
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.RestController;
-import javax.validation.Valid;
import java.util.List;
@Slf4j
@@ -24,7 +23,7 @@ public class PaymentResource implements PaymentsApi {
private final Converter> paymentsChangesRequestToPaymentsConverter;
@Override
- public ResponseEntity insertPaymentsChanges(@Valid PaymentsChangesRequest paymentsChangesRequest) {
+ public ResponseEntity insertPaymentsChanges(PaymentsChangesRequest paymentsChangesRequest) {
log.debug("-> insertPaymentsChanges request: {}", paymentsChangesRequest);
if (!CollectionUtils.isEmpty(paymentsChangesRequest.getPaymentsChanges())) {
List payments = paymentsChangesRequestToPaymentsConverter.convert(paymentsChangesRequest);
diff --git a/src/main/java/dev/vality/fraudbusters/api/resource/RefundResource.java b/src/main/java/dev/vality/fraudbusters/api/resource/RefundResource.java
index 9b0ae51..74ad14c 100644
--- a/src/main/java/dev/vality/fraudbusters/api/resource/RefundResource.java
+++ b/src/main/java/dev/vality/fraudbusters/api/resource/RefundResource.java
@@ -12,7 +12,6 @@
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.RestController;
-import javax.validation.Valid;
import java.util.List;
@Slf4j
@@ -24,7 +23,7 @@ public class RefundResource implements RefundsApi {
private final Converter> refundsRequestToRefundsConverter;
@Override
- public ResponseEntity insertRefunds(@Valid RefundsRequest refundsRequest) {
+ public ResponseEntity insertRefunds(RefundsRequest refundsRequest) {
log.debug("-> insertRefunds request: {}", refundsRequest);
if (!CollectionUtils.isEmpty(refundsRequest.getRefunds())) {
List refunds = refundsRequestToRefundsConverter.convert(refundsRequest);
diff --git a/src/main/java/dev/vality/fraudbusters/api/resource/UserInspectorResource.java b/src/main/java/dev/vality/fraudbusters/api/resource/UserInspectorResource.java
new file mode 100644
index 0000000..3232477
--- /dev/null
+++ b/src/main/java/dev/vality/fraudbusters/api/resource/UserInspectorResource.java
@@ -0,0 +1,31 @@
+package dev.vality.fraudbusters.api.resource;
+
+import dev.vality.damsel.proxy_inspector.InspectUserContext;
+import dev.vality.fraudbusters.api.service.FraudbustersInspectorService;
+import dev.vality.swag.fraudbusters.api.InspectUserApi;
+import dev.vality.swag.fraudbusters.model.UserInspectRequest;
+import dev.vality.swag.fraudbusters.model.UserInspectResult;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.http.ResponseEntity;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.RestController;
+
+@Slf4j
+@RestController
+@RequiredArgsConstructor
+public class UserInspectorResource implements InspectUserApi {
+
+ private final FraudbustersInspectorService fraudbustersInspectorService;
+ private final Converter userInspectRequestToInspectUserContextConverter;
+
+ @Override
+ public ResponseEntity inspectUser(UserInspectRequest userInspectRequest) {
+ log.debug("-> inspectUser request: {}", userInspectRequest);
+ InspectUserContext context = userInspectRequestToInspectUserContextConverter.convert(userInspectRequest);
+ UserInspectResult result = fraudbustersInspectorService.inspectUser(context);
+ log.debug("<- inspectUser result: {}", result);
+ return ResponseEntity.ok(result);
+ }
+}
diff --git a/src/main/java/dev/vality/fraudbusters/api/resource/WithdrawalResource.java b/src/main/java/dev/vality/fraudbusters/api/resource/WithdrawalResource.java
index 2b871ec..561081f 100644
--- a/src/main/java/dev/vality/fraudbusters/api/resource/WithdrawalResource.java
+++ b/src/main/java/dev/vality/fraudbusters/api/resource/WithdrawalResource.java
@@ -12,7 +12,6 @@
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.RestController;
-import javax.validation.Valid;
import java.util.List;
@Slf4j
@@ -25,7 +24,7 @@ public class WithdrawalResource implements WithdrawalsApi {
@Override
- public ResponseEntity insertWithdrawals(@Valid WithdrawalsRequest withdrawalsRequest) {
+ public ResponseEntity insertWithdrawals(WithdrawalsRequest withdrawalsRequest) {
log.debug("-> insertWithdrawals request: {}", withdrawalsRequest);
if (!CollectionUtils.isEmpty(withdrawalsRequest.getWithdrawals())) {
List withdrawals = withdrawalsRequestToWithdrawalsConverter.convert(withdrawalsRequest);
diff --git a/src/main/java/dev/vality/fraudbusters/api/service/FraudbustersInspectorService.java b/src/main/java/dev/vality/fraudbusters/api/service/FraudbustersInspectorService.java
index 1592ee0..5ee7394 100644
--- a/src/main/java/dev/vality/fraudbusters/api/service/FraudbustersInspectorService.java
+++ b/src/main/java/dev/vality/fraudbusters/api/service/FraudbustersInspectorService.java
@@ -1,12 +1,14 @@
package dev.vality.fraudbusters.api.service;
import dev.vality.damsel.domain.RiskScore;
-import dev.vality.damsel.proxy_inspector.Context;
-import dev.vality.damsel.proxy_inspector.InspectorProxySrv;
+import dev.vality.damsel.proxy_inspector.*;
import dev.vality.fraudbusters.api.exceptions.RemoteInvocationException;
+import dev.vality.swag.fraudbusters.model.Merchant;
import dev.vality.swag.fraudbusters.model.RiskScoreResult;
+import dev.vality.swag.fraudbusters.model.UserInspectResult;
import lombok.RequiredArgsConstructor;
import org.apache.thrift.TException;
+import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Service;
@Service
@@ -14,6 +16,7 @@
public class FraudbustersInspectorService {
private final InspectorProxySrv.Iface proxyInspectorSrv;
+ private final Converter shopContextToMerchantConverter;
public dev.vality.swag.fraudbusters.model.RiskScore inspectPayment(Context context) {
try {
@@ -25,4 +28,20 @@ public dev.vality.swag.fraudbusters.model.RiskScore inspectPayment(Context conte
throw new RemoteInvocationException(e);
}
}
+
+ public UserInspectResult inspectUser(InspectUserContext context) {
+ try {
+ BlockedShops blockedShops = proxyInspectorSrv.inspectUser(context);
+ UserInspectResult result = new UserInspectResult();
+ if (blockedShops != null && blockedShops.getShopList() != null) {
+ result.setBlockedMerchants(blockedShops.getShopList().stream()
+ .map(shopContextToMerchantConverter::convert)
+ .toList());
+ }
+ return result;
+ } catch (TException e) {
+ throw new RemoteInvocationException(e);
+ }
+ }
+
}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index cbdc9b9..f9e6fa5 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -1,29 +1,24 @@
server:
- port: '@server.port@'
+ port: ${server.port}
management:
- security:
- flag: false
server:
- port: '@management.port@'
- metrics:
- export:
- statsd:
- flavor: etsy
- enabled: false
- prometheus:
- enabled: false
+ port: ${management.port}
endpoint:
health:
show-details: always
metrics:
- enabled: true
+ access: unrestricted
prometheus:
- enabled: true
+ access: unrestricted
endpoints:
web:
exposure:
include: health,info,prometheus
+ prometheus:
+ metrics:
+ export:
+ enabled: false
spring:
application:
@@ -31,6 +26,15 @@ spring:
output:
ansi:
enabled: always
+ security:
+ oauth2:
+ resourceserver:
+ url: https://auth.domain
+ jwt:
+ realm: internal
+ issuer-uri: >
+ ${spring.security.oauth2.resourceserver.url}/auth/realms/
+ ${spring.security.oauth2.resourceserver.jwt.realm}
info:
version: '@project.version@'
stage: dev
diff --git a/src/test/java/dev/vality/fraudbusters/api/auth/JwtTokenTestConfiguration.java b/src/test/java/dev/vality/fraudbusters/api/auth/JwtTokenTestConfiguration.java
new file mode 100644
index 0000000..da1073f
--- /dev/null
+++ b/src/test/java/dev/vality/fraudbusters/api/auth/JwtTokenTestConfiguration.java
@@ -0,0 +1,25 @@
+package dev.vality.fraudbusters.api.auth;
+
+import dev.vality.fraudbusters.api.auth.utils.JwtTokenBuilder;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+
+@Configuration
+public class JwtTokenTestConfiguration {
+
+ @Bean
+ public JwtTokenBuilder jwtTokenBuilder(KeyPair keyPair) {
+ return new JwtTokenBuilder(keyPair);
+ }
+
+ @Bean
+ public KeyPair keyPair() throws GeneralSecurityException {
+ KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
+ keyGen.initialize(2048);
+ return keyGen.generateKeyPair();
+ }
+}
diff --git a/src/test/java/dev/vality/fraudbusters/api/auth/KeycloakOpenIdTestConfiguration.java b/src/test/java/dev/vality/fraudbusters/api/auth/KeycloakOpenIdTestConfiguration.java
new file mode 100644
index 0000000..59be5c4
--- /dev/null
+++ b/src/test/java/dev/vality/fraudbusters/api/auth/KeycloakOpenIdTestConfiguration.java
@@ -0,0 +1,22 @@
+package dev.vality.fraudbusters.api.auth;
+
+
+import dev.vality.fraudbusters.api.auth.utils.JwtTokenBuilder;
+import dev.vality.fraudbusters.api.auth.utils.KeycloakOpenIdStub;
+import lombok.SneakyThrows;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class KeycloakOpenIdTestConfiguration {
+
+ @Bean
+ @SneakyThrows
+ public KeycloakOpenIdStub keycloakOpenIdStub(
+ @Value("${spring.security.oauth2.resourceserver.url}") String keycloakAuthServerUrl,
+ @Value("${spring.security.oauth2.resourceserver.jwt.realm}") String keycloakRealm,
+ JwtTokenBuilder jwtTokenBuilder) {
+ return new KeycloakOpenIdStub(keycloakAuthServerUrl + "/auth", keycloakRealm, jwtTokenBuilder);
+ }
+}
diff --git a/src/test/java/dev/vality/fraudbusters/api/auth/utils/JwtTokenBuilder.java b/src/test/java/dev/vality/fraudbusters/api/auth/utils/JwtTokenBuilder.java
new file mode 100644
index 0000000..e0a75b1
--- /dev/null
+++ b/src/test/java/dev/vality/fraudbusters/api/auth/utils/JwtTokenBuilder.java
@@ -0,0 +1,91 @@
+package dev.vality.fraudbusters.api.auth.utils;
+
+import io.jsonwebtoken.Jwts;
+import lombok.SneakyThrows;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.time.Instant;
+import java.util.UUID;
+
+public class JwtTokenBuilder {
+
+ public static final String DEFAULT_USERNAME = "Darth Vader";
+
+ public static final String DEFAULT_EMAIL = "darkside-the-best@mail.com";
+
+ private final String userId;
+
+ private final String username;
+
+ private final String email;
+
+ private final PrivateKey privateKey;
+
+ private final PublicKey publicKey;
+
+ public JwtTokenBuilder(KeyPair keyPair) {
+ this(UUID.randomUUID().toString(), DEFAULT_USERNAME, DEFAULT_EMAIL, keyPair.getPrivate(), keyPair.getPublic());
+ }
+
+ public JwtTokenBuilder(String userId, String username, String email, PrivateKey privateKey, PublicKey publicKey) {
+ this.userId = userId;
+ this.username = username;
+ this.email = email;
+ this.privateKey = privateKey;
+ this.publicKey = publicKey;
+ }
+
+ public String generateJwtWithRoles(String issuer, String... roles) {
+ long iat = Instant.now().getEpochSecond();
+ long exp = iat + 60 * 10;
+ return generateJwtWithRoles(iat, exp, issuer, roles);
+ }
+
+ public String generateJwtWithRoles(long iat, long exp, String issuer, String... roles) {
+ return generateJwtWithRoles(privateKey, iat, exp, issuer, roles);
+ }
+
+ public String generateJwtWithRoles(PrivateKey privateKey, long iat, long exp, String issuer, String... roles) {
+ String payload;
+ try {
+ payload = new JSONObject()
+ .put("jti", UUID.randomUUID().toString())
+ .put("exp", exp)
+ .put("nbf", 0L)
+ .put("iat", iat)
+ .put("iss", issuer)
+ .put("aud", "private-api")
+ .put("sub", userId)
+ .put("typ", "Bearer")
+ .put("azp", "private-api")
+ .put("resource_access", new JSONObject()
+ .put("common-api", new JSONObject()
+ .put("roles", new JSONArray(roles))))
+ .put("preferred_username", username)
+ .put("email", email).toString();
+ } catch (JSONException e) {
+ throw new RuntimeException(e);
+ }
+
+ return Jwts.builder()
+ .content(payload)
+ .signWith(privateKey, Jwts.SIG.RS256)
+ .compact();
+ }
+
+ @SneakyThrows
+ public PublicKey getPublicKey() {
+ return this.publicKey;
+ }
+
+ @SneakyThrows
+ public PrivateKey getPrivateKey() {
+ return this.privateKey;
+ }
+
+}
diff --git a/src/test/java/dev/vality/fraudbusters/api/auth/utils/KeycloakOpenIdStub.java b/src/test/java/dev/vality/fraudbusters/api/auth/utils/KeycloakOpenIdStub.java
new file mode 100644
index 0000000..5f1bf8c
--- /dev/null
+++ b/src/test/java/dev/vality/fraudbusters/api/auth/utils/KeycloakOpenIdStub.java
@@ -0,0 +1,94 @@
+package dev.vality.fraudbusters.api.auth.utils;
+
+import dev.vality.fraudbusters.api.testutil.GenerateSelfSigned;
+import dev.vality.fraudbusters.api.testutil.PublicKeyUtil;
+import lombok.SneakyThrows;
+
+import java.security.KeyPair;
+import java.util.Base64;
+
+import static com.github.tomakehurst.wiremock.client.WireMock.*;
+
+public class KeycloakOpenIdStub {
+
+ private final String keycloakRealm;
+ private final String issuer;
+ private final String openidConfig;
+ private final String jwkConfig;
+ private final JwtTokenBuilder jwtTokenBuilder;
+
+ @SneakyThrows
+ public KeycloakOpenIdStub(String keycloakAuthServerUrl, String keycloakRealm, JwtTokenBuilder jwtTokenBuilder) {
+ this.keycloakRealm = keycloakRealm;
+ this.jwtTokenBuilder = jwtTokenBuilder;
+ this.issuer = keycloakAuthServerUrl + "/realms/" + keycloakRealm;
+ this.openidConfig = "{\n" +
+ " \"issuer\": \"" + issuer + "\",\n" +
+ " \"authorization_endpoint\": \"" + keycloakAuthServerUrl + "/realms/" + keycloakRealm +
+ "/protocol/openid-connect/auth\",\n" +
+ " \"token_endpoint\": \"" + keycloakAuthServerUrl + "/realms/" + keycloakRealm +
+ "/protocol/openid-connect/token\",\n" +
+ " \"token_introspection_endpoint\": \"" + keycloakAuthServerUrl + "/realms/" +
+ keycloakRealm +
+ "/protocol/openid-connect/token/introspect\",\n" +
+ " \"userinfo_endpoint\": \"" + keycloakAuthServerUrl + "/realms/" + keycloakRealm +
+ "/protocol/openid-connect/userinfo\",\n" +
+ " \"end_session_endpoint\": \"" + keycloakAuthServerUrl + "/realms/" + keycloakRealm +
+ "/protocol/openid-connect/logout\",\n" +
+ " \"jwks_uri\": \"" + keycloakAuthServerUrl + "/realms/" + keycloakRealm +
+ "/protocol/openid-connect/certs\",\n" +
+ " \"check_session_iframe\": \"" + keycloakAuthServerUrl + "/realms/" + keycloakRealm +
+ "/protocol/openid-connect/login-status-iframe.html\",\n" +
+ " \"registration_endpoint\": \"" + keycloakAuthServerUrl + "/realms/" + keycloakRealm +
+ "/clients-registrations/openid-connect\",\n" +
+ " \"introspection_endpoint\": \"" + keycloakAuthServerUrl + "/realms/" + keycloakRealm +
+ "/protocol/openid-connect/token/introspect\"\n" +
+ "}";
+ this.jwkConfig = """
+ {
+ "keys": [
+ {
+ "alg": "RS256",
+ "e": "%s",
+ "kid": "BZdHlAdlt3F1XatlYtZg3f1Cfpk5IpEINuIgviUW59s",
+ "kty": "RSA",
+ "n": "%s",
+ "use": "sig",
+ "x5c": [
+ "%s"
+ ],
+ "x5t": "9APiqOME1mVmyv8hak6HB_PTezA",
+ "x5t#S256": "kweH93DnMHKD_NrAZF-mgpAM3Njv_8-oxaDAzki4t48"
+ }
+ ]
+ }
+ """.formatted(
+ PublicKeyUtil.getExponent(jwtTokenBuilder.getPublicKey()),
+ PublicKeyUtil.getModulus(jwtTokenBuilder.getPublicKey()),
+ Base64.getEncoder().encodeToString(
+ GenerateSelfSigned.generateCertificate(new KeyPair(jwtTokenBuilder.getPublicKey(),
+ jwtTokenBuilder.getPrivateKey())).getEncoded()));
+ }
+
+ public void givenStub() {
+ stubFor(get(urlEqualTo(String.format("/auth/realms/%s/.well-known/openid-configuration", keycloakRealm)))
+ .willReturn(aResponse()
+ .withHeader("Content-Type", "application/json")
+ .withBody(openidConfig)
+ )
+ );
+ stubFor(get(urlEqualTo(String.format("/auth/realms/%s/protocol/openid-connect/certs", keycloakRealm)))
+ .willReturn(aResponse()
+ .withHeader("Content-Type", "application/json")
+ .withBody(jwkConfig)
+ ));
+ }
+
+ public String generateJwt(String... roles) {
+ return jwtTokenBuilder.generateJwtWithRoles(issuer, roles);
+ }
+
+ public String generateJwt(long iat, long exp, String... roles) {
+ return jwtTokenBuilder.generateJwtWithRoles(iat, exp, issuer, roles);
+ }
+}
diff --git a/src/test/java/dev/vality/fraudbusters/api/config/AbstractKeycloakOpenIdAsWiremockConfig.java b/src/test/java/dev/vality/fraudbusters/api/config/AbstractKeycloakOpenIdAsWiremockConfig.java
new file mode 100644
index 0000000..4c829b4
--- /dev/null
+++ b/src/test/java/dev/vality/fraudbusters/api/config/AbstractKeycloakOpenIdAsWiremockConfig.java
@@ -0,0 +1,37 @@
+package dev.vality.fraudbusters.api.config;
+
+import dev.vality.fraudbusters.api.FraudbustersApiApplication;
+import dev.vality.fraudbusters.api.auth.utils.KeycloakOpenIdStub;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.wiremock.spring.EnableWireMock;
+
+@SuppressWarnings("LineLength")
+@SpringBootTest(
+ webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
+ classes = {FraudbustersApiApplication.class},
+ properties = {
+ "spring.security.oauth2.resourceserver.url=${wiremock.server.baseUrl}",
+ "spring.security.oauth2.resourceserver.jwt.issuer-uri=${wiremock.server.baseUrl}/auth/realms/" +
+ "${spring.security.oauth2.resourceserver.jwt.realm}"})
+@AutoConfigureMockMvc
+@EnableWireMock
+@ExtendWith(SpringExtension.class)
+public abstract class AbstractKeycloakOpenIdAsWiremockConfig {
+
+ @Autowired
+ private KeycloakOpenIdStub keycloakOpenIdStub;
+
+ @BeforeEach
+ public void setUp(@Autowired KeycloakOpenIdStub keycloakOpenIdStub) throws Exception {
+ keycloakOpenIdStub.givenStub();
+ }
+
+ protected String generateSimpleJwt() {
+ return keycloakOpenIdStub.generateJwt();
+ }
+}
diff --git a/src/test/java/dev/vality/fraudbusters/api/resource/ResourceTest.java b/src/test/java/dev/vality/fraudbusters/api/resource/ResourceTest.java
index 5a9d5b5..25cc411 100644
--- a/src/test/java/dev/vality/fraudbusters/api/resource/ResourceTest.java
+++ b/src/test/java/dev/vality/fraudbusters/api/resource/ResourceTest.java
@@ -2,25 +2,32 @@
import dev.vality.damsel.fraudbusters.PaymentServiceSrv;
import dev.vality.damsel.proxy_inspector.InspectorProxySrv;
+import dev.vality.fraudbusters.api.config.AbstractKeycloakOpenIdAsWiremockConfig;
import dev.vality.fraudbusters.api.service.FraudbustersDataService;
import dev.vality.fraudbusters.api.service.FraudbustersInspectorService;
import dev.vality.fraudbusters.api.utils.ApiBeanGenerator;
import dev.vality.swag.fraudbusters.model.*;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.boot.test.mock.mockito.MockBean;
-import org.springframework.boot.web.server.LocalServerPort;
+import org.springframework.boot.test.context.TestConfiguration;
+import org.springframework.boot.test.web.server.LocalServerPort;
+import org.springframework.context.annotation.Bean;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.security.oauth2.jwt.JwtDecoder;
+import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;
+import java.time.Instant;
import java.util.List;
-import static org.junit.Assert.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
-class ResourceTest {
+class ResourceTest extends AbstractKeycloakOpenIdAsWiremockConfig {
public static final String CHARGEBACKS = "/chargebacks";
public static final String REFUNDS = "/refunds";
@@ -28,22 +35,31 @@ class ResourceTest {
public static final String FRAUD_PAYMENTS = "/fraud-payments";
public static final String WITHDRAWALS = "/withdrawals";
public static final String INSPECTOR = "/inspect-payment";
+ public static final String INSPECT_USER = "/inspect-user";
public static final String BASE_URL = "http://localhost:";
@LocalServerPort
int serverPort;
- @MockBean
+ @MockitoBean
FraudbustersDataService fraudbustersDataService;
- @MockBean
+ @MockitoBean
FraudbustersInspectorService fraudbustersInspectorService;
- @MockBean
+ @MockitoBean
PaymentServiceSrv.Iface paymentServiceSrv;
- @MockBean
+ @MockitoBean
InspectorProxySrv.Iface proxyInspectorSrv;
RestTemplate restTemplate = new RestTemplate();
+ @BeforeEach
+ void setUp() {
+ restTemplate.getInterceptors().add((request, body, execution) -> {
+ request.getHeaders().setBearerAuth(generateSimpleJwt());
+ return execution.execute(request, body);
+ });
+ }
+
@Test
void insertChargebacksTest() {
ChargebacksRequest request = new ChargebacksRequest();
@@ -145,8 +161,36 @@ void inspectPaymentsTest() {
verify(fraudbustersInspectorService, times(1)).inspectPayment(any());
}
+ @Test
+ void inspectUserTest() {
+ when(fraudbustersInspectorService.inspectUser(any())).thenReturn(new UserInspectResult());
+
+ UserInspectRequest request = new UserInspectRequest();
+ assertThrows(HttpClientErrorException.BadRequest.class, () ->
+ restTemplate.postForEntity(initUrl(INSPECT_USER), request, UserInspectResult.class));
+
+ request.user(ApiBeanGenerator.initCustomer());
+ request.merchants(List.of(ApiBeanGenerator.initMerchant()));
+ restTemplate.postForEntity(initUrl(INSPECT_USER), request, UserInspectResult.class);
+ verify(fraudbustersInspectorService, times(1)).inspectUser(any());
+ }
+
private String initUrl(String fraudbustersRefunds) {
return BASE_URL + serverPort + fraudbustersRefunds;
}
+ @TestConfiguration
+ static class JwtTestConfig {
+
+ @Bean
+ JwtDecoder jwtDecoder() {
+ return token -> Jwt.withTokenValue(token)
+ .header("alg", "none")
+ .claim("sub", "test")
+ .issuedAt(Instant.now())
+ .expiresAt(Instant.now().plusSeconds(3600))
+ .build();
+ }
+ }
+
}
diff --git a/src/test/java/dev/vality/fraudbusters/api/testutil/GenerateSelfSigned.java b/src/test/java/dev/vality/fraudbusters/api/testutil/GenerateSelfSigned.java
new file mode 100644
index 0000000..8bf217c
--- /dev/null
+++ b/src/test/java/dev/vality/fraudbusters/api/testutil/GenerateSelfSigned.java
@@ -0,0 +1,37 @@
+package dev.vality.fraudbusters.api.testutil;
+
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.cert.X509v3CertificateBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.SecureRandom;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.time.LocalDate;
+import java.time.ZoneOffset;
+import java.util.Date;
+
+public class GenerateSelfSigned {
+
+ public static X509Certificate generateCertificate(KeyPair keyPair) throws CertificateException,
+ OperatorCreationException {
+ X500Name x500Name = new X500Name("CN=***.com, OU=Security&Defense, O=*** Crypto., L=Ottawa, ST=Ontario, C=CA");
+ SubjectPublicKeyInfo pubKeyInfo = SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded());
+ final Date start = new Date();
+ final Date until = Date.from(LocalDate.now().plusDays(365).atStartOfDay().toInstant(ZoneOffset.UTC));
+ final X509v3CertificateBuilder certificateBuilder = new X509v3CertificateBuilder(x500Name,
+ new BigInteger(10, new SecureRandom()), start, until, x500Name, pubKeyInfo
+ );
+ ContentSigner contentSigner = new JcaContentSignerBuilder("SHA256WithRSA").build(keyPair.getPrivate());
+ return new JcaX509CertificateConverter()
+ .setProvider(new BouncyCastleProvider())
+ .getCertificate(certificateBuilder.build(contentSigner));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/dev/vality/fraudbusters/api/testutil/PublicKeyUtil.java b/src/test/java/dev/vality/fraudbusters/api/testutil/PublicKeyUtil.java
new file mode 100644
index 0000000..44e0537
--- /dev/null
+++ b/src/test/java/dev/vality/fraudbusters/api/testutil/PublicKeyUtil.java
@@ -0,0 +1,25 @@
+package dev.vality.fraudbusters.api.testutil;
+
+import lombok.SneakyThrows;
+import lombok.experimental.UtilityClass;
+
+import java.math.BigInteger;
+import java.security.PublicKey;
+import java.security.interfaces.RSAPublicKey;
+import java.util.Base64;
+
+@UtilityClass
+public class PublicKeyUtil {
+
+ @SneakyThrows
+ public String getModulus(PublicKey publicKey) {
+ BigInteger publicKeyModulus = ((RSAPublicKey) (publicKey)).getModulus();
+ return Base64.getUrlEncoder().encodeToString(publicKeyModulus.toByteArray());
+ }
+
+ @SneakyThrows
+ public String getExponent(PublicKey publicKey) {
+ BigInteger publicKeyExponent = ((RSAPublicKey) (publicKey)).getPublicExponent();
+ return Base64.getUrlEncoder().encodeToString(publicKeyExponent.toByteArray());
+ }
+}
diff --git a/src/test/java/dev/vality/fraudbusters/api/testutil/RandomUtil.java b/src/test/java/dev/vality/fraudbusters/api/testutil/RandomUtil.java
new file mode 100644
index 0000000..071d444
--- /dev/null
+++ b/src/test/java/dev/vality/fraudbusters/api/testutil/RandomUtil.java
@@ -0,0 +1,37 @@
+package dev.vality.fraudbusters.api.testutil;
+
+import lombok.experimental.UtilityClass;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Random;
+
+import static java.util.UUID.randomUUID;
+
+@UtilityClass
+public class RandomUtil {
+
+ private static final Random random = new Random();
+
+ public static int randomInt(int from, int to) {
+ return random.nextInt(to - from) + from;
+ }
+
+ public static String randomIntegerAsString(int from, int to) {
+ return String.valueOf(random.nextInt(to - from) + from);
+ }
+
+ public static String randomString(int length) {
+ return new String(randomBytes(length), StandardCharsets.UTF_8);
+ }
+
+ public static byte[] randomBytes(int length) {
+ byte[] array = new byte[length];
+ new Random().nextBytes(array);
+ return array;
+ }
+
+ public static String randomRequestId() {
+ return randomUUID().toString().substring(0, 32);
+ }
+
+}
diff --git a/src/test/java/dev/vality/fraudbusters/api/utils/ApiBeanGenerator.java b/src/test/java/dev/vality/fraudbusters/api/utils/ApiBeanGenerator.java
index 20f68fa..4e5dbdb 100644
--- a/src/test/java/dev/vality/fraudbusters/api/utils/ApiBeanGenerator.java
+++ b/src/test/java/dev/vality/fraudbusters/api/utils/ApiBeanGenerator.java
@@ -1,7 +1,7 @@
package dev.vality.fraudbusters.api.utils;
-import dev.vality.swag.fraudbusters.model.Error;
import dev.vality.swag.fraudbusters.model.*;
+import dev.vality.swag.fraudbusters.model.Error;
import java.time.OffsetDateTime;
import java.util.UUID;
diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml
index 766a9a2..0b7fc8e 100644
--- a/src/test/resources/logback-test.xml
+++ b/src/test/resources/logback-test.xml
@@ -6,5 +6,5 @@
-
+