Auth via tokenkeeper (#73)

* Auth via tokenkeeper

* Update dependency dev.vality:damsel to v1.614-3df747f (#72)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* Update dependency dev.vality:bouncer-proto to v1.51-23d1761 (#71)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* Update dependency dev.vality:analytics-proto to v1.43-2d1fdc2 (#70)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* Update dependency com.github.tomakehurst:wiremock-jre8-standalone to v2.35.1 [SECURITY] (#66)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* Update all non-major maven dependencies (#68)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* Feedback edits

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
This commit is contained in:
Egor Cherniak 2024-06-19 10:09:59 +03:00 committed by GitHub
parent 193f05fb0c
commit 520a524690
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 374 additions and 435 deletions

69
pom.xml
View File

@ -10,7 +10,7 @@
</parent>
<artifactId>anapi-v2</artifactId>
<version>1.0.3</version>
<version>1.0.4</version>
<packaging>jar</packaging>
<name>anapi-v2</name>
@ -44,7 +44,7 @@
<dependency>
<groupId>dev.vality</groupId>
<artifactId>bouncer-proto</artifactId>
<version>1.46-d5e5639</version>
<version>1.51-23d1761</version>
</dependency>
<dependency>
<groupId>dev.vality</groupId>
@ -69,7 +69,7 @@
<dependency>
<groupId>dev.vality</groupId>
<artifactId>analytics-proto</artifactId>
<version>1.41-f691834</version>
<version>1.43-2d1fdc2</version>
</dependency>
<dependency>
<groupId>dev.vality</groupId>
@ -83,60 +83,19 @@
<dependency>
<groupId>dev.vality</groupId>
<artifactId>damsel</artifactId>
<version>1.597-bfedcb9</version>
<version>1.614-3df747f</version>
</dependency>
<dependency>
<groupId>dev.vality</groupId>
<artifactId>token-keeper-proto</artifactId>
<version>1.32-8b8bb43</version>
</dependency>
<!--spring-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-admin-client</artifactId>
<version>15.1.1</version>
<exclusions>
<exclusion>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-client</artifactId>
</exclusion>
<exclusion>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-multipart-provider</artifactId>
</exclusion>
<exclusion>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jackson2-provider</artifactId>
</exclusion>
<exclusion>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxb-provider</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-spring-security-adapter</artifactId>
<version>15.1.1</version>
<exclusions>
<exclusion>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
@ -173,7 +132,7 @@
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>1.6.8</version>
<version>1.6.12</version>
<scope>provided</scope>
</dependency>
<dependency>
@ -222,7 +181,7 @@
<dependency>
<groupId>org.openapitools</groupId>
<artifactId>jackson-databind-nullable</artifactId>
<version>0.2.3</version>
<version>0.2.6</version>
<scope>provided</scope>
</dependency>
<dependency>
@ -254,7 +213,7 @@
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock-jre8-standalone</artifactId>
<version>2.34.0</version>
<version>2.35.1</version>
<scope>test</scope>
</dependency>
<dependency>
@ -297,12 +256,12 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-remote-resources-plugin</artifactId>
<version>3.0.0</version>
<version>3.1.0</version>
<dependencies>
<dependency>
<groupId>org.apache.maven.shared</groupId>
<artifactId>maven-filtering</artifactId>
<version>3.3.0</version>
<version>3.3.1</version>
</dependency>
</dependencies>
<configuration>

View File

@ -1,16 +1,17 @@
package dev.vality.anapi.v2.config;
import dev.vality.bouncer.decisions.ArbiterSrv;
import dev.vality.damsel.analytics.AnalyticsServiceSrv;
import dev.vality.damsel.vortigon.VortigonServiceSrv;
import dev.vality.magista.MerchantStatisticsServiceSrv;
import dev.vality.orgmanagement.AuthContextProviderSrv;
import dev.vality.reporter.ReportingSrv;
import dev.vality.token.keeper.TokenAuthenticatorSrv;
import dev.vality.woody.api.trace.context.metadata.user.UserIdentityEmailExtensionKit;
import dev.vality.woody.api.trace.context.metadata.user.UserIdentityIdExtensionKit;
import dev.vality.woody.api.trace.context.metadata.user.UserIdentityRealmExtensionKit;
import dev.vality.woody.api.trace.context.metadata.user.UserIdentityUsernameExtensionKit;
import dev.vality.woody.thrift.impl.http.THSpawnClientBuilder;
import dev.vality.damsel.analytics.AnalyticsServiceSrv;
import dev.vality.damsel.vortigon.VortigonServiceSrv;
import dev.vality.orgmanagement.AuthContextProviderSrv;
import dev.vality.reporter.ReportingSrv;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -86,4 +87,14 @@ public class ApplicationConfig {
.withAddress(resource.getURI())
.build(ReportingSrv.Iface.class);
}
@Bean
public TokenAuthenticatorSrv.Iface tokenKeeperClient(
@Value("${service.tokenKeeper.url}") Resource resource,
@Value("${service.tokenKeeper.networkTimeout}") int networkTimeout) throws IOException {
return new THSpawnClientBuilder()
.withNetworkTimeout(networkTimeout)
.withAddress(resource.getURI())
.build(TokenAuthenticatorSrv.Iface.class);
}
}

View File

@ -1,60 +0,0 @@
package dev.vality.anapi.v2.config;
import dev.vality.anapi.v2.config.properties.KeycloakProperties;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.keycloak.adapters.KeycloakConfigResolver;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.KeycloakDeploymentBuilder;
import org.keycloak.representations.adapters.config.AdapterConfig;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import java.util.stream.Collectors;
@RequiredArgsConstructor
@Configuration
@ConditionalOnProperty(value = "auth.enabled", havingValue = "true")
public class KeycloakConfig {
private final KeycloakProperties keycloakProperties;
@Bean
public KeycloakConfigResolver keycloakConfigResolver() {
return facade -> {
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(adapterConfig());
deployment.setNotBefore(keycloakProperties.getNotBefore());
return deployment;
};
}
private AdapterConfig adapterConfig() {
if (StringUtils.hasLength(keycloakProperties.getRealmPublicKeyPath())) {
keycloakProperties.setRealmPublicKey(readKeyFromFile(keycloakProperties.getRealmPublicKeyPath()));
}
AdapterConfig adapterConfig = new AdapterConfig();
adapterConfig.setRealm(keycloakProperties.getRealm());
adapterConfig.setRealmKey(keycloakProperties.getRealmPublicKey());
adapterConfig.setResource(keycloakProperties.getResource());
adapterConfig.setAuthServerUrl(keycloakProperties.getAuthServerUrl());
adapterConfig.setUseResourceRoleMappings(true);
adapterConfig.setBearerOnly(true);
adapterConfig.setSslRequired(keycloakProperties.getSslRequired());
return adapterConfig;
}
@SneakyThrows
private String readKeyFromFile(String filePath) {
List<String> strings = Files.readAllLines(Paths.get(filePath));
strings.remove(strings.size() - 1);
strings.remove(0);
return strings.stream().map(String::trim).collect(Collectors.joining());
}
}

View File

@ -1,67 +0,0 @@
package dev.vality.anapi.v2.config;
import org.keycloak.adapters.springsecurity.KeycloakSecurityComponents;
import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
@Configuration
@EnableWebSecurity
@ComponentScan(
basePackageClasses = KeycloakSecurityComponents.class,
excludeFilters = @ComponentScan.Filter(
type = FilterType.REGEX,
pattern = "org.keycloak.adapters.springsecurity.management.HttpSessionManager"))
@EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true)
@ConditionalOnProperty(value = "auth.enabled", havingValue = "true")
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
@Bean
@Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new NullAuthenticatedSessionStrategy();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http
.cors().and()
.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers(HttpMethod.GET, "/**/health").permitAll()
.antMatchers(HttpMethod.GET, "/**/prometheus").permitAll()
.anyRequest().authenticated();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(keycloakAuthenticationProvider());
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.applyPermitDefaultValues();
configuration.addAllowedMethod(HttpMethod.PUT);
configuration.addAllowedMethod(HttpMethod.DELETE);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}

View File

