aboutsummaryrefslogtreecommitdiffstats
path: root/a1-policy-management/src
diff options
context:
space:
mode:
authorPatrikBuhr <patrik.buhr@est.tech>2023-04-05 14:40:07 +0200
committerPatrikBuhr <patrik.buhr@est.tech>2023-04-06 17:36:06 +0200
commitf07e4b397c60c21ae275a7c98471b64e60f14f04 (patch)
treeaad1e2ae65154a4e78875e9a1e41d72b301cda52 /a1-policy-management/src
parent44499d09ab2842ecad245ac73de523790a5d64eb (diff)
A1 PMS support for fine grained access control -A1 London
Issue-ID: CCSDK-3885 Signed-off-by: PatrikBuhr <patrik.buhr@est.tech> Change-Id: I2ee8f40389d1d53cbfd9433232e0f35f2644361b
Diffstat (limited to 'a1-policy-management/src')
-rw-r--r--a1-policy-management/src/main/java/org/onap/ccsdk/oran/a1policymanagementservice/configuration/ApplicationConfig.java6
-rw-r--r--a1-policy-management/src/main/java/org/onap/ccsdk/oran/a1policymanagementservice/controllers/authorization/AuthorizationCheck.java109
-rw-r--r--a1-policy-management/src/main/java/org/onap/ccsdk/oran/a1policymanagementservice/controllers/authorization/AuthorizationConsts.java37
-rw-r--r--a1-policy-management/src/main/java/org/onap/ccsdk/oran/a1policymanagementservice/controllers/authorization/AuthorizationResult.java (renamed from a1-policy-management/src/main/java/org/onap/ccsdk/oran/a1policymanagementservice/controllers/v2/PolicyAuthorizationRequest.java)22
-rw-r--r--a1-policy-management/src/main/java/org/onap/ccsdk/oran/a1policymanagementservice/controllers/authorization/PolicyAuthorizationRequest.java77
-rw-r--r--a1-policy-management/src/main/java/org/onap/ccsdk/oran/a1policymanagementservice/controllers/v2/PolicyController.java78
-rw-r--r--a1-policy-management/src/main/java/org/onap/ccsdk/oran/a1policymanagementservice/controllers/v2/ServiceController.java2
-rw-r--r--a1-policy-management/src/main/java/org/onap/ccsdk/oran/a1policymanagementservice/repository/Policies.java2
-rw-r--r--a1-policy-management/src/test/java/org/onap/ccsdk/oran/a1policymanagementservice/controllers/OpenPolicyAgentSimulatorController.java112
-rw-r--r--a1-policy-management/src/test/java/org/onap/ccsdk/oran/a1policymanagementservice/controllers/v2/ApplicationTest.java78
10 files changed, 483 insertions, 40 deletions
diff --git a/a1-policy-management/src/main/java/org/onap/ccsdk/oran/a1policymanagementservice/configuration/ApplicationConfig.java b/a1-policy-management/src/main/java/org/onap/ccsdk/oran/a1policymanagementservice/configuration/ApplicationConfig.java
index eea96927..3de674bd 100644
--- a/a1-policy-management/src/main/java/org/onap/ccsdk/oran/a1policymanagementservice/configuration/ApplicationConfig.java
+++ b/a1-policy-management/src/main/java/org/onap/ccsdk/oran/a1policymanagementservice/configuration/ApplicationConfig.java
@@ -28,6 +28,7 @@ import java.util.HashMap;
import java.util.Map;
import lombok.Getter;
+import lombok.Setter;
import org.onap.ccsdk.oran.a1policymanagementservice.configuration.WebClientConfig.HttpProxyConfig;
import org.onap.ccsdk.oran.a1policymanagementservice.exceptions.ServiceException;
@@ -98,6 +99,11 @@ public class ApplicationConfig {
@Value("${app.s3.bucket:}")
private String s3Bucket;
+ @Getter
+ @Setter
+ @Value("${app.authorization-provider:}")
+ private String authProviderUrl;
+
private Map<String, RicConfig> ricConfigs = new HashMap<>();
private WebClientConfig webClientConfig = null;
diff --git a/a1-policy-management/src/main/java/org/onap/ccsdk/oran/a1policymanagementservice/controllers/authorization/AuthorizationCheck.java b/a1-policy-management/src/main/java/org/onap/ccsdk/oran/a1policymanagementservice/controllers/authorization/AuthorizationCheck.java
new file mode 100644
index 00000000..0f376c06
--- /dev/null
+++ b/a1-policy-management/src/main/java/org/onap/ccsdk/oran/a1policymanagementservice/controllers/authorization/AuthorizationCheck.java
@@ -0,0 +1,109 @@
+/*-
+ * ========================LICENSE_START=================================
+ * ONAP : ccsdk oran
+ * ======================================================================
+ * Copyright (C) 2023 Nordix Foundation. All rights reserved.
+ * ======================================================================
+ * 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.
+ * ========================LICENSE_END===================================
+ */
+
+package org.onap.ccsdk.oran.a1policymanagementservice.controllers.authorization;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+import java.lang.invoke.MethodHandles;
+import java.util.Map;
+
+import org.onap.ccsdk.oran.a1policymanagementservice.clients.AsyncRestClient;
+import org.onap.ccsdk.oran.a1policymanagementservice.clients.AsyncRestClientFactory;
+import org.onap.ccsdk.oran.a1policymanagementservice.clients.SecurityContext;
+import org.onap.ccsdk.oran.a1policymanagementservice.configuration.ApplicationConfig;
+import org.onap.ccsdk.oran.a1policymanagementservice.controllers.authorization.PolicyAuthorizationRequest.Input.AccessType;
+import org.onap.ccsdk.oran.a1policymanagementservice.exceptions.ServiceException;
+import org.onap.ccsdk.oran.a1policymanagementservice.repository.Policy;
+import org.onap.ccsdk.oran.a1policymanagementservice.repository.PolicyType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpStatus;
+import org.springframework.stereotype.Component;
+
+import reactor.core.publisher.Mono;
+
+@Component
+public class AuthorizationCheck {
+
+ private final ApplicationConfig applicationConfig;
+ private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+ private final AsyncRestClient restClient;
+ private static Gson gson = new GsonBuilder().disableHtmlEscaping().create();
+
+ public AuthorizationCheck(ApplicationConfig applicationConfig, SecurityContext securityContext) {
+
+ this.applicationConfig = applicationConfig;
+ AsyncRestClientFactory restClientFactory =
+ new AsyncRestClientFactory(applicationConfig.getWebClientConfig(), securityContext);
+ this.restClient = restClientFactory.createRestClientUseHttpProxy("");
+ }
+
+ public Mono<Policy> doAccessControl(Map<String, String> receivedHttpHeaders, Policy policy, AccessType accessType) {
+ return doAccessControl(receivedHttpHeaders, policy.getType(), accessType) //
+ .map(x -> policy);
+ }
+
+ public Mono<PolicyType> doAccessControl(Map<String, String> receivedHttpHeaders, PolicyType type,
+ AccessType accessType) {
+ if (this.applicationConfig.getAuthProviderUrl().isEmpty()) {
+ return Mono.just(type);
+ }
+
+ String tkn = getAuthToken(receivedHttpHeaders);
+ PolicyAuthorizationRequest.Input input = PolicyAuthorizationRequest.Input.builder() //
+ .authToken(tkn) //
+ .policyTypeId(type.getId()) //
+ .accessType(accessType).build();
+
+ PolicyAuthorizationRequest req = PolicyAuthorizationRequest.builder().input(input).build();
+
+ String url = this.applicationConfig.getAuthProviderUrl();
+ return this.restClient.post(url, gson.toJson(req)) //
+ .doOnError(t -> logger.warn("Error returned from auth server: {}", t.getMessage())) //
+ .onErrorResume(t -> Mono.just("")) //
+ .flatMap(this::checkAuthResult) //
+ .map(rsp -> type);
+
+ }
+
+ private String getAuthToken(Map<String, String> httpHeaders) {
+ String tkn = httpHeaders.get("authorization");
+ if (tkn == null) {
+ logger.debug("No authorization token received in {}", httpHeaders);
+ return "";
+ }
+ tkn = tkn.substring("Bearer ".length());
+ return tkn;
+ }
+
+ private Mono<String> checkAuthResult(String response) {
+ logger.debug("Auth result: {}", response);
+ try {
+ AuthorizationResult res = gson.fromJson(response, AuthorizationResult.class);
+ return res != null && res.isResult() ? Mono.just(response)
+ : Mono.error(new ServiceException("Not authorized", HttpStatus.UNAUTHORIZED));
+ } catch (Exception e) {
+ return Mono.error(e);
+ }
+ }
+
+}
diff --git a/a1-policy-management/src/main/java/org/onap/ccsdk/oran/a1policymanagementservice/controllers/authorization/AuthorizationConsts.java b/a1-policy-management/src/main/java/org/onap/ccsdk/oran/a1policymanagementservice/controllers/authorization/AuthorizationConsts.java
new file mode 100644
index 00000000..a905b9d1
--- /dev/null
+++ b/a1-policy-management/src/main/java/org/onap/ccsdk/oran/a1policymanagementservice/controllers/authorization/AuthorizationConsts.java
@@ -0,0 +1,37 @@
+/*-
+ * ========================LICENSE_START=================================
+ * ONAP : ccsdk oran
+ * ======================================================================
+ * Copyright (C) 2023 Nordix Foundation. All rights reserved.
+ * ======================================================================
+ * 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.
+ * ========================LICENSE_END===================================
+ */
+
+package org.onap.ccsdk.oran.a1policymanagementservice.controllers.authorization;
+
+public class AuthorizationConsts {
+
+ public static final String AUTH_API_NAME = "Authorization API";
+ public static final String AUTH_API_DESCRIPTION =
+ """
+ API used for authorization of information A1 policy access (this is provided by an authorization producer such as OPA).
+ Note that this API is called by PMS, it is not provided.
+ """;
+
+ public static final String GRANT_ACCESS_SUMMARY = "Request for access authorization.";
+ public static final String GRANT_ACCESS_DESCRIPTION = "The authorization function decides if access is granted.";
+
+ private AuthorizationConsts() {}
+
+}
diff --git a/a1-policy-management/src/main/java/org/onap/ccsdk/oran/a1policymanagementservice/controllers/v2/PolicyAuthorizationRequest.java b/a1-policy-management/src/main/java/org/onap/ccsdk/oran/a1policymanagementservice/controllers/authorization/AuthorizationResult.java
index 63a53109..796c44e9 100644
--- a/a1-policy-management/src/main/java/org/onap/ccsdk/oran/a1policymanagementservice/controllers/v2/PolicyAuthorizationRequest.java
+++ b/a1-policy-management/src/main/java/org/onap/ccsdk/oran/a1policymanagementservice/controllers/authorization/AuthorizationResult.java
@@ -2,7 +2,7 @@
* ========================LICENSE_START=================================
* ONAP : ccsdk oran
* ======================================================================
- * Copyright (C) 2022 Nordix Foundation. All rights reserved.
+ * Copyright (C) 2023 Nordix Foundation. All rights reserved.
* ======================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,7 +18,7 @@
* ========================LICENSE_END===================================
*/
-package org.onap.ccsdk.oran.a1policymanagementservice.controllers.v2;
+package org.onap.ccsdk.oran.a1policymanagementservice.controllers.authorization;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.gson.annotations.SerializedName;
@@ -28,20 +28,14 @@ import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Getter;
-
-@Schema(name = "policy_authorization", description = "Authorization request for A1 policy requests")
+@Schema(name = "authorization_result", description = "Result of authorization")
@Builder
-public class PolicyAuthorizationRequest {
-
- @Schema(name = "acces_type", description = "Access type")
- public enum AccessType {
- READ, WRITE, DELETE
- }
+public class AuthorizationResult {
- @Schema(name = "access_type", description = "Access type", required = true)
- @JsonProperty(value = "access_type", required = true)
- @SerializedName("access_type")
+ @Schema(name = "result", description = "If true, the access is granted", required = true)
+ @JsonProperty(value = "result", required = true)
+ @SerializedName("result")
@Getter
- private String accessType;
+ private boolean result;
}
diff --git a/a1-policy-management/src/main/java/org/onap/ccsdk/oran/a1policymanagementservice/controllers/authorization/PolicyAuthorizationRequest.java b/a1-policy-management/src/main/java/org/onap/ccsdk/oran/a1policymanagementservice/controllers/authorization/PolicyAuthorizationRequest.java
new file mode 100644
index 00000000..8dd4e7bb
--- /dev/null
+++ b/a1-policy-management/src/main/java/org/onap/ccsdk/oran/a1policymanagementservice/controllers/authorization/PolicyAuthorizationRequest.java
@@ -0,0 +1,77 @@
+/*-
+ * ========================LICENSE_START=================================
+ * ONAP : ccsdk oran
+ * ======================================================================
+ * Copyright (C) 2023 Nordix Foundation. All rights reserved.
+ * ======================================================================
+ * 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.
+ * ========================LICENSE_END===================================
+ */
+
+package org.onap.ccsdk.oran.a1policymanagementservice.controllers.authorization;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.gson.annotations.SerializedName;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.ToString;
+
+@Schema(name = "policy_authorization", description = "Authorization request for A1 policy requests")
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+public class PolicyAuthorizationRequest {
+
+ @Schema(name = "input", description = "input")
+ @Builder
+ @AllArgsConstructor
+ @NoArgsConstructor
+ @Getter
+ @ToString
+ public static class Input {
+
+ @Schema(name = "acces_type", description = "Access type")
+ public enum AccessType {
+ READ, WRITE, DELETE
+ }
+
+ @Schema(name = "access_type", description = "Access type", required = true)
+ @JsonProperty(value = "access_type", required = true)
+ @SerializedName("access_type")
+ @Getter
+ private AccessType accessType;
+
+ @Schema(name = "policy_type_id", description = "Policy type identifier", required = true)
+ @SerializedName("policy_type_id")
+ @JsonProperty(value = "policy_type_id", required = true)
+ private String policyTypeId;
+
+ @Schema(name = "auth_token", description = "Authorization token", required = true)
+ @SerializedName("auth_token")
+ @JsonProperty(value = "auth_token", required = true)
+ private String authToken;
+
+ }
+
+ @Schema(name = "input", description = "Input", required = true)
+ @JsonProperty(value = "input", required = true)
+ @SerializedName("input")
+ private Input input;
+
+}
diff --git a/a1-policy-management/src/main/java/org/onap/ccsdk/oran/a1policymanagementservice/controllers/v2/PolicyController.java b/a1-policy-management/src/main/java/org/onap/ccsdk/oran/a1policymanagementservice/controllers/v2/PolicyController.java
index 395daa30..64905f44 100644
--- a/a1-policy-management/src/main/java/org/onap/ccsdk/oran/a1policymanagementservice/controllers/v2/PolicyController.java
+++ b/a1-policy-management/src/main/java/org/onap/ccsdk/oran/a1policymanagementservice/controllers/v2/PolicyController.java
@@ -36,11 +36,14 @@ import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import java.util.Map;
import lombok.Getter;
import org.onap.ccsdk.oran.a1policymanagementservice.clients.A1ClientFactory;
import org.onap.ccsdk.oran.a1policymanagementservice.controllers.VoidResponse;
+import org.onap.ccsdk.oran.a1policymanagementservice.controllers.authorization.AuthorizationCheck;
+import org.onap.ccsdk.oran.a1policymanagementservice.controllers.authorization.PolicyAuthorizationRequest.Input.AccessType;
import org.onap.ccsdk.oran.a1policymanagementservice.exceptions.EntityNotFoundException;
import org.onap.ccsdk.oran.a1policymanagementservice.exceptions.ServiceException;
import org.onap.ccsdk.oran.a1policymanagementservice.repository.Lock;
@@ -64,11 +67,13 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.function.client.WebClientException;
import org.springframework.web.reactive.function.client.WebClientResponseException;
+import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@RestController("PolicyControllerV2")
@@ -104,6 +109,9 @@ public class PolicyController {
@Autowired
private Services services;
+ @Autowired
+ private AuthorizationCheck authorization;
+
private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private static Gson gson = new GsonBuilder() //
.create(); //
@@ -175,10 +183,13 @@ public class PolicyController {
description = "Policy is not found", //
content = @Content(schema = @Schema(implementation = ErrorResponse.ErrorInfo.class))) //
})
- public ResponseEntity<Object> getPolicy( //
- @PathVariable(name = Consts.POLICY_ID_PARAM, required = true) String id) throws EntityNotFoundException {
- Policy p = policies.getPolicy(id);
- return new ResponseEntity<>(gson.toJson(toPolicyInfo(p)), HttpStatus.OK);
+ public Mono<ResponseEntity<Object>> getPolicy( //
+ @PathVariable(name = Consts.POLICY_ID_PARAM, required = true) String id,
+ @RequestHeader Map<String, String> headers) throws EntityNotFoundException {
+ Policy policy = policies.getPolicy(id);
+ return authorization.doAccessControl(headers, policy, AccessType.READ) //
+ .map(x -> new ResponseEntity<>((Object) gson.toJson(toPolicyInfo(policy)), HttpStatus.OK)) //
+ .onErrorResume(this::handleException);
}
@DeleteMapping(Consts.V2_API_ROOT + "/policies/{policy_id:.+}")
@@ -198,12 +209,15 @@ public class PolicyController {
content = @Content(schema = @Schema(implementation = ErrorResponse.ErrorInfo.class))) //
})
public Mono<ResponseEntity<Object>> deletePolicy( //
- @PathVariable(Consts.POLICY_ID_PARAM) String policyId) throws EntityNotFoundException {
+ @PathVariable(Consts.POLICY_ID_PARAM) String policyId, @RequestHeader Map<String, String> headers)
+ throws EntityNotFoundException {
Policy policy = policies.getPolicy(policyId);
keepServiceAlive(policy.getOwnerServiceId());
- return policy.getRic().getLock().lock(LockType.SHARED, "deletePolicy") //
- .flatMap(grant -> deletePolicy(grant, policy));
+ return authorization.doAccessControl(headers, policy, AccessType.WRITE)
+ .flatMap(x -> policy.getRic().getLock().lock(LockType.SHARED, "deletePolicy")) //
+ .flatMap(grant -> deletePolicy(grant, policy)) //
+ .onErrorResume(this::handleException);
}
Mono<ResponseEntity<Object>> deletePolicy(Lock.Grant grant, Policy policy) {
@@ -232,7 +246,8 @@ public class PolicyController {
description = "Near-RT RIC or policy type is not found", //
content = @Content(schema = @Schema(implementation = ErrorResponse.ErrorInfo.class))) //
})
- public Mono<ResponseEntity<Object>> putPolicy(@RequestBody PolicyInfo policyInfo) throws EntityNotFoundException {
+ public Mono<ResponseEntity<Object>> putPolicy(@RequestBody PolicyInfo policyInfo,
+ @RequestHeader Map<String, String> headers) throws EntityNotFoundException {
if (!policyInfo.validate()) {
return ErrorResponse.createMono("Missing required parameter in body", HttpStatus.BAD_REQUEST);
@@ -255,8 +270,10 @@ public class PolicyController {
.statusNotificationUri(policyInfo.statusNotificationUri == null ? "" : policyInfo.statusNotificationUri) //
.build();
- return ric.getLock().lock(LockType.SHARED, "putPolicy") //
- .flatMap(grant -> putPolicy(grant, policy));
+ return authorization.doAccessControl(headers, policy, AccessType.WRITE) //
+ .flatMap(x -> ric.getLock().lock(LockType.SHARED, "putPolicy")) //
+ .flatMap(grant -> putPolicy(grant, policy)) //
+ .onErrorResume(this::handleException);
}
private Mono<ResponseEntity<Object>> putPolicy(Lock.Grant grant, Policy policy) {
@@ -285,6 +302,9 @@ public class PolicyController {
} else if (throwable instanceof RejectionException) {
RejectionException e = (RejectionException) throwable;
return ErrorResponse.createMono(e.getMessage(), e.getStatus());
+ } else if (throwable instanceof ServiceException) {
+ ServiceException e = (ServiceException) throwable;
+ return ErrorResponse.createMono(e.getMessage(), e.getHttpStatus());
} else {
return ErrorResponse.createMono(throwable.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
@@ -339,7 +359,7 @@ public class PolicyController {
description = "Near-RT RIC, policy type or service not found", //
content = @Content(schema = @Schema(implementation = ErrorResponse.ErrorInfo.class))) //
})
- public ResponseEntity<Object> getPolicyInstances( //
+ public Mono<ResponseEntity<Object>> getPolicyInstances( //
@Parameter(name = Consts.POLICY_TYPE_ID_PARAM, required = false,
description = "Select policies with a given type identity.") //
@RequestParam(name = Consts.POLICY_TYPE_ID_PARAM, required = false) String typeId, //
@@ -351,8 +371,8 @@ public class PolicyController {
@RequestParam(name = Consts.SERVICE_ID_PARAM, required = false) String service,
@Parameter(name = Consts.TYPE_NAME_PARAM, required = false, //
description = "Select policies of a given type name (type identity has the format <typename_version>)") //
- @RequestParam(name = Consts.TYPE_NAME_PARAM, required = false) String typeName)
- throws EntityNotFoundException //
+ @RequestParam(name = Consts.TYPE_NAME_PARAM, required = false) String typeName,
+ @RequestHeader Map<String, String> headers) throws EntityNotFoundException //
{
if ((typeId != null && this.policyTypes.get(typeId) == null)) {
throw new EntityNotFoundException("Policy type identity not found");
@@ -361,8 +381,14 @@ public class PolicyController {
throw new EntityNotFoundException("Near-RT RIC not found");
}
- String filteredPolicies = policiesToJson(policies.filterPolicies(typeId, ric, service, typeName));
- return new ResponseEntity<>(filteredPolicies, HttpStatus.OK);
+ Collection<Policy> filtered = policies.filterPolicies(typeId, ric, service, typeName);
+ return Flux.fromIterable(filtered) //
+ .flatMap(policy -> authorization.doAccessControl(headers, policy, AccessType.READ)) //
+ .doOnError(e -> logger.debug("Unauthorized to read policy: {}", e.getMessage())) //
+ .onErrorResume(e -> Mono.empty()) //
+ .collectList() //
+ .map(authPolicies -> policiesToJson(authPolicies)) //
+ .map(str -> new ResponseEntity<>(str, HttpStatus.OK));
}
@GetMapping(path = Consts.V2_API_ROOT + "/policies", produces = MediaType.APPLICATION_JSON_VALUE) //
@@ -375,7 +401,7 @@ public class PolicyController {
description = "Near-RT RIC or type not found", //
content = @Content(schema = @Schema(implementation = ErrorResponse.ErrorInfo.class))) //
})
- public ResponseEntity<Object> getPolicyIds( //
+ public Mono<ResponseEntity<Object>> getPolicyIds( //
@Parameter(name = Consts.POLICY_TYPE_ID_PARAM, required = false, //
description = "Select policies of a given policy type identity.") //
@RequestParam(name = Consts.POLICY_TYPE_ID_PARAM, required = false) String policyTypeId, //
@@ -387,8 +413,8 @@ public class PolicyController {
@RequestParam(name = Consts.SERVICE_ID_PARAM, required = false) String serviceId,
@Parameter(name = Consts.TYPE_NAME_PARAM, required = false, //
description = "Select policies of types with the given type name (type identity has the format <typename_version>)") //
- @RequestParam(name = Consts.TYPE_NAME_PARAM, required = false) String typeName)
- throws EntityNotFoundException //
+ @RequestParam(name = Consts.TYPE_NAME_PARAM, required = false) String typeName,
+ @RequestHeader Map<String, String> headers) throws EntityNotFoundException //
{
if ((policyTypeId != null && this.policyTypes.get(policyTypeId) == null)) {
throw new EntityNotFoundException("Policy type not found");
@@ -397,8 +423,14 @@ public class PolicyController {
throw new EntityNotFoundException("Near-RT RIC not found");
}
- String policyIdsJson = toPolicyIdsJson(policies.filterPolicies(policyTypeId, ricId, serviceId, typeName));
- return new ResponseEntity<>(policyIdsJson, HttpStatus.OK);
+ Collection<Policy> filtered = policies.filterPolicies(policyTypeId, ricId, serviceId, typeName);
+ return Flux.fromIterable(filtered) //
+ .flatMap(policy -> authorization.doAccessControl(headers, policy, AccessType.READ)) //
+ .doOnError(e -> logger.debug("Unauthorized to read policy: {}", e.getMessage())) //
+ .onErrorResume(e -> Mono.empty()) //
+ .collectList() //
+ .map(authPolicies -> toPolicyIdsJson(authPolicies)) //
+ .map(policyIdsJson -> new ResponseEntity<>(policyIdsJson, HttpStatus.OK));
}
@GetMapping(path = Consts.V2_API_ROOT + "/policies/{policy_id}/status", produces = MediaType.APPLICATION_JSON_VALUE)
@@ -412,10 +444,12 @@ public class PolicyController {
content = @Content(schema = @Schema(implementation = ErrorResponse.ErrorInfo.class))) //
})
public Mono<ResponseEntity<Object>> getPolicyStatus( //
- @PathVariable(Consts.POLICY_ID_PARAM) String policyId) throws EntityNotFoundException {
+ @PathVariable(Consts.POLICY_ID_PARAM) String policyId, @RequestHeader Map<String, String> headers)
+ throws EntityNotFoundException {
Policy policy = policies.getPolicy(policyId);
- return a1ClientFactory.createA1Client(policy.getRic()) //
+ return authorization.doAccessControl(headers, policy, AccessType.READ) //
+ .flatMap(notUsed -> a1ClientFactory.createA1Client(policy.getRic())) //
.flatMap(client -> client.getPolicyStatus(policy).onErrorResume(e -> Mono.just("{}"))) //
.flatMap(status -> createPolicyStatus(policy, status)) //
.onErrorResume(this::handleException);
diff --git a/a1-policy-management/src/main/java/org/onap/ccsdk/oran/a1policymanagementservice/controllers/v2/ServiceController.java b/a1-policy-management/src/main/java/org/onap/ccsdk/oran/a1policymanagementservice/controllers/v2/ServiceController.java
index c3f41768..ed97820f 100644
--- a/a1-policy-management/src/main/java/org/onap/ccsdk/oran/a1policymanagementservice/controllers/v2/ServiceController.java
+++ b/a1-policy-management/src/main/java/org/onap/ccsdk/oran/a1policymanagementservice/controllers/v2/ServiceController.java
@@ -43,7 +43,6 @@ import org.onap.ccsdk.oran.a1policymanagementservice.repository.Policies;
import org.onap.ccsdk.oran.a1policymanagementservice.repository.Policy;
import org.onap.ccsdk.oran.a1policymanagementservice.repository.Service;
import org.onap.ccsdk.oran.a1policymanagementservice.repository.Services;
-import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
@@ -71,7 +70,6 @@ public class ServiceController {
private static Gson gson = new GsonBuilder().create();
- @Autowired
ServiceController(Services services, Policies policies) {
this.services = services;
this.policies = policies;
diff --git a/a1-policy-management/src/main/java/org/onap/ccsdk/oran/a1policymanagementservice/repository/Policies.java b/a1-policy-management/src/main/java/org/onap/ccsdk/oran/a1policymanagementservice/repository/Policies.java
index 77ac0f4a..7eddeae1 100644
--- a/a1-policy-management/src/main/java/org/onap/ccsdk/oran/a1policymanagementservice/repository/Policies.java
+++ b/a1-policy-management/src/main/java/org/onap/ccsdk/oran/a1policymanagementservice/repository/Policies.java
@@ -197,7 +197,7 @@ public class Policies {
byte[] bytes = gson.toJson(toStorageObject(policy)).getBytes();
this.dataStore.writeObject(this.getPath(policy), bytes) //
- .doOnError(t -> logger.error("Could not store job in S3, reason: {}", t.getMessage())) //
+ .doOnError(t -> logger.error("Could not store policy in S3, reason: {}", t.getMessage())) //
.subscribe();
}
diff --git a/a1-policy-management/src/test/java/org/onap/ccsdk/oran/a1policymanagementservice/controllers/OpenPolicyAgentSimulatorController.java b/a1-policy-management/src/test/java/org/onap/ccsdk/oran/a1policymanagementservice/controllers/OpenPolicyAgentSimulatorController.java
new file mode 100644
index 00000000..236c31b8
--- /dev/null
+++ b/a1-policy-management/src/test/java/org/onap/ccsdk/oran/a1policymanagementservice/controllers/OpenPolicyAgentSimulatorController.java
@@ -0,0 +1,112 @@
+/*-
+ * ========================LICENSE_START=================================
+ * ONAP : ccsdk oran
+ * ======================================================================
+ * Copyright (C) 2023 Nordix Foundation. All rights reserved.
+ * ======================================================================
+ * 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.
+ * ========================LICENSE_END===================================
+ */
+
+package org.onap.ccsdk.oran.a1policymanagementservice.controllers;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import io.swagger.v3.oas.annotations.tags.Tag;
+
+import java.lang.invoke.MethodHandles;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import lombok.Getter;
+
+import org.onap.ccsdk.oran.a1policymanagementservice.controllers.authorization.AuthorizationConsts;
+import org.onap.ccsdk.oran.a1policymanagementservice.controllers.authorization.AuthorizationResult;
+import org.onap.ccsdk.oran.a1policymanagementservice.controllers.authorization.PolicyAuthorizationRequest;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestHeader;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController("OpenPolicyAgentSimulatorController")
+@Tag(name = AuthorizationConsts.AUTH_API_NAME, description = AuthorizationConsts.AUTH_API_DESCRIPTION)
+public class OpenPolicyAgentSimulatorController {
+ private static Gson gson = new GsonBuilder().disableHtmlEscaping().create();
+
+ private final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ public static final String ACCESS_CONTROL_URL = "/example-authz-check";
+ public static final String ACCESS_CONTROL_URL_REJECT = "/example-authz-check-reject";
+
+ public static class TestResults {
+
+ public List<PolicyAuthorizationRequest> receivedRequests =
+ Collections.synchronizedList(new ArrayList<PolicyAuthorizationRequest>());
+
+ public TestResults() {}
+
+ public void reset() {
+ receivedRequests.clear();
+
+ }
+ }
+
+ @Getter
+ private TestResults testResults = new TestResults();
+
+ @PostMapping(path = ACCESS_CONTROL_URL, produces = MediaType.APPLICATION_JSON_VALUE)
+ @Operation(summary = AuthorizationConsts.GRANT_ACCESS_SUMMARY,
+ description = AuthorizationConsts.GRANT_ACCESS_DESCRIPTION)
+ @ApiResponses(value = { //
+ @ApiResponse(responseCode = "200", description = "OK", //
+ content = @Content(schema = @Schema(implementation = AuthorizationResult.class))) //
+ })
+ public ResponseEntity<Object> performAccessControl( //
+ @RequestHeader Map<String, String> headers, //
+ @RequestBody PolicyAuthorizationRequest request) {
+ logger.debug("Auth {}", request);
+ testResults.receivedRequests.add(request);
+
+ String res = gson.toJson(AuthorizationResult.builder().result(true).build());
+ return new ResponseEntity<>(res, HttpStatus.OK);
+ }
+
+ @PostMapping(path = ACCESS_CONTROL_URL_REJECT, produces = MediaType.APPLICATION_JSON_VALUE)
+ @Operation(summary = "Rejecting", description = "", hidden = true)
+ @ApiResponses(value = { //
+ @ApiResponse(responseCode = "200", description = "OK", //
+ content = @Content(schema = @Schema(implementation = VoidResponse.class))) //
+ })
+ public ResponseEntity<Object> performAccessControlReject( //
+ @RequestHeader Map<String, String> headers, //
+ @RequestBody PolicyAuthorizationRequest request) {
+ logger.debug("Auth Reject {}", request);
+ testResults.receivedRequests.add(request);
+ String res = gson.toJson(AuthorizationResult.builder().result(false).build());
+ return new ResponseEntity<>(res, HttpStatus.OK);
+ }
+
+}
diff --git a/a1-policy-management/src/test/java/org/onap/ccsdk/oran/a1policymanagementservice/controllers/v2/ApplicationTest.java b/a1-policy-management/src/test/java/org/onap/ccsdk/oran/a1policymanagementservice/controllers/v2/ApplicationTest.java
index 9f0473df..066adc48 100644
--- a/a1-policy-management/src/test/java/org/onap/ccsdk/oran/a1policymanagementservice/controllers/v2/ApplicationTest.java
+++ b/a1-policy-management/src/test/java/org/onap/ccsdk/oran/a1policymanagementservice/controllers/v2/ApplicationTest.java
@@ -46,6 +46,7 @@ import java.util.List;
import org.json.JSONObject;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Test;
@@ -58,7 +59,10 @@ import org.onap.ccsdk.oran.a1policymanagementservice.configuration.ApplicationCo
import org.onap.ccsdk.oran.a1policymanagementservice.configuration.ApplicationConfig.RicConfigUpdate;
import org.onap.ccsdk.oran.a1policymanagementservice.configuration.RicConfig;
import org.onap.ccsdk.oran.a1policymanagementservice.configuration.WebClientConfig;
+import org.onap.ccsdk.oran.a1policymanagementservice.controllers.OpenPolicyAgentSimulatorController;
import org.onap.ccsdk.oran.a1policymanagementservice.controllers.ServiceCallbackInfo;
+import org.onap.ccsdk.oran.a1policymanagementservice.controllers.authorization.PolicyAuthorizationRequest;
+import org.onap.ccsdk.oran.a1policymanagementservice.controllers.authorization.PolicyAuthorizationRequest.Input.AccessType;
import org.onap.ccsdk.oran.a1policymanagementservice.exceptions.ServiceException;
import org.onap.ccsdk.oran.a1policymanagementservice.repository.Lock;
import org.onap.ccsdk.oran.a1policymanagementservice.repository.Lock.LockType;
@@ -145,6 +149,9 @@ class ApplicationTest {
@Autowired
SecurityContext securityContext;
+ @Autowired
+ OpenPolicyAgentSimulatorController openPolicyAgentSimulatorController;
+
private static Gson gson = new GsonBuilder().create();
/**
@@ -174,6 +181,11 @@ class ApplicationTest {
@LocalServerPort
private int port;
+ @BeforeEach
+ void init() {
+ this.applicationConfig.setAuthProviderUrl(baseUrl() + OpenPolicyAgentSimulatorController.ACCESS_CONTROL_URL);
+ }
+
@AfterEach
void reset() {
rics.clear();
@@ -184,6 +196,7 @@ class ApplicationTest {
this.rAppSimulator.getTestResults().clear();
this.a1ClientFactory.setPolicyTypes(policyTypes); // Default same types in RIC and in this app
this.securityContext.setAuthTokenFilePath(null);
+ this.openPolicyAgentSimulatorController.getTestResults().reset();
}
@AfterAll
@@ -513,6 +526,15 @@ class ApplicationTest {
this.rics.getRic(ricId).setState(Ric.RicState.AVAILABLE);
restClient().put(url, policyBody).block();
+ {
+ // Check the authorization request
+ OpenPolicyAgentSimulatorController.TestResults res =
+ this.openPolicyAgentSimulatorController.getTestResults();
+ assertThat(res.receivedRequests).hasSize(1);
+ PolicyAuthorizationRequest req = res.receivedRequests.get(0);
+ assertThat(req.getInput().getAccessType()).isEqualTo(AccessType.WRITE);
+ assertThat(req.getInput().getPolicyTypeId()).isEqualTo(policyTypeName);
+ }
Policy policy = policies.getPolicy(policyInstanceId);
assertThat(policy).isNotNull();
@@ -551,6 +573,57 @@ class ApplicationTest {
}
@Test
+ void testFineGrainedAuth() throws Exception {
+ final String POLICY_ID = "policyId";
+ final String RIC_ID = "ric1";
+ final String TYPE_ID = "typeName";
+ addPolicy(POLICY_ID, TYPE_ID, null, RIC_ID);
+ assertThat(policies.size()).isEqualTo(1);
+
+ this.applicationConfig
+ .setAuthProviderUrl(baseUrl() + OpenPolicyAgentSimulatorController.ACCESS_CONTROL_URL_REJECT);
+
+ String url = "/policy-instances";
+ String rsp = restClient().get(url).block();
+ assertThat(rsp).as("Response contains no policy instance ID.").contains("[]");
+
+ url = "/policies/" + POLICY_ID;
+ testErrorCode(restClient().delete(url), HttpStatus.UNAUTHORIZED, "Not authorized");
+
+ url = "/policies";
+ String policyBody = putPolicyBody(null, RIC_ID, TYPE_ID, POLICY_ID, false, null);
+ testErrorCode(restClient().put(url, policyBody), HttpStatus.UNAUTHORIZED, "Not authorized");
+
+ rsp = restClient().get(url).block();
+ assertThat(rsp).as("Response contains no policy instance ID.").contains("[]");
+ }
+
+ @Test
+ void testFineGrainedAuth_OPA_UNAVALIABLE() throws Exception {
+ final String POLICY_ID = "policyId";
+ final String RIC_ID = "ric1";
+ final String TYPE_ID = "typeName";
+ addPolicy(POLICY_ID, TYPE_ID, null, RIC_ID);
+ assertThat(policies.size()).isEqualTo(1);
+
+ this.applicationConfig.setAuthProviderUrl("junk");
+
+ String url = "/policy-instances";
+ String rsp = restClient().get(url).block();
+ assertThat(rsp).as("Response contains no policy instance ID.").contains("[]");
+
+ url = "/policies/" + POLICY_ID;
+ testErrorCode(restClient().delete(url), HttpStatus.UNAUTHORIZED, "Not authorized");
+
+ url = "/policies";
+ String policyBody = putPolicyBody(null, RIC_ID, TYPE_ID, POLICY_ID, false, null);
+ testErrorCode(restClient().put(url, policyBody), HttpStatus.UNAUTHORIZED, "Not authorized");
+
+ rsp = restClient().get(url).block();
+ assertThat(rsp).as("Response contains no policy instance ID.").contains("[]");
+ }
+
+ @Test
@DisplayName("test Put Policy No Service No Status Uri")
void testPutPolicy_NoServiceNoStatusUri() throws Exception {
String ricId = "ric.1";
@@ -1042,6 +1115,7 @@ class ApplicationTest {
@Test
@DisplayName("test Concurrency")
void testConcurrency() throws Exception {
+ this.applicationConfig.setAuthProviderUrl("");
logger.info("Concurrency test starting");
final Instant startTime = Instant.now();
List<Thread> threads = new ArrayList<>();
@@ -1138,8 +1212,10 @@ class ApplicationTest {
boolean expectApplicationProblemJsonMediaType) {
assertTrue(throwable instanceof WebClientResponseException);
WebClientResponseException responseException = (WebClientResponseException) throwable;
+ String body = responseException.getResponseBodyAsString();
+ assertThat(body).contains(responseContains);
assertThat(responseException.getStatusCode()).isEqualTo(expStatus);
- assertThat(responseException.getResponseBodyAsString()).contains(responseContains);
+
if (expectApplicationProblemJsonMediaType) {
assertThat(responseException.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_PROBLEM_JSON);
}