A1 PMS support for fine grained access control -A1 London 52/134052/1
authorPatrikBuhr <patrik.buhr@est.tech>
Wed, 5 Apr 2023 12:40:07 +0000 (14:40 +0200)
committerPatrikBuhr <patrik.buhr@est.tech>
Thu, 6 Apr 2023 15:36:06 +0000 (17:36 +0200)
Issue-ID: CCSDK-3885
Signed-off-by: PatrikBuhr <patrik.buhr@est.tech>
Change-Id: I2ee8f40389d1d53cbfd9433232e0f35f2644361b

15 files changed:
a1-policy-management/api/pms-api.json
a1-policy-management/api/pms-api.yaml
a1-policy-management/api/pms-api/index.html
a1-policy-management/config/application.yaml
a1-policy-management/src/main/java/org/onap/ccsdk/oran/a1policymanagementservice/configuration/ApplicationConfig.java
a1-policy-management/src/main/java/org/onap/ccsdk/oran/a1policymanagementservice/controllers/authorization/AuthorizationCheck.java [new file with mode: 0644]
a1-policy-management/src/main/java/org/onap/ccsdk/oran/a1policymanagementservice/controllers/authorization/AuthorizationConsts.java [new file with mode: 0644]
a1-policy-management/src/main/java/org/onap/ccsdk/oran/a1policymanagementservice/controllers/authorization/AuthorizationResult.java [moved from a1-policy-management/src/main/java/org/onap/ccsdk/oran/a1policymanagementservice/controllers/v2/PolicyAuthorizationRequest.java with 68% similarity]
a1-policy-management/src/main/java/org/onap/ccsdk/oran/a1policymanagementservice/controllers/authorization/PolicyAuthorizationRequest.java [new file with mode: 0644]
a1-policy-management/src/main/java/org/onap/ccsdk/oran/a1policymanagementservice/controllers/v2/PolicyController.java
a1-policy-management/src/main/java/org/onap/ccsdk/oran/a1policymanagementservice/controllers/v2/ServiceController.java
a1-policy-management/src/main/java/org/onap/ccsdk/oran/a1policymanagementservice/repository/Policies.java
a1-policy-management/src/test/java/org/onap/ccsdk/oran/a1policymanagementservice/controllers/OpenPolicyAgentSimulatorController.java [new file with mode: 0644]
a1-policy-management/src/test/java/org/onap/ccsdk/oran/a1policymanagementservice/controllers/v2/ApplicationTest.java
docs/offeredapis/swagger/pms-api.json

index 7574032..9efa7b7 100644 (file)
                 "type": "string"
             }}
         },
+        "authorization_result": {
+            "description": "Result of authorization",
+            "type": "object",
+            "required": ["result"],
+            "properties": {"result": {
+                "description": "If true, the access is granted",
+                "type": "boolean"
+            }}
+        },
         "ric_info_v2": {
             "description": "Information for a Near-RT RIC",
             "type": "object",
                 "type": "object"
             }}
         },
+        "input": {
+            "description": "input",
+            "type": "object",
+            "required": [
+                "access_type",
+                "auth_token",
+                "policy_type_id"
+            ],
+            "properties": {
+                "access_type": {
+                    "description": "Access type",
+                    "type": "string",
+                    "enum": [
+                        "READ",
+                        "WRITE",
+                        "DELETE"
+                    ]
+                },
+                "auth_token": {
+                    "description": "Authorization token",
+                    "type": "string"
+                },
+                "policy_type_id": {
+                    "description": "Policy type identifier",
+                    "type": "string"
+                }
+            }
+        },
+        "policy_authorization": {
+            "description": "Authorization request for A1 policy requests",
+            "type": "object",
+            "required": ["input"],
+            "properties": {"input": {"$ref": "#/components/schemas/input"}}
+        },
         "policytype_id_list_v2": {
             "description": "Information about policy types",
             "type": "object",
             ],
             "tags": ["A1 Policy Management"]
         }},
