add KeycloakPermissionFilter 95/138995/10
authorStefan Dierichs <s.dierichs@telekom.de>
Thu, 19 Sep 2024 13:39:02 +0000 (15:39 +0200)
committerStefan Dierichs <s.dierichs@telekom.de>
Wed, 23 Oct 2024 09:29:33 +0000 (11:29 +0200)
Issue-ID: PORTALNG-117

Change-Id: If3ba5969dbbdd6fbf7488e7f46831d6f9ff5550c
Signed-off-by: Stefan Dierichs <s.dierichs@telekom.de>
14 files changed:
app/src/main/resources/application-access-control.yml [deleted file]
app/src/main/resources/application.yml
app/src/test/java/org/onap/portalng/bff/BaseIntegrationTest.java
app/src/test/java/org/onap/portalng/bff/rbac/RoleBaseAccessIntegrationTest.java [new file with mode: 0644]
app/src/test/resources/application-development.yml
app/src/test/resources/application.yml
lib/src/main/java/org/onap/portalng/bff/config/BffConfig.java
lib/src/main/java/org/onap/portalng/bff/config/KeycloakPermissionFilter.java [new file with mode: 0644]
lib/src/main/java/org/onap/portalng/bff/config/SecurityConfig.java
lib/src/main/java/org/onap/portalng/bff/controller/AbstractBffController.java
lib/src/main/java/org/onap/portalng/bff/controller/ActionsController.java
lib/src/main/java/org/onap/portalng/bff/controller/PreferencesController.java
lib/src/main/java/org/onap/portalng/bff/controller/RolesController.java
lib/src/main/java/org/onap/portalng/bff/controller/UsersController.java

diff --git a/app/src/main/resources/application-access-control.yml b/app/src/main/resources/application-access-control.yml
deleted file mode 100644 (file)
index 6fda781..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-bff:
-  access-control:
-    ACTIONS_CREATE: [ portal_admin, portal_designer, portal_operator ]
-    ACTIONS_GET: [ portal_admin, portal_designer, portal_operator ]
-    ACTIONS_LIST: [ portal_admin, portal_designer, portal_operator ]
-    ACTIVE_ALARM_LIST: [portal_admin, portal_designer, portal_operator]
-    KEY_ENCRYPT_BY_USER: [portal_admin, portal_designer, portal_operator]
-    KEY_ENCRYPT_BY_VALUE: [portal_admin, portal_designer, portal_operator]
-    PREFERENCES_CREATE: [portal_admin, portal_designer, portal_operator]
-    PREFERENCES_GET: [portal_admin, portal_designer, portal_operator]
-    PREFERENCES_UPDATE: [portal_admin, portal_designer, portal_operator]
-    ROLE_LIST: ["*"]
-    USER_CREATE: [portal_admin, portal_designer, portal_operator]
-    USER_DELETE: [portal_admin, portal_designer, portal_operator]
-    USER_GET: [portal_admin, portal_designer, portal_operator]
-    USER_LIST_AVAILABLE_ROLES: [portal_admin, portal_designer, portal_operator]
-    USER_LIST_ROLES: [portal_admin, portal_designer, portal_operator]
-    USER_LIST: [portal_admin, portal_designer, portal_operator]
-    USER_UPDATE_PASSWORD: [portal_admin, portal_designer, portal_operator]
-    USER_UPDATE_ROLES: [portal_admin, portal_designer, portal_operator]
-    USER_UPDATE: [portal_admin, portal_designer, portal_operator]
index a99ff0b..f93d4d6 100644 (file)
@@ -52,8 +52,10 @@ bff:
   preferences-url: ${PREFERENCES_URL}
   history-url: ${HISTORY_URL}
   keycloak-url: ${KEYCLOAK_URL}
