diff options
author | Deena Mukundan <dm00536893@techmahindra.com> | 2024-12-23 10:19:53 +0100 |
---|---|---|
committer | Deena Mukundan <dm00536893@techmahindra.com> | 2024-12-23 15:30:03 +0100 |
commit | 38b5a22de90e3055d00136fb8b036a692f0a79a7 (patch) | |
tree | ed3f029a75771c4208da8bf2b840cbf5d58f60a8 | |
parent | bc208ae0a6b0c995f65c26564fb8b06ef270bf67 (diff) |
OPA-PDP URL changes and bugfix
Issue-ID: POLICY-5222
Change-Id: Ia3c214b93f3b5fa18e482e100ac8c255544e0ba6
Signed-off-by: Deena Mukundan <dm00536893@techmahindra.com>
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | api/openapi.yaml | 12 | ||||
-rw-r--r-- | api/register-handlers.go | 6 | ||||
-rw-r--r-- | api/register-handlers_test.go | 4 | ||||
-rw-r--r-- | pkg/decision/decision-provider.go | 21 | ||||
-rw-r--r-- | pkg/kafkacomm/handler/pdp_state_change_handler_test.go | 31 | ||||
-rw-r--r-- | pkg/kafkacomm/publisher/1 | 148 | ||||
-rw-r--r-- | pkg/kafkacomm/publisher/pdp-heartbeat.go | 8 | ||||
-rw-r--r-- | pkg/kafkacomm/publisher/pdp-heartbeat_test.go | 18 | ||||
-rw-r--r-- | test/README.md | 34 | ||||
-rw-r--r-- | test/data/docs/data.json | 7 | ||||
-rw-r--r-- | test/data/vehicle/data.json | 7 | ||||
-rw-r--r-- | test/data/zone/data.json | 10 | ||||
-rw-r--r-- | test/policies/docs/policy.rego | 22 | ||||
-rw-r--r-- | test/policies/vehicle/policy.rego | 23 | ||||
-rw-r--r-- | test/policies/zone/policy.rego | 23 | ||||
-rw-r--r-- | version | 2 |
17 files changed, 326 insertions, 52 deletions
@@ -14,7 +14,7 @@ deploy: build_image .PHONY: test test: - @go test -v ./... + @go test -v -p 1 ./... format: @go fmt ./... diff --git a/api/openapi.yaml b/api/openapi.yaml index a9b8191..bd94ec2 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -27,8 +27,8 @@ info: name: Deena Mukundan
email: dm00536893@techmahindra.com
servers:
-- url: http://policy-opa-pdp:8282/policy/pdpx/v1
-- url: https://policy-opa-pdp:8282/policy/pdpx/v1
+- url: http://policy-opa-pdp:8282/policy/pdpo/v1
+- url: https://policy-opa-pdp:8282/policy/pdpo/v1
tags:
- name: Decision
- name: Statistics
@@ -110,7 +110,7 @@ paths: - basicAuth: []
x-interface info:
last-mod-release: Paris
- pdpx-version: 1.0.0
+ pdpo-version: 1.0.0
x-codegen-request-body-name: body
/healthcheck:
get:
@@ -170,13 +170,13 @@ paths: - basicAuth: []
x-interface info:
last-mod-release: Paris
- pdpx-version: 1.0.0
+ pdpo-version: 1.0.0
/statistics:
get:
tags:
- Statistics
summary: Fetch current statistics
- description: Provides current statistics of the Policy OPA PDP component
+ description: Provides current statistics of the Policy OPA PDP component
operationId: statistics
parameters:
- name: X-ONAP-RequestID
@@ -229,7 +229,7 @@ paths: - basicAuth: []
x-interface info:
last-mod-release: Paris
- pdpx-version: 1.0.0
+ pdpo-version: 1.0.0
components:
schemas:
ErrorResponse:
diff --git a/api/register-handlers.go b/api/register-handlers.go index 4b21314..c5eb5df 100644 --- a/api/register-handlers.go +++ b/api/register-handlers.go @@ -35,7 +35,7 @@ func RegisterHandlers() { // Handler for OPA decision making opaDecisionHandler := http.HandlerFunc(decision.OpaDecision) - http.Handle("/policy/pdpx/v1/decision", basicAuth(opaDecisionHandler)) + http.Handle("/policy/pdpo/v1/decision", basicAuth(opaDecisionHandler)) //This api is used internally by OPA-SDK bundleServerHandler := http.HandlerFunc(bundleserver.GetBundle) @@ -47,11 +47,11 @@ func RegisterHandlers() { // Handler for health checks healthCheckHandler := http.HandlerFunc(healthcheck.HealthCheckHandler) - http.HandleFunc("/policy/pdpx/v1/healthcheck", basicAuth(healthCheckHandler)) + http.HandleFunc("/policy/pdpo/v1/healthcheck", basicAuth(healthCheckHandler)) // Handler for statistics report statisticsReportHandler := http.HandlerFunc(metrics.FetchCurrentStatistics) - http.HandleFunc("/policy/pdpx/v1/statistics", basicAuth(statisticsReportHandler)) + http.HandleFunc("/policy/pdpo/v1/statistics", basicAuth(statisticsReportHandler)) } diff --git a/api/register-handlers_test.go b/api/register-handlers_test.go index 801cb0e..f67c1f5 100644 --- a/api/register-handlers_test.go +++ b/api/register-handlers_test.go @@ -43,10 +43,10 @@ func TestRegisterHandlers(t *testing.T) { handler http.HandlerFunc statusCode int }{ - {"/policy/pdpx/v1/decision", decision.OpaDecision, http.StatusUnauthorized}, + {"/policy/pdpo/v1/decision", decision.OpaDecision, http.StatusUnauthorized}, {"/opa/bundles/", bundleserver.GetBundle, http.StatusInternalServerError}, {"/ready", readinessProbe, http.StatusOK}, - {"/policy/pdpx/v1/healthcheck", healthcheck.HealthCheckHandler, http.StatusUnauthorized}, + {"/policy/pdpo/v1/healthcheck", healthcheck.HealthCheckHandler, http.StatusUnauthorized}, } for _, tt := range tests { diff --git a/pkg/decision/decision-provider.go b/pkg/decision/decision-provider.go index 48d6edf..5f45668 100644 --- a/pkg/decision/decision-provider.go +++ b/pkg/decision/decision-provider.go @@ -76,7 +76,7 @@ func writeErrorJSONResponse(res http.ResponseWriter, status int, errorDescriptio // creates a decision response based on the provided parameters func createSuccessDecisionResponse(statusMessage, decision, policyName string, output map[string]interface{}) *oapicodegen.OPADecisionResponse { return &oapicodegen.OPADecisionResponse{ - StatusMessage: &statusMessage, + StatusMessage: &statusMessage, Decision: (*oapicodegen.OPADecisionResponseDecision)(&decision), PolicyName: &policyName, Output: &output, @@ -128,13 +128,14 @@ func OpaDecision(res http.ResponseWriter, req *http.Request) { log.Debugf("%s: %s", key, value) } // Check if the system is in an active state + if pdpstate.GetCurrentState() != model.Active { msg := " System Is In PASSIVE State so Unable To Handle Decision wait until it becomes ACTIVE" errorMsg := " System Is In PASSIVE State so error Handling the request" decisionExc := createDecisionExceptionResponse(http.StatusInternalServerError, msg, []string{errorMsg}, "") metrics.IncrementTotalErrorCount() writeErrorJSONResponse(res, http.StatusInternalServerError, msg, *decisionExc) - return + return } ctx := context.Background() @@ -184,7 +185,7 @@ func OpaDecision(res http.ResponseWriter, req *http.Request) { log.Debugf("SDK making a decision") options := sdk.DecisionOptions{Path: *decisionReq.PolicyName, Input: decisionReq.Input} - decision, err := opa.Decision(ctx, options) + decision, decision_err := opa.Decision(ctx, options) jsonOutput, err := json.MarshalIndent(decision, "", " ") if err != nil { @@ -194,18 +195,18 @@ func OpaDecision(res http.ResponseWriter, req *http.Request) { log.Debugf("RAW opa Decision output:\n%s\n", string(jsonOutput)) // Check for errors in the OPA decision - if err != nil { - if strings.Contains(err.Error(), "opa_undefined_error") { - decisionRes := createSuccessDecisionResponse(err.Error(), string(oapicodegen.INDETERMINATE), + if decision_err != nil { + if strings.Contains(decision_err.Error(), "opa_undefined_error") { + decisionRes := createSuccessDecisionResponse(decision_err.Error(), string(oapicodegen.INDETERMINATE), *decisionReq.PolicyName, nil) writeOpaJSONResponse(res, http.StatusOK, *decisionRes) metrics.IncrementIndeterminantDecisionsCount() return } else { decisionExc := createDecisionExceptionResponse(http.StatusBadRequest, "Error from OPA while making decision", - []string{err.Error()}, *decisionReq.PolicyName) + []string{decision_err.Error()}, *decisionReq.PolicyName) metrics.IncrementTotalErrorCount() - writeErrorJSONResponse(res, http.StatusBadRequest, err.Error(), *decisionExc) + writeErrorJSONResponse(res, http.StatusBadRequest, decision_err.Error(), *decisionExc) return } } @@ -310,8 +311,8 @@ func OpaDecision(res http.ResponseWriter, req *http.Request) { default: // Handle unexpected types in decision.Result - decisionRes := createSuccessDecisionResponse("Invalid decision result format", string(oapicodegen.DENY), *decisionReq.PolicyName, nil) - metrics.IncrementDenyDecisionsCount() + decisionRes := createSuccessDecisionResponse("Invalid decision result format", string(oapicodegen.INDETERMINATE), *decisionReq.PolicyName, nil) + metrics.IncrementIndeterminantDecisionsCount() writeOpaJSONResponse(res, http.StatusOK, *decisionRes) return } diff --git a/pkg/kafkacomm/handler/pdp_state_change_handler_test.go b/pkg/kafkacomm/handler/pdp_state_change_handler_test.go index 67edd6f..da3832b 100644 --- a/pkg/kafkacomm/handler/pdp_state_change_handler_test.go +++ b/pkg/kafkacomm/handler/pdp_state_change_handler_test.go @@ -24,7 +24,7 @@ import ( "policy-opa-pdp/pkg/model" "policy-opa-pdp/pkg/pdpstate" "testing" - + "fmt" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) @@ -56,6 +56,7 @@ func TestPdpStateChangeMessageHandler(t *testing.T) { expectedState string mockError error expectError bool + checkNotEqual bool }{ { name: "Valid state change", @@ -63,20 +64,36 @@ func TestPdpStateChangeMessageHandler(t *testing.T) { expectedState: "ACTIVE", mockError: nil, expectError: false, + checkNotEqual: false, }, { name: "Invalid JSON", message: []byte(`{"state":}`), mockError: nil, expectError: true, + checkNotEqual: true, }, + { + name: "Error in SendStateChangeResponse", + message: []byte(`{"state":"PASSIVE"}`), + expectedState: "PASSIVE", + mockError: assert.AnError, + expectError: true, + checkNotEqual: false, + }, } - for _, tt := range tests { + for i, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Set up the mock to return the expected error - mockSender.On("SendStateChangeResponse", mock.Anything, mock.Anything).Return(tt.mockError) - mockSender.On("SendPdpStatus", mock.Anything).Return(nil) + if i == 0 { + mockSender.On("SendStateChangeResponse", mock.Anything, mock.Anything).Return(tt.mockError).Once() + mockSender.On("SendPdpStatus", mock.Anything).Return(nil).Once() + } else if i != 1 { + mockSender.On("SendStateChangeResponse", mock.Anything, mock.Anything).Return(fmt.Errorf("failed to send PDP status")) + mockSender.On("SendPdpStatus", mock.Anything).Return(fmt.Errorf("failed to send PDP status")) + } + // Call the handler err := PdpStateChangeMessageHandler(tt.message, mockSender) @@ -86,7 +103,11 @@ func TestPdpStateChangeMessageHandler(t *testing.T) { assert.Error(t, err) } else { assert.NoError(t, err) - assert.Equal(t, tt.expectedState, pdpstate.GetState().String()) + if tt.checkNotEqual { + assert.NotEqual(t, tt.expectedState, pdpstate.GetState().String()) + } else { + assert.Equal(t, tt.expectedState, pdpstate.GetState().String()) + } } }) diff --git a/pkg/kafkacomm/publisher/1 b/pkg/kafkacomm/publisher/1 new file mode 100644 index 0000000..bfd5272 --- /dev/null +++ b/pkg/kafkacomm/publisher/1 @@ -0,0 +1,148 @@ +// - +// ========================LICENSE_START================================= +// Copyright (C) 2024: Deutsche Telekom +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 +// ========================LICENSE_END=================================== +// + +package publisher + +import ( + /* "fmt" + "policy-opa-pdp/cfg" + "policy-opa-pdp/consts" + "policy-opa-pdp/pkg/log" + "policy-opa-pdp/pkg/model" + "policy-opa-pdp/pkg/pdpstate"*/ + "errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "policy-opa-pdp/pkg/kafkacomm/publisher/mocks" + "testing" + // "time" + /* "github.com/google/uuid"*/) + +var ( +// ticker *time.Ticker +// stopChan chan bool +// currentInterval int64 +) + +/* +Success Case 1 +TestStartHeartbeatIntervalTimer_ValidInterval +Description: Test starting the heartbeat interval timer with a valid interval. +Input: intervalMs = 1000 +Expected Output: The ticker starts with an interval of 1000 milliseconds, and heartbeat messages are sent at this interval. +*/ +func TestStartHeartbeatIntervalTimer_ValidInterval(t *testing.T) { + + intervalMs := int64(1000) + mockSender := new(mocks.PdpStatusSender) + mockSender.On("SendPdpStatus", mock.Anything).Return(nil) + + StartHeartbeatIntervalTimer(intervalMs, mockSender) + mu.Lock() + defer mu.Unlock() + if ticker == nil { + t.Errorf("Expected ticker to be initialized") + } + if currentInterval != intervalMs { + t.Errorf("Expected currentInterval to be %d, got %d", intervalMs, currentInterval) + } +} + +/* +Failure Case 1 +TestStartHeartbeatIntervalTimer_InvalidInterval +Description: Test starting the heartbeat interval timer with an invalid interval. +Input: intervalMs = -1000 +Expected Output: The function should handle the invalid interval gracefully, possibly by logging an error message and not starting the ticker. +*/ +func TestStartHeartbeatIntervalTimer_InvalidInterval(t *testing.T) { + intervalMs := int64(-1000) + mockSender := new(mocks.PdpStatusSender) + mockSender.On("SendPdpStatus", mock.Anything).Return(nil) + + StartHeartbeatIntervalTimer(intervalMs, mockSender) + mu.Lock() + defer mu.Unlock() + + if ticker != nil { + t.Log("Expected ticker to be nil for invalid interval") + } +} + +/* +TestSendPDPHeartBeat_Success 2 +Description: Test sending a heartbeat successfully. +Input: Valid pdpStatus object +Expected Output: Heartbeat message is sent successfully, and a debug log "Message sent successfully" is generated. +*/ +/* +func TestSendPDPHeartBeat_Success(t *testing.T) { + + mockSender := new(mocks.PdpStatusSender) + mockSender.On("SendPdpStatus", mock.Anything).Return(nil) + err := sendPDPHeartBeat(mockSender) + assert.NoError(t, err) +} +*/ +/* +TestSendPDPHeartBeat_Failure 2 +Description: Test failing to send a heartbeat. +Input: Invalid pdpStatus object or network failure +Expected Output: An error occurs while sending the heartbeat, and a warning log "Error producing message: ..." is generated. +*/ +/* +func TestSendPDPHeartBeat_Failure(t *testing.T) { + // Mock SendPdpStatus to return an error + mockSender := new(mocks.PdpStatusSender) + mockSender.On("SendPdpStatus", mock.Anything).Return(errors.New("Error producing message")) + err := sendPDPHeartBeat(mockSender) + assert.Error(t, err) +} +*/ + +/* +TestStopTicker_Success 3 +Description: Test stopping the ticker. +Input: Ticker is running +Expected Output: The ticker stops, and the stop channel is closed. +*/ +func TestStopTicker_Success(t *testing.T) { + mockSender := new(mocks.PdpStatusSender) + mockSender.On("SendPdpStatus", mock.Anything).Return(nil) + StartHeartbeatIntervalTimer(1000, mockSender) + + StopTicker() + mu.Lock() + defer mu.Unlock() + if ticker != nil { + t.Errorf("Expected ticker to be nil") + } +} + +/* +TestStopTicker_NotRunning 3 +Description: Test stopping the ticker when it is not running. +Input: Ticker is not running +Expected Output: The function should handle this case gracefully, possibly by logging a debug message indicating that the ticker is not running. +*/ +func TestStopTicker_NotRunning(t *testing.T) { + StopTicker() + mu.Lock() + defer mu.Unlock() +} diff --git a/pkg/kafkacomm/publisher/pdp-heartbeat.go b/pkg/kafkacomm/publisher/pdp-heartbeat.go index fbd07d6..0891add 100644 --- a/pkg/kafkacomm/publisher/pdp-heartbeat.go +++ b/pkg/kafkacomm/publisher/pdp-heartbeat.go @@ -30,7 +30,7 @@ import ( "policy-opa-pdp/pkg/pdpattributes" "policy-opa-pdp/pkg/pdpstate" "time" - + "sync" "github.com/google/uuid" ) @@ -38,6 +38,7 @@ var ( ticker *time.Ticker stopChan chan bool currentInterval int64 + mu sync.Mutex ) // Initializes a timer that sends periodic heartbeat messages to indicate the health and state of the PDP. @@ -47,6 +48,8 @@ func StartHeartbeatIntervalTimer(intervalMs int64, s PdpStatusSender) { ticker = nil return } + mu.Lock() + defer mu.Unlock() if ticker != nil && intervalMs == currentInterval { log.Debug("Ticker is already running") @@ -102,10 +105,13 @@ func sendPDPHeartBeat(s PdpStatusSender) error { // Stops the running ticker and terminates the goroutine managing heartbeat messages. func StopTicker() { + mu.Lock() + defer mu.Unlock() if ticker != nil && stopChan != nil { stopChan <- true close(stopChan) ticker = nil + stopChan = nil } else { log.Debugf("Ticker is not Running") } diff --git a/pkg/kafkacomm/publisher/pdp-heartbeat_test.go b/pkg/kafkacomm/publisher/pdp-heartbeat_test.go index e95866e..7548177 100644 --- a/pkg/kafkacomm/publisher/pdp-heartbeat_test.go +++ b/pkg/kafkacomm/publisher/pdp-heartbeat_test.go @@ -20,12 +20,6 @@ package publisher import ( - /* "fmt" - "policy-opa-pdp/cfg" - "policy-opa-pdp/consts" - "policy-opa-pdp/pkg/log" - "policy-opa-pdp/pkg/model" - "policy-opa-pdp/pkg/pdpstate"*/ "errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -54,6 +48,8 @@ func TestStartHeartbeatIntervalTimer_ValidInterval(t *testing.T) { mockSender.On("SendPdpStatus", mock.Anything).Return(nil) StartHeartbeatIntervalTimer(intervalMs, mockSender) + mu.Lock() + defer mu.Unlock() if ticker == nil { t.Errorf("Expected ticker to be initialized") } @@ -75,6 +71,8 @@ func TestStartHeartbeatIntervalTimer_InvalidInterval(t *testing.T) { mockSender.On("SendPdpStatus", mock.Anything).Return(nil) StartHeartbeatIntervalTimer(intervalMs, mockSender) + mu.Lock() + defer mu.Unlock() if ticker != nil { t.Log("Expected ticker to be nil for invalid interval") @@ -87,6 +85,7 @@ Description: Test sending a heartbeat successfully. Input: Valid pdpStatus object Expected Output: Heartbeat message is sent successfully, and a debug log "Message sent successfully" is generated. */ + func TestSendPDPHeartBeat_Success(t *testing.T) { mockSender := new(mocks.PdpStatusSender) @@ -101,6 +100,7 @@ Description: Test failing to send a heartbeat. Input: Invalid pdpStatus object or network failure Expected Output: An error occurs while sending the heartbeat, and a warning log "Error producing message: ..." is generated. */ + func TestSendPDPHeartBeat_Failure(t *testing.T) { // Mock SendPdpStatus to return an error mockSender := new(mocks.PdpStatusSender) @@ -109,6 +109,7 @@ func TestSendPDPHeartBeat_Failure(t *testing.T) { assert.Error(t, err) } + /* TestStopTicker_Success 3 Description: Test stopping the ticker. @@ -119,7 +120,10 @@ func TestStopTicker_Success(t *testing.T) { mockSender := new(mocks.PdpStatusSender) mockSender.On("SendPdpStatus", mock.Anything).Return(nil) StartHeartbeatIntervalTimer(1000, mockSender) + StopTicker() + mu.Lock() + defer mu.Unlock() if ticker != nil { t.Errorf("Expected ticker to be nil") } @@ -133,4 +137,6 @@ Expected Output: The function should handle this case gracefully, possibly by lo */ func TestStopTicker_NotRunning(t *testing.T) { StopTicker() + mu.Lock() + defer mu.Unlock() } diff --git a/test/README.md b/test/README.md index 1052749..51cdcf0 100644 --- a/test/README.md +++ b/test/README.md @@ -2,91 +2,91 @@ ## Verification API Calls -curl -u 'policyadmin:zb!XztG34' -H 'Content-Type: application/json' -H 'Accept: application/json' -d '{"onapName":"CDS","onapComponent":"CDS","onapInstance":"CDS","currentDate": "2024-11-22", "currentTime": "2024-11-22T11:34:56Z", "timeZone": "UTC", "timeOffset": "+05:30", "currentDateTime": "2024-11-22T12:08:00Z","policyName":"example/allow","input":{"method":"POST","path":["users"]}}' -X POST http://0.0.0.0:8282/policy/pdpx/v1/decision +curl -u 'policyadmin:zb!XztG34' -H 'Content-Type: application/json' -H 'Accept: application/json' -d '{"onapName":"CDS","onapComponent":"CDS","onapInstance":"CDS","currentDate": "2024-11-22", "currentTime": "2024-11-22T11:34:56Z", "timeZone": "UTC", "timeOffset": "+05:30", "currentDateTime": "2024-11-22T12:08:00Z","policyName":"example/allow","input":{"method":"POST","path":["users"]}}' -X POST http://0.0.0.0:8282/policy/pdpo/v1/decision -curl -u 'policyadmin:zb!XztG34' -H 'Content-Type: application/json' -H 'Accept: application/json' --header 'X-ONAP-RequestID:8e6f784e-c9cb-42f6-bcc9-edb5d0af1ce1' -d '{"onapName":"CDS","onapComponent":"CDS","onapInstance":"CDS","currentDate": "2024-11-22", "currentTime": "2024-11-22T11:34:56Z", "timeZone": "UTC", "timeOffset": "+05:30", "currentDateTime": "2024-11-22T12:08:00Z", "policyName":"role/allow","input":{"user":"alice","action":"write","object":"id123","type":"dog"}}' -X POST http://0.0.0.0:8282/policy/pdpx/v1/decision +curl -u 'policyadmin:zb!XztG34' -H 'Content-Type: application/json' -H 'Accept: application/json' --header 'X-ONAP-RequestID:8e6f784e-c9cb-42f6-bcc9-edb5d0af1ce1' -d '{"onapName":"CDS","onapComponent":"CDS","onapInstance":"CDS","currentDate": "2024-11-22", "currentTime": "2024-11-22T11:34:56Z", "timeZone": "UTC", "timeOffset": "+05:30", "currentDateTime": "2024-11-22T12:08:00Z", "policyName":"role/allow","input":{"user":"alice","action":"write","object":"id123","type":"dog"}}' -X POST http://0.0.0.0:8282/policy/pdpo/v1/decision ## PERMIT for policy:action -curl -u 'policyadmin:zb!XztG34' -H 'Content-Type: application/json' -H 'Accept: application/json' --header 'X-ONAP-RequestID:8e6f784e-c9cb-42f6-bcc9-edb5d0af1ce1' -d '{"onapName":"CDS","onapComponent":"CDS","onapInstance":"CDS", "currentDate": "2024-11-22", "currentTime": "2024-11-22T11:34:56Z", "timeZone": "UTC", "timeOffset": "+05:30", "currentDateTime": "2024-11-22T12:08:00Z","policyName":"action/allow","input":{"user":"alice","action":"delete","type":"server"}}' -X POST http://0.0.0.0:8282/policy/pdpx/v1/decision +curl -u 'policyadmin:zb!XztG34' -H 'Content-Type: application/json' -H 'Accept: application/json' --header 'X-ONAP-RequestID:8e6f784e-c9cb-42f6-bcc9-edb5d0af1ce1' -d '{"onapName":"CDS","onapComponent":"CDS","onapInstance":"CDS", "currentDate": "2024-11-22", "currentTime": "2024-11-22T11:34:56Z", "timeZone": "UTC", "timeOffset": "+05:30", "currentDateTime": "2024-11-22T12:08:00Z","policyName":"action/allow","input":{"user":"alice","action":"delete","type":"server"}}' -X POST http://0.0.0.0:8282/policy/pdpo/v1/decision {"decision":"PERMIT","policyName":"action/allow","statusMessage":"OPA Allowed"} ## DENY for policy:action -curl -u 'policyadmin:zb!XztG34' -H 'Content-Type: application/json' -H 'Accept: application/json' --header 'X-ONAP-RequestID:8e6f784e-c9cb-42f6-bcc9-edb5d0af1ce1' -d '{"onapName":"CDS","onapComponent":"CDS","onapInstance":"CDS", "currentDate": "2024-11-22", "currentTime": "2024-11-22T11:34:56Z", "timeZone": "UTC", "timeOffset": "+05:30", "currentDateTime": "2024-11-22T12:08:00Z","policyName":"action/allow","input":{"user":"charlie","action":"delete","type":"server"}}' -X POST http://0.0.0.0:8282/policy/pdpx/v1/decision +curl -u 'policyadmin:zb!XztG34' -H 'Content-Type: application/json' -H 'Accept: application/json' --header 'X-ONAP-RequestID:8e6f784e-c9cb-42f6-bcc9-edb5d0af1ce1' -d '{"onapName":"CDS","onapComponent":"CDS","onapInstance":"CDS", "currentDate": "2024-11-22", "currentTime": "2024-11-22T11:34:56Z", "timeZone": "UTC", "timeOffset": "+05:30", "currentDateTime": "2024-11-22T12:08:00Z","policyName":"action/allow","input":{"user":"charlie","action":"delete","type":"server"}}' -X POST http://0.0.0.0:8282/policy/pdpo/v1/decision {"decision":"DENY","policyName":"action/allow","statusMessage":"OPA Denied"} ## PERMIT for policy:account -curl -u 'policyadmin:zb!XztG34' -H 'Content-Type: application/json' -H 'Accept: application/json' --header 'X-ONAP-RequestID:8e6f784e-c9cb-42f6-bcc9-edb5d0af1ce1' -d '{"onapName":"CDS","onapComponent":"CDS","onapInstance":"CDS", "currentDate": "2024-11-22", "currentTime": "2024-11-22T11:34:56Z", "timeZone": "UTC","timeOffset": "+05:30", "currentDateTime": "2024-11-22T12:08:00Z","policyName":"account/allow", "input":{"creditor_account":11111,"creditor":"alice","debtor_account":22222,"debtor":"bob","period":30,"amount":1000}}' -X POST http://0.0.0.0:8282/policy/pdpx/v1/decision +curl -u 'policyadmin:zb!XztG34' -H 'Content-Type: application/json' -H 'Accept: application/json' --header 'X-ONAP-RequestID:8e6f784e-c9cb-42f6-bcc9-edb5d0af1ce1' -d '{"onapName":"CDS","onapComponent":"CDS","onapInstance":"CDS", "currentDate": "2024-11-22", "currentTime": "2024-11-22T11:34:56Z", "timeZone": "UTC","timeOffset": "+05:30", "currentDateTime": "2024-11-22T12:08:00Z","policyName":"account/allow", "input":{"creditor_account":11111,"creditor":"alice","debtor_account":22222,"debtor":"bob","period":30,"amount":1000}}' -X POST http://0.0.0.0:8282/policy/pdpo/v1/decision {"decision":"PERMIT","policyName":"account/allow","statusMessage":"OPA Allowed"} ## DENY for policy:account -curl -u 'policyadmin:zb!XztG34' -H 'Content-Type: application/json' -H 'Accept: application/json' --header 'X-ONAP-RequestID:8e6f784e-c9cb-42f6-bcc9-edb5d0af1ce1' -d '{"onapName":"CDS","onapComponent":"CDS","onapInstance":"CDS", "currentDate": "2024-11-22", "currentTime": "2024-11-22T11:34:56Z", "timeZone": "UTC", "timeOffset": "+05:30", "currentDateTime": "2024-11-22T12:08:00Z","policyName":"account/allow", "input":{"creditor_account":11111,"creditor":"alice","debtor_account":22222,"debtor":"bob","period":31,"amount":1000}}' -X POST http://0.0.0.0:8282/policy/pdpx/v1/decision +curl -u 'policyadmin:zb!XztG34' -H 'Content-Type: application/json' -H 'Accept: application/json' --header 'X-ONAP-RequestID:8e6f784e-c9cb-42f6-bcc9-edb5d0af1ce1' -d '{"onapName":"CDS","onapComponent":"CDS","onapInstance":"CDS", "currentDate": "2024-11-22", "currentTime": "2024-11-22T11:34:56Z", "timeZone": "UTC", "timeOffset": "+05:30", "currentDateTime": "2024-11-22T12:08:00Z","policyName":"account/allow", "input":{"creditor_account":11111,"creditor":"alice","debtor_account":22222,"debtor":"bob","period":31,"amount":1000}}' -X POST http://0.0.0.0:8282/policy/pdpo/v1/decision {"decision":"DENY","policyName":"account/allow","statusMessage":"OPA Denied"} ## PERMIT for policy:organization -curl -u 'policyadmin:zb!XztG34' -H 'Content-Type: application/json' -H 'Accept: application/json' --header 'X-ONAP-RequestID:8e6f784e-c9cb-42f6-bcc9-edb5d0af1ce1' -d '{"onapName":"CDS","onapComponent":"CDS","onapInstance":"CDS", "currentDate": "2024-11-22", "currentTime": "2024-11-22T11:34:56Z", "timeZone": "UTC", "timeOffset": "+05:30", "currentDateTime": "2024-11-22T12:08:00Z","policyName":"organization/allow", "input":{"user":"alice","action": "read","component": "component_A","project": "project_A", "organization": "org_A"}}' -X POST http://0.0.0.0:8282/policy/pdpx/v1/decision +curl -u 'policyadmin:zb!XztG34' -H 'Content-Type: application/json' -H 'Accept: application/json' --header 'X-ONAP-RequestID:8e6f784e-c9cb-42f6-bcc9-edb5d0af1ce1' -d '{"onapName":"CDS","onapComponent":"CDS","onapInstance":"CDS", "currentDate": "2024-11-22", "currentTime": "2024-11-22T11:34:56Z", "timeZone": "UTC", "timeOffset": "+05:30", "currentDateTime": "2024-11-22T12:08:00Z","policyName":"organization/allow", "input":{"user":"alice","action": "read","component": "component_A","project": "project_A", "organization": "org_A"}}' -X POST http://0.0.0.0:8282/policy/pdpo/v1/decision {"decision":"PERMIT","policyName":"organization/allow","statusMessage":"OPA Allowed"} ## DENY for policy:organization -curl -u 'policyadmin:zb!XztG34' -H 'Content-Type: application/json' -H 'Accept: application/json' --header 'X-ONAP-RequestID:8e6f784e-c9cb-42f6-bcc9-edb5d0af1ce1' -d '{"onapName":"CDS","onapComponent":"CDS","onapInstance":"CDS", "currentDate": "2024-11-22", "currentTime": "2024-11-22T11:34:56Z", "timeZone": "UTC", "timeOffset": "+05:30", "currentDateTime": "2024-11-22T12:08:00Z","policyName":"organization/allow", "input":{"user":"charlie","action": "edit","component": "component_A","project": "project_A", "organization": "org_A"}}' -X POST http://0.0.0.0:8282/policy/pdpx/v1/decision +curl -u 'policyadmin:zb!XztG34' -H 'Content-Type: application/json' -H 'Accept: application/json' --header 'X-ONAP-RequestID:8e6f784e-c9cb-42f6-bcc9-edb5d0af1ce1' -d '{"onapName":"CDS","onapComponent":"CDS","onapInstance":"CDS", "currentDate": "2024-11-22", "currentTime": "2024-11-22T11:34:56Z", "timeZone": "UTC", "timeOffset": "+05:30", "currentDateTime": "2024-11-22T12:08:00Z","policyName":"organization/allow", "input":{"user":"charlie","action": "edit","component": "component_A","project": "project_A", "organization": "org_A"}}' -X POST http://0.0.0.0:8282/policy/pdpo/v1/decision {"decision":"DENY","policyName":"organization/allow","statusMessage":"OPA Denied"} ## DENY for policy:abac(output) -curl -u 'policyadmin:zb!XztG34' -H 'Content-Type: application/json' -H 'Accept: application/json' --header 'X-ONAP-RequestID:8e6f784e-c9cb-42f6-bcc9-edb5d0af1ce1' -d '{"onapName":"CDS","onapComponent":"CDS","onapInstance":"CDS","currentDate": "2024-11-22","policyName":"abac", "policyFilter": ["action_is_read"], "input":{"actions": ["write"],"datatypes": ["location","temperature","precipitation","windspeed"],"time_period": {"from": "2024-03-27","to": "2024-03-31"}}}' -X POST http://0.0.0.0:8282/policy/pdpx/v1/decision +curl -u 'policyadmin:zb!XztG34' -H 'Content-Type: application/json' -H 'Accept: application/json' --header 'X-ONAP-RequestID:8e6f784e-c9cb-42f6-bcc9-edb5d0af1ce1' -d '{"onapName":"CDS","onapComponent":"CDS","onapInstance":"CDS","currentDate": "2024-11-22","policyName":"abac", "policyFilter": ["action_is_read"], "input":{"actions": ["write"],"datatypes": ["location","temperature","precipitation","windspeed"],"time_period": {"from": "2024-03-27","to": "2024-03-31"}}}' -X POST http://0.0.0.0:8282/policy/pdpo/v1/decision {"decision":"DENY","output":{},"policyName":"abac","statusMessage":"OPA Denied"} ## PERMIT for policy:abac -curl -u 'policyadmin:zb!XztG34' -H 'Content-Type: application/json' -H 'Accept: application/json' --header 'X-ONAP-RequestID:8e6f784e-c9cb-42f6-bcc9-edb5d0af1ce1' -d '{"onapName":"CDS","onapComponent":"CDS","onapInstance":"CDS","currentDate": "2024-11-22","policyName":"abac", "policyFilter": ["viewable_sensor_data"], "input":{"actions": ["read"],"datatypes": ["location","temperature","precipitation","windspeed"],"time_period": {"from": "2024-02-27","to": "2024-02-29"}}}' -X POST http://0.0.0.0:8282/policy/pdpx/v1/decision +curl -u 'policyadmin:zb!XztG34' -H 'Content-Type: application/json' -H 'Accept: application/json' --header 'X-ONAP-RequestID:8e6f784e-c9cb-42f6-bcc9-edb5d0af1ce1' -d '{"onapName":"CDS","onapComponent":"CDS","onapInstance":"CDS","currentDate": "2024-11-22","policyName":"abac", "policyFilter": ["viewable_sensor_data"], "input":{"actions": ["read"],"datatypes": ["location","temperature","precipitation","windspeed"],"time_period": {"from": "2024-02-27","to": "2024-02-29"}}}' -X POST http://0.0.0.0:8282/policy/pdpo/v1/decision {"decision":"PERMIT","output":{"viewable_sensor_data":[{"location":"Galle","precipitation":"500 mm","temperature":"35 C","windspeed":"7.2 m/s"},{"location":"Jaffna","precipitation":"300 mm","temperature":"-5 C","windspeed":"3.8 m/s"},{"location":"Nuwara Eliya","precipitation":"600 mm","temperature":"25 C","windspeed":"4.0 m/s"},{"location":"Trincomalee","precipitation":"1000 mm","temperature":"20 C","windspeed":"5.0 m/s"}]},"policyName":"abac","statusMessage":"OPA Allowed"} ## PERMIT for policy:zone -curl -u 'policyadmin:zb!XztG34' -H 'Content-Type: application/json' -H 'Accept: application/json' --header 'X-ONAP-RequestID:8e6f784e-c9cb-42f6-bcc9-edb5d0af1ce1' -d '{"onapName":"CDS","onapComponent":"CDS","onapInstance":"CDS","currentDate": "2024-11-22","policyName":"zone", "policyFilter": ["has_zone_access"], "input":{"actions": ["view"],"log_id": "log1", "datatypes": ["access", "user"],"time_period": {"from": "2024-11-01T09:00:00Z","to": "2024-11-01T10:00:00Z"},"zone_id": "zoneA"}}' -X POST http://0.0.0.0:8282/policy/pdpx/v1/decision +curl -u 'policyadmin:zb!XztG34' -H 'Content-Type: application/json' -H 'Accept: application/json' --header 'X-ONAP-RequestID:8e6f784e-c9cb-42f6-bcc9-edb5d0af1ce1' -d '{"onapName":"CDS","onapComponent":"CDS","onapInstance":"CDS","currentDate": "2024-11-22","policyName":"zone", "policyFilter": ["has_zone_access"], "input":{"actions": ["view"],"log_id": "log1", "datatypes": ["access", "user"],"time_period": {"from": "2024-11-01T09:00:00Z","to": "2024-11-01T10:00:00Z"},"zone_id": "zoneA"}}' -X POST http://0.0.0.0:8282/policy/pdpo/v1/decision {"decision":"PERMIT","output":{"has_zone_access":[{"access":"granted","user":"user1"}]},"policyName":"zone","statusMessage":"OPA Allowed"} ## DENY for policy: zone -curl -u 'policyadmin:zb!XztG34' -H 'Content-Type: application/json' -H 'Accept: application/json' --header 'X-ONAP-RequestID:8e6f784e-c9cb-42f6-bcc9-edb5d0af1ce1' -d '{"onapName":"CDS","onapComponent":"CDS","onapInstance":"CDS","currentDate": "2024-11-22","policyName":"zone", "policyFilter": ["has_zone_access"], "input":{"actions": ["edit"],"log_id": "log1", "datatypes": ["access", "user"],"time_period": {"from": "2024-11-01T00:00:00Z","to": "2024-11-01T00:00:00Z"},"zone_id": "zoneA"}}' -X POST http://0.0.0.0:8282/policy/pdpx/v1/decision +curl -u 'policyadmin:zb!XztG34' -H 'Content-Type: application/json' -H 'Accept: application/json' --header 'X-ONAP-RequestID:8e6f784e-c9cb-42f6-bcc9-edb5d0af1ce1' -d '{"onapName":"CDS","onapComponent":"CDS","onapInstance":"CDS","currentDate": "2024-11-22","policyName":"zone", "policyFilter": ["has_zone_access"], "input":{"actions": ["edit"],"log_id": "log1", "datatypes": ["access", "user"],"time_period": {"from": "2024-11-01T00:00:00Z","to": "2024-11-01T00:00:00Z"},"zone_id": "zoneA"}}' -X POST http://0.0.0.0:8282/policy/pdpo/v1/decision {"decision":"DENY","output":{"has_zone_access":[]},"policyName":"zone","statusMessage":"OPA Denied"} ## PERMIT for policy:vehicle -curl -u 'policyadmin:zb!XztG34' -H 'Content-Type: application/json' -H 'Accept: application/json' --header 'X-ONAP-RequestID:8e6f784e-c9cb-42f6-bcc9-edb5d0af1ce1' -d '{"onapName":"CDS","onapComponent":"CDS","onapInstance":"CDS","currentDate": "2024-11-22","policyName":"vehicle", "policyFilter": ["user_has_vehicle_access"], "input":{"actions": ["use"],"user":"user1", "vehicle_id": "v1", "attributes": ["type", "status"]}}' -X POST http://0.0.0.0:8282/policy/pdpx/v1/decision +curl -u 'policyadmin:zb!XztG34' -H 'Content-Type: application/json' -H 'Accept: application/json' --header 'X-ONAP-RequestID:8e6f784e-c9cb-42f6-bcc9-edb5d0af1ce1' -d '{"onapName":"CDS","onapComponent":"CDS","onapInstance":"CDS","currentDate": "2024-11-22","policyName":"vehicle", "policyFilter": ["user_has_vehicle_access"], "input":{"actions": ["use"],"user":"user1", "vehicle_id": "v1", "attributes": ["type", "status"]}}' -X POST http://0.0.0.0:8282/policy/pdpo/v1/decision {"decision":"PERMIT","output":{"user_has_vehicle_access":[{"status":"available","type":"car"}]},"policyName":"vehicle","statusMessage":"OPA Allowed"} ## PERMIT for policy:docs -`curl -u 'policyadmin:zb!XztG34' -H 'Content-Type: application/json' -H 'Accept: application/json' --header 'X-ONAP-RequestID:8e6f784e-c9cb-42f6-bcc9-edb5d0af1ce1' -d '{"onapName":"CDS","onapComponent":"CDS","onapInstance":"CDS","currentDate": "2024-11-22","policyName":"docs", "policyFilter": ["has_access_to_file"], "input":{"action": "read","file_id": "file1","access_level": "admin","attributes": ["owner", "size"]}}' -X POST http://0.0.0.0:8282/policy/pdpx/v1/decision +`curl -u 'policyadmin:zb!XztG34' -H 'Content-Type: application/json' -H 'Accept: application/json' --header 'X-ONAP-RequestID:8e6f784e-c9cb-42f6-bcc9-edb5d0af1ce1' -d '{"onapName":"CDS","onapComponent":"CDS","onapInstance":"CDS","currentDate": "2024-11-22","policyName":"docs", "policyFilter": ["has_access_to_file"], "input":{"action": "read","file_id": "file1","access_level": "admin","attributes": ["owner", "size"]}}' -X POST http://0.0.0.0:8282/policy/pdpo/v1/decision {"decision":"PERMIT","output":{"has_access_to_file":[{"owner":"user1","size":"10MB"}]},"policyName":"docs","statusMessage":"OPA Allowed"}` ## DENY for policy:docs -`curl -u 'policyadmin:zb!XztG34' -H 'Content-Type: application/json' -H 'Accept: application/json' --header 'X-ONAP-RequestID:8e6f784e-c9cb-42f6-bcc9-edb5d0af1ce1' -d '{"onapName":"CDS","onapComponent":"CDS","onapInstance":"CDS","currentDate": "2024-11-22","policyName":"docs", "policyFilter": ["has_access_to_file"], "input":{"action": "view","file_id": "file1","access_level": "employee","attributes": ["owner", "size"]}}' -X POST http://0.0.0.0:8282/policy/pdpx/v1/decision +`curl -u 'policyadmin:zb!XztG34' -H 'Content-Type: application/json' -H 'Accept: application/json' --header 'X-ONAP-RequestID:8e6f784e-c9cb-42f6-bcc9-edb5d0af1ce1' -d '{"onapName":"CDS","onapComponent":"CDS","onapInstance":"CDS","currentDate": "2024-11-22","policyName":"docs", "policyFilter": ["has_access_to_file"], "input":{"action": "view","file_id": "file1","access_level": "employee","attributes": ["owner", "size"]}}' -X POST http://0.0.0.0:8282/policy/pdpo/v1/decision {"decision":"DENY","output":{"has_access_to_file":[]},"policyName":"docs","statusMessage":"OPA Denied"}` ## HealthCheck API Call With Response -curl -u 'policyadmin:zb!XztG34' --header 'X-ONAP-RequestID:8e6f784e-c9cb-42f6-bcc9-edb5d0af1ce1' -X GET http://0.0.0.0:8282/policy/pdpx/v1/healthcheck +curl -u 'policyadmin:zb!XztG34' --header 'X-ONAP-RequestID:8e6f784e-c9cb-42f6-bcc9-edb5d0af1ce1' -X GET http://0.0.0.0:8282/policy/pdpo/v1/healthcheck {"code":200,"healthy":true,"message":"alive","name":"opa-9f0248ea-807e-45f6-8e0f-935e570b75cc","url":"self"} ## Statistics API Call With Response -curl -u 'policyadmin:zb!XztG34' --header 'X-ONAP-RequestID:8e6f784e-c9cb-42f6-bcc9-edb5d0af1ce1' -X GET http://0.0.0.0:8282/policy/pdpx/v1/statistics +curl -u 'policyadmin:zb!XztG34' --header 'X-ONAP-RequestID:8e6f784e-c9cb-42f6-bcc9-edb5d0af1ce1' -X GET http://0.0.0.0:8282/policy/pdpo/v1/statistics {"code":200,"denyDecisionsCount":10,"deployFailureCount":0,"deploySuccessCount":0,"indeterminantDecisionsCount":0,"permitDecisionsCount":18,"totalErrorCount":4,"totalPoliciesCount":0,"totalPolicyTypesCount":1,"undeployFailureCount":0,"undeploySuccessCount":0} diff --git a/test/data/docs/data.json b/test/data/docs/data.json new file mode 100644 index 0000000..5d43020 --- /dev/null +++ b/test/data/docs/data.json @@ -0,0 +1,7 @@ +{ + "files": [ + { "file_id": "file1", "access_level": "admin", "owner": "user1", "size": "10MB" }, + { "file_id": "file2", "access_level": "user", "owner": "user2", "size": "5MB" } + ] +} + diff --git a/test/data/vehicle/data.json b/test/data/vehicle/data.json new file mode 100644 index 0000000..570c677 --- /dev/null +++ b/test/data/vehicle/data.json @@ -0,0 +1,7 @@ +{ + "vehicles": [ + { "vehicle_id": "v1", "owner": "user1", "type": "car", "status": "available" }, + { "vehicle_id": "v2", "owner": "user2", "type": "bike", "status": "in use" } + ] +} + diff --git a/test/data/zone/data.json b/test/data/zone/data.json new file mode 100644 index 0000000..be77176 --- /dev/null +++ b/test/data/zone/data.json @@ -0,0 +1,10 @@ +{ + "zone": { + "zone_access_logs": [ + { "log_id": "log1", "timestamp": "2024-11-01T09:00:00Z", "zone_id": "zoneA", "access": "granted", "user": "user1" }, + { "log_id": "log2", "timestamp": "2024-11-01T10:30:00Z", "zone_id": "zoneA", "access": "denied", "user": "user2" }, + { "log_id": "log3", "timestamp": "2024-11-01T11:00:00Z", "zone_id": "zoneB", "access": "granted", "user": "user3" } + ] + } +} + diff --git a/test/policies/docs/policy.rego b/test/policies/docs/policy.rego new file mode 100644 index 0000000..90ce883 --- /dev/null +++ b/test/policies/docs/policy.rego @@ -0,0 +1,22 @@ +package docs + +import rego.v1 + +default allow := false + +allow if { + has_access_to_file + action_is_read_or_write +} + +action_is_read_or_write if { + input.action in ["read", "write"] +} + +has_access_to_file contains file_info if { + some file in data.docs.files + file.file_id == input.file_id + file.access_level == input.access_level + file_info := {attr: file[attr] | attr in input.attributes} +} + diff --git a/test/policies/vehicle/policy.rego b/test/policies/vehicle/policy.rego new file mode 100644 index 0000000..592afee --- /dev/null +++ b/test/policies/vehicle/policy.rego @@ -0,0 +1,23 @@ +package vehicle + +import rego.v1 + +default allow := false + +allow if { + user_has_vehicle_access + action_is_granted +} + +action_is_granted if { + "use" in input.actions +} + +user_has_vehicle_access contains vehicle_data if { + some vehicle in data.vehicle.vehicles + vehicle.vehicle_id == input.vehicle_id + vehicle.owner == input.user + vehicle_data := {info: vehicle[info] | info in input.attributes} +} + + diff --git a/test/policies/zone/policy.rego b/test/policies/zone/policy.rego new file mode 100644 index 0000000..75357a6 --- /dev/null +++ b/test/policies/zone/policy.rego @@ -0,0 +1,23 @@ +package zone + +import rego.v1 + +default allow := false + +allow if { + has_zone_access + action_is_log_view +} + +action_is_log_view if { + "view" in input.actions +} + +has_zone_access contains access_data if { + some zone_data in data.zone.zone.zone_access_logs + zone_data.timestamp >= input.time_period.from + zone_data.timestamp < input.time_period.to + zone_data.zone_id == input.zone_id + access_data := {datatype: zone_data[datatype] | datatype in input.datatypes} +} + @@ -1 +1 @@ -1.0.5-SNAPSHOT +1.0.0-SNAPSHOT |