@ -1,17 +1,12 @@
package dev.vality.anapi.v2.config;
import dev.vality.woody.api.flow.WFlow;
import dev.vality.woody.api.trace.context.metadata.user.UserIdentityEmailExtensionKit;
import dev.vality.woody.api.trace.context.metadata.user.UserIdentityIdExtensionKit;
import dev.vality.woody.api.trace.context.metadata.user.UserIdentityRealmExtensionKit;
import dev.vality.woody.api.trace.context.metadata.user.UserIdentityUsernameExtensionKit;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
import org.keycloak.representations.AccessToken;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
@ -19,18 +14,27 @@ import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.security.Principal;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import static dev.vality.anapi.v2.util.DeadlineUtil.*;
import static dev.vality.woody.api.trace.ContextUtils.setCustomMetadataValue;
import static dev.vality.woody.api.trace.ContextUtils.setDeadline;
@Configuration
@SuppressWarnings({"ParameterName", "LocalVariableName"})
public class WebConfig {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("*");
}
};
}
@Bean
public FilterRegistrationBean woodyFilter() {
WFlow woodyFlow = new WFlow();
@ -41,20 +45,15 @@ public class WebConfig {
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
woodyFlow.createServiceFork(
() -> {
try {
if (request.getUserPrincipal() != null) {
addWoodyContext(request.getUserPrincipal());
() -> {
try {
setWoodyDeadline(request);
filterChain.doFilter(request, response);
} catch (IOException | ServletException e) {
sneakyThrow(e);
}
}
setWoodyDeadline(request);
filterChain.doFilter(request, response);
} catch (IOException | ServletException e) {
sneakyThrow(e);
}
}
)
)
.run();
}
@ -71,17 +70,6 @@ public class WebConfig {
return filterRegistrationBean;
}
private void addWoodyContext(Principal principal) {
KeycloakSecurityContext keycloakSecurityContext =
((KeycloakAuthenticationToken) principal).getAccount().getKeycloakSecurityContext();
AccessToken accessToken = keycloakSecurityContext.getToken();
setCustomMetadataValue(UserIdentityIdExtensionKit.KEY, accessToken.getSubject());
setCustomMetadataValue(UserIdentityUsernameExtensionKit.KEY, accessToken.getPreferredUsername());
setCustomMetadataValue(UserIdentityEmailExtensionKit.KEY, accessToken.getEmail());
setCustomMetadataValue(UserIdentityRealmExtensionKit.KEY, keycloakSecurityContext.getRealm());
}
private void setWoodyDeadline(HttpServletRequest request) {
String xRequestDeadline = request.getHeader("X-Request-Deadline");
String xRequestId = request.getHeader("X-Request-ID");

View File

@ -1,34 +0,0 @@
package dev.vality.anapi.v2.config.properties;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.NotEmpty;
@Getter
@Setter
@Component
@Validated
@ConfigurationProperties(prefix = "keycloak")
public class KeycloakProperties {
@NotEmpty
private String realm;
@NotEmpty
private String authServerUrl;
@NotEmpty
private String resource;
private Integer notBefore;
@NotEmpty
private String sslRequired;
private String realmPublicKey;
private String realmPublicKeyPath;
}

View File

@ -1,9 +1,6 @@
package dev.vality.anapi.v2.controller;
import dev.vality.anapi.v2.exception.AuthorizationException;
import dev.vality.anapi.v2.exception.BadRequestException;
import dev.vality.anapi.v2.exception.DeadlineException;
import dev.vality.anapi.v2.exception.NotFoundException;
import dev.vality.anapi.v2.exception.*;
import dev.vality.anapi.v2.model.DefaultLogicError;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -11,7 +8,6 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.util.CollectionUtils;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
@ -84,15 +80,15 @@ public class ErrorControllerAdvice {
}
@ExceptionHandler({AccessDeniedException.class})
@ExceptionHandler({TokenKeeperException.class})
@ResponseStatus(HttpStatus.FORBIDDEN)
public void handleAccessDeniedException(AccessDeniedException e) {
public void handleTokenKeeperException(TokenKeeperException e) {
log.warn("<- Res [403]: Request denied access", e);
}
@ExceptionHandler({AuthorizationException.class})
@ResponseStatus(HttpStatus.FORBIDDEN)
public void handleAccessDeniedException(AuthorizationException e) {
public void handleAuthorizationException(AuthorizationException e) {
log.warn("<- Res [403]: Request denied access", e);
}

View File

@ -0,0 +1,12 @@
package dev.vality.anapi.v2.exception;
public class TokenKeeperException extends RuntimeException {
public TokenKeeperException(String s) {
super(s);
}
public TokenKeeperException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -3,7 +3,7 @@ package dev.vality.anapi.v2.security;
import dev.vality.anapi.v2.exception.AuthorizationException;
import dev.vality.anapi.v2.exception.BouncerException;
import dev.vality.anapi.v2.service.BouncerService;
import dev.vality.anapi.v2.service.KeycloakService;
import dev.vality.anapi.v2.service.TokenKeeperService;
import dev.vality.anapi.v2.service.VortigonService;
import dev.vality.bouncer.base.Entity;
import lombok.RequiredArgsConstructor;
@ -24,7 +24,7 @@ public class AccessService {
private final VortigonService vortigonService;
private final BouncerService bouncerService;
private final KeycloakService keycloakService;
private final TokenKeeperService tokenKeeperService;
@Value("${service.bouncer.auth.enabled}")
private boolean authEnabled;
@ -86,16 +86,13 @@ public class AccessService {
}
private AnapiBouncerContext buildAnapiBouncerContext(AccessData accessData, @Nullable List<String> shopIds) {
var token = keycloakService.getAccessToken();
return AnapiBouncerContext.builder()
.operationId(accessData.getOperationId())
.partyId(accessData.getPartyId())
.shopIds(shopIds)
.fileId(accessData.getFileId())
.reportId(accessData.getReportId())
.tokenExpiration(token.getExp())
.tokenId(token.getId())
.userId(token.getSubject())
.authData(tokenKeeperService.getAuthData())
.build();
}
}

View File

@ -1,5 +1,6 @@
package dev.vality.anapi.v2.security;
import dev.vality.token.keeper.AuthData;
import lombok.Builder;
import lombok.Data;
@ -17,6 +18,6 @@ public class AnapiBouncerContext {
private final List<String> shopIds;
private final String reportId;
private final String fileId;
private final AuthData authData;
}

View File

@ -1,8 +1,8 @@
package dev.vality.anapi.v2.security;
import dev.vality.anapi.v2.config.properties.BouncerProperties;
import dev.vality.anapi.v2.service.KeycloakService;
import dev.vality.anapi.v2.service.OrgManagerService;
import dev.vality.anapi.v2.util.JwtUtil;
import dev.vality.bouncer.base.Entity;
import dev.vality.bouncer.context.v1.*;
import dev.vality.bouncer.ctx.ContextFragmentType;
@ -14,6 +14,7 @@ import org.apache.thrift.TSerializer;
import org.springframework.stereotype.Component;
import java.time.Instant;
import java.util.Optional;
import java.util.Set;
@RequiredArgsConstructor
@ -23,7 +24,6 @@ public class BouncerContextFactory {
private final BouncerProperties bouncerProperties;
private final OrgManagerService orgManagerService;
private final KeycloakService keycloakService;
@SneakyThrows
public Context buildContext(AnapiBouncerContext bouncerContext) {
@ -32,11 +32,17 @@ public class BouncerContextFactory {
var fragment = new dev.vality.bouncer.ctx.ContextFragment()
.setType(ContextFragmentType.v1_thrift_binary)
.setContent(serializer.serialize(contextFragment));
var userFragment = orgManagerService.getUserAuthContext(
keycloakService.getAccessToken().getSubject());
var context = new Context();
context.putToFragments(bouncerProperties.getContextFragmentId(), fragment);
context.putToFragments("user", userFragment);
context.putToFragments("anapi", fragment);
context.putToFragments("token-keeper", bouncerContext.getAuthData().getContext());
Optional<String> subject = JwtUtil.getSubject(bouncerContext.getAuthData().getToken());
if (subject.isPresent()) {
log.debug("User context fragment will be added");
var userFragment = orgManagerService.getUserAuthContext(subject.get());
context.putToFragments("user", userFragment);
}
return context;
}
@ -46,21 +52,11 @@ public class BouncerContextFactory {
var contextReports = buildReportContext(bouncerContext);
ContextFragment fragment = new ContextFragment();
return fragment
.setAuth(buildAuth())
.setEnv(env)
.setAnapi(contextAnalyticsApi)
.setReports(contextReports);
}
private Auth buildAuth() {
var auth = new Auth();
var accessToken = keycloakService.getAccessToken();
return auth
.setToken(new Token().setId(accessToken.getId()))
.setMethod(bouncerProperties.getAuthMethod())
.setExpiration(Instant.ofEpochSecond(accessToken.getExp()).toString());
}
private Environment buildEnvironment() {
var deployment = new Deployment()
.setId(bouncerProperties.getDeploymentId());

View File

@ -1,17 +0,0 @@
package dev.vality.anapi.v2.service;
import org.keycloak.KeycloakPrincipal;
import org.keycloak.representations.AccessToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
@Service
public class KeycloakService {
public AccessToken getAccessToken() {
var keycloakPrincipal = (KeycloakPrincipal) SecurityContextHolder.getContext()
.getAuthentication()
.getPrincipal();
return keycloakPrincipal.getKeycloakSecurityContext().getToken();
}
}

View File

@ -0,0 +1,48 @@
package dev.vality.anapi.v2.service;
import dev.vality.anapi.v2.exception.TokenKeeperException;
import dev.vality.token.keeper.AuthData;
import dev.vality.token.keeper.TokenAuthenticatorSrv;
import dev.vality.token.keeper.TokenSourceContext;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpHeaders;
import org.apache.thrift.TException;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.util.Optional;
@Slf4j
@Service
@RequiredArgsConstructor
public class TokenKeeperService {
private static final String bearerPrefix = "Bearer ";
private final TokenAuthenticatorSrv.Iface tokenKeeperClient;
public AuthData getAuthData() {
log.debug("Retrieving auth info");
try {
var token = getBearerToken();
return tokenKeeperClient.authenticate(
token.orElseThrow(() -> new TokenKeeperException("Token not found!")), new TokenSourceContext());
} catch (TException e) {
throw new TokenKeeperException("Error while call token keeper: ", e);
}
}
private Optional<String> getBearerToken() {
var attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (ObjectUtils.isEmpty(attributes)
|| ObjectUtils.isEmpty(attributes.getRequest().getHeader(HttpHeaders.AUTHORIZATION))) {
return Optional.empty();
}
String token = attributes.getRequest().getHeader(HttpHeaders.AUTHORIZATION).substring(bearerPrefix.length());
return Optional.of(token);
}
}

View File

@ -0,0 +1,33 @@
package dev.vality.anapi.v2.util;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
@Slf4j
@UtilityClass
public class JwtUtil {
private static final ObjectMapper objectMapper = new ObjectMapper();
private static final String subjectFieldName = "sub";
public static Optional<String> getSubject(String token) {
try {
String[] chunks = token.split("\\.");
Base64.Decoder decoder = Base64.getUrlDecoder();
String payload = new String(decoder.decode(chunks[1]));
Map<String, Object> parsedPayload = objectMapper.readValue(payload, HashMap.class);
if (parsedPayload.containsKey(subjectFieldName)) {
return Optional.of(String.valueOf(parsedPayload.get(subjectFieldName)));
}
} catch (Exception e) {
log.warn("Unable to parse jwt token while looking for subject: ", e);
}
return Optional.empty();
}
}

View File

@ -33,6 +33,9 @@ service:
orgManager:
url: http://localhost:8022/change_it
networkTimeout: 5000
tokenKeeper:
url: http://localhost:8022/change_it
networkTimeout: 5000
bouncer:
url: http://localhost:8022/change_it
networkTimeout: 10000
@ -57,13 +60,3 @@ spring:
info:
version: '@project.version@'
stage: dev
keycloak:
realm: internal
auth-server-url: http://keycloak:8080/auth/
resource: common-api
not-before: 0
ssl-required: none
realm-public-key:
auth.enabled: true

View File

@ -1,6 +1,6 @@
package dev.vality.anapi.v2;
import dev.vality.anapi.v2.config.AbstractKeycloakOpenIdAsWiremockConfig;
import dev.vality.anapi.v2.config.AbstractConfig;
import dev.vality.anapi.v2.model.DefaultLogicError;
import dev.vality.anapi.v2.testutil.AnalyticsUtil;
import dev.vality.anapi.v2.testutil.OpenApiUtil;
@ -8,6 +8,7 @@ import dev.vality.bouncer.decisions.ArbiterSrv;
import dev.vality.damsel.analytics.AnalyticsServiceSrv;
import dev.vality.damsel.vortigon.VortigonServiceSrv;
import dev.vality.orgmanagement.AuthContextProviderSrv;
import dev.vality.token.keeper.TokenAuthenticatorSrv;
import lombok.SneakyThrows;
import org.apache.thrift.TException;
import org.junit.jupiter.api.AfterEach;
@ -24,8 +25,9 @@ import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.List;
import static dev.vality.anapi.v2.testutil.MagistaUtil.createContextFragment;
import static dev.vality.anapi.v2.testutil.MagistaUtil.createJudgementAllowed;
import static dev.vality.anapi.v2.testutil.BouncerUtil.createContextFragment;
import static dev.vality.anapi.v2.testutil.BouncerUtil.createJudgementAllowed;
import static dev.vality.anapi.v2.testutil.TokenKeeperUtil.createAuthData;
import static java.util.UUID.randomUUID;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ -34,7 +36,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
class AnalyticsTest extends AbstractKeycloakOpenIdAsWiremockConfig {
class AnalyticsTest extends AbstractConfig {
@MockBean
public VortigonServiceSrv.Iface vortigonClient;
@ -44,6 +46,8 @@ class AnalyticsTest extends AbstractKeycloakOpenIdAsWiremockConfig {
public ArbiterSrv.Iface bouncerClient;
@MockBean
private AnalyticsServiceSrv.Iface analyticsClient;
@MockBean
public TokenAuthenticatorSrv.Iface tokenKeeperClient;
@Autowired
private MockMvc mvc;
@ -55,7 +59,8 @@ class AnalyticsTest extends AbstractKeycloakOpenIdAsWiremockConfig {
@BeforeEach
public void init() {
mocks = MockitoAnnotations.openMocks(this);
preparedMocks = new Object[]{analyticsClient, vortigonClient, orgManagerClient, bouncerClient};
preparedMocks = new Object[]{analyticsClient, vortigonClient,
orgManagerClient, bouncerClient, tokenKeeperClient};
}
@AfterEach
@ -68,6 +73,7 @@ class AnalyticsTest extends AbstractKeycloakOpenIdAsWiremockConfig {
@SneakyThrows
void getAveragePaymentRequiredParamsRequestSuccess() {
when(vortigonClient.getShopsIds(any(), any())).thenReturn(List.of("1", "2", "3"));
when(tokenKeeperClient.authenticate(any(), any())).thenReturn(createAuthData(generateSimpleJwt()));
when(orgManagerClient.getUserContext(any())).thenReturn(createContextFragment());
when(bouncerClient.judge(any(), any())).thenReturn(createJudgementAllowed());
when(analyticsClient.getAveragePayment(any())).thenReturn(AnalyticsUtil.createAveragePaymentRequiredResponse());
@ -82,6 +88,7 @@ class AnalyticsTest extends AbstractKeycloakOpenIdAsWiremockConfig {
.andExpect(status().is2xxSuccessful())
.andExpect(jsonPath("$").exists());
verify(vortigonClient, times(1)).getShopsIds(any(), any());
verify(tokenKeeperClient, times(1)).authenticate(any(), any());
verify(orgManagerClient, times(1)).getUserContext(any());
verify(bouncerClient, times(1)).judge(any(), any());
verify(analyticsClient, times(1)).getAveragePayment(any());
@ -91,6 +98,7 @@ class AnalyticsTest extends AbstractKeycloakOpenIdAsWiremockConfig {
@SneakyThrows
void getAveragePaymentAllParamsRequestSuccess() {
when(vortigonClient.getShopsIds(any(), any())).thenReturn(List.of("1", "2", "3"));
when(tokenKeeperClient.authenticate(any(), any())).thenReturn(createAuthData(generateSimpleJwt()));
when(orgManagerClient.getUserContext(any())).thenReturn(createContextFragment());
when(bouncerClient.judge(any(), any())).thenReturn(createJudgementAllowed());
when(analyticsClient.getAveragePayment(any())).thenReturn(AnalyticsUtil.createAveragePaymentAllResponse());
@ -105,6 +113,7 @@ class AnalyticsTest extends AbstractKeycloakOpenIdAsWiremockConfig {
.andExpect(status().is2xxSuccessful())
.andExpect(jsonPath("$").exists());
verify(vortigonClient, times(1)).getShopsIds(any(), any());
verify(tokenKeeperClient, times(1)).authenticate(any(), any());
verify(orgManagerClient, times(1)).getUserContext(any());
verify(bouncerClient, times(1)).judge(any(), any());
verify(analyticsClient, times(1)).getAveragePayment(any());
@ -132,6 +141,7 @@ class AnalyticsTest extends AbstractKeycloakOpenIdAsWiremockConfig {
@SneakyThrows
void getAveragePaymentRequestServerUnavailable() {
when(vortigonClient.getShopsIds(any(), any())).thenReturn(List.of("1", "2", "3"));
when(tokenKeeperClient.authenticate(any(), any())).thenReturn(createAuthData(generateSimpleJwt()));
when(orgManagerClient.getUserContext(any())).thenReturn(createContextFragment());
when(bouncerClient.judge(any(), any())).thenReturn(createJudgementAllowed());
when(analyticsClient.getAveragePayment(any())).thenThrow(TException.class);
@ -145,6 +155,7 @@ class AnalyticsTest extends AbstractKeycloakOpenIdAsWiremockConfig {
.andDo(print())
.andExpect(status().is5xxServerError());
verify(vortigonClient, times(1)).getShopsIds(any(), any());
verify(tokenKeeperClient, times(1)).authenticate(any(), any());
verify(orgManagerClient, times(1)).getUserContext(any());
verify(bouncerClient, times(1)).judge(any(), any());
verify(analyticsClient, times(1)).getAveragePayment(any());

View File

@ -1,11 +1,12 @@
package dev.vality.anapi.v2;
import dev.vality.anapi.v2.config.AbstractKeycloakOpenIdAsWiremockConfig;
import dev.vality.anapi.v2.config.AbstractConfig;
import dev.vality.anapi.v2.testutil.OpenApiUtil;
import dev.vality.bouncer.decisions.ArbiterSrv;
import dev.vality.damsel.vortigon.VortigonServiceSrv;
import dev.vality.orgmanagement.AuthContextProviderSrv;
import dev.vality.reporter.ReportingSrv;
import dev.vality.token.keeper.TokenAuthenticatorSrv;
import lombok.SneakyThrows;
import org.apache.thrift.TException;
import org.junit.jupiter.api.AfterEach;
@ -21,13 +22,14 @@ import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.List;
import static dev.vality.anapi.v2.testutil.MagistaUtil.createContextFragment;
import static dev.vality.anapi.v2.testutil.MagistaUtil.createJudgementAllowed;
import static dev.vality.anapi.v2.testutil.BouncerUtil.createContextFragment;
import static dev.vality.anapi.v2.testutil.BouncerUtil.createJudgementAllowed;
import static dev.vality.anapi.v2.testutil.OpenApiUtil.getReportsRequiredParams;
import static dev.vality.anapi.v2.testutil.RandomUtil.randomInt;
import static dev.vality.anapi.v2.testutil.RandomUtil.randomIntegerAsString;
import static dev.vality.anapi.v2.testutil.ReporterUtil.createReport;
import static dev.vality.anapi.v2.testutil.ReporterUtil.createSearchReportsResponse;
import static dev.vality.anapi.v2.testutil.TokenKeeperUtil.createAuthData;
import static java.util.UUID.randomUUID;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ -37,7 +39,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
class ReportsTest extends AbstractKeycloakOpenIdAsWiremockConfig {
class ReportsTest extends AbstractConfig {
@MockBean
public VortigonServiceSrv.Iface vortigonClient;
@ -47,6 +49,8 @@ class ReportsTest extends AbstractKeycloakOpenIdAsWiremockConfig {
public ArbiterSrv.Iface bouncerClient;
@MockBean
private ReportingSrv.Iface reporterClient;
@MockBean
public TokenAuthenticatorSrv.Iface tokenKeeperClient;
@Autowired
private MockMvc mvc;
@ -58,7 +62,8 @@ class ReportsTest extends AbstractKeycloakOpenIdAsWiremockConfig {
@BeforeEach
public void init() {
mocks = MockitoAnnotations.openMocks(this);
preparedMocks = new Object[] {reporterClient, vortigonClient, orgManagerClient, bouncerClient};
preparedMocks = new Object[] {reporterClient, vortigonClient, orgManagerClient,
bouncerClient, tokenKeeperClient};
}
@AfterEach
@ -72,6 +77,7 @@ class ReportsTest extends AbstractKeycloakOpenIdAsWiremockConfig {
void cancelReportRequestSuccess() {
when(orgManagerClient.getUserContext(any())).thenReturn(createContextFragment());
when(bouncerClient.judge(any(), any())).thenReturn(createJudgementAllowed());
when(tokenKeeperClient.authenticate(any(), any())).thenReturn(createAuthData(generateSimpleJwt()));
int reportId = randomInt(1, 1000);
mvc.perform(post("/lk/v2/reports/{reportId}/cancel", reportId)
.header("Authorization", "Bearer " + generateSimpleJwt())
@ -84,6 +90,7 @@ class ReportsTest extends AbstractKeycloakOpenIdAsWiremockConfig {
.andExpect(status().isAccepted())
.andExpect(jsonPath("$").doesNotExist());
verify(orgManagerClient, times(1)).getUserContext(any());
verify(tokenKeeperClient, times(1)).authenticate(any(), any());
verify(bouncerClient, times(1)).judge(any(), any());
verify(reporterClient, times(1)).cancelReport(reportId);
}
@ -93,6 +100,7 @@ class ReportsTest extends AbstractKeycloakOpenIdAsWiremockConfig {
void cancelReportRequestServerUnavailable() {
when(orgManagerClient.getUserContext(any())).thenReturn(createContextFragment());
when(bouncerClient.judge(any(), any())).thenReturn(createJudgementAllowed());
when(tokenKeeperClient.authenticate(any(), any())).thenReturn(createAuthData(generateSimpleJwt()));
int reportId = randomInt(1, 1000);
doThrow(new TException()).when(reporterClient).cancelReport(reportId);
mvc.perform(post("/lk/v2/reports/{reportId}/cancel", reportId)
@ -106,6 +114,7 @@ class ReportsTest extends AbstractKeycloakOpenIdAsWiremockConfig {
.andExpect(status().isInternalServerError())
.andExpect(jsonPath("$").doesNotExist());
verify(orgManagerClient, times(1)).getUserContext(any());
verify(tokenKeeperClient, times(1)).authenticate(any(), any());
verify(bouncerClient, times(1)).judge(any(), any());
verify(reporterClient, times(1)).cancelReport(reportId);
}
@ -115,6 +124,7 @@ class ReportsTest extends AbstractKeycloakOpenIdAsWiremockConfig {
void createReportRequestSuccess() {
when(orgManagerClient.getUserContext(any())).thenReturn(createContextFragment());
when(bouncerClient.judge(any(), any())).thenReturn(createJudgementAllowed());
when(tokenKeeperClient.authenticate(any(), any())).thenReturn(createAuthData(generateSimpleJwt()));
long reportId = randomInt(1, 1000);
when(reporterClient.createReport(any(), any())).thenReturn(reportId);
when(reporterClient.getReport(reportId)).thenReturn(createReport(reportId));
@ -129,6 +139,7 @@ class ReportsTest extends AbstractKeycloakOpenIdAsWiremockConfig {
.andExpect(status().isCreated())
.andExpect(jsonPath("$").exists());
verify(orgManagerClient, times(1)).getUserContext(any());
verify(tokenKeeperClient, times(1)).authenticate(any(), any());
verify(bouncerClient, times(1)).judge(any(), any());
verify(reporterClient, times(1)).createReport(any(), any());
verify(reporterClient, times(1)).getReport(reportId);
@ -139,6 +150,7 @@ class ReportsTest extends AbstractKeycloakOpenIdAsWiremockConfig {
void downloadUrlRequestSuccess() {
when(orgManagerClient.getUserContext(any())).thenReturn(createContextFragment());
when(bouncerClient.judge(any(), any())).thenReturn(createJudgementAllowed());
when(tokenKeeperClient.authenticate(any(), any())).thenReturn(createAuthData(generateSimpleJwt()));
String reportId = randomIntegerAsString(1, 1000);
String fileId = randomIntegerAsString(1, 1000);
when(reporterClient.generatePresignedUrl(eq(fileId), any())).thenReturn("www.google.ru");
@ -153,6 +165,7 @@ class ReportsTest extends AbstractKeycloakOpenIdAsWiremockConfig {
.andExpect(status().isOk())
.andExpect(jsonPath("$").exists());
verify(orgManagerClient, times(1)).getUserContext(any());
verify(tokenKeeperClient, times(1)).authenticate(any(), any());
verify(bouncerClient, times(1)).judge(any(), any());
verify(reporterClient, times(1)).generatePresignedUrl(eq(fileId), notNull());
}
@ -162,6 +175,7 @@ class ReportsTest extends AbstractKeycloakOpenIdAsWiremockConfig {
void getReportRequestSuccess() {
when(orgManagerClient.getUserContext(any())).thenReturn(createContextFragment());
when(bouncerClient.judge(any(), any())).thenReturn(createJudgementAllowed());
when(tokenKeeperClient.authenticate(any(), any())).thenReturn(createAuthData(generateSimpleJwt()));
long reportId = randomInt(1, 1000);
when(reporterClient.getReport(reportId)).thenReturn(createReport(reportId));
mvc.perform(get("/lk/v2/reports/{reportID}", reportId)
@ -175,6 +189,7 @@ class ReportsTest extends AbstractKeycloakOpenIdAsWiremockConfig {
.andExpect(status().isOk())
.andExpect(jsonPath("$").exists());
verify(orgManagerClient, times(1)).getUserContext(any());
verify(tokenKeeperClient, times(1)).authenticate(any(), any());
verify(bouncerClient, times(1)).judge(any(), any());
verify(reporterClient, times(1)).getReport(reportId);
}
@ -183,6 +198,7 @@ class ReportsTest extends AbstractKeycloakOpenIdAsWiremockConfig {
@SneakyThrows
void getSearchReportsRequestSuccess() {
when(vortigonClient.getShopsIds(any(), any())).thenReturn(List.of("1", "2", "3"));
when(tokenKeeperClient.authenticate(any(), any())).thenReturn(createAuthData(generateSimpleJwt()));
when(orgManagerClient.getUserContext(any())).thenReturn(createContextFragment());
when(bouncerClient.judge(any(), any())).thenReturn(createJudgementAllowed());
when(reporterClient.getReports(any())).thenReturn(createSearchReportsResponse());
@ -197,6 +213,7 @@ class ReportsTest extends AbstractKeycloakOpenIdAsWiremockConfig {
.andExpect(status().isOk())
.andExpect(jsonPath("$").exists());
verify(vortigonClient, times(1)).getShopsIds(any(), any());
verify(tokenKeeperClient, times(1)).authenticate(any(), any());
verify(orgManagerClient, times(1)).getUserContext(any());
verify(bouncerClient, times(1)).judge(any(), any());
verify(reporterClient, times(1)).getReports(notNull());

View File

@ -1,6 +1,6 @@
package dev.vality.anapi.v2;
import dev.vality.anapi.v2.config.AbstractKeycloakOpenIdAsWiremockConfig;
import dev.vality.anapi.v2.config.AbstractConfig;
import dev.vality.anapi.v2.model.DefaultLogicError;
import dev.vality.anapi.v2.testutil.MagistaUtil;
import dev.vality.anapi.v2.testutil.OpenApiUtil;
@ -8,6 +8,7 @@ import dev.vality.bouncer.decisions.ArbiterSrv;
import dev.vality.damsel.vortigon.VortigonServiceSrv;
import dev.vality.magista.MerchantStatisticsServiceSrv;
import dev.vality.orgmanagement.AuthContextProviderSrv;
import dev.vality.token.keeper.TokenAuthenticatorSrv;
import lombok.SneakyThrows;
import org.apache.thrift.TException;
import org.junit.jupiter.api.AfterEach;
@ -24,8 +25,9 @@ import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.List;
import static dev.vality.anapi.v2.testutil.MagistaUtil.createContextFragment;
import static dev.vality.anapi.v2.testutil.MagistaUtil.createJudgementAllowed;
import static dev.vality.anapi.v2.testutil.BouncerUtil.createContextFragment;
import static dev.vality.anapi.v2.testutil.BouncerUtil.createJudgementAllowed;
import static dev.vality.anapi.v2.testutil.TokenKeeperUtil.createAuthData;
import static java.util.UUID.randomUUID;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ -35,7 +37,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
class SearchChargebacksTest extends AbstractKeycloakOpenIdAsWiremockConfig {
class SearchChargebacksTest extends AbstractConfig {
@MockBean
public MerchantStatisticsServiceSrv.Iface magistaClient;
@ -45,6 +47,8 @@ class SearchChargebacksTest extends AbstractKeycloakOpenIdAsWiremockConfig {
public AuthContextProviderSrv.Iface orgManagerClient;
@MockBean
public ArbiterSrv.Iface bouncerClient;
@MockBean
public TokenAuthenticatorSrv.Iface tokenKeeperClient;
@Autowired
private MockMvc mvc;
@ -56,7 +60,7 @@ class SearchChargebacksTest extends AbstractKeycloakOpenIdAsWiremockConfig {
@BeforeEach
public void init() {
mocks = MockitoAnnotations.openMocks(this);
preparedMocks = new Object[]{magistaClient, vortigonClient, orgManagerClient, bouncerClient};
preparedMocks = new Object[]{magistaClient, vortigonClient, orgManagerClient, bouncerClient, tokenKeeperClient};
}
@AfterEach
@ -69,6 +73,7 @@ class SearchChargebacksTest extends AbstractKeycloakOpenIdAsWiremockConfig {
@SneakyThrows
void searchChargebacksRequiredParamsRequestSuccess() {
when(vortigonClient.getShopsIds(any(), any())).thenReturn(List.of("1", "2", "3"));
when(tokenKeeperClient.authenticate(any(), any())).thenReturn(createAuthData(generateSimpleJwt()));
when(orgManagerClient.getUserContext(any())).thenReturn(createContextFragment());
when(bouncerClient.judge(any(), any())).thenReturn(createJudgementAllowed());
when(magistaClient.searchChargebacks(any())).thenReturn(MagistaUtil.createSearchChargebackRequiredResponse());
@ -83,6 +88,7 @@ class SearchChargebacksTest extends AbstractKeycloakOpenIdAsWiremockConfig {
.andExpect(status().is2xxSuccessful())
.andExpect(jsonPath("$").exists());
verify(vortigonClient, times(1)).getShopsIds(any(), any());
verify(tokenKeeperClient, times(1)).authenticate(any(), any());
verify(orgManagerClient, times(1)).getUserContext(any());
verify(bouncerClient, times(1)).judge(any(), any());
verify(magistaClient, times(1)).searchChargebacks(any());
@ -92,6 +98,7 @@ class SearchChargebacksTest extends AbstractKeycloakOpenIdAsWiremockConfig {
@SneakyThrows
void searchChargebacksAllParamsRequestSuccess() {
when(vortigonClient.getShopsIds(any(), any())).thenReturn(List.of("1", "2", "3"));
when(tokenKeeperClient.authenticate(any(), any())).thenReturn(createAuthData(generateSimpleJwt()));
when(orgManagerClient.getUserContext(any())).thenReturn(createContextFragment());
when(bouncerClient.judge(any(), any())).thenReturn(createJudgementAllowed());
when(magistaClient.searchChargebacks(any())).thenReturn(MagistaUtil.createSearchChargebackAllResponse());
@ -106,6 +113,7 @@ class SearchChargebacksTest extends AbstractKeycloakOpenIdAsWiremockConfig {
.andExpect(status().is2xxSuccessful())
.andExpect(jsonPath("$").exists());
verify(vortigonClient, times(1)).getShopsIds(any(), any());
verify(tokenKeeperClient, times(1)).authenticate(any(), any());
verify(orgManagerClient, times(1)).getUserContext(any());
verify(bouncerClient, times(1)).judge(any(), any());
verify(magistaClient, times(1)).searchChargebacks(any());
@ -133,6 +141,7 @@ class SearchChargebacksTest extends AbstractKeycloakOpenIdAsWiremockConfig {
@SneakyThrows
void searchChargebacksRequestMagistaUnavailable() {
when(vortigonClient.getShopsIds(any(), any())).thenReturn(List.of("1", "2", "3"));
when(tokenKeeperClient.authenticate(any(), any())).thenReturn(createAuthData(generateSimpleJwt()));
when(orgManagerClient.getUserContext(any())).thenReturn(createContextFragment());
when(bouncerClient.judge(any(), any())).thenReturn(createJudgementAllowed());
when(magistaClient.searchPayments(any())).thenThrow(TException.class);
@ -146,6 +155,7 @@ class SearchChargebacksTest extends AbstractKeycloakOpenIdAsWiremockConfig {
.andDo(print())
.andExpect(status().is5xxServerError());
verify(vortigonClient, times(1)).getShopsIds(any(), any());
verify(tokenKeeperClient, times(1)).authenticate(any(), any());
verify(orgManagerClient, times(1)).getUserContext(any());
verify(bouncerClient, times(1)).judge(any(), any());
verify(magistaClient, times(1)).searchChargebacks(any());

View File

@ -1,6 +1,6 @@
package dev.vality.anapi.v2;
import dev.vality.anapi.v2.config.AbstractKeycloakOpenIdAsWiremockConfig;
import dev.vality.anapi.v2.config.AbstractConfig;
import dev.vality.anapi.v2.model.DefaultLogicError;
import dev.vality.anapi.v2.testutil.MagistaUtil;
import dev.vality.anapi.v2.testutil.OpenApiUtil;
@ -8,6 +8,7 @@ import dev.vality.bouncer.decisions.ArbiterSrv;
import dev.vality.damsel.vortigon.VortigonServiceSrv;
import dev.vality.magista.MerchantStatisticsServiceSrv;
import dev.vality.orgmanagement.AuthContextProviderSrv;
import dev.vality.token.keeper.TokenAuthenticatorSrv;
import lombok.SneakyThrows;
import org.apache.thrift.TException;
import org.junit.jupiter.api.AfterEach;
@ -24,8 +25,9 @@ import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.List;
import static dev.vality.anapi.v2.testutil.MagistaUtil.createContextFragment;
import static dev.vality.anapi.v2.testutil.MagistaUtil.createJudgementAllowed;
import static dev.vality.anapi.v2.testutil.BouncerUtil.createContextFragment;
import static dev.vality.anapi.v2.testutil.BouncerUtil.createJudgementAllowed;
import static dev.vality.anapi.v2.testutil.TokenKeeperUtil.createAuthData;
import static java.util.UUID.randomUUID;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ -34,7 +36,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
class SearchInvoiceTemplatesTest extends AbstractKeycloakOpenIdAsWiremockConfig {
class SearchInvoiceTemplatesTest extends AbstractConfig {
@MockBean
public MerchantStatisticsServiceSrv.Iface magistaClient;
@ -44,6 +46,8 @@ class SearchInvoiceTemplatesTest extends AbstractKeycloakOpenIdAsWiremockConfig
public AuthContextProviderSrv.Iface orgManagerClient;
@MockBean
public ArbiterSrv.Iface bouncerClient;
@MockBean
public TokenAuthenticatorSrv.Iface tokenKeeperClient;
@Autowired
private MockMvc mvc;
@ -55,7 +59,7 @@ class SearchInvoiceTemplatesTest extends AbstractKeycloakOpenIdAsWiremockConfig
@BeforeEach
public void init() {
mocks = MockitoAnnotations.openMocks(this);
preparedMocks = new Object[]{magistaClient, vortigonClient, orgManagerClient, bouncerClient};
preparedMocks = new Object[]{magistaClient, vortigonClient, orgManagerClient, bouncerClient, tokenKeeperClient};
}
@AfterEach
@ -68,6 +72,7 @@ class SearchInvoiceTemplatesTest extends AbstractKeycloakOpenIdAsWiremockConfig
@SneakyThrows
void searchInvoiceTemplatesRequiredParamsRequestSuccess() {
when(vortigonClient.getShopsIds(any(), any())).thenReturn(List.of("1", "2", "3"));
when(tokenKeeperClient.authenticate(any(), any())).thenReturn(createAuthData(generateSimpleJwt()));
when(orgManagerClient.getUserContext(any())).thenReturn(createContextFragment());
when(bouncerClient.judge(any(), any())).thenReturn(createJudgementAllowed());
when(magistaClient.searchInvoiceTemplates(any())).thenReturn(
@ -83,6 +88,7 @@ class SearchInvoiceTemplatesTest extends AbstractKeycloakOpenIdAsWiremockConfig
.andExpect(status().is2xxSuccessful())
.andExpect(jsonPath("$").exists());
verify(vortigonClient, times(1)).getShopsIds(any(), any());
verify(tokenKeeperClient, times(1)).authenticate(any(), any());
verify(orgManagerClient, times(1)).getUserContext(any());
verify(bouncerClient, times(1)).judge(any(), any());
verify(magistaClient, times(1)).searchInvoiceTemplates(any());
@ -92,6 +98,7 @@ class SearchInvoiceTemplatesTest extends AbstractKeycloakOpenIdAsWiremockConfig
@SneakyThrows
void searchInvoiceTemplatesAllParamsRequestSuccess() {
when(vortigonClient.getShopsIds(any(), any())).thenReturn(List.of("1", "2", "3"));
when(tokenKeeperClient.authenticate(any(), any())).thenReturn(createAuthData(generateSimpleJwt()));
when(orgManagerClient.getUserContext(any())).thenReturn(createContextFragment());
when(bouncerClient.judge(any(), any())).thenReturn(createJudgementAllowed());
when(magistaClient.searchInvoiceTemplates(any())).thenReturn(
@ -107,6 +114,7 @@ class SearchInvoiceTemplatesTest extends AbstractKeycloakOpenIdAsWiremockConfig
.andExpect(status().is2xxSuccessful())
.andExpect(jsonPath("$").exists());
verify(vortigonClient, times(1)).getShopsIds(any(), any());
verify(tokenKeeperClient, times(1)).authenticate(any(), any());
verify(orgManagerClient, times(1)).getUserContext(any());
verify(bouncerClient, times(1)).judge(any(), any());
verify(magistaClient, times(1)).searchInvoiceTemplates(any());
@ -134,6 +142,7 @@ class SearchInvoiceTemplatesTest extends AbstractKeycloakOpenIdAsWiremockConfig
@SneakyThrows
void searchInvoiceTemplatesRequestMagistaUnavailable() {
when(vortigonClient.getShopsIds(any(), any())).thenReturn(List.of("1", "2", "3"));
when(tokenKeeperClient.authenticate(any(), any())).thenReturn(createAuthData(generateSimpleJwt()));
when(orgManagerClient.getUserContext(any())).thenReturn(createContextFragment());
when(bouncerClient.judge(any(), any())).thenReturn(createJudgementAllowed());
when(magistaClient.searchInvoiceTemplates(any())).thenThrow(TException.class);
@ -147,6 +156,7 @@ class SearchInvoiceTemplatesTest extends AbstractKeycloakOpenIdAsWiremockConfig
.andDo(print())
.andExpect(status().is5xxServerError());
verify(vortigonClient, times(1)).getShopsIds(any(), any());
verify(tokenKeeperClient, times(1)).authenticate(any(), any());
verify(orgManagerClient, times(1)).getUserContext(any());
verify(bouncerClient, times(1)).judge(any(), any());
verify(magistaClient, times(1)).searchInvoiceTemplates(any());

View File

@ -1,6 +1,6 @@
package dev.vality.anapi.v2;
import dev.vality.anapi.v2.config.AbstractKeycloakOpenIdAsWiremockConfig;
import dev.vality.anapi.v2.config.AbstractConfig;
import dev.vality.anapi.v2.model.DefaultLogicError;
import dev.vality.anapi.v2.testutil.MagistaUtil;
import dev.vality.anapi.v2.testutil.OpenApiUtil;
@ -8,6 +8,7 @@ import dev.vality.bouncer.decisions.ArbiterSrv;
import dev.vality.damsel.vortigon.VortigonServiceSrv;
import dev.vality.magista.MerchantStatisticsServiceSrv;
import dev.vality.orgmanagement.AuthContextProviderSrv;
import dev.vality.token.keeper.TokenAuthenticatorSrv;
import lombok.SneakyThrows;
import org.apache.thrift.TException;
import org.junit.jupiter.api.AfterEach;
@ -24,8 +25,9 @@ import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.List;
import static dev.vality.anapi.v2.testutil.MagistaUtil.createContextFragment;
import static dev.vality.anapi.v2.testutil.MagistaUtil.createJudgementAllowed;
import static dev.vality.anapi.v2.testutil.BouncerUtil.createContextFragment;
import static dev.vality.anapi.v2.testutil.BouncerUtil.createJudgementAllowed;
import static dev.vality.anapi.v2.testutil.TokenKeeperUtil.createAuthData;
import static java.util.UUID.randomUUID;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ -34,7 +36,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
class SearchInvoicesTest extends AbstractKeycloakOpenIdAsWiremockConfig {
class SearchInvoicesTest extends AbstractConfig {
@MockBean
public MerchantStatisticsServiceSrv.Iface magistaClient;
@ -43,6 +45,8 @@ class SearchInvoicesTest extends AbstractKeycloakOpenIdAsWiremockConfig {
@MockBean
public AuthContextProviderSrv.Iface orgManagerClient;
@MockBean
public TokenAuthenticatorSrv.Iface tokenKeeperClient;
@MockBean
public ArbiterSrv.Iface bouncerClient;
@Autowired
@ -55,7 +59,7 @@ class SearchInvoicesTest extends AbstractKeycloakOpenIdAsWiremockConfig {
@BeforeEach
public void init() {
mocks = MockitoAnnotations.openMocks(this);
preparedMocks = new Object[]{magistaClient, vortigonClient, orgManagerClient, bouncerClient};
preparedMocks = new Object[]{magistaClient, vortigonClient, orgManagerClient, bouncerClient, tokenKeeperClient};
}
@AfterEach
@ -68,6 +72,7 @@ class SearchInvoicesTest extends AbstractKeycloakOpenIdAsWiremockConfig {
@SneakyThrows
void searchInvoicesRequiredParamsRequestSuccess() {
when(vortigonClient.getShopsIds(any(), any())).thenReturn(List.of("1", "2", "3"));
when(tokenKeeperClient.authenticate(any(), any())).thenReturn(createAuthData(generateSimpleJwt()));
when(orgManagerClient.getUserContext(any())).thenReturn(createContextFragment());
when(bouncerClient.judge(any(), any())).thenReturn(createJudgementAllowed());
when(magistaClient.searchInvoices(any())).thenReturn(MagistaUtil.createSearchInvoiceRequiredResponse());
@ -82,6 +87,7 @@ class SearchInvoicesTest extends AbstractKeycloakOpenIdAsWiremockConfig {
.andExpect(status().is2xxSuccessful())
.andExpect(jsonPath("$").exists());
verify(vortigonClient, times(1)).getShopsIds(any(), any());
verify(tokenKeeperClient, times(1)).authenticate(any(), any());
verify(orgManagerClient, times(1)).getUserContext(any());
verify(bouncerClient, times(1)).judge(any(), any());
verify(magistaClient, times(1)).searchInvoices(any());
@ -91,6 +97,7 @@ class SearchInvoicesTest extends AbstractKeycloakOpenIdAsWiremockConfig {
@SneakyThrows
void searchInvoicesAllParamsRequestSuccess() {
when(vortigonClient.getShopsIds(any(), any())).thenReturn(List.of("1", "2", "3"));
when(tokenKeeperClient.authenticate(any(), any())).thenReturn(createAuthData(generateSimpleJwt()));
when(orgManagerClient.getUserContext(any())).thenReturn(createContextFragment());
when(bouncerClient.judge(any(), any())).thenReturn(createJudgementAllowed());
when(magistaClient.searchInvoices(any())).thenReturn(MagistaUtil.createSearchInvoiceAllResponse());
@ -105,6 +112,7 @@ class SearchInvoicesTest extends AbstractKeycloakOpenIdAsWiremockConfig {
.andExpect(status().is2xxSuccessful())
.andExpect(jsonPath("$").exists());
verify(vortigonClient, times(1)).getShopsIds(any(), any());
verify(tokenKeeperClient, times(1)).authenticate(any(), any());
verify(orgManagerClient, times(1)).getUserContext(any());
verify(bouncerClient, times(1)).judge(any(), any());
verify(magistaClient, times(1)).searchInvoices(any());
@ -132,6 +140,7 @@ class SearchInvoicesTest extends AbstractKeycloakOpenIdAsWiremockConfig {
@SneakyThrows
void searchInvoicesRequestMagistaUnavailable() {
when(vortigonClient.getShopsIds(any(), any())).thenReturn(List.of("1", "2", "3"));
when(tokenKeeperClient.authenticate(any(), any())).thenReturn(createAuthData(generateSimpleJwt()));
when(orgManagerClient.getUserContext(any())).thenReturn(createContextFragment());
when(bouncerClient.judge(any(), any())).thenReturn(createJudgementAllowed());
when(magistaClient.searchInvoices(any())).thenThrow(TException.class);
@ -145,6 +154,7 @@ class SearchInvoicesTest extends AbstractKeycloakOpenIdAsWiremockConfig {
.andDo(print())
.andExpect(status().is5xxServerError());
verify(vortigonClient, times(1)).getShopsIds(any(), any());
verify(tokenKeeperClient, times(1)).authenticate(any(), any());
verify(orgManagerClient, times(1)).getUserContext(any());
verify(bouncerClient, times(1)).judge(any(), any());
verify(magistaClient, times(1)).searchInvoices(any());

View File

@ -1,6 +1,6 @@
package dev.vality.anapi.v2;
import dev.vality.anapi.v2.config.AbstractKeycloakOpenIdAsWiremockConfig;
import dev.vality.anapi.v2.config.AbstractConfig;
import dev.vality.anapi.v2.model.DefaultLogicError;
import dev.vality.anapi.v2.testutil.MagistaUtil;
import dev.vality.anapi.v2.testutil.OpenApiUtil;
@ -8,6 +8,7 @@ import dev.vality.bouncer.decisions.ArbiterSrv;
import dev.vality.damsel.vortigon.VortigonServiceSrv;
import dev.vality.magista.MerchantStatisticsServiceSrv;
import dev.vality.orgmanagement.AuthContextProviderSrv;
import dev.vality.token.keeper.TokenAuthenticatorSrv;
import lombok.SneakyThrows;
import org.apache.thrift.TException;
import org.junit.jupiter.api.AfterEach;
@ -24,8 +25,9 @@ import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.List;
import static dev.vality.anapi.v2.testutil.MagistaUtil.createContextFragment;
import static dev.vality.anapi.v2.testutil.MagistaUtil.createJudgementAllowed;
import static dev.vality.anapi.v2.testutil.BouncerUtil.createContextFragment;
import static dev.vality.anapi.v2.testutil.BouncerUtil.createJudgementAllowed;
import static dev.vality.anapi.v2.testutil.TokenKeeperUtil.createAuthData;
import static java.util.UUID.randomUUID;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ -34,7 +36,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
class SearchPaymentsTest extends AbstractKeycloakOpenIdAsWiremockConfig {
class SearchPaymentsTest extends AbstractConfig {
@MockBean
public MerchantStatisticsServiceSrv.Iface magistaClient;
@ -44,6 +46,8 @@ class SearchPaymentsTest extends AbstractKeycloakOpenIdAsWiremockConfig {
public AuthContextProviderSrv.Iface orgManagerClient;
@MockBean
public ArbiterSrv.Iface bouncerClient;
@MockBean
public TokenAuthenticatorSrv.Iface tokenKeeperClient;
@Autowired
private MockMvc mvc;
@ -55,7 +59,7 @@ class SearchPaymentsTest extends AbstractKeycloakOpenIdAsWiremockConfig {
@BeforeEach
public void init() {
mocks = MockitoAnnotations.openMocks(this);
preparedMocks = new Object[]{magistaClient, vortigonClient, orgManagerClient, bouncerClient};
preparedMocks = new Object[]{magistaClient, vortigonClient, orgManagerClient, bouncerClient, tokenKeeperClient};
}
@AfterEach
@ -68,6 +72,7 @@ class SearchPaymentsTest extends AbstractKeycloakOpenIdAsWiremockConfig {
@SneakyThrows
void searchPaymentsRequiredParamsRequestSuccess() {
when(vortigonClient.getShopsIds(any(), any())).thenReturn(List.of("1", "2", "3"));
when(tokenKeeperClient.authenticate(any(), any())).thenReturn(createAuthData(generateSimpleJwt()));
when(orgManagerClient.getUserContext(any())).thenReturn(createContextFragment());
when(bouncerClient.judge(any(), any())).thenReturn(createJudgementAllowed());
when(magistaClient.searchPayments(any())).thenReturn(MagistaUtil.createSearchPaymentRequiredResponse());
@ -82,6 +87,7 @@ class SearchPaymentsTest extends AbstractKeycloakOpenIdAsWiremockConfig {
.andExpect(status().is2xxSuccessful())
.andExpect(jsonPath("$").exists());
verify(vortigonClient, times(1)).getShopsIds(any(), any());
verify(tokenKeeperClient, times(1)).authenticate(any(), any());
verify(orgManagerClient, times(1)).getUserContext(any());
verify(bouncerClient, times(1)).judge(any(), any());
verify(magistaClient, times(1)).searchPayments(any());
@ -91,6 +97,7 @@ class SearchPaymentsTest extends AbstractKeycloakOpenIdAsWiremockConfig {
@SneakyThrows
void searchPaymentsAllParamsRequestSuccess() {
when(vortigonClient.getShopsIds(any(), any())).thenReturn(List.of("1", "2", "3"));
when(tokenKeeperClient.authenticate(any(), any())).thenReturn(createAuthData(generateSimpleJwt()));
when(orgManagerClient.getUserContext(any())).thenReturn(createContextFragment());
when(bouncerClient.judge(any(), any())).thenReturn(createJudgementAllowed());
when(magistaClient.searchPayments(any())).thenReturn(MagistaUtil.createSearchPaymentAllResponse());
@ -105,6 +112,7 @@ class SearchPaymentsTest extends AbstractKeycloakOpenIdAsWiremockConfig {
.andExpect(status().is2xxSuccessful())
.andExpect(jsonPath("$").exists());
verify(vortigonClient, times(1)).getShopsIds(any(), any());
verify(tokenKeeperClient, times(1)).authenticate(any(), any());
verify(orgManagerClient, times(1)).getUserContext(any());
verify(bouncerClient, times(1)).judge(any(), any());
verify(magistaClient, times(1)).searchPayments(any());
@ -132,6 +140,7 @@ class SearchPaymentsTest extends AbstractKeycloakOpenIdAsWiremockConfig {
@SneakyThrows
void searchPaymentsRequestMagistaUnavailable() {
when(vortigonClient.getShopsIds(any(), any())).thenReturn(List.of("1", "2", "3"));
when(tokenKeeperClient.authenticate(any(), any())).thenReturn(createAuthData(generateSimpleJwt()));
when(orgManagerClient.getUserContext(any())).thenReturn(createContextFragment());
when(bouncerClient.judge(any(), any())).thenReturn(createJudgementAllowed());
when(magistaClient.searchPayments(any())).thenThrow(TException.class);
@ -145,6 +154,7 @@ class SearchPaymentsTest extends AbstractKeycloakOpenIdAsWiremockConfig {
.andDo(print())
.andExpect(status().is5xxServerError());
verify(vortigonClient, times(1)).getShopsIds(any(), any());
verify(tokenKeeperClient, times(1)).authenticate(any(), any());
verify(orgManagerClient, times(1)).getUserContext(any());
verify(bouncerClient, times(1)).judge(any(), any());
verify(magistaClient, times(1)).searchPayments(any());

View File

@ -1,6 +1,6 @@
package dev.vality.anapi.v2;
import dev.vality.anapi.v2.config.AbstractKeycloakOpenIdAsWiremockConfig;
import dev.vality.anapi.v2.config.AbstractConfig;
import dev.vality.anapi.v2.model.DefaultLogicError;
import dev.vality.anapi.v2.testutil.MagistaUtil;
import dev.vality.anapi.v2.testutil.OpenApiUtil;
@ -8,6 +8,7 @@ import dev.vality.bouncer.decisions.ArbiterSrv;
import dev.vality.damsel.vortigon.VortigonServiceSrv;
import dev.vality.magista.MerchantStatisticsServiceSrv;
import dev.vality.orgmanagement.AuthContextProviderSrv;
import dev.vality.token.keeper.TokenAuthenticatorSrv;
import lombok.SneakyThrows;
import org.apache.thrift.TException;
import org.junit.jupiter.api.AfterEach;
@ -24,8 +25,9 @@ import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.List;
import static dev.vality.anapi.v2.testutil.MagistaUtil.createContextFragment;
import static dev.vality.anapi.v2.testutil.MagistaUtil.createJudgementAllowed;
import static dev.vality.anapi.v2.testutil.BouncerUtil.createContextFragment;
import static dev.vality.anapi.v2.testutil.BouncerUtil.createJudgementAllowed;
import static dev.vality.anapi.v2.testutil.TokenKeeperUtil.createAuthData;
import static java.util.UUID.randomUUID;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ -34,7 +36,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
class SearchPayoutsTest extends AbstractKeycloakOpenIdAsWiremockConfig {
class SearchPayoutsTest extends AbstractConfig {
@MockBean
public MerchantStatisticsServiceSrv.Iface magistaClient;
@ -44,6 +46,8 @@ class SearchPayoutsTest extends AbstractKeycloakOpenIdAsWiremockConfig {
public AuthContextProviderSrv.Iface orgManagerClient;
@MockBean
public ArbiterSrv.Iface bouncerClient;
@MockBean
public TokenAuthenticatorSrv.Iface tokenKeeperClient;
@Autowired
private MockMvc mvc;
@ -55,7 +59,7 @@ class SearchPayoutsTest extends AbstractKeycloakOpenIdAsWiremockConfig {
@BeforeEach
public void init() {
mocks = MockitoAnnotations.openMocks(this);
preparedMocks = new Object[]{magistaClient, vortigonClient, orgManagerClient, bouncerClient};
preparedMocks = new Object[]{magistaClient, vortigonClient, orgManagerClient, bouncerClient, tokenKeeperClient};
}
@AfterEach
@ -68,6 +72,7 @@ class SearchPayoutsTest extends AbstractKeycloakOpenIdAsWiremockConfig {
@SneakyThrows
void searchPayoutsRequiredParamsRequestSuccess() {
when(vortigonClient.getShopsIds(any(), any())).thenReturn(List.of("1", "2", "3"));
when(tokenKeeperClient.authenticate(any(), any())).thenReturn(createAuthData(generateSimpleJwt()));
when(orgManagerClient.getUserContext(any())).thenReturn(createContextFragment());
when(bouncerClient.judge(any(), any())).thenReturn(createJudgementAllowed());
when(magistaClient.searchPayouts(any())).thenReturn(MagistaUtil.createSearchPayoutRequiredResponse());
@ -82,6 +87,7 @@ class SearchPayoutsTest extends AbstractKeycloakOpenIdAsWiremockConfig {
.andExpect(status().is2xxSuccessful())
.andExpect(jsonPath("$").exists());
verify(vortigonClient, times(1)).getShopsIds(any(), any());
verify(tokenKeeperClient, times(1)).authenticate(any(), any());
verify(orgManagerClient, times(1)).getUserContext(any());
verify(bouncerClient, times(1)).judge(any(), any());
verify(magistaClient, times(1)).searchPayouts(any());
@ -91,6 +97,7 @@ class SearchPayoutsTest extends AbstractKeycloakOpenIdAsWiremockConfig {
@SneakyThrows
void searchPayoutsAllParamsRequestSuccess() {
when(vortigonClient.getShopsIds(any(), any())).thenReturn(List.of("1", "2", "3"));
when(tokenKeeperClient.authenticate(any(), any())).thenReturn(createAuthData(generateSimpleJwt()));
when(orgManagerClient.getUserContext(any())).thenReturn(createContextFragment());
when(bouncerClient.judge(any(), any())).thenReturn(createJudgementAllowed());
when(magistaClient.searchPayouts(any())).thenReturn(MagistaUtil.createSearchPayoutAllResponse());
@ -105,6 +112,7 @@ class SearchPayoutsTest extends AbstractKeycloakOpenIdAsWiremockConfig {
.andExpect(status().is2xxSuccessful())
.andExpect(jsonPath("$").exists());
verify(vortigonClient, times(1)).getShopsIds(any(), any());
verify(tokenKeeperClient, times(1)).authenticate(any(), any());
verify(orgManagerClient, times(1)).getUserContext(any());
verify(bouncerClient, times(1)).judge(any(), any());
verify(magistaClient, times(1)).searchPayouts(any());
@ -132,6 +140,7 @@ class SearchPayoutsTest extends AbstractKeycloakOpenIdAsWiremockConfig {
@SneakyThrows
void searchPayoutsRequestMagistaUnavailable() {
when(vortigonClient.getShopsIds(any(), any())).thenReturn(List.of("1", "2", "3"));
when(tokenKeeperClient.authenticate(any(), any())).thenReturn(createAuthData(generateSimpleJwt()));
when(orgManagerClient.getUserContext(any())).thenReturn(createContextFragment());
when(bouncerClient.judge(any(), any())).thenReturn(createJudgementAllowed());
when(magistaClient.searchPayouts(any())).thenThrow(TException.class);
@ -145,6 +154,7 @@ class SearchPayoutsTest extends AbstractKeycloakOpenIdAsWiremockConfig {
.andDo(print())
.andExpect(status().is5xxServerError());
verify(vortigonClient, times(1)).getShopsIds(any(), any());
verify(tokenKeeperClient, times(1)).authenticate(any(), any());
verify(orgManagerClient, times(1)).getUserContext(any());
verify(bouncerClient, times(1)).judge(any(), any());
verify(magistaClient, times(1)).searchPayouts(any());

View File

@ -1,6 +1,6 @@
package dev.vality.anapi.v2;
import dev.vality.anapi.v2.config.AbstractKeycloakOpenIdAsWiremockConfig;
import dev.vality.anapi.v2.config.AbstractConfig;
import dev.vality.anapi.v2.model.DefaultLogicError;
import dev.vality.anapi.v2.testutil.MagistaUtil;
import dev.vality.anapi.v2.testutil.OpenApiUtil;
@ -8,6 +8,7 @@ import dev.vality.bouncer.decisions.ArbiterSrv;
import dev.vality.damsel.vortigon.VortigonServiceSrv;
import dev.vality.magista.MerchantStatisticsServiceSrv;
import dev.vality.orgmanagement.AuthContextProviderSrv;
import dev.vality.token.keeper.TokenAuthenticatorSrv;
import lombok.SneakyThrows;
import org.apache.thrift.TException;
import org.junit.jupiter.api.AfterEach;
@ -24,8 +25,9 @@ import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.List;
import static dev.vality.anapi.v2.testutil.MagistaUtil.createContextFragment;
import static dev.vality.anapi.v2.testutil.MagistaUtil.createJudgementAllowed;
import static dev.vality.anapi.v2.testutil.BouncerUtil.createContextFragment;
import static dev.vality.anapi.v2.testutil.BouncerUtil.createJudgementAllowed;
import static dev.vality.anapi.v2.testutil.TokenKeeperUtil.createAuthData;
import static java.util.UUID.randomUUID;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ -34,7 +36,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
class SearchRefundsTest extends AbstractKeycloakOpenIdAsWiremockConfig {
class SearchRefundsTest extends AbstractConfig {
@MockBean
public MerchantStatisticsServiceSrv.Iface magistaClient;
@ -44,6 +46,8 @@ class SearchRefundsTest extends AbstractKeycloakOpenIdAsWiremockConfig {
public AuthContextProviderSrv.Iface orgManagerClient;
@MockBean
public ArbiterSrv.Iface bouncerClient;
@MockBean
public TokenAuthenticatorSrv.Iface tokenKeeperClient;
@Autowired
private MockMvc mvc;
@ -55,7 +59,7 @@ class SearchRefundsTest extends AbstractKeycloakOpenIdAsWiremockConfig {
@BeforeEach
public void init() {
mocks = MockitoAnnotations.openMocks(this);
preparedMocks = new Object[]{magistaClient, vortigonClient, orgManagerClient, bouncerClient};
preparedMocks = new Object[]{magistaClient, vortigonClient, orgManagerClient, bouncerClient, tokenKeeperClient};
}
@AfterEach
@ -68,6 +72,7 @@ class SearchRefundsTest extends AbstractKeycloakOpenIdAsWiremockConfig {
@SneakyThrows
void searchRefundsRequiredParamsRequestSuccess() {
when(vortigonClient.getShopsIds(any(), any())).thenReturn(List.of("1", "2", "3"));
when(tokenKeeperClient.authenticate(any(), any())).thenReturn(createAuthData(generateSimpleJwt()));
when(orgManagerClient.getUserContext(any())).thenReturn(createContextFragment());
when(bouncerClient.judge(any(), any())).thenReturn(createJudgementAllowed());
when(magistaClient.searchRefunds(any())).thenReturn(MagistaUtil.createSearchRefundRequiredResponse());
@ -82,6 +87,7 @@ class SearchRefundsTest extends AbstractKeycloakOpenIdAsWiremockConfig {
.andExpect(status().is2xxSuccessful())
.andExpect(jsonPath("$").exists());
verify(vortigonClient, times(1)).getShopsIds(any(), any());
verify(tokenKeeperClient, times(1)).authenticate(any(), any());
verify(orgManagerClient, times(1)).getUserContext(any());
verify(bouncerClient, times(1)).judge(any(), any());
verify(magistaClient, times(1)).searchRefunds(any());
@ -91,6 +97,7 @@ class SearchRefundsTest extends AbstractKeycloakOpenIdAsWiremockConfig {
@SneakyThrows
void searchRefundsAllParamsRequestSuccess() {
when(vortigonClient.getShopsIds(any(), any())).thenReturn(List.of("1", "2", "3"));
when(tokenKeeperClient.authenticate(any(), any())).thenReturn(createAuthData(generateSimpleJwt()));
when(orgManagerClient.getUserContext(any())).thenReturn(createContextFragment());
when(bouncerClient.judge(any(), any())).thenReturn(createJudgementAllowed());
when(magistaClient.searchRefunds(any())).thenReturn(MagistaUtil.createSearchRefundAllResponse());
@ -105,6 +112,7 @@ class SearchRefundsTest extends AbstractKeycloakOpenIdAsWiremockConfig {
.andExpect(status().is2xxSuccessful())
.andExpect(jsonPath("$").exists());
verify(vortigonClient, times(1)).getShopsIds(any(), any());
verify(tokenKeeperClient, times(1)).authenticate(any(), any());
verify(orgManagerClient, times(1)).getUserContext(any());
verify(bouncerClient, times(1)).judge(any(), any());
verify(magistaClient, times(1)).searchRefunds(any());
@ -132,6 +140,7 @@ class SearchRefundsTest extends AbstractKeycloakOpenIdAsWiremockConfig {
@SneakyThrows
void searchRefundsRequestMagistaUnavailable() {
when(vortigonClient.getShopsIds(any(), any())).thenReturn(List.of("1", "2", "3"));
when(tokenKeeperClient.authenticate(any(), any())).thenReturn(createAuthData(generateSimpleJwt()));
when(orgManagerClient.getUserContext(any())).thenReturn(createContextFragment());
when(bouncerClient.judge(any(), any())).thenReturn(createJudgementAllowed());
when(magistaClient.searchRefunds(any())).thenThrow(TException.class);
@ -145,6 +154,7 @@ class SearchRefundsTest extends AbstractKeycloakOpenIdAsWiremockConfig {
.andDo(print())
.andExpect(status().is5xxServerError());
verify(vortigonClient, times(1)).getShopsIds(any(), any());
verify(tokenKeeperClient, times(1)).authenticate(any(), any());
verify(orgManagerClient, times(1)).getUserContext(any());
verify(bouncerClient, times(1)).judge(any(), any());
verify(magistaClient, times(1)).searchRefunds(any());

View File

@ -10,9 +10,7 @@ import org.springframework.context.annotation.Configuration;
public class KeycloakOpenIdTestConfiguration {
@Bean
public KeycloakOpenIdStub keycloakOpenIdStub(@Value("${keycloak.auth-server-url}") String keycloakAuthServerUrl,
@Value("${keycloak.realm}") String keycloakRealm,
JwtTokenBuilder jwtTokenBuilder) {
return new KeycloakOpenIdStub(keycloakAuthServerUrl, keycloakRealm, jwtTokenBuilder);
public KeycloakOpenIdStub keycloakOpenIdStub(JwtTokenBuilder jwtTokenBuilder) {
return new KeycloakOpenIdStub(jwtTokenBuilder);
}
}

View File

@ -1,55 +1,16 @@
package dev.vality.anapi.v2.auth.utils;
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 JwtTokenBuilder jwtTokenBuilder;
public KeycloakOpenIdStub(String keycloakAuthServerUrl, String keycloakRealm, JwtTokenBuilder jwtTokenBuilder) {
this.keycloakRealm = keycloakRealm;
public KeycloakOpenIdStub(JwtTokenBuilder jwtTokenBuilder) {
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" +
"}";
}
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)
)
);
this.issuer = "test/realms/test";
}
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);
}
}

View File

@ -2,7 +2,6 @@ package dev.vality.anapi.v2.config;
import dev.vality.anapi.v2.AnapiV2Application;
import dev.vality.anapi.v2.auth.utils.KeycloakOpenIdStub;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
@ -13,22 +12,15 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
@SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = {AnapiV2Application.class},
properties = {
"wiremock.server.baseUrl=http://localhost:${wiremock.server.port}",
"keycloak.auth-server-url=${wiremock.server.baseUrl}/auth"})
properties = {"wiremock.server.baseUrl=http://localhost:${wiremock.server.port}"})
@AutoConfigureMockMvc
@AutoConfigureWireMock(port = 0)
@ExtendWith(SpringExtension.class)
public abstract class AbstractKeycloakOpenIdAsWiremockConfig {
public abstract class AbstractConfig {
@Autowired
private KeycloakOpenIdStub keycloakOpenIdStub;
@BeforeAll
public static void setUp(@Autowired KeycloakOpenIdStub keycloakOpenIdStub) throws Exception {
keycloakOpenIdStub.givenStub();
}
protected String generateSimpleJwt() {
return keycloakOpenIdStub.generateJwt();
}

View File

@ -1,14 +1,14 @@
package dev.vality.anapi.v2.controller;
import dev.vality.anapi.v2.config.AbstractKeycloakOpenIdAsWiremockConfig;
import dev.vality.anapi.v2.config.AbstractConfig;
import dev.vality.anapi.v2.converter.magista.request.ParamsToRefundSearchQueryConverter;
import dev.vality.anapi.v2.exception.BadRequestException;
import dev.vality.anapi.v2.model.DefaultLogicError;
import dev.vality.anapi.v2.testutil.OpenApiUtil;
import dev.vality.anapi.v2.testutil.MagistaUtil;
import dev.vality.bouncer.decisions.ArbiterSrv;
import dev.vality.damsel.vortigon.VortigonServiceSrv;
import dev.vality.orgmanagement.AuthContextProviderSrv;
import dev.vality.token.keeper.TokenAuthenticatorSrv;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -24,6 +24,9 @@ import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.List;
import static dev.vality.anapi.v2.testutil.BouncerUtil.createContextFragment;
import static dev.vality.anapi.v2.testutil.BouncerUtil.createJudgementAllowed;
import static dev.vality.anapi.v2.testutil.TokenKeeperUtil.createAuthData;
import static java.util.UUID.randomUUID;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ -32,7 +35,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
class ErrorControllerTest extends AbstractKeycloakOpenIdAsWiremockConfig {
class ErrorControllerTest extends AbstractConfig {
@Autowired
private MockMvc mockMvc;
@ -44,6 +47,8 @@ class ErrorControllerTest extends AbstractKeycloakOpenIdAsWiremockConfig {
public AuthContextProviderSrv.Iface orgManagerClient;
@MockBean
public ArbiterSrv.Iface bouncerClient;
@MockBean
public TokenAuthenticatorSrv.Iface tokenKeeperClient;
private AutoCloseable mocks;
@ -52,7 +57,8 @@ class ErrorControllerTest extends AbstractKeycloakOpenIdAsWiremockConfig {
@BeforeEach
public void init() {
mocks = MockitoAnnotations.openMocks(this);
preparedMocks = new Object[]{refundSearchConverter, vortigonClient, orgManagerClient, bouncerClient};
preparedMocks = new Object[]{refundSearchConverter, vortigonClient, orgManagerClient,
bouncerClient, tokenKeeperClient};
}
@AfterEach
@ -83,8 +89,9 @@ class ErrorControllerTest extends AbstractKeycloakOpenIdAsWiremockConfig {
@Test
void testBadRequestException() throws Exception {
when(vortigonClient.getShopsIds(any(), any())).thenReturn(List.of("1", "2", "3"));
when(orgManagerClient.getUserContext(any())).thenReturn(MagistaUtil.createContextFragment());
when(bouncerClient.judge(any(), any())).thenReturn(MagistaUtil.createJudgementAllowed());
when(tokenKeeperClient.authenticate(any(), any())).thenReturn(createAuthData(generateSimpleJwt()));
when(orgManagerClient.getUserContext(any())).thenReturn(createContextFragment());
when(bouncerClient.judge(any(), any())).thenReturn(createJudgementAllowed());
String message = "Error!";
Mockito.doThrow(new BadRequestException(message)).when(refundSearchConverter)
.convert(any(), any(), any(), any(),
@ -106,6 +113,7 @@ class ErrorControllerTest extends AbstractKeycloakOpenIdAsWiremockConfig {
.andExpect(jsonPath("$.code").value(DefaultLogicError.CodeEnum.INVALIDREQUEST.getValue()))
.andExpect(jsonPath("$.message").value(message));
verify(vortigonClient, times(1)).getShopsIds(any(), any());
verify(tokenKeeperClient, times(1)).authenticate(any(), any());
verify(orgManagerClient, times(1)).getUserContext(any());
verify(bouncerClient, times(1)).judge(any(), any());
verify(refundSearchConverter, times(1))
@ -153,8 +161,9 @@ class ErrorControllerTest extends AbstractKeycloakOpenIdAsWiremockConfig {
@Test
void testInternalException() throws Exception {
when(vortigonClient.getShopsIds(any(), any())).thenReturn(List.of("1", "2", "3"));
when(orgManagerClient.getUserContext(any())).thenReturn(MagistaUtil.createContextFragment());
when(bouncerClient.judge(any(), any())).thenReturn(MagistaUtil.createJudgementAllowed());
when(tokenKeeperClient.authenticate(any(), any())).thenReturn(createAuthData(generateSimpleJwt()));
when(orgManagerClient.getUserContext(any())).thenReturn(createContextFragment());
when(bouncerClient.judge(any(), any())).thenReturn(createJudgementAllowed());
doThrow(new RuntimeException()).when(refundSearchConverter)
.convert(any(), any(), any(), any(),
any(), any(), any(), any(),
@ -174,6 +183,7 @@ class ErrorControllerTest extends AbstractKeycloakOpenIdAsWiremockConfig {
.andExpect(status().isInternalServerError())
.andExpect(jsonPath("$").doesNotExist());
verify(vortigonClient, times(1)).getShopsIds(any(), any());
verify(tokenKeeperClient, times(1)).authenticate(any(), any());
verify(orgManagerClient, times(1)).getUserContext(any());
verify(bouncerClient, times(1)).judge(any(), any());
verify(refundSearchConverter, times(1))
@ -184,16 +194,19 @@ class ErrorControllerTest extends AbstractKeycloakOpenIdAsWiremockConfig {
@Test
void testUnauthorizedException() throws Exception {
when(vortigonClient.getShopsIds(any(), any())).thenReturn(List.of("1", "2", "3"));
mockMvc.perform(
get("/lk/v2/refunds")
.header("X-Request-ID", randomUUID())
.header("X-Request-Deadline", "fail")
.header("X-Request-Deadline",
Instant.now().plus(1, ChronoUnit.DAYS).toString())
.params(OpenApiUtil.getSearchRequiredParams())
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(""))
.andDo(print())
.andExpect(status().isUnauthorized())
.andExpect(status().isForbidden())
.andExpect(jsonPath("$").doesNotExist());
verify(vortigonClient, times(1)).getShopsIds(any(), any());
}
}

View File

@ -0,0 +1,26 @@
package dev.vality.anapi.v2.testutil;
import dev.vality.bouncer.ctx.ContextFragment;
import dev.vality.bouncer.decisions.Judgement;
import dev.vality.bouncer.decisions.Resolution;
import dev.vality.bouncer.decisions.ResolutionAllowed;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;
import org.apache.thrift.TSerializer;
@UtilityClass
public class BouncerUtil {
@SneakyThrows
public static ContextFragment createContextFragment() {
ContextFragment fragment = DamselUtil.fillRequiredTBaseObject(new ContextFragment(), ContextFragment.class);
fragment.setContent(new TSerializer().serialize(new dev.vality.bouncer.context.v1.ContextFragment()));
return fragment;
}
public static Judgement createJudgementAllowed() {
Resolution resolution = new Resolution();
resolution.setAllowed(new ResolutionAllowed());
return new Judgement().setResolution(resolution);
}
}

View File

@ -1,9 +1,5 @@
package dev.vality.anapi.v2.testutil;
import dev.vality.bouncer.ctx.ContextFragment;
import dev.vality.bouncer.decisions.Judgement;
import dev.vality.bouncer.decisions.Resolution;
import dev.vality.bouncer.decisions.ResolutionAllowed;
import dev.vality.damsel.base.Content;
import dev.vality.damsel.domain.InvoicePaymentRefundStatus;
import dev.vality.damsel.domain.InvoicePaymentStatus;
@ -15,9 +11,7 @@ import dev.vality.magista.InvoicePaymentFlowHold;
import dev.vality.magista.InvoicePaymentFlowInstant;
import dev.vality.magista.Payer;
import dev.vality.magista.*;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;
import org.apache.thrift.TSerializer;
import java.time.Instant;
import java.util.HashMap;
@ -169,19 +163,6 @@ public class MagistaUtil {
return DamselUtil.fillRequiredTBaseObject(new StatInvoiceTemplateResponse(), StatInvoiceTemplateResponse.class);
}
@SneakyThrows
public static ContextFragment createContextFragment() {
ContextFragment fragment = DamselUtil.fillRequiredTBaseObject(new ContextFragment(), ContextFragment.class);
fragment.setContent(new TSerializer().serialize(new dev.vality.bouncer.context.v1.ContextFragment()));
return fragment;
}
public static Judgement createJudgementAllowed() {
Resolution resolution = new Resolution();
resolution.setAllowed(new ResolutionAllowed());
return new Judgement().setResolution(resolution);
}
public static InvoicePaymentFlow createInvoicePaymentFlowHold() {
return InvoicePaymentFlow.hold(
DamselUtil.fillRequiredTBaseObject(new InvoicePaymentFlowHold(), InvoicePaymentFlowHold.class));

View File

@ -0,0 +1,24 @@
package dev.vality.anapi.v2.testutil;
import dev.vality.token.keeper.AuthData;
import dev.vality.token.keeper.AuthDataStatus;
import lombok.experimental.UtilityClass;
import java.util.UUID;
import static dev.vality.anapi.v2.testutil.BouncerUtil.createContextFragment;
@UtilityClass
public class TokenKeeperUtil {
public static AuthData createAuthData(String token) {
return new AuthData()
.setId(UUID.randomUUID().toString())
.setAuthority(UUID.randomUUID().toString())
.setToken(token)
.setStatus(AuthDataStatus.active)
.setContext(createContextFragment());
}
}