+  keycloak-client-id: ${KEYCLOAK_CLIENT_ID}
   endpoints:
     unauthenticated: /api-docs.html, /api.yaml, /webjars/**, /actuator/**
+    
   rbac:
-    endpoints-excluded: /actuator/**, **/actuator/**, */actuator/**, /**/actuator/**, /*/actuator/**
+    endpoints-excluded: ${RBAC_EXCLUDED_ENDPOINTS}:-/api-docs.html, /api.yaml, /webjars/**, /actuator/**, /users**, /roles**, /preferences**, /actions**}
 
index a69516c..63d702c 100644 (file)
@@ -115,6 +115,20 @@ public abstract class BaseIntegrationTest {
                             .put("session_state", UUID.randomUUID().toString())
                             .put("scope", "email profile")
                             .toString())));
+
+    /*
+     * MockAuth for new RBAC permission via keycloak
+     */
+    WireMock.stubFor(
+        WireMock.post(
+                WireMock.urlMatching(
+                    String.format("/realms/%s/protocol/openid-connect/token", realm)))
+            .withRequestBody(
+                WireMock.containing("grant_type=urn:ietf:params:oauth:grant-type:uma-ticket"))
+            .willReturn(
+                WireMock.aResponse()
+                    .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE)
+                    .withBody(objectMapper.createObjectNode().put("result", "true").toString())));
   }
 
   /**
diff --git a/app/src/test/java/org/onap/portalng/bff/rbac/RoleBaseAccessIntegrationTest.java b/app/src/test/java/org/onap/portalng/bff/rbac/RoleBaseAccessIntegrationTest.java
new file mode 100644 (file)
index 0000000..2c89547
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ *
+ * Copyright (c) 2024. Deutsche Telekom AG
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ *
+ */
+
+package org.onap.portalng.bff.rbac;
+
+import com.github.tomakehurst.wiremock.client.WireMock;
+import io.restassured.http.Header;
+import org.junit.jupiter.api.Test;
+import org.onap.portalng.bff.BaseIntegrationTest;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+
+public class RoleBaseAccessIntegrationTest extends BaseIntegrationTest {
+
+  @Test
+  void thatRoleIsNotSufficient() {
+
+    WireMock.stubFor(
+        WireMock.post(
+                WireMock.urlMatching(
+                    String.format("/realms/%s/protocol/openid-connect/token", realm)))
+            .withRequestBody(
+                WireMock.containing("grant_type=urn:ietf:params:oauth:grant-type:uma-ticket"))
+            .willReturn(
+                WireMock.aResponse()
+                    .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE)
+                    .withStatus(HttpStatus.FORBIDDEN.value())));
+
+    requestSpecification()
+        .given()
+        .accept(MediaType.APPLICATION_JSON_VALUE)
+        .header(new Header("X-Request-Id", "addf6005-3075-4c80-b7bc-2c70b7d42b57"))
+        .when()
+        .get("/roles")
+        .then()
+        .statusCode(HttpStatus.FORBIDDEN.value());
+  }
+
+  @Test
+  void thatResourceIsNotAvailable() {
+
+    WireMock.stubFor(
+        WireMock.post(
+                WireMock.urlMatching(
+                    String.format("/realms/%s/protocol/openid-connect/token", realm)))
+            .withRequestBody(
+                WireMock.containing("grant_type=urn:ietf:params:oauth:grant-type:uma-ticket"))
+            .willReturn(
+                WireMock.aResponse()
+                    .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE)
+                    .withStatus(HttpStatus.BAD_REQUEST.value())));
+
+    requestSpecification()
+        .given()
+        .accept(MediaType.APPLICATION_JSON_VALUE)
+        .header(new Header("X-Request-Id", "addf6005-3075-4c80-b7bc-2c70b7d42b57"))
+        .when()
+        .get("/roles")
+        .then()
+        .statusCode(HttpStatus.FORBIDDEN.value());
+  }
+
+  @Test
+  void thatRoleBaseCheckIsMalformed() {
+
+    WireMock.stubFor(
+        WireMock.post(
+                WireMock.urlMatching(
+                    String.format("/realms/%s/protocol/openid-connect/token", realm)))
+            .withRequestBody(
+                WireMock.containing("grant_type=urn:ietf:params:oauth:grant-type:uma-ticket"))
+            .willReturn(
+                WireMock.aResponse()
+                    .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE)
+                    .withBody(objectMapper.createObjectNode().put("result", "false").toString())));
+
+    requestSpecification()
+        .given()
+        .accept(MediaType.APPLICATION_JSON_VALUE)
+        .header(new Header("X-Request-Id", "addf6005-3075-4c80-b7bc-2c70b7d42b57"))
+        .when()
+        .get("/roles")
+        .then()
+        .statusCode(HttpStatus.FORBIDDEN.value());
+  }
+}
index 23602d1..5dbb9f6 100644 (file)
@@ -30,3 +30,8 @@ bff:
   preferences-url: http://localhost:${wiremock.server.port}
   history-url: http://localhost:${wiremock.server.port}
   keycloak-url: http://localhost:${wiremock.server.port}