+        "/example-authz-check": {"post": {
+            "summary": "Request for access authorization.",
+            "requestBody": {
+                "content": {"application/json": {"schema": {"$ref": "#/components/schemas/policy_authorization"}}},
+                "required": true
+            },
+            "description": "The authorization function decides if access is granted.",
+            "operationId": "performAccessControl",
+            "responses": {"200": {
+                "description": "OK",
+                "content": {"application/json": {"schema": {"$ref": "#/components/schemas/authorization_result"}}}
+            }},
+            "tags": ["Authorization API"]
+        }},
         "/actuator/threaddump": {"get": {
             "summary": "Actuator web endpoint 'threaddump'",
             "operationId": "threaddump",
         "title": "A1 Policy Management Service",
         "version": "1.1.0"
     },
-    "tags": [{
-        "name": "Actuator",
-        "description": "Monitor and interact",
-        "externalDocs": {
-            "description": "Spring Boot Actuator Web API Documentation",
-            "url": "https://docs.spring.io/spring-boot/docs/current/actuator-api/html/"
+    "tags": [
+        {
+            "name": "Authorization API",
+            "description": "API used for authorization of information A1 policy access (this is provided by an authorization producer such as OPA).\nNote that this API is called by PMS, it is not provided.\n"
+        },
+        {
+            "name": "Actuator",
+            "description": "Monitor and interact",
+            "externalDocs": {
+                "description": "Spring Boot Actuator Web API Documentation",
+                "url": "https://docs.spring.io/spring-boot/docs/current/actuator-api/html/"
+            }
         }
-    }]
+    ]
 }
\ No newline at end of file
index 0cd28d0..a905c40 100644 (file)
@@ -31,6 +31,10 @@ info:
 servers:
 - url: /
 tags:
+- description: "API used for authorization of information A1 policy access (this is\
+    \ provided by an authorization producer such as OPA).\nNote that this API is called\
+    \ by PMS, it is not provided.\n"
+  name: Authorization API
 - description: Monitor and interact
   externalDocs:
     description: Spring Boot Actuator Web API Documentation
@@ -93,6 +97,26 @@ paths:
       summary: Query for A1 policy instances
       tags:
       - A1 Policy Management
+  /example-authz-check:
+    post:
+      description: The authorization function decides if access is granted.
+      operationId: performAccessControl
+      requestBody:
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/policy_authorization'
+        required: true
+      responses:
+        "200":
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/authorization_result'
+          description: OK
+      summary: Request for access authorization.
+      tags:
+      - Authorization API
   /actuator/threaddump:
     get:
       operationId: threaddump
@@ -957,6 +981,17 @@ components:
           description: status text
           type: string
       type: object
+    authorization_result:
+      description: Result of authorization
+      example:
+        result: true
+      properties:
+        result:
+          description: "If true, the access is granted"
+          type: boolean
+      required:
+      - result
+      type: object
     ric_info_v2:
       description: Information for a Near-RT RIC
       example:
@@ -1113,6 +1148,35 @@ components:
             http://json-schema.org/draft-07/schema
           type: object
       type: object
+    input:
+      description: input
+      properties:
+        access_type:
+          description: Access type
+          enum:
+          - READ
+          - WRITE
+          - DELETE
+          type: string
+        auth_token:
+          description: Authorization token
+          type: string
+        policy_type_id:
+          description: Policy type identifier
+          type: string
+      required:
+      - access_type
+      - auth_token
+      - policy_type_id
+      type: object
+    policy_authorization:
+      description: Authorization request for A1 policy requests
+      properties:
+        input:
+          $ref: '#/components/schemas/input'
+      required:
+      - input
+      type: object
     policytype_id_list_v2:
       description: Information about policy types
       example:
index c8f97a8..8b29a31 100644 (file)
@@ -846,6 +846,17 @@ ul.nav-tabs {
   <script>
     // Script section to load models into a JS Var
     var defs = {}
+    defs["authorization_result"] = {
+  "required" : [ "result" ],
+  "type" : "object",
+  "properties" : {
+    "result" : {
+      "type" : "boolean",
+      "description" : "If true, the access is granted"
+    }
+  },
+  "description" : "Result of authorization"
+};
     defs["error_information"] = {
   "type" : "object",
   "properties" : {
@@ -862,6 +873,26 @@ ul.nav-tabs {
     }
   },
   "description" : "Problem as defined in https://tools.ietf.org/html/rfc7807"
+};
+    defs["input"] = {
+  "required" : [ "access_type", "auth_token", "policy_type_id" ],
+  "type" : "object",
+  "properties" : {
+    "access_type" : {
+      "type" : "string",
+      "description" : "Access type",
+      "enum" : [ "READ", "WRITE", "DELETE" ]
+    },
+    "auth_token" : {
+      "type" : "string",
+      "description" : "Authorization token"
+    },
+    "policy_type_id" : {
+      "type" : "string",
+      "description" : "Policy type identifier"
+    }
+  },
+  "description" : "input"
 };
     defs["Link"] = {
   "type" : "object",
@@ -873,6 +904,16 @@ ul.nav-tabs {
       "type" : "string"
     }
   }
+};
+    defs["policy_authorization"] = {
+  "required" : [ "input" ],
+  "type" : "object",
+  "properties" : {
+    "input" : {
+      "$ref" : "#/components/schemas/input"
+    }
+  },
+  "description" : "Authorization request for A1 policy requests"
 };
     defs["policy_id_list_v2"] = {
   "type" : "object",
@@ -1185,6 +1226,10 @@ ul.nav-tabs {
                     <li data-group="Actuator" data-name="threaddump" class="">
                       <a href="#api-Actuator-threaddump">threaddump</a>
                     </li>
+                  <li class="nav-header" data-group="AuthorizationAPI"><a href="#api-AuthorizationAPI">API Methods - AuthorizationAPI</a></li>
+                    <li data-group="AuthorizationAPI" data-name="performAccessControl" class="">
+                      <a href="#api-AuthorizationAPI-performAccessControl">performAccessControl</a>
+                    </li>
                   <li class="nav-header" data-group="Callbacks"><a href="#api-Callbacks">API Methods - Callbacks</a></li>
                     <li data-group="Callbacks" data-name="serviceCallback" class="">
                       <a href="#api-Callbacks-serviceCallback">serviceCallback</a>
@@ -9221,6 +9266,368 @@ pub fn main() {
                       </div>
                       <hr>
                   </section>
+                <section id="api-AuthorizationAPI">
+                  <h1>AuthorizationAPI</h1>
+                    <div id="api-AuthorizationAPI-performAccessControl">
+                      <article id="api-AuthorizationAPI-performAccessControl-0" data-group="User" data-name="performAccessControl" data-version="0">
+                        <div class="pull-left">
+                          <h1>performAccessControl</h1>
+                          <p>Request for access authorization.</p>
+                        </div>
+                        <div class="pull-right"></div>
+                        <div class="clearfix"></div>
+                        <p></p>
+                        <p class="marked">The authorization function decides if access is granted.</p>
+                        <p></p>
+                        <br />
+                        <pre class="prettyprint language-html prettyprinted" data-type="post"><code><span class="pln">/example-authz-check</span></code></pre>
+                        <p>
+                          <h3>Usage and SDK Samples</h3>
+                        </p>
+                        <ul class="nav nav-tabs nav-tabs-examples">
+                          <li class="active"><a href="#examples-AuthorizationAPI-performAccessControl-0-curl">Curl</a></li>
+                          <li class=""><a href="#examples-AuthorizationAPI-performAccessControl-0-java">Java</a></li>
+                          <li class=""><a href="#examples-AuthorizationAPI-performAccessControl-0-android">Android</a></li>
+                          <!--<li class=""><a href="#examples-AuthorizationAPI-performAccessControl-0-groovy">Groovy</a></li>-->
+                          <li class=""><a href="#examples-AuthorizationAPI-performAccessControl-0-objc">Obj-C</a></li>
+                          <li class=""><a href="#examples-AuthorizationAPI-performAccessControl-0-javascript">JavaScript</a></li>
+                          <!--<li class=""><a href="#examples-AuthorizationAPI-performAccessControl-0-angular">Angular</a></li>-->
+                          <li class=""><a href="#examples-AuthorizationAPI-performAccessControl-0-csharp">C#</a></li>
+                          <li class=""><a href="#examples-AuthorizationAPI-performAccessControl-0-php">PHP</a></li>
+                          <li class=""><a href="#examples-AuthorizationAPI-performAccessControl-0-perl">Perl</a></li>
+                          <li class=""><a href="#examples-AuthorizationAPI-performAccessControl-0-python">Python</a></li>
+                          <li class=""><a href="#examples-AuthorizationAPI-performAccessControl-0-rust">Rust</a></li>
+                        </ul>
+
+                        <div class="tab-content">
+                          <div class="tab-pane active" id="examples-AuthorizationAPI-performAccessControl-0-curl">
+                            <pre class="prettyprint"><code class="language-bsh">curl -X POST \
+ -H "Accept: application/json" \
+ -H "Content-Type: application/json" \
+ "http://localhost/example-authz-check" \
+ -d ''
+</code></pre>
+                          </div>
+                          <div class="tab-pane" id="examples-AuthorizationAPI-performAccessControl-0-java">
+                            <pre class="prettyprint"><code class="language-java">import org.openapitools.client.*;
+import org.openapitools.client.auth.*;
+import org.openapitools.client.model.*;
+import org.openapitools.client.api.AuthorizationAPIApi;
+
+import java.io.File;
+import java.util.*;
+
+public class AuthorizationAPIApiExample {
+    public static void main(String[] args) {
+
+        // Create an instance of the API class
+        AuthorizationAPIApi apiInstance = new AuthorizationAPIApi();
+        PolicyAuthorization policyAuthorization = ; // PolicyAuthorization | 
+
+        try {
+            authorization_result result = apiInstance.performAccessControl(policyAuthorization);
+            System.out.println(result);
+        } catch (ApiException e) {
+            System.err.println("Exception when calling AuthorizationAPIApi#performAccessControl");
+            e.printStackTrace();
+        }
+    }
+}
+</code></pre>
+                          </div>
+
+                          <div class="tab-pane" id="examples-AuthorizationAPI-performAccessControl-0-android">
+                            <pre class="prettyprint"><code class="language-java">import org.openapitools.client.api.AuthorizationAPIApi;
+
+public class AuthorizationAPIApiExample {
+    public static void main(String[] args) {
+        AuthorizationAPIApi apiInstance = new AuthorizationAPIApi();
+        PolicyAuthorization policyAuthorization = ; // PolicyAuthorization | 
+
+        try {
+            authorization_result result = apiInstance.performAccessControl(policyAuthorization);
+            System.out.println(result);
+        } catch (ApiException e) {
+            System.err.println("Exception when calling AuthorizationAPIApi#performAccessControl");
+            e.printStackTrace();
+        }
+    }
+}</code></pre>
+                          </div>
+  <!--
+  <div class="tab-pane" id="examples-AuthorizationAPI-performAccessControl-0-groovy">
+  <pre class="prettyprint language-json prettyprinted" data-type="json"><code>Coming Soon!</code></pre>
+  </div> -->
+                            <div class="tab-pane" id="examples-AuthorizationAPI-performAccessControl-0-objc">
+                              <pre class="prettyprint"><code class="language-cpp">
+
+// Create an instance of the API class
+AuthorizationAPIApi *apiInstance = [[AuthorizationAPIApi alloc] init];
+PolicyAuthorization *policyAuthorization = ; // 
+
+// Request for access authorization.
+[apiInstance performAccessControlWith:policyAuthorization
+              completionHandler: ^(authorization_result output, NSError* error) {
+    if (output) {
+        NSLog(@"%@", output);
+    }
+    if (error) {
+        NSLog(@"Error: %@", error);
+    }
+}];
+</code></pre>
+                            </div>
+
+                            <div class="tab-pane" id="examples-AuthorizationAPI-performAccessControl-0-javascript">
+                              <pre class="prettyprint"><code class="language-js">var A1PolicyManagementService = require('a1_policy_management_service');
+
+// Create an instance of the API class
+var api = new A1PolicyManagementService.AuthorizationAPIApi()
+var policyAuthorization = ; // {PolicyAuthorization} 
+
+var callback = function(error, data, response) {
+  if (error) {
+    console.error(error);
+  } else {
+    console.log('API called successfully. Returned data: ' + data);
+  }
+};
+api.performAccessControl(policyAuthorization, callback);
+</code></pre>
+                            </div>
+
+                            <!--<div class="tab-pane" id="examples-AuthorizationAPI-performAccessControl-0-angular">
+              <pre class="prettyprint language-json prettyprinted" data-type="json"><code>Coming Soon!</code></pre>
+            </div>-->
+                            <div class="tab-pane" id="examples-AuthorizationAPI-performAccessControl-0-csharp">
+                              <pre class="prettyprint"><code class="language-cs">using System;
+using System.Diagnostics;
+using Org.OpenAPITools.Api;
+using Org.OpenAPITools.Client;
+using Org.OpenAPITools.Model;
+
+namespace Example
+{
+    public class performAccessControlExample
+    {
+        public void main()
+        {
+
+            // Create an instance of the API class
+            var apiInstance = new AuthorizationAPIApi();
+            var policyAuthorization = new PolicyAuthorization(); // PolicyAuthorization | 
+
+            try {
+                // Request for access authorization.
+                authorization_result result = apiInstance.performAccessControl(policyAuthorization);
+                Debug.WriteLine(result);
+            } catch (Exception e) {
+                Debug.Print("Exception when calling AuthorizationAPIApi.performAccessControl: " + e.Message );
+            }
+        }
+    }
+}
+</code></pre>
+                            </div>
+
+                            <div class="tab-pane" id="examples-AuthorizationAPI-performAccessControl-0-php">
+                              <pre class="prettyprint"><code class="language-php"><&#63;php
+require_once(__DIR__ . '/vendor/autoload.php');
+
+// Create an instance of the API class
+$api_instance = new OpenAPITools\Client\Api\AuthorizationAPIApi();
+$policyAuthorization = ; // PolicyAuthorization | 
+
+try {
+    $result = $api_instance->performAccessControl($policyAuthorization);
+    print_r($result);
+} catch (Exception $e) {
+    echo 'Exception when calling AuthorizationAPIApi->performAccessControl: ', $e->getMessage(), PHP_EOL;
+}
+?></code></pre>
+                            </div>
+
+                            <div class="tab-pane" id="examples-AuthorizationAPI-performAccessControl-0-perl">
+                              <pre class="prettyprint"><code class="language-perl">use Data::Dumper;
+use WWW::OPenAPIClient::Configuration;
+use WWW::OPenAPIClient::AuthorizationAPIApi;
+
+# Create an instance of the API class
+my $api_instance = WWW::OPenAPIClient::AuthorizationAPIApi->new();
+my $policyAuthorization = WWW::OPenAPIClient::Object::PolicyAuthorization->new(); # PolicyAuthorization | 
+
+eval {
+    my $result = $api_instance->performAccessControl(policyAuthorization => $policyAuthorization);
+    print Dumper($result);
+};
+if ($@) {
+    warn "Exception when calling AuthorizationAPIApi->performAccessControl: $@\n";
+}</code></pre>
+                            </div>
+
+                            <div class="tab-pane" id="examples-AuthorizationAPI-performAccessControl-0-python">
+                              <pre class="prettyprint"><code class="language-python">from __future__ import print_statement
+import time
+import openapi_client
+from openapi_client.rest import ApiException
+from pprint import pprint
+
+# Create an instance of the API class
+api_instance = openapi_client.AuthorizationAPIApi()
+policyAuthorization =  # PolicyAuthorization | 
+
+try:
+    # Request for access authorization.
+    api_response = api_instance.perform_access_control(policyAuthorization)
+    pprint(api_response)
+except ApiException as e:
+    print("Exception when calling AuthorizationAPIApi->performAccessControl: %s\n" % e)</code></pre>
+                            </div>
+
+                            <div class="tab-pane" id="examples-AuthorizationAPI-performAccessControl-0-rust">
+                              <pre class="prettyprint"><code class="language-rust">extern crate AuthorizationAPIApi;
+
+pub fn main() {
+    let policyAuthorization = ; // PolicyAuthorization
+
+    let mut context = AuthorizationAPIApi::Context::default();
+    let result = client.performAccessControl(policyAuthorization, &context).wait();
+
+    println!("{:?}", result);
+}
+</code></pre>
+                            </div>
+                          </div>
+
+                          <h2>Scopes</h2>
+                          <table>
+                            
+                          </table>
+
+                          <h2>Parameters</h2>
+
+
+
+                            <div class="methodsubtabletitle">Body parameters</div>
+                            <table id="methodsubtable">
+                              <tr>
+                                <th width="150px">Name</th>
+                                <th>Description</th>
+                              </tr>
+                                <tr><td style="width:150px;">policyAuthorization <span style="color:red;">*</span></td>
+<td>
+<p class="marked"></p>
+<script>
+$(document).ready(function() {
+  var schemaWrapper = {
+  "content" : {
+    "application/json" : {
+      "schema" : {
+        "$ref" : "#/components/schemas/policy_authorization"
+      }
+    }
+  },
+  "required" : true
+};
+
+  var schema = findNode('schema',schemaWrapper).schema;
+  if (!schema) {
+    schema = schemaWrapper.schema;
+  }
+  if (schema.$ref != null) {
+    schema = defsParser.$refs.get(schema.$ref);
+  } else {
+    schemaWrapper.definitions = Object.assign({}, defs);
+    $RefParser.dereference(schemaWrapper).catch(function(err) {
+      console.log(err);
+    });
+  }
+
+  var view = new JSONSchemaView(schema,2,{isBodyParam: true});
+  var result = $('#d2e199_performAccessControl_policyAuthorization');
+  result.empty();
+  result.append(view.render());
+});
+</script>
+<div id="d2e199_performAccessControl_policyAuthorization"></div>
+</td>
+</tr>
+
+                            </table>
+
+
+
+                          <h2>Responses</h2>
+                            <h3 id="examples-AuthorizationAPI-performAccessControl-title-200"></h3>
+                            <p id="examples-AuthorizationAPI-performAccessControl-description-200" class="marked"></p>
+                            <script>
+                              var responseAuthorizationAPI200_description = `OK`;
+                              var responseAuthorizationAPI200_description_break = responseAuthorizationAPI200_description.indexOf('\n');
+                              if (responseAuthorizationAPI200_description_break == -1) {
+                                $("#examples-AuthorizationAPI-performAccessControl-title-200").text("Status: 200 - " + responseAuthorizationAPI200_description);
+                              } else {
+                                $("#examples-AuthorizationAPI-performAccessControl-title-200").text("Status: 200 - " + responseAuthorizationAPI200_description.substring(0, responseAuthorizationAPI200_description_break));
+                                $("#examples-AuthorizationAPI-performAccessControl-description-200").html(responseAuthorizationAPI200_description.substring(responseAuthorizationAPI200_description_break));
+                              }
+                            </script>
+
+
+                            <ul id="responses-detail-AuthorizationAPI-performAccessControl-200" class="nav nav-tabs nav-tabs-examples" >
+                                <li class="active">
+                                  <a data-toggle="tab" href="#responses-AuthorizationAPI-performAccessControl-200-schema">Schema</a>
+                                </li>
+
+
+
+
+                            </ul>
+
+
+                            <div class="tab-content" id="responses-AuthorizationAPI-performAccessControl-200-wrapper" style='margin-bottom: 10px;'>
+                                <div class="tab-pane active" id="responses-AuthorizationAPI-performAccessControl-200-schema">
+                                  <div id="responses-AuthorizationAPI-performAccessControl-schema-200" class="exampleStyle">
+                                    <script>
+                                      $(document).ready(function() {
+                                        var schemaWrapper = {
+  "description" : "OK",
+  "content" : {
+    "application/json" : {
+      "schema" : {
+        "$ref" : "#/components/schemas/authorization_result"
+      }
+    }
+  }
+};
+                                        var schema = findNode('schema',schemaWrapper).schema;
+                                        if (!schema) {
+                                          schema = schemaWrapper.schema;
+                                        }
+                                        if (schema.$ref != null) {
+                                          schema = defsParser.$refs.get(schema.$ref);
+                                        } else if (schema.items != null && schema.items.$ref != null) {
+                                            schema.items = defsParser.$refs.get(schema.items.$ref);
+                                        } else {
+                                          schemaWrapper.definitions = Object.assign({}, defs);
+                                          $RefParser.dereference(schemaWrapper).catch(function(err) {
+                                            console.log(err);
+                                          });
+                                        }
+
+                                        var view = new JSONSchemaView(schema, 3);
+                                        $('#responses-AuthorizationAPI-performAccessControl-200-schema-data').val(JSON.stringify(schema));
+                                        var result = $('#responses-AuthorizationAPI-performAccessControl-schema-200');
+                                        result.empty();
+                                        result.append(view.render());
+                                      });
+                                    </script>
+                                  </div>
+                                  <input id='responses-AuthorizationAPI-performAccessControl-200-schema-data' type='hidden' value=''></input>
+                                </div>
+                            </div>
+                        </article>
+                      </div>
+                      <hr>
+                  </section>
                 <section id="api-Callbacks">
                   <h1>Callbacks</h1>
                     <div id="api-Callbacks-serviceCallback">
index 44e0b07..4f80d2e 100644 (file)
@@ -46,7 +46,7 @@ logging:
     org.springframework: ERROR
     org.springframework.data: ERROR
     org.springframework.web.reactive.function.client.ExchangeFunctions: ERROR
-    org.springframework.web.servlet.DispatcherServlet: INFO
+    org.springframework.web.servlet.DispatcherServlet: ERROR
     org.onap.ccsdk.oran.a1policymanagementservice: INFO
   pattern:
     console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] [%thread] %logger{20} - %msg%n"
@@ -91,6 +91,9 @@ app:
   # A file containing an authorization token, which shall be inserted in each HTTP header (authorization).
   # If the file name is empty, no authorization token is sent.
   auth-token-file:
+  # A URL to authorization provider such as OPA. Each time an A1 Policy is accessed, a call to this
+  # authorization provider is done for access control. If this is empty, no fine grained access control is done.
+  authorization-provider:
   # S3 object store usage is enabled by defining the bucket to use. This will override the vardata-directory parameter.
   s3:
     endpointOverride: http://localhost:9000
index eea9692..3de674b 100644 (file)
@@ -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 (file)
index 0000000..0f376c0
--- /dev/null
@@ -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 (file)
index 0000000..a905b9d
--- /dev/null
@@ -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() {}
+
+}
@@ -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 (file)
index 0000000..8dd4e7b
--- /dev/null
@@ -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;
+
+}
index 395daa3..64905f4 100644 (file)
@@ -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);
index c3f4176..ed97820 100644 (file)
@@ -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;
index 77ac0f4..7eddeae 100644 (file)
@@ -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 (file)
index 0000000..236c31b
--- /dev/null
@@ -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);
+    }
+
+}
index 9f0473d..066adc4 100644 (file)
@@ -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();
@@ -550,6 +572,57 @@ class ApplicationTest {
         this.rics.getRic(ricId).setState(Ric.RicState.AVAILABLE);
     }
 
