diff options
author | Fiete Ostkamp <fiete.ostkamp@telekom.de> | 2024-12-17 09:09:23 +0000 |
---|---|---|
committer | Gerrit Code Review <gerrit@onap.org> | 2024-12-17 09:09:23 +0000 |
commit | 11fa7721b17cd453583a3c0f91107794e837f7d6 (patch) | |
tree | 424559f732cb34dcb06981c55be8ffaac6c0dda1 /lib | |
parent | d4f1e5129ff72bc0af6f29e9b9c3eb0c362f4d6e (diff) | |
parent | c5fab8a832454dd8c641dd2991cb91e3c8f688ca (diff) |
Diffstat (limited to 'lib')
8 files changed, 151 insertions, 68 deletions
diff --git a/lib/src/main/java/org/onap/portalng/bff/config/BffConfig.java b/lib/src/main/java/org/onap/portalng/bff/config/BffConfig.java index 3fada84..786f4bc 100644 --- a/lib/src/main/java/org/onap/portalng/bff/config/BffConfig.java +++ b/lib/src/main/java/org/onap/portalng/bff/config/BffConfig.java @@ -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 index 0000000..f6b7d21 --- /dev/null +++ b/lib/src/main/java/org/onap/portalng/bff/config/KeycloakPermissionFilter.java @@ -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; + } +} diff --git a/lib/src/main/java/org/onap/portalng/bff/config/SecurityConfig.java b/lib/src/main/java/org/onap/portalng/bff/config/SecurityConfig.java index 4ae842a..6c5e8ea 100644 --- a/lib/src/main/java/org/onap/portalng/bff/config/SecurityConfig.java +++ b/lib/src/main/java/org/onap/portalng/bff/config/SecurityConfig.java @@ -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(); } diff --git a/lib/src/main/java/org/onap/portalng/bff/controller/AbstractBffController.java b/lib/src/main/java/org/onap/portalng/bff/controller/AbstractBffController.java index afcb448..c311cb9 100644 --- a/lib/src/main/java/org/onap/portalng/bff/controller/AbstractBffController.java +++ b/lib/src/main/java/org/onap/portalng/bff/controller/AbstractBffController.java @@ -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)); - } } diff --git a/lib/src/main/java/org/onap/portalng/bff/controller/ActionsController.java b/lib/src/main/java/org/onap/portalng/bff/controller/ActionsController.java index 8b8f615..9d7706c 100644 --- a/lib/src/main/java/org/onap/portalng/bff/controller/ActionsController.java +++ b/lib/src/main/java/org/onap/portalng/bff/controller/ActionsController.java @@ -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); } } diff --git a/lib/src/main/java/org/onap/portalng/bff/controller/PreferencesController.java b/lib/src/main/java/org/onap/portalng/bff/controller/PreferencesController.java index af665db..b8b428f 100644 --- a/lib/src/main/java/org/onap/portalng/bff/controller/PreferencesController.java +++ b/lib/src/main/java/org/onap/portalng/bff/controller/PreferencesController.java @@ -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); } diff --git a/lib/src/main/java/org/onap/portalng/bff/controller/RolesController.java b/lib/src/main/java/org/onap/portalng/bff/controller/RolesController.java index 69b4093..1d948a2 100644 --- a/lib/src/main/java/org/onap/portalng/bff/controller/RolesController.java +++ b/lib/src/main/java/org/onap/portalng/bff/controller/RolesController.java @@ -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); diff --git a/lib/src/main/java/org/onap/portalng/bff/controller/UsersController.java b/lib/src/main/java/org/onap/portalng/bff/controller/UsersController.java index a8561af..3efb28f 100644 --- a/lib/src/main/java/org/onap/portalng/bff/controller/UsersController.java +++ b/lib/src/main/java/org/onap/portalng/bff/controller/UsersController.java @@ -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); } |