+  keycloak-client-id: test
+  endpoints:
+    unauthenticated: /api-docs.html, /api.yaml, /webjars/**, /actuator/**
+  rbac:
+    endpoints-excluded: /api-docs.html, /api.yaml, /webjars/**, /actuator/**
index 04e6a57..7764fbf 100644 (file)
@@ -27,8 +27,8 @@ bff:
   preferences-url: http://localhost:${wiremock.server.port}
   history-url: http://localhost:${wiremock.server.port}
   keycloak-url: http://localhost:${wiremock.server.port}
+  keycloak-client-id: test
   endpoints:
     unauthenticated: /api-docs.html, /api.yaml, /webjars/**, /actuator/**
   rbac:
-    endpoints-excluded: /actuator/**, **/actuator/**, */actuator/**, /**/actuator/**, /*/actuator/**
-
+    endpoints-excluded: /api-docs.html, /api.yaml, /webjars/**, /actuator/**
index 3fada84..786f4bc 100644 (file)
@@ -45,6 +45,7 @@ public class BffConfig {
   @NotBlank private final String preferencesUrl;
   @NotBlank private final String historyUrl;
   @NotBlank private final String keycloakUrl;
+  @NotBlank private final String keycloakClientId;
 
   @NotNull private final Map<String, Set<String>> accessControl;
 
diff --git a/lib/src/main/java/org/onap/portalng/bff/config/KeycloakPermissionFilter.java b/lib/src/main/java/org/onap/portalng/bff/config/KeycloakPermissionFilter.java
new file mode 100644 (file)
index 0000000..f6b7d21
--- /dev/null
@@ -0,0 +1,122 @@
+/*
+ *
+ * Copyright (c) 2024. Deutsche Telekom AG
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ *
+ */
+
+package org.onap.portalng.bff.config;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Component;
+import org.springframework.web.reactive.function.client.WebClient;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.WebFilter;
+import org.springframework.web.server.WebFilterChain;
+import reactor.core.publisher.Mono;
+
+@Component
+@Slf4j
+public class KeycloakPermissionFilter implements WebFilter {
+
+  private final WebClient webClient;
+
+  private final ObjectMapper objectMapper;
+  private final BffConfig bffConfig;
+
+  @Value("${bff.rbac.endpoints-excluded}")
+  private String[] EXCLUDED_PATHS;
+
+  public KeycloakPermissionFilter(
+      WebClient.Builder webClientBuilder, ObjectMapper objectMapper, BffConfig bffConfig) {
+    this.webClient = webClientBuilder.build();
+    this.objectMapper = objectMapper;
+    this.bffConfig = bffConfig;
+  }
+
+  @Override
+  public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
+    String accessToken = exchange.getRequest().getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
+    String uri = exchange.getRequest().getURI().getPath();
+    String method = exchange.getRequest().getMethod().toString();
+
+    for (String excludedPath : EXCLUDED_PATHS) {
+      if (uri.matches(excludedPath.replace("**", ".*"))) {
+        return chain.filter(exchange);
+      }
+    }
+
+    String body =
+        new StringBuilder()
+            .append("grant_type=urn:ietf:params:oauth:grant-type:uma-ticket")
+            .append("&audience=")
+            .append(bffConfig.getKeycloakClientId())
+            .append("&permission_resource_format=uri")
+            .append("&permission_resource_matching_uri=true")
+            .append("&permission=")
+            .append(uri)
+            .append("#")
+            .append(method)
+            .append("&response_mode=decision")
+            .toString();
+
+    return webClient
+        .post()
+        .uri(
+            bffConfig.getKeycloakUrl()
+                + "/realms/"
+                + bffConfig.getRealm()
+                + "/protocol/openid-connect/token")
+        .header(HttpHeaders.AUTHORIZATION, accessToken)
+        .contentType(MediaType.APPLICATION_FORM_URLENCODED)
+        .bodyValue(body)
+        .retrieve()
+        .bodyToMono(String.class)
+        .flatMap(
+            response -> {
+              if (isPermissionGranted(response)) {
+                return chain.filter(exchange);
+              } else {
+                exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
+                return exchange.getResponse().setComplete();
+              }
+            })
+        .onErrorResume(
+            ex -> {
+              exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
+              return exchange.getResponse().setComplete();
+            });
+  }
+
+  private boolean isPermissionGranted(String response) {
+    try {
+      JsonNode jsonNode = objectMapper.readTree(response);
+      if (jsonNode.has("result") && jsonNode.get("result").asBoolean()) {
+        return true;
+      }
+    } catch (Exception e) {
+      log.error("Error parsing JSON response", e);
+    }
+    return false;
+  }
+}
index 4ae842a..6c5e8ea 100644 (file)
@@ -23,10 +23,12 @@ package org.onap.portalng.bff.config;
 
 import static org.springframework.security.config.Customizer.withDefaults;
 