+    @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 {
@@ -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);
         }
index 7574032..9efa7b7 100644 (file)
                 "type": "string"
             }}
         },
+        "authorization_result": {
+            "description": "Result of authorization",
+            "type": "object",
+            "required": ["result"],
+            "properties": {"result": {
+                "description": "If true, the access is granted",
+                "type": "boolean"
+            }}
+        },
         "ric_info_v2": {
             "description": "Information for a Near-RT RIC",
             "type": "object",
                 "type": "object"
             }}
         },
+        "input": {
+            "description": "input",
+            "type": "object",
+            "required": [
+                "access_type",
+                "auth_token",
+                "policy_type_id"
+            ],
+            "properties": {
+                "access_type": {
+                    "description": "Access type",
+                    "type": "string",
+                    "enum": [
+                        "READ",
+                        "WRITE",
+                        "DELETE"
+                    ]
+                },
+                "auth_token": {
+                    "description": "Authorization token",
+                    "type": "string"
+                },
+                "policy_type_id": {
+                    "description": "Policy type identifier",
+                    "type": "string"
+                }
+            }
+        },
+        "policy_authorization": {
+            "description": "Authorization request for A1 policy requests",
+            "type": "object",
+            "required": ["input"],
+            "properties": {"input": {"$ref": "#/components/schemas/input"}}
+        },
         "policytype_id_list_v2": {
             "description": "Information about policy types",
             "type": "object",
             ],
             "tags": ["A1 Policy Management"]
         }},
