From a51554b84f3a8a8343c779f44b279d48334fdba1 Mon Sep 17 00:00:00 2001 From: malkoas <41993717+malkoas@users.noreply.github.com> Date: Tue, 6 Sep 2022 19:18:41 +0300 Subject: [PATCH] OPS-175: del bouncer, add authorization in wachter (#17) --- .../vality/wachter/client/WachterClient.java | 5 +- .../wachter/config/ApplicationConfig.java | 15 ---- .../config/properties/BouncerProperties.java | 28 ------- .../config/properties/WachterProperties.java | 3 +- .../wachter/exeptions/BouncerException.java | 12 --- .../exeptions/OrgManagerException.java | 8 -- .../vality/wachter/security/AccessData.java | 12 ++- .../wachter/security/AccessService.java | 40 +++------ .../security/BouncerContextFactory.java | 79 ----------------- .../wachter/security/RoleAccessService.java | 52 ++++++++++++ .../wachter/service/BouncerService.java | 37 -------- .../wachter/service/WachterService.java | 17 ++-- src/main/resources/application.yml | 84 +++++++++---------- ...bstractKeycloakOpenIdAsWiremockConfig.java | 6 +- .../controller/ErrorControllerTest.java | 60 ++++++------- .../WachterControllerDisabledAuthTest.java | 76 +++++++++++++++++ .../controller/WachterControllerTest.java | 28 +++++-- .../vality/wachter/testutil/ContextUtil.java | 28 ------- 18 files changed, 249 insertions(+), 341 deletions(-) delete mode 100644 src/main/java/dev/vality/wachter/config/properties/BouncerProperties.java delete mode 100644 src/main/java/dev/vality/wachter/exeptions/BouncerException.java delete mode 100644 src/main/java/dev/vality/wachter/exeptions/OrgManagerException.java delete mode 100644 src/main/java/dev/vality/wachter/security/BouncerContextFactory.java create mode 100644 src/main/java/dev/vality/wachter/security/RoleAccessService.java delete mode 100644 src/main/java/dev/vality/wachter/service/BouncerService.java create mode 100644 src/test/java/dev/vality/wachter/controller/WachterControllerDisabledAuthTest.java diff --git a/src/main/java/dev/vality/wachter/client/WachterClient.java b/src/main/java/dev/vality/wachter/client/WachterClient.java index 6ec508e..2b62331 100644 --- a/src/main/java/dev/vality/wachter/client/WachterClient.java +++ b/src/main/java/dev/vality/wachter/client/WachterClient.java @@ -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); diff --git a/src/main/java/dev/vality/wachter/config/ApplicationConfig.java b/src/main/java/dev/vality/wachter/config/ApplicationConfig.java index 8cb223d..eabd3d9 100644 --- a/src/main/java/dev/vality/wachter/config/ApplicationConfig.java +++ b/src/main/java/dev/vality/wachter/config/ApplicationConfig.java @@ -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, diff --git a/src/main/java/dev/vality/wachter/config/properties/BouncerProperties.java b/src/main/java/dev/vality/wachter/config/properties/BouncerProperties.java deleted file mode 100644 index 52c59e0..0000000 --- a/src/main/java/dev/vality/wachter/config/properties/BouncerProperties.java +++ /dev/null @@ -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; -} diff --git a/src/main/java/dev/vality/wachter/config/properties/WachterProperties.java b/src/main/java/dev/vality/wachter/config/properties/WachterProperties.java index 6361cb3..80af146 100644 --- a/src/main/java/dev/vality/wachter/config/properties/WachterProperties.java +++ b/src/main/java/dev/vality/wachter/config/properties/WachterProperties.java @@ -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 services; + private Map services = new LinkedCaseInsensitiveMap<>(); @Getter @Setter diff --git a/src/main/java/dev/vality/wachter/exeptions/BouncerException.java b/src/main/java/dev/vality/wachter/exeptions/BouncerException.java deleted file mode 100644 index f8f9571..0000000 --- a/src/main/java/dev/vality/wachter/exeptions/BouncerException.java +++ /dev/null @@ -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); - } -} diff --git a/src/main/java/dev/vality/wachter/exeptions/OrgManagerException.java b/src/main/java/dev/vality/wachter/exeptions/OrgManagerException.java deleted file mode 100644 index d4e85f5..0000000 --- a/src/main/java/dev/vality/wachter/exeptions/OrgManagerException.java +++ /dev/null @@ -1,8 +0,0 @@ -package dev.vality.wachter.exeptions; - -public class OrgManagerException extends WachterException { - - public OrgManagerException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/src/main/java/dev/vality/wachter/security/AccessData.java b/src/main/java/dev/vality/wachter/security/AccessData.java index bda528e..f7d83b1 100644 --- a/src/main/java/dev/vality/wachter/security/AccessData.java +++ b/src/main/java/dev/vality/wachter/security/AccessData.java @@ -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 tokenRoles; + private final String serviceName; } diff --git a/src/main/java/dev/vality/wachter/security/AccessService.java b/src/main/java/dev/vality/wachter/security/AccessService.java index 1317b06..1fc504d 100644 --- a/src/main/java/dev/vality/wachter/security/AccessService.java +++ b/src/main/java/dev/vality/wachter/security/AccessService.java @@ -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()); } } + } diff --git a/src/main/java/dev/vality/wachter/security/BouncerContextFactory.java b/src/main/java/dev/vality/wachter/security/BouncerContextFactory.java deleted file mode 100644 index 53747bd..0000000 --- a/src/main/java/dev/vality/wachter/security/BouncerContextFactory.java +++ /dev/null @@ -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 access = new HashSet<>(); - resource.forEach((id, roles) -> access.add(new ResourceAccess().setId(id).setRoles(roles.getRoles()))); - Set 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())); - } -} diff --git a/src/main/java/dev/vality/wachter/security/RoleAccessService.java b/src/main/java/dev/vality/wachter/security/RoleAccessService.java new file mode 100644 index 0000000..5f9892b --- /dev/null +++ b/src/main/java/dev/vality/wachter/security/RoleAccessService.java @@ -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()); + } + +} diff --git a/src/main/java/dev/vality/wachter/service/BouncerService.java b/src/main/java/dev/vality/wachter/service/BouncerService.java deleted file mode 100644 index 03a93eb..0000000 --- a/src/main/java/dev/vality/wachter/service/BouncerService.java +++ /dev/null @@ -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); - } - } -} diff --git a/src/main/java/dev/vality/wachter/service/WachterService.java b/src/main/java/dev/vality/wachter/service/WachterService.java index ba3a122..9d72d7b 100644 --- a/src/main/java/dev/vality/wachter/service/WachterService.java +++ b/src/main/java/dev/vality/wachter/service/WachterService.java @@ -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 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 diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 2caea18..4eecb0a 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -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/ diff --git a/src/test/java/dev/vality/wachter/config/AbstractKeycloakOpenIdAsWiremockConfig.java b/src/test/java/dev/vality/wachter/config/AbstractKeycloakOpenIdAsWiremockConfig.java index 103f0a5..4bb1dbf 100644 --- a/src/test/java/dev/vality/wachter/config/AbstractKeycloakOpenIdAsWiremockConfig.java +++ b/src/test/java/dev/vality/wachter/config/AbstractKeycloakOpenIdAsWiremockConfig.java @@ -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(); } } diff --git a/src/test/java/dev/vality/wachter/controller/ErrorControllerTest.java b/src/test/java/dev/vality/wachter/controller/ErrorControllerTest.java index e0fdb26..b6b7c88 100644 --- a/src/test/java/dev/vality/wachter/controller/ErrorControllerTest.java +++ b/src/test/java/dev/vality/wachter/controller/ErrorControllerTest.java @@ -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())); + } } diff --git a/src/test/java/dev/vality/wachter/controller/WachterControllerDisabledAuthTest.java b/src/test/java/dev/vality/wachter/controller/WachterControllerDisabledAuthTest.java new file mode 100644 index 0000000..5640ab1 --- /dev/null +++ b/src/test/java/dev/vality/wachter/controller/WachterControllerDisabledAuthTest.java @@ -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()); + } + +} diff --git a/src/test/java/dev/vality/wachter/controller/WachterControllerTest.java b/src/test/java/dev/vality/wachter/controller/WachterControllerTest.java index 304ef17..c382182 100644 --- a/src/test/java/dev/vality/wachter/controller/WachterControllerTest.java +++ b/src/test/java/dev/vality/wachter/controller/WachterControllerTest.java @@ -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()); } diff --git a/src/test/java/dev/vality/wachter/testutil/ContextUtil.java b/src/test/java/dev/vality/wachter/testutil/ContextUtil.java index 6937f5e..d6d7114 100644 --- a/src/test/java/dev/vality/wachter/testutil/ContextUtil.java +++ b/src/test/java/dev/vality/wachter/testutil/ContextUtil.java @@ -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 fillRequiredTBaseObject(T tbase, Class 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); - } }