+import lombok.RequiredArgsConstructor;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
+import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
 import org.springframework.security.config.web.server.ServerHttpSecurity;
 import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager;
 import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientProvider;
@@ -38,8 +40,11 @@ import org.springframework.security.web.server.SecurityWebFilterChain;
 
 @Configuration
 @EnableWebFluxSecurity
+@RequiredArgsConstructor
 public class SecurityConfig {
 
+  private final KeycloakPermissionFilter keycloakPermissionFilter;
+
   @Value("${bff.endpoints.unauthenticated}")
   private String[] unauthenticatedEndpoints;
 
@@ -59,6 +64,7 @@ public class SecurityConfig {
                     .authenticated())
         .oauth2ResourceServer(ServerHttpSecurity.OAuth2ResourceServerSpec::jwt)
         .oauth2Client(withDefaults())
+        .addFilterAfter(keycloakPermissionFilter, SecurityWebFiltersOrder.AUTHORIZATION)
         .build();
   }
 
index afcb448..c311cb9 100644 (file)
@@ -22,9 +22,6 @@
 package org.onap.portalng.bff.controller;
 
 import org.onap.portalng.bff.config.BffConfig;
-import org.onap.portalng.bff.config.IdTokenExchangeFilterFunction;
-import org.springframework.web.server.ServerWebExchange;
-import reactor.core.publisher.Mono;
 
 public abstract class AbstractBffController {
 
@@ -33,14 +30,4 @@ public abstract class AbstractBffController {
   protected AbstractBffController(BffConfig bffConfig) {
     this.bffConfig = bffConfig;
   }
-
-  public Mono<Void> checkRoleAccess(String method, ServerWebExchange exchange) {
-    return bffConfig
-        .getRoles(method)
-        .flatMap(
-            roles ->
-                roles.contains("*")
-                    ? Mono.empty()
-                    : IdTokenExchangeFilterFunction.validateAccess(exchange, roles));
-  }
 }
index 8b8f615..9d7706c 100644 (file)
@@ -34,9 +34,6 @@ import reactor.core.publisher.Mono;
 
 @RestController
 public class ActionsController extends AbstractBffController implements ActionsApi {
-  public static final String CREATE = "ACTIONS_CREATE";
-  public static final String GET = "ACTIONS_GET";
-  public static final String LIST = "ACTIONS_LIST";
 
   private final ActionService actionService;
 
@@ -51,8 +48,7 @@ public class ActionsController extends AbstractBffController implements ActionsA
       String xRequestId,
       Mono<CreateActionRequestApiDto> createActionRequestApiDto,
       ServerWebExchange exchange) {
-    return checkRoleAccess(CREATE, exchange)
-        .then(createActionRequestApiDto)
+    return createActionRequestApiDto
         .flatMap(action -> actionService.createAction(userId, xRequestId, action))
         .map(ResponseEntity::ok);
   }
@@ -65,8 +61,8 @@ public class ActionsController extends AbstractBffController implements ActionsA
       Integer showLastHours,
       String xRequestId,
       ServerWebExchange exchange) {
-    return checkRoleAccess(GET, exchange)
-        .then(actionService.getActions(userId, xRequestId, page, pageSize, showLastHours))
+    return actionService
+        .getActions(userId, xRequestId, page, pageSize, showLastHours)
         .map(ResponseEntity::ok);
   }
 
@@ -77,8 +73,8 @@ public class ActionsController extends AbstractBffController implements ActionsA
       Integer showLastHours,
       String xRequestId,
       ServerWebExchange exchange) {
-    return checkRoleAccess(LIST, exchange)
-        .then(actionService.listActions(xRequestId, page, pageSize, showLastHours))
+    return actionService
+        .listActions(xRequestId, page, pageSize, showLastHours)
         .map(ResponseEntity::ok);
   }
 }
