An open API service indexing awesome lists of open source software.

https://github.com/open-policy-agent/opa-csharp

A driver to connect via C# to Open Policy Agent (OPA) deployments
https://github.com/open-policy-agent/opa-csharp

opa open-policy-agent sdk-csharp sdk-dotnet sdk-net

Last synced: 8 months ago
JSON representation

A driver to connect via C# to Open Policy Agent (OPA) deployments

Awesome Lists containing this project

README

          

# OPA C# SDK

[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
[![NuGet Version](https://img.shields.io/nuget/v/OpenPolicyAgent.Opa?style=flat&color=%2324b6e0)](https://www.nuget.org/packages/OpenPolicyAgent.Opa/)

> [!IMPORTANT]
> Reference documentation is available at

You can use the OPA C# SDK to connect to [Open Policy Agent](https://www.openpolicyagent.org/) and [EOPA](https://github.com/open-policy-agent/eopa) deployments.

## SDK Installation

### Nuget

```bash
dotnet add package OpenPolicyAgent.Opa
```

## SDK Example Usage (high-level)

The following examples assume an OPA server at `http://localhost:8181` equipped with the following Rego policy in `authz.rego`:

```rego
package authz
import rego.v1

default allow := false
allow if input.subject == "alice"
```

and this `data.json`:

```json
{
"roles": {
"admin": ["read", "write"]
}
}
```

### Simple Query

For a simple boolean response with input, use the SDK as follows:

```csharp
using OpenPolicyAgent.Opa;

string opaUrl = "http://localhost:8181";
OpaClient opa = new OpaClient(opaUrl);

var input = new Dictionary() {
{"subject", "alice"},
{"action", "read"},
};

bool allowed = false;

try
{
allowed = await opa.Check("authz/allow", input);
}
catch (OpaException e)
{
Console.WriteLine("exception while making request against OPA: " + e);
}

Console.WriteLine("allowed: " + allowed);
```

Result

```txt
allowed: True
```

### Simple Query with Output

The `.Evaluate()` method can be used instead of `.Check()` for non-boolean output types:

```csharp
using System;
using System.Collections.Generic;
using System.Linq;
using OpenPolicyAgent.Opa;

var opaUrl = "http://localhost:8181";
var opa = new OpaClient(opaUrl);

var input = new Dictionary() {
{"subject", "alice"},
{"action", "read"},
};

var result = new Dictionary>();

try
{
result = await opa.Evaluate>>("roles", input);
}
catch (OpaException e)
{
Console.WriteLine("exception while making request against OPA: " + e.Message);
}

Console.WriteLine("content of data.roles:");
foreach (var pair in result)
{
Console.Write(" {0} => [ ", pair.Key);
foreach (var item in pair.Value)
{
Console.Write("{0} ", item);
}
Console.WriteLine("]");
}
```

Result

```txt
content of data.roles:
admin => [ read write ]
```

### Default Rule

For evaluating the default rule (configured with your OPA service), use `EvaluateDefault`. `input` is optional, and left out in this example:

```csharp
using OpenPolicyAgent.Opa;

string opaUrl = "http://localhost:8181";
OpaClient opa = new OpaClient(opaUrl);

bool allowed = false;

try {
allowed = await opa.EvaluateDefault
Result

```txt
allowed: False
```

### Batched Queries

EOPA supports executing many queries in a single request with the [Batch API][eopa-batch-api].

[eopa-batch-api]: https://github.com/open-policy-agent/eopa/blob/main/docs/eopa/reference/api-reference/batch-api.md

The OPA C# SDK has native support for EOPA's batch API, with a fallback behavior of sequentially executing single queries if the Batch API is unavailable (such as with open source Open Policy Agent).

```csharp
using OpenPolicyAgent.Opa;

string opaUrl = "http://localhost:8181";
OpaClient opa = new OpaClient(opaUrl);

var input = new Dictionary>() {
{ "AAA", new Dictionary() { { "subject", "alice" }, { "action", "read" } } },
{ "BBB", new Dictionary() { { "subject", "bob" }, { "action", "write" } } },
{ "CCC", new Dictionary() { { "subject", "dave" }, { "action", "read" } } },
{ "DDD", new Dictionary() { { "subject", "sybil" }, { "action", "write" } } },
};

OpaBatchResults results = new OpaBatchResults();
OpaBatchErrors errors = new OpaBatchErrors();
try
{
(results, errors) = await opa.EvaluateBatch("authz/allow", input);
}
catch (OpaException e)
{
Console.WriteLine("exception while making request against OPA: " + e.Message);
}

Console.WriteLine("Query results, by key:");
foreach (var pair in results)
{
Console.WriteLine(" {0} => {1}", pair.Key, pair.Value.Result.Boolean);
}

if (errors.Count > 0)
{
Console.WriteLine("Query errors, by key:");
foreach (var pair in errors)
{
Console.WriteLine(" {0} => {1}", pair.Key, pair.Value);
}
}
```

Result

```txt
Query results, by key:
AAA => True
BBB => False
CCC => False
DDD => False
```

See the [API Documentation](https://open-policy-agent.github.io/opa-csharp/api/OpenPolicyAgent.Opa.OpenApi.Models.Components.Result.html) for reference on the properties and types available from a result.

### Using Custom Classes for Input and Output

Using the OPA C# SDK, it can be more natural to use custom class types as inputs and outputs to a policy, rather than `System.Collections.Dictionary` (or `Collections.List`). Internally, the OPA C# SDK uses [`Newtonsoft.Json`](https://www.newtonsoft.com/json) to serialize and deserialize inputs and outputs JSON to the provided types.

In the example below, note:

- Using an `enum` for an input field
- Hiding the sensitive `UUID` with the `JsonIgnore` property
- Deserializing the query response to a `bool`

```csharp
using System;
using OpenPolicyAgent.Opa;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

namespace Application
{
class Program
{
public enum ActionType
{
invalid,
create,
read,
update,
delete
}

private class CustomRBACObject
{

[JsonProperty("user")]
public string User = "";

[JsonProperty("action")]
[JsonConverter(typeof(StringEnumConverter))]
public ActionType Action = ActionType.invalid;

[JsonIgnore]
public string UUID = System.Guid.NewGuid().ToString();

public CustomRBACObject() { }

public CustomRBACObject(string user, ActionType action)
{
User = user;
Action = action;
}
}

static async Task Main(string[] args)
{
string opaUrl = "http://localhost:8181";
OpaClient opa = new OpaClient(opaUrl);

var input = new CustomRBACObject("bob", ActionType.read);
Console.WriteLine("The JSON that OPA will receive: {{\"input\": {0}}}", JsonConvert.SerializeObject(input));

bool allowed = false;
try
{
allowed = await opa.Evaluate("authz/allow", input);
}
catch (OpaException e)
{
Console.WriteLine("exception while making request against OPA: " + e.Message);
}

Console.WriteLine("allowed: " + allowed);
return 0;
}
}
}
```

Result

```txt
The JSON that OPA will receive: {"input": {"user":"bob","action":"read"}}
allowed: False
```

### Integrating logging with the OPA C# SDK

The OPA C# SDK uses opt-in, [compile-time source generated logging](https://learn.microsoft.com/en-us/dotnet/core/extensions/logger-message-generator), which can be integrated as a part of the overall logs of a larger application.

Here's a quick example:

```csharp
using Microsoft.Extensions.Logging;
using OpenPolicyAgent.Opa;

internal class Program
{
static async Task Main(string[] args)
{
using ILoggerFactory factory = LoggerFactory.Create(builder => builder.AddConsole());
ILogger logger = factory.CreateLogger();

var opaURL = "http://localhost:8181";
OpaClient opa = new OpaClient(opaURL, logger);

logger.LogInformation("Initialized an OPA client for the OPA at: {Description}.", opaURL);

var allow = await opa.Evaluate("this/rule/does/not/exist", false);

return 0;
}
}
```

Result

```log
info: OpenPolicyAgent.Opa.OpaClient[0]
Initialized an OPA client for the OPA at: http://localhost:8181.
warn: OpenPolicyAgent.Opa.OpaClient[2066302899]
executing policy at 'this/rule/does/not/exist' succeeded, but OPA did not reply with a result
Unhandled exception. OpaException: executing policy at 'this/rule/does/not/exist' succeeded, but OPA did not reply with a result
...
```

> [!NOTE]
> For low-level SDK usage, see the sections below.

---

# OPA OpenAPI SDK (low-level)

## Summary

For more information about the API: [EOPA documentation](https://github.com/open-policy-agent/eopa/docs)

## Table of Contents

- [OPA C# SDK](#opa-c-sdk)
- [SDK Installation](#sdk-installation)
- [SDK Example Usage (high-level)](#sdk-example-usage-high-level)
- [OPA OpenAPI SDK (low-level)](#opa-openapi-sdk-low-level)
- [SDK Example Usage](#sdk-example-usage)
- [Available Resources and Operations](#available-resources-and-operations)
- [Server Selection](#server-selection)
- [Error Handling](#error-handling)
- [Authentication](#authentication)
- [Community](#community)

## SDK Example Usage

### Example 1

```csharp
using OpenPolicyAgent.Opa.OpenApi;
using OpenPolicyAgent.Opa.OpenApi.Models.Components;

var sdk = new OpaApiClient();

var res = await sdk.ExecuteDefaultPolicyWithInputAsync(
input: Input.CreateNumber(
4963.69D
),
pretty: false,
acceptEncoding: GzipAcceptEncoding.Gzip
);

// handle response
```

### Example 2

```csharp
using OpenPolicyAgent.Opa.OpenApi;
using OpenPolicyAgent.Opa.OpenApi.Models.Requests;

var sdk = new OpaApiClient();

ExecutePolicyWithInputRequest req = new ExecutePolicyWithInputRequest() {
Path = "app/rbac",
RequestBody = new ExecutePolicyWithInputRequestBody() {
Input = Input.CreateBoolean(
false
),
},
};

var res = await sdk.ExecutePolicyWithInputAsync(req);

// handle response
```

### Example 3

```csharp
using OpenPolicyAgent.Opa.OpenApi;
using OpenPolicyAgent.Opa.OpenApi.Models.Components;
using OpenPolicyAgent.Opa.OpenApi.Models.Requests;
using System.Collections.Generic;

var sdk = new OpaApiClient();

ExecuteBatchPolicyWithInputRequest req = new ExecuteBatchPolicyWithInputRequest() {
Path = "app/rbac",
RequestBody = new ExecuteBatchPolicyWithInputRequestBody() {
Inputs = new Dictionary() {
{ "key", Input.CreateStr(
""
) },
},
},
};

var res = await sdk.ExecuteBatchPolicyWithInputAsync(req);

// handle response
```

## Available Resources and Operations

Available methods

### [OpaApiClient SDK](docs/sdks/opaapiclient/README.md)

- [ExecuteDefaultPolicyWithInput](docs/sdks/opaapiclient/README.md#executedefaultpolicywithinput) - Execute the default decision given an input
- [ExecutePolicy](docs/sdks/opaapiclient/README.md#executepolicy) - Execute a policy
- [ExecutePolicyWithInput](docs/sdks/opaapiclient/README.md#executepolicywithinput) - Execute a policy given an input
- [ExecuteBatchPolicyWithInput](docs/sdks/opaapiclient/README.md#executebatchpolicywithinput) - Execute a policy given a batch of inputs
- [CompileQueryWithPartialEvaluation](docs/sdks/opaapiclient/README.md#compilequerywithpartialevaluation) - Partially evaluate a query
- [Health](docs/sdks/opaapiclient/README.md#health) - Verify the server is operational

## Server Selection

### Override Server URL Per-Client

The default server can be overridden globally by passing a URL to the `serverUrl: string` optional parameter when initializing the SDK client instance. For example:

```csharp
using OpenPolicyAgent.Opa.OpenApi;
using OpenPolicyAgent.Opa.OpenApi.Models.Components;

var sdk = new OpaApiClient(serverUrl: "http://localhost:8181");

var res = await sdk.ExecuteDefaultPolicyWithInputAsync(
input: Input.CreateNumber(
4963.69D
),
pretty: false,
acceptEncoding: GzipAcceptEncoding.Gzip
);

// handle response
```

## Error Handling

Handling errors in this SDK should largely match your expectations. All operations return a response object or throw an exception.

By default, an API error will raise a `OpenPolicyAgent.Opa.OpenApi.Models.Errors.SDKException` exception, which has the following properties:

| Property | Type | Description |
|---------------|-----------------------|-----------------------|
| `Message` | *string* | The error message |
| `StatusCode` | *int* | The HTTP status code |
| `RawResponse` | *HttpResponseMessage* | The raw HTTP response |
| `Body` | *string* | The response content |

When custom error responses are specified for an operation, the SDK may also throw their associated exceptions. You can refer to respective *Errors* tables in SDK docs for more details on possible exception types for each operation. For example, the `ExecuteDefaultPolicyWithInputAsync` method throws the following exceptions:

| Error Type | Status Code | Content Type |
| -------------------------------------------- | ----------- | ---------------- |
| OpenPolicyAgent.Opa.OpenApi.Models.Errors.ClientError | 400, 404 | application/json |
| OpenPolicyAgent.Opa.OpenApi.Models.Errors.ServerError | 500 | application/json |
| OpenPolicyAgent.Opa.OpenApi.Models.Errors.SDKException | 4XX, 5XX | \*/\* |

### Example

```csharp
using OpenPolicyAgent.Opa.OpenApi;
using OpenPolicyAgent.Opa.OpenApi.Models.Components;
using OpenPolicyAgent.Opa.OpenApi.Models.Errors;

var sdk = new OpaApiClient();

try
{
var res = await sdk.ExecuteDefaultPolicyWithInputAsync(
input: Input.CreateNumber(
4963.69D
),
pretty: false,
acceptEncoding: GzipAcceptEncoding.Gzip
);

// handle response
}
catch (Exception ex)
{
if (ex is ClientError)
{
// Handle exception data
throw;
}
else if (ex is Models.Errors.ServerError)
{
// Handle exception data
throw;
}
else if (ex is OpenPolicyAgent.Opa.OpenApi.Models.Errors.SDKException)
{
// Handle default exception
throw;
}
}
```

## Authentication

### Per-Client Security Schemes

This SDK supports the following security scheme globally:

| Name | Type | Scheme |
| ------------ | ---- | ----------- |
| `BearerAuth` | http | HTTP Bearer |

To authenticate with the API the `BearerAuth` parameter must be set when initializing the SDK client instance. For example:

```csharp
using OpenPolicyAgent.Opa.OpenApi;
using OpenPolicyAgent.Opa.OpenApi.Models.Components;

var sdk = new OpaApiClient(bearerAuth: "");

var res = await sdk.ExecuteDefaultPolicyWithInputAsync(
input: Input.CreateNumber(
4963.69D
),
pretty: false,
acceptEncoding: GzipAcceptEncoding.Gzip
);

// handle response
```

## Community

For questions, discussions and announcements, please join
the OPA community on [Slack](https://slack.openpolicyagent.org/)!