+        "/example-authz-check": {"post": {
+            "summary": "Request for access authorization.",
+            "requestBody": {
+                "content": {"application/json": {"schema": {"$ref": "#/components/schemas/policy_authorization"}}},
+                "required": true
+            },
+            "description": "The authorization function decides if access is granted.",
+            "operationId": "performAccessControl",
+            "responses": {"200": {
+                "description": "OK",
+                "content": {"application/json": {"schema": {"$ref": "#/components/schemas/authorization_result"}}}
+            }},
+            "tags": ["Authorization API"]
+        }},
         "/actuator/threaddump": {"get": {
             "summary": "Actuator web endpoint 'threaddump'",
             "operationId": "threaddump",
         "title": "A1 Policy Management Service",
         "version": "1.1.0"
     },
-    "tags": [{
-        "name": "Actuator",
-        "description": "Monitor and interact",
-        "externalDocs": {
-            "description": "Spring Boot Actuator Web API Documentation",
-            "url": "https://docs.spring.io/spring-boot/docs/current/actuator-api/html/"
+    "tags": [
+        {
+            "name": "Authorization API",
+            "description": "API used for authorization of information A1 policy access (this is provided by an authorization producer such as OPA).\nNote that this API is called by PMS, it is not provided.\n"
+        },
+        {
+            "name": "Actuator",
+            "description": "Monitor and interact",
+            "externalDocs": {
+                "description": "Spring Boot Actuator Web API Documentation",
+                "url": "https://docs.spring.io/spring-boot/docs/current/actuator-api/html/"
+            }
         }
-    }]
+    ]
 }
\ No newline at end of file