index af665db..b8b428f 100644 (file)
@@ -34,9 +34,6 @@ import reactor.core.publisher.Mono;
 
 @RestController
 public class PreferencesController extends AbstractBffController implements PreferencesApi {
-  public static final String CREATE = "PREFERENCES_CREATE";
-  public static final String GET = "PREFERENCES_GET";
-  public static final String UPDATE = "PREFERENCES_UPDATE";
 
   private final PreferencesService preferencesService;
 
@@ -48,9 +45,7 @@ public class PreferencesController extends AbstractBffController implements Pref
   @Override
   public Mono<ResponseEntity<PreferencesResponseApiDto>> getPreferences(
       String xRequestId, ServerWebExchange exchange) {
-    return checkRoleAccess(GET, exchange)
-        .then(preferencesService.getPreferences(xRequestId))
-        .map(ResponseEntity::ok);
+    return preferencesService.getPreferences(xRequestId).map(ResponseEntity::ok);
   }
 
   @Override
@@ -58,8 +53,7 @@ public class PreferencesController extends AbstractBffController implements Pref
       @Valid Mono<CreatePreferencesRequestApiDto> preferencesApiDto,
       String xRequestId,
       ServerWebExchange exchange) {
-    return checkRoleAccess(CREATE, exchange)
-        .then(preferencesApiDto)
+    return preferencesApiDto
         .flatMap(request -> preferencesService.createPreferences(xRequestId, request))
         .map(ResponseEntity::ok);
   }
@@ -69,8 +63,7 @@ public class PreferencesController extends AbstractBffController implements Pref
       @Valid Mono<CreatePreferencesRequestApiDto> preferencesApiDto,
       String xRequestId,
       ServerWebExchange exchange) {
-    return checkRoleAccess(UPDATE, exchange)
-        .then(preferencesApiDto)
+    return preferencesApiDto
         .flatMap(request -> preferencesService.updatePreferences(xRequestId, request))
         .map(ResponseEntity::ok);
   }
