OPS-175: del bouncer, add authorization in wachter (#17)

This commit is contained in:
malkoas 2022-09-06 19:18:41 +03:00 committed by GitHub
parent 34aec8c0ef
commit a51554b84f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 249 additions and 341 deletions

View File

@ -1,6 +1,5 @@
package dev.vality.wachter.client;
import dev.vality.wachter.config.properties.WachterProperties;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.apache.http.HttpResponse;
@ -20,8 +19,8 @@ public class WachterClient {
private final HttpClient httpclient;
@SneakyThrows
public byte[] send(HttpServletRequest request, byte[] contentData, WachterProperties.Service service) {
HttpPost httppost = new HttpPost(service.getUrl());
public byte[] send(HttpServletRequest request, byte[] contentData, String url) {
HttpPost httppost = new HttpPost(url);
setHeader(request, httppost);
httppost.setEntity(new ByteArrayEntity(contentData));
HttpResponse response = httpclient.execute(httppost);

View File

@ -1,7 +1,5 @@
package dev.vality.wachter.config;
import dev.vality.bouncer.decisions.ArbiterSrv;
import dev.vality.woody.thrift.impl.http.THSpawnClientBuilder;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.client.HttpClient;
@ -12,23 +10,10 @@ import org.apache.http.protocol.HttpContext;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import java.io.IOException;
@Configuration
public class ApplicationConfig {
@Bean
public ArbiterSrv.Iface bouncerClient(
@Value("${bouncer.url}") Resource resource,
@Value("${bouncer.networkTimeout}") int networkTimeout) throws IOException {
return new THSpawnClientBuilder()
.withNetworkTimeout(networkTimeout)
.withAddress(resource.getURI())
.build(ArbiterSrv.Iface.class);
}
@Bean
public HttpClient httpclient(@Value("${http-client.connectTimeout}") int connectTimeout,
@Value("${http-client.connectionRequestTimeout}") int connectionRequestTimeout,

View File

@ -1,28 +0,0 @@
package dev.vality.wachter.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 = "bouncer")
public class BouncerProperties {
@NotEmpty
private String deploymentId;
@NotEmpty
private String authMethod;
@NotEmpty
private String realm;
@NotEmpty
private String ruleSetId;
@NotEmpty
private String contextFragmentId;
}

View File

@ -4,6 +4,7 @@ import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedCaseInsensitiveMap;
import org.springframework.validation.annotation.Validated;
import java.util.Map;
@ -16,7 +17,7 @@ import java.util.Map;
public class WachterProperties {
private String serviceHeader;
private Map<String, Service> services;
private Map<String, Service> services = new LinkedCaseInsensitiveMap<>();
@Getter
@Setter

View File

@ -1,12 +0,0 @@
package dev.vality.wachter.exeptions;
public class BouncerException extends WachterException {
public BouncerException(String s) {
super(s);
}
public BouncerException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -1,8 +0,0 @@
package dev.vality.wachter.exeptions;
public class OrgManagerException extends WachterException {
public OrgManagerException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -1,19 +1,17 @@
package dev.vality.wachter.security;
import dev.vality.wachter.config.properties.WachterProperties;
import lombok.Builder;
import lombok.Data;
import java.util.List;
@Builder
@Data
public class AccessData {
private final String operationId;
private final String partyId;
private final long tokenExpirationSec;
private final String tokenId;
private final String userId;
private final String methodName;
private final String userEmail;
private final WachterProperties.Service service;
private final List<String> tokenRoles;
private final String serviceName;
}

View File

@ -1,8 +1,5 @@
package dev.vality.wachter.security;
import dev.vality.wachter.exeptions.AuthorizationException;
import dev.vality.wachter.exeptions.BouncerException;
import dev.vality.wachter.service.BouncerService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
@ -13,37 +10,24 @@ import org.springframework.stereotype.Service;
@Service
public class AccessService {
private final BouncerService bouncerService;
@Value("${bouncer.auth.enabled}")
@Value("${wachter.auth.enabled}")
private boolean authEnabled;
private final RoleAccessService roleAccessService;
public void checkUserAccess(AccessData accessData) {
log.info("Check the {} rights to perform the operation {} in service {}",
accessData.getUserEmail(),
accessData.getOperationId(),
accessData.getService().getName());
var resolution = bouncerService.getResolution(accessData);
switch (resolution.getSetField()) {
case FORBIDDEN, RESTRICTED -> {
if (authEnabled) {
throw new AuthorizationException(
String.format("No rights for %s to perform %s in service %s",
accessData.getUserEmail(),
accessData.getOperationId(),
accessData.getService().getName()));
} else {
log.warn("No rights for {} to perform {} in service {}",
accessData.getUserEmail(),
accessData.getOperationId(),
accessData.getService().getName());
}
}
case ALLOWED -> log.info("Rights for {} to perform {} in service {} are allowed",
accessData.getMethodName(),
accessData.getServiceName());
if (authEnabled) {
roleAccessService.checkRolesAccess(accessData);
} else {
log.warn("Authorization disabled. Access check was not performed for user {} to method {} in service {}",
accessData.getUserEmail(),
accessData.getOperationId(),
accessData.getService().getName());
default -> throw new BouncerException(String.format("Resolution %s cannot be processed", resolution));
accessData.getMethodName(),
accessData.getServiceName());
}
}
}

View File

@ -1,79 +0,0 @@
package dev.vality.wachter.security;
import dev.vality.bouncer.base.Entity;
import dev.vality.bouncer.context.v1.*;
import dev.vality.bouncer.ctx.ContextFragmentType;
import dev.vality.bouncer.decisions.Context;
import dev.vality.wachter.config.properties.BouncerProperties;
import dev.vality.wachter.service.KeycloakService;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.thrift.TSerializer;
import org.springframework.stereotype.Component;
import java.time.Instant;
import java.util.HashSet;
import java.util.Set;
@Slf4j
@RequiredArgsConstructor
@Component
public class BouncerContextFactory {
private final BouncerProperties bouncerProperties;
private final KeycloakService keycloakService;
@SneakyThrows
public Context buildContext(AccessData accessData) {
var contextFragment = buildContextFragment(accessData);
var serializer = new TSerializer();
var fragment = new dev.vality.bouncer.ctx.ContextFragment()
.setType(ContextFragmentType.v1_thrift_binary)
.setContent(serializer.serialize(contextFragment));
var context = new Context();
context.putToFragments(bouncerProperties.getContextFragmentId(), fragment);
return context;
}
private ContextFragment buildContextFragment(AccessData accessData) {
var env = buildEnvironment();
var contextFragment = new ContextFragment();
contextFragment.setAuth(buildAuth(accessData))
.setEnv(env)
.setWachter(buildWachterContext(accessData));
log.debug("Context fragment to bouncer {}", contextFragment);
return contextFragment;
}
private Auth buildAuth(AccessData accessData) {
var auth = new Auth();
var resource = keycloakService.getAccessToken().getResourceAccess();
Set<ResourceAccess> access = new HashSet<>();
resource.forEach((id, roles) -> access.add(new ResourceAccess().setId(id).setRoles(roles.getRoles())));
Set<AuthScope> authScopeSet = new HashSet<>();
authScopeSet.add(new AuthScope()
.setParty(new Entity().setId(accessData.getPartyId())));
return auth
.setMethod(bouncerProperties.getAuthMethod())
.setExpiration(Instant.ofEpochSecond(accessData.getTokenExpirationSec()).toString())
.setToken(new Token()
.setAccess(access))
.setScope(authScopeSet);
}
private Environment buildEnvironment() {
var deployment = new Deployment()
.setId(bouncerProperties.getDeploymentId());
return new Environment()
.setDeployment(deployment)
.setNow(Instant.now().toString());
}
private ContextWachter buildWachterContext(AccessData accessData) {
return new ContextWachter()
.setOp(new WachterOperation()
.setId(accessData.getOperationId())
.setServiceName(accessData.getService().getName()));
}
}

View File

@ -0,0 +1,52 @@
package dev.vality.wachter.security;
import dev.vality.wachter.exeptions.AuthorizationException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Slf4j
@RequiredArgsConstructor
@Service
public class RoleAccessService {
private static final String ROLE_DELIMITER = ":";
public void checkRolesAccess(AccessData accessData) {
if (accessData.getTokenRoles().isEmpty()) {
throw new AuthorizationException(
String.format("User %s don't have roles", accessData.getUserEmail()));
}
for (String role : accessData.getTokenRoles()) {
if (role.equalsIgnoreCase(getServiceAndMethodName(accessData))) {
log.info("Rights allowed in service {} and method {} for user {}",
accessData.getServiceName(),
accessData.getMethodName(),
accessData.getUserEmail());
} else if (role.equalsIgnoreCase(getServiceName(accessData))) {
log.info("Rights allowed in all service {} for user {}",
accessData.getServiceName(),
accessData.getUserEmail());
} else {
throw new AuthorizationException(
String.format("User %s don't have access to %s in service %s",
accessData.getUserEmail(),
accessData.getMethodName(),
accessData.getServiceName()));
}
}
}
private String getServiceName(AccessData accessData) {
return accessData.getServiceName();
}
private String getServiceAndMethodName(AccessData accessData) {
return String.join(
ROLE_DELIMITER,
accessData.getServiceName(),
accessData.getMethodName());
}
}

View File

@ -1,37 +0,0 @@
package dev.vality.wachter.service;
import dev.vality.bouncer.decisions.ArbiterSrv;
import dev.vality.bouncer.decisions.Resolution;
import dev.vality.wachter.config.properties.BouncerProperties;
import dev.vality.wachter.exeptions.BouncerException;
import dev.vality.wachter.security.AccessData;
import dev.vality.wachter.security.BouncerContextFactory;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.thrift.TException;
import org.springframework.stereotype.Service;
@Slf4j
@Service
@RequiredArgsConstructor
public class BouncerService {
private final BouncerProperties bouncerProperties;
private final BouncerContextFactory bouncerContextFactory;
private final ArbiterSrv.Iface bouncerClient;
public Resolution getResolution(AccessData accessData) {
log.debug("Check access with bouncer context");
var context = bouncerContextFactory.buildContext(accessData);
log.debug("Built thrift context: {}", context);
try {
var judge = bouncerClient.judge(bouncerProperties.getRuleSetId(), context);
log.debug("Have judge: {}", judge);
var resolution = judge.getResolution();
log.debug("Resolution: {}", resolution);
return resolution;
} catch (TException e) {
throw new BouncerException("Error while call bouncer", e);
}
}
}

View File

@ -11,6 +11,8 @@ import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.List;
@RequiredArgsConstructor
@Service
@ -26,19 +28,18 @@ public class WachterService {
public byte[] process(HttpServletRequest request) {
byte[] contentData = getContentData(request);
var methodName = methodNameReaderService.getMethodName(contentData);
var partyID = keycloakService.getPartyId();
var token = keycloakService.getAccessToken();
var resources = token.getResourceAccess();
List<String> tokenRoles = new ArrayList<>();
resources.forEach((id, role) -> tokenRoles.addAll(role.getRoles()));
var service = serviceMapper.getService(request);
accessService.checkUserAccess(AccessData.builder()
.operationId(methodName)
.partyId(partyID)
.tokenExpirationSec(token.getExp())
.tokenId(token.getId())
.userId(token.getSubject())
.methodName(methodName)
.userEmail(token.getEmail())
.service(service)
.serviceName(service.getName())
.tokenRoles(tokenRoles)
.build());
return wachterClient.send(request, contentData, service);
return wachterClient.send(request, contentData, service.getUrl());
}
@SneakyThrows

View File

@ -34,41 +34,52 @@ info:
stage: dev
wachter:
auth:
enabled: true
serviceHeader: Service
services:
messages:
name: messages
url: http://localhost:8097/v1/messages
automaton:
name: automaton
url: http://localhost:8022/v1/automaton
repairer:
name: repairer
url: http://localhost:8022/v1/repair/withdrawal/session
claimManagement:
name: claimManagement
ClaimManagement:
name: ClaimManagement
url: http://localhost:8097/v1/cm
fistfulAdmin:
name: fistfulAdmin
url: http://localhost:8022/v1/admin
fistfulStatistics:
name: fistfulStatistics
url: http://localhost:8022/fistful/stat
fileStorage:
name: fileStorage
url: http://localhost:8022/file_storage
deanonimus:
name: deanonimus
url: http://localhost:8022/deanonimus
merchantStatistics:
name: merchantStatistics
url: http://localhost:8022/stat
paymentProcessing:
name: paymentProcessing
url: http://localhost:8022/v1/processing/invoicing
domain:
name: domain
DepositManagement:
name: DepositManagement
url: http://localhost:8022/v1/deposit
Domain:
name: Domain
url: http://localhost:8022/v1/domain/repository
DominantCache:
name: DominantCache
url: http://localhost:8022/v1/dominant/cache
FileStorage:
name: FileStorage
url: http://localhost:8022/file_storage
FistfulStatistics:
name: FistfulStatistics
url: http://localhost:8022/fistful/stat
MerchantStatistics:
name: MerchantStatistics
url: http://localhost:8022/v3/stat
Messages:
name: Messages
url: http://localhost:8097/v1/messages
Invoicing:
name: Invoicing
url: http://localhost:8097/v1/processing/invoicin
PartyManagement:
name: PartyManagement
url: http://localhost:8097/v1/processing/partymgmt
PayoutManagement:
name: PayoutManagement
url: http://localhost:8097/payout/management
RepairManagement:
name: repairManagement
url: http://localhost:8097/v1/repair
WalletManagement:
name: WalletManagement
url: http://localhost:8022/v1/wallet
WithdrawalManagement:
name: WithdrawalManagement
url: http://localhost:8022/v1/wallet
http-client:
@ -76,17 +87,6 @@ http-client:
connectionRequestTimeout: 10000
socketTimeout: 10000
bouncer:
url: http://localhost:8022/v1/arbiter
networkTimeout: 10000
deployment-id: production
auth-method: SessionToken
realm: external
rule-set-id: change_it
context-fragment-id: wachter
auth:
enabled: true
keycloak:
realm: internal
auth-server-url: http://keycloak:8080/auth/

View File

@ -29,7 +29,11 @@ public abstract class AbstractKeycloakOpenIdAsWiremockConfig {
keycloakOpenIdStub.givenStub();
}
protected String generateSimpleJwt() {
protected String generateSimpleJwtWithRoles() {
return keycloakOpenIdStub.generateJwt("messages", "messages:methodName");
}
protected String generateSimpleJwtWithoutRoles() {
return keycloakOpenIdStub.generateJwt();
}
}

View File

@ -1,11 +1,11 @@
package dev.vality.wachter.controller;
import dev.vality.bouncer.decisions.ArbiterSrv;
import dev.vality.wachter.config.AbstractKeycloakOpenIdAsWiremockConfig;
import dev.vality.wachter.exeptions.AuthorizationException;
import dev.vality.wachter.exeptions.WachterException;
import dev.vality.wachter.testutil.TMessageUtil;
import lombok.SneakyThrows;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.thrift.protocol.TProtocolFactory;
import org.junit.jupiter.api.AfterEach;
@ -20,12 +20,10 @@ import org.springframework.test.web.servlet.MockMvc;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import static dev.vality.wachter.testutil.ContextUtil.*;
import static java.util.UUID.randomUUID;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ -33,8 +31,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
@TestPropertySource(properties = {"auth.enabled=true"})
class ErrorControllerTest extends AbstractKeycloakOpenIdAsWiremockConfig {
@MockBean
public ArbiterSrv.Iface bouncerClient;
@MockBean
private HttpClient httpClient;
@ -52,7 +48,7 @@ class ErrorControllerTest extends AbstractKeycloakOpenIdAsWiremockConfig {
@BeforeEach
public void init() {
mocks = MockitoAnnotations.openMocks(this);
preparedMocks = new Object[]{httpClient, bouncerClient};
preparedMocks = new Object[]{httpClient};
}
@AfterEach
@ -61,13 +57,11 @@ class ErrorControllerTest extends AbstractKeycloakOpenIdAsWiremockConfig {
mocks.close();
}
@Test
@SneakyThrows
void requestJudgementRestricted() {
when(bouncerClient.judge(any(), any())).thenReturn(createJudgementRestricted());
void requestAccessDenied() {
mvc.perform(post("/wachter")
.header("Authorization", "Bearer " + generateSimpleJwt())
.header("Authorization", "Bearer " + generateSimpleJwtWithoutRoles())
.header("Service", "messages")
.header("X-Request-ID", randomUUID())
.header("X-Request-Deadline", Instant.now().plus(1, ChronoUnit.DAYS).toString())
@ -75,36 +69,15 @@ class ErrorControllerTest extends AbstractKeycloakOpenIdAsWiremockConfig {
.andDo(print())
.andExpect(status().is4xxClientError())
.andExpect(result -> assertTrue(result.getResolvedException() instanceof AuthorizationException))
.andExpect(result -> assertEquals("No rights for darkside-the-best@mail.com to " +
"perform methodName in service messages",
.andExpect(result -> assertEquals("User darkside-the-best@mail.com don't have roles",
result.getResolvedException().getMessage()));
verify(bouncerClient, times(1)).judge(any(), any());
}
@Test
@SneakyThrows
void requestJudgementForbidden() {
when(bouncerClient.judge(any(), any())).thenReturn(createJudgementForbidden());
mvc.perform(post("/wachter")
.header("Authorization", "Bearer " + generateSimpleJwt())
.header("Service", "messages")
.header("X-Request-ID", randomUUID())
.header("X-Request-Deadline", Instant.now().plus(1, ChronoUnit.DAYS).toString())
.content(TMessageUtil.createTMessage(protocolFactory)))
.andDo(print())
.andExpect(status().is4xxClientError())
.andExpect(result -> assertTrue(result.getResolvedException() instanceof AuthorizationException))
.andExpect(result -> assertEquals("No rights for darkside-the-best@mail.com to " +
"perform methodName in service messages",
result.getResolvedException().getMessage()));
verify(bouncerClient, times(1)).judge(any(), any());
}
@Test
@SneakyThrows
void requestWithoutServiceHeader() {
mvc.perform(post("/wachter")
.header("Authorization", "Bearer " + generateSimpleJwt())
.header("Authorization", "Bearer " + generateSimpleJwtWithRoles())
.header("X-Request-ID", randomUUID())
.header("X-Request-Deadline", Instant.now().plus(1, ChronoUnit.DAYS).toString())
.content(TMessageUtil.createTMessage(protocolFactory)))
@ -119,7 +92,7 @@ class ErrorControllerTest extends AbstractKeycloakOpenIdAsWiremockConfig {
@SneakyThrows
void requestWithWrongServiceHeader() {
mvc.perform(post("/wachter")
.header("Authorization", "Bearer " + generateSimpleJwt())
.header("Authorization", "Bearer " + generateSimpleJwtWithRoles())
.header("X-Request-ID", randomUUID())
.header("Service", "wrong")
.header("X-Request-Deadline", Instant.now().plus(1, ChronoUnit.DAYS).toString())
@ -130,4 +103,21 @@ class ErrorControllerTest extends AbstractKeycloakOpenIdAsWiremockConfig {
.andExpect(result -> assertEquals("Service \"wrong\" not found in configuration",
result.getResolvedException().getMessage()));
}
@Test
@SneakyThrows
void requestWithUnknownRoles() {
mvc.perform(post("/wachter")
.header("Authorization", "Bearer " + generateSimpleJwtWithRoles())
.header("X-Request-ID", randomUUID())
.header("Service", "invoicing")
.header("X-Request-Deadline", Instant.now().plus(1, ChronoUnit.DAYS).toString())
.content(TMessageUtil.createTMessage(protocolFactory)))
.andDo(print())
.andExpect(status().is4xxClientError())
.andExpect(result -> assertTrue(result.getResolvedException() instanceof AuthorizationException))
.andExpect(result -> assertEquals("User darkside-the-best@mail.com don't have access" +
" to methodName in service Invoicing",
result.getResolvedException().getMessage()));
}
}

View File

@ -0,0 +1,76 @@
package dev.vality.wachter.controller;
import dev.vality.wachter.config.AbstractKeycloakOpenIdAsWiremockConfig;
import dev.vality.wachter.testutil.TMessageUtil;
import lombok.SneakyThrows;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.entity.StringEntity;
import org.apache.thrift.protocol.TProtocolFactory;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.MockitoAnnotations;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.servlet.MockMvc;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import static java.util.UUID.randomUUID;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@TestPropertySource(properties = {"wachter.auth.enabled=false"})
class WachterControllerDisabledAuthTest extends AbstractKeycloakOpenIdAsWiremockConfig {
@MockBean
private HttpClient httpClient;
@MockBean
private HttpResponse httpResponse;
@Autowired
private MockMvc mvc;
@Autowired
private TProtocolFactory protocolFactory;
private AutoCloseable mocks;
private Object[] preparedMocks;
@BeforeEach
public void init() {
mocks = MockitoAnnotations.openMocks(this);
preparedMocks = new Object[]{httpClient};
}
@AfterEach
public void clean() throws Exception {
verifyNoMoreInteractions(preparedMocks);
mocks.close();
}
@Test
@SneakyThrows
void requestSuccess() {
when(httpResponse.getEntity()).thenReturn(new StringEntity(""));
when(httpClient.execute(any())).thenReturn(httpResponse);
mvc.perform(post("/wachter")
.header("Authorization", "Bearer " + generateSimpleJwtWithoutRoles())
.header("Service", "messages")
.header("X-Request-ID", randomUUID())
.header("X-Request-Deadline", Instant.now().plus(1, ChronoUnit.DAYS).toString())
.content(TMessageUtil.createTMessage(protocolFactory)))
.andDo(print())
.andExpect(status().is2xxSuccessful());
verify(httpClient, times(1)).execute(any());
}
}

View File

@ -1,6 +1,5 @@
package dev.vality.wachter.controller;
import dev.vality.bouncer.decisions.ArbiterSrv;
import dev.vality.wachter.config.AbstractKeycloakOpenIdAsWiremockConfig;
import dev.vality.wachter.testutil.TMessageUtil;
import lombok.SneakyThrows;
@ -19,7 +18,6 @@ import org.springframework.test.web.servlet.MockMvc;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import static dev.vality.wachter.testutil.ContextUtil.createJudgementAllowed;
import static java.util.UUID.randomUUID;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ -29,8 +27,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
class WachterControllerTest extends AbstractKeycloakOpenIdAsWiremockConfig {
@MockBean
public ArbiterSrv.Iface bouncerClient;
@MockBean
private HttpClient httpClient;
@MockBean
@ -50,7 +46,7 @@ class WachterControllerTest extends AbstractKeycloakOpenIdAsWiremockConfig {
@BeforeEach
public void init() {
mocks = MockitoAnnotations.openMocks(this);
preparedMocks = new Object[]{httpClient, bouncerClient};
preparedMocks = new Object[]{httpClient};
}
@AfterEach
@ -61,19 +57,33 @@ class WachterControllerTest extends AbstractKeycloakOpenIdAsWiremockConfig {
@Test
@SneakyThrows
void requestSuccess() {
when(bouncerClient.judge(any(), any())).thenReturn(createJudgementAllowed());
void requestSuccessWithServiceRole() {
when(httpResponse.getEntity()).thenReturn(new StringEntity(""));
when(httpClient.execute(any())).thenReturn(httpResponse);
mvc.perform(post("/wachter")
.header("Authorization", "Bearer " + generateSimpleJwt())
.header("Authorization", "Bearer " + generateSimpleJwtWithRoles())
.header("Service", "messages")
.header("X-Request-ID", randomUUID())
.header("X-Request-Deadline", Instant.now().plus(1, ChronoUnit.DAYS).toString())
.content(TMessageUtil.createTMessage(protocolFactory)))
.andDo(print())
.andExpect(status().is2xxSuccessful());
verify(httpClient, times(1)).execute(any());
}
@Test
@SneakyThrows
void requestSuccessWithMethodRole() {
when(httpResponse.getEntity()).thenReturn(new StringEntity(""));
when(httpClient.execute(any())).thenReturn(httpResponse);
mvc.perform(post("/wachter")
.header("Authorization", "Bearer " + generateSimpleJwtWithRoles())
.header("Service", "messages")
.header("X-Request-ID", randomUUID())
.header("X-Request-Deadline", Instant.now().plus(1, ChronoUnit.DAYS).toString())
.content(TMessageUtil.createTMessage(protocolFactory)))
.andDo(print())
.andExpect(status().is2xxSuccessful());
verify(bouncerClient, times(1)).judge(any(), any());
verify(httpClient, times(1)).execute(any());
}

View File

@ -1,7 +1,5 @@
package dev.vality.wachter.testutil;
import dev.vality.bouncer.ctx.ContextFragment;
import dev.vality.bouncer.decisions.*;
import dev.vality.geck.serializer.kit.mock.FieldHandler;
import dev.vality.geck.serializer.kit.mock.MockMode;
import dev.vality.geck.serializer.kit.mock.MockTBaseProcessor;
@ -9,7 +7,6 @@ import dev.vality.geck.serializer.kit.tbase.TBaseHandler;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;
import org.apache.thrift.TBase;
import org.apache.thrift.TSerializer;
import java.time.Instant;
import java.util.Map;
@ -33,29 +30,4 @@ public class ContextUtil {
public static <T extends TBase> T fillRequiredTBaseObject(T tbase, Class<T> type) {
return ContextUtil.mockRequiredTBaseProcessor.process(tbase, new TBaseHandler<>(type));
}
@SneakyThrows
public static ContextFragment createContextFragment() {
ContextFragment fragment = ContextUtil.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 Judgement createJudgementRestricted() {
Resolution resolution = new Resolution();
resolution.setRestricted(new ResolutionRestricted());
return new Judgement().setResolution(resolution);
}
public static Judgement createJudgementForbidden() {
Resolution resolution = new Resolution();
resolution.setForbidden(new ResolutionForbidden());
return new Judgement().setResolution(resolution);
}
}