mirror of
https://github.com/valitydev/wachter.git
synced 2024-11-06 00:35:24 +00:00
OPS-175: del bouncer, add authorization in wachter (#17)
This commit is contained in:
parent
34aec8c0ef
commit
a51554b84f
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
package dev.vality.wachter.exeptions;
|
||||
|
||||
public class OrgManagerException extends WachterException {
|
||||
|
||||
public OrgManagerException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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()));
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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/
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
@ -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());
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user