index 69b4093..1d948a2 100644 (file)
@@ -33,8 +33,6 @@ import reactor.core.publisher.Mono;
 @RestController
 public class RolesController extends AbstractBffController implements RolesApi {
 
-  public static final String LIST = "ROLE_LIST";
-
   private final KeycloakService keycloakService;
 
   public RolesController(BffConfig bffConfig, KeycloakService keycloakService) {
@@ -45,8 +43,8 @@ public class RolesController extends AbstractBffController implements RolesApi {
   @Override
   public Mono<ResponseEntity<RoleListResponseApiDto>> listRoles(
       String xRequestId, ServerWebExchange exchange) {
-    return checkRoleAccess(LIST, exchange)
-        .thenMany(keycloakService.listRoles(xRequestId))
+    return keycloakService
+        .listRoles(xRequestId)
         .collectList()
         .map(roles -> new RoleListResponseApiDto().items(roles).totalCount(roles.size()))
         .map(ResponseEntity::ok);
index a8561af..3efb28f 100644 (file)
@@ -40,16 +40,6 @@ import reactor.core.publisher.Mono;
 @RestController
 public class UsersController extends AbstractBffController implements UsersApi {
 
-  public static final String CREATE = "USER_CREATE";
-  public static final String GET = "USER_GET";
-  public static final String UPDATE = "USER_UPDATE";
-  public static final String DELETE = "USER_DELETE";
-  public static final String LIST = "USER_LIST";
-  public static final String UPDATE_PASSWORD = "USER_UPDATE_PASSWORD";
-  public static final String UPDATE_ROLES = "USER_UPDATE_ROLES";
-  public static final String LIST_ROLES = "USER_LIST_ROLES";
-  public static final String LIST_AVAILABLE_ROLES = "USER_LIST_AVAILABLE_ROLES";
-
   private final KeycloakService keycloakService;
 
   public UsersController(BffConfig bffConfig, KeycloakService keycloakService) {
@@ -60,17 +50,15 @@ public class UsersController extends AbstractBffController implements UsersApi {
   @Override
   public Mono<ResponseEntity<UserResponseApiDto>> createUser(
       Mono<CreateUserRequestApiDto> requestMono, String xRequestId, ServerWebExchange exchange) {
-    return checkRoleAccess(CREATE, exchange)
-        .then(requestMono.flatMap(request -> keycloakService.createUser(request, xRequestId)))
+    return requestMono
+        .flatMap(request -> keycloakService.createUser(request, xRequestId))
         .map(ResponseEntity::ok);
   }
 
   @Override
   public Mono<ResponseEntity<UserResponseApiDto>> getUser(
       String userId, String xRequestId, ServerWebExchange exchange) {
-    return checkRoleAccess(GET, exchange)
-        .then(keycloakService.getUser(userId, xRequestId))
-        .map(ResponseEntity::ok);
+    return keycloakService.getUser(userId, xRequestId).map(ResponseEntity::ok);
   }
 
   @Override
@@ -79,8 +67,7 @@ public class UsersController extends AbstractBffController implements UsersApi {
       Mono<UpdateUserRequestApiDto> requestMono,
       String xRequestId,
       ServerWebExchange exchange) {
-    return checkRoleAccess(UPDATE, exchange)
-        .then(requestMono)
+    return requestMono
         .flatMap(request -> keycloakService.updateUser(userId, request, xRequestId))
         .map(ResponseEntity::ok);
   }
@@ -88,8 +75,8 @@ public class UsersController extends AbstractBffController implements UsersApi {
   @Override
   public Mono<ResponseEntity<Void>> deleteUser(
       String userId, String xRequestId, ServerWebExchange exchange) {
-    return checkRoleAccess(DELETE, exchange)
-        .then(keycloakService.deleteUser(userId, xRequestId))
+    return keycloakService
+        .deleteUser(userId, xRequestId)
         .thenReturn(ResponseEntity.noContent().build());
   }
 
@@ -97,9 +84,7 @@ public class UsersController extends AbstractBffController implements UsersApi {
   public Mono<ResponseEntity<UserListResponseApiDto>> listUsers(
       Integer page, Integer pageSize, String xRequestId, ServerWebExchange exchange) {
 
-    return checkRoleAccess(LIST, exchange)
-        .then(keycloakService.listUsers(page, pageSize, xRequestId))
-        .map(ResponseEntity::ok);
+    return keycloakService.listUsers(page, pageSize, xRequestId).map(ResponseEntity::ok);
   }
 
   @Override
@@ -108,8 +93,7 @@ public class UsersController extends AbstractBffController implements UsersApi {
       Mono<UpdateUserPasswordRequestApiDto> requestMono,
       String xRequestId,
       ServerWebExchange exchange) {
-    return checkRoleAccess(UPDATE_PASSWORD, exchange)
-        .then(requestMono)
+    return requestMono
         .flatMap(request -> keycloakService.updateUserPassword(userId, request))
         .thenReturn(ResponseEntity.noContent().build());
   }
@@ -117,24 +101,20 @@ public class UsersController extends AbstractBffController implements UsersApi {
   @Override
   public Mono<ResponseEntity<RoleListResponseApiDto>> listAvailableRoles(
       String userId, String xRequestId, ServerWebExchange exchange) {
-    return checkRoleAccess(LIST_AVAILABLE_ROLES, exchange)
-        .then(keycloakService.getAvailableRoles(userId, xRequestId))
-        .map(ResponseEntity::ok);
+    return keycloakService.getAvailableRoles(userId, xRequestId).map(ResponseEntity::ok);
   }
 
   @Override
   public Mono<ResponseEntity<RoleListResponseApiDto>> listAssignedRoles(
       String userId, String xRequestId, ServerWebExchange exchange) {
-    return checkRoleAccess(LIST_ROLES, exchange)
-        .then(keycloakService.getAssignedRoles(userId, xRequestId))
-        .map(ResponseEntity::ok);
+    return keycloakService.getAssignedRoles(userId, xRequestId).map(ResponseEntity::ok);
   }
 
   @Override
   public Mono<ResponseEntity<RoleListResponseApiDto>> updateAssignedRoles(
       String userId, String xRequestId, Flux<RoleApiDto> rolesFlux, ServerWebExchange exchange) {
-    return checkRoleAccess(UPDATE_ROLES, exchange)
-        .then(rolesFlux.collectList())
+    return rolesFlux
+        .collectList()
         .flatMap(roles -> keycloakService.updateAssignedRoles(userId, roles, xRequestId))
         .map(ResponseEntity::ok);
   }