Building blocks for your custom OAA integration
The Veza
package provides data models, methods, and helpers for using the Open Authorization API. It provides helper methods to populate OAA templates for custom applications, filesystems, HRIS systems, and identity providers, and push the payloads to Veza. The SDK can also be used as a generic Veza API client.
The Veza
SDK includes the following core components:
Veza.Sdk.Client
: A base API client for making REST calls to a Veza tenant
Veza.OAA.Client
: An OAA API client for interacting with integration providers, data sources, and pushing OAA metadata to a Veza tenant.
For example usage, see C# OAA Application Connector.
Sample Workflow
Create the Veza API connection and a new custom application:
using Veza.OAA;
using Veza.OAA.Application;
using Veza.OAA.Base;
// inside namespace/class
OAAClient oaaClient = new(api_key: <your_api_key>, url: <veza_tenant_url>);
CustomApplication customApp = new(name: "sample app",
applicationType: "sample", description: "This is a sample application");
Once the CustomApplication
class is instantiated, you can use its public methods to populate the new app with users, groups, resources, and permissions metadata:
// add custom permissions
customApp.AddCustomPermission(name: "Admin", permissions: new List<Permission>
{
Permission.DataRead,
Permission.DataWrite,
Permission.MetadataRead,
Permission.MetadataWrite,
Permission.NonData
},
applyToSubResources: true
);
// define custom user properties
customApp.DefinedProperties[typeof(User)].DefineProperty("is_guest", typeof(bool));
// add user
User user = customApp.AddUser(name: "bob");
user.AddIdentity("[email protected]");
user.IsActive = true;
user.CreatedAt = "2001-01-01T00:00:00.000Z".FromRFC3339();
user.DeactivatedAt = "2003-03-01T00:00:00.000Z".FromRFC3339();
user.LastLoginAt = "2002-02-01T00:00:00.000Z".FromRFC3339();
user.PasswordLastChangedAt = "2004-04-01T00:00:00.000Z".FromRFC3339();
user.SetProperty(name: "is_guest", value: false);
// define group properties
customApp.DefinedProperties[typeof(Group)].DefineProperty("group_id", typeof(int));
// add group
Group group1 = customApp.AddGroup("group1");
group1.CreatedAt = "2001-01-01T00:00:00.000Z".FromRFC3339();
group1.SetProperty(name: "group_id", 1);
customApp.Users["bob"].AddGroup("group1");
Group group2 = customApp.AddGroup("group2");
group2.AddGroup("group1");
// idp identities
customApp.AddIdPIdentity("[email protected]");
// define role properties
customApp.DefinedProperties[typeof(Role)].DefineProperty("custom", typeof(bool));
// add roles
Role role1 = customApp.AddRole(name: "role1", permissions: new List<string> { "all", "Admin", "Manage_Thing" });
role1.SetProperty(name: "custom", value: false);
// define resource properties
customApp.DefineResourceProperty("private", typeof(bool), "thing");
// add resources
Resource thing1 = customApp.AddResource(name: "thing1", resourceType: "thing", description: "thing1");
thing1.SetProperty(name: "private", false);
thing1.AddTag(name: "tag1", value: "This is a value @,-_.");
Resource cog1 = thing1.AddSubResource(name: "cog1", resourceType: "cog");
cog1.AddConnection(id: "[email protected]", nodeType: "GoogleCloudServiceAccount");
// authorizations
customApp.Users["bob"].AddRole(name: "role1", applyToApplication: true);
customApp.Groups["group2"].AddRole(name: "role1", resources: new List<Resource> { thing1 });
customApp.IdPIdentities["[email protected]"].AddRole(name: "role1", applyToApplication: true);
return customApp;
Once all identities, permissions, and resources are added to the CustomApplication object, use the client connection to push the data to Veza:
await oaaClient.CreateProvider(provider_name: "sample app", custom_template: "application");
await oaaClient.PushApplication(provider_name: "sample app", data_source_name: "sample app 1", customApp);
See the quickstarts directory for full examples.
The Veza.OAA
namespace provides exception types for common errors that occur when interacting with Veza APIs.
An OAAClientException
is raised if there are errors interacting with the Veza API.
A TemplateException
is raised if a provided payload does not conform to the template requirements. The inner exception will contain details about the exact issues encountered.
Each OAA connector will be slightly different, depending on the methods each source application or service provides for retrieving entities, authorization, and other required metadata. You should consult the API documentation for your application when considering how you will source the information, and refer to existing Veza-supported OAA connectors for real-world examples.
OAA connector source code and Veza
components are thoroughly annotated for reference when building your own integrations.
For additional information about developing a custom OAA integration, please see Open Authorization API section of the User Guide.
This document provides a high-level overview of and examples for getting started with a new OAA connector to integrate Veza with SaaS applications, infrastructure systems, custom-built applications, and other systems. These examples use C# and the Veza.OAA
SDK.
When developing a connector, source system specifics and individual customer requirements will require alterations to code flow. However, the overall goals, best practices, and development flow are common to most integrations.
The example code was written with the following goals in mind:
Connector should be easy to run from automation platforms and the Command Prompt.
Parameters are passed through environment variables as well as command line flags.
Connector does not require maintenance of state:
Connector does not require any persistent data between invocations.
There is no special invocation for the first execution.
The connector handles all provider and data source management logic.
Data source name is unique to the discovered environment.
The exact flow of an OAA connector can change to meet specific requirements, but the general steps are as follows:
Process and validate configuration parameters. Ensure that all required values are present and valid.
Initialize the API client connection to the Veza tenant. Doing so early in the application flow validates the URL and API key before continuing discovery.
Create an instance of the Veza.OAA.Application.CustomApplication
class to populate with application metadata.
Connect to the system and perform discovery of required entities.
In your custom integrations, discovery order for users, groups, roles, and other entities can adapt to suit application requirements.
Populate the CustomApplication
instance with the identity, role, permission, resource, and authorization information collected.
Check if the Provider and Data Source exist on Veza. Create them if they do not exist.
Push the application to the Data Source on Veza. The SDK creates the required JSON payload from the CustomApplication
instance.
Process any returned warnings or errors.
Exit.
To use this example as a starting point for your application integration, follow these steps:
Update the name
, applicationType
, and description
of the CustomApplication
object based on the source system for integration with Veza.
Define any custom_properties
needed. Properties must be defined on the CustomApplication
object before their values are set on any entities.
Implement the discovery steps in the Discover()
function to collect user, group, role, resource, and permission data for the application. As entities are collected, add them to the CustomApplication
object.
Run the connector to validate the output in Veza.
The following code provides a template and examples for creating a new application integration using the Veza.OAA
SDK.
using RestSharp;
using RestSharp.Authenticators;
using Veza.OAA;
using Veza.OAA.Application;
using Veza.OAA.Client;
using NLog;
namespace Example.Integrations
{
/// <summary>
/// Example application group object
/// </summary>
public class AppGroup {
public string Name { get; set; }
public string Type { get; set; }
public string CreatedAt { get; set; }
public List<string> Users { get; set; }
}
/// <summary>
/// Example application resource object
/// </summary>
public class AppResource {
public string Name { get; set; }
public int SiteId { get; set; }
public string ResourceType { get; set; }
}
/// <summary>
/// Example application role object
/// </summary>
public class AppRole {
public string Name { get; set; }
public int SystemId { get; set; }
public string CreatedAt { get; set; }
public List<string> Users { get; set; }
public List<string> Resources { get; set; }
}
public class ExampleApp
{
// Optional NLog logger; can be replaced with any standard logging interface
private static Logger logger = LogManager.GetCurrentClassLogger();
// The base64-encoded logo for the application (displayed on the Veza UI)
public string LogoBase64;
// The name of the provider for the application
public string ProviderName;
// The custom application object for the example application
// This object is used to build the application payload for Veza
public CustomApplication CustomApp { get; set; }
// The example application client for the example application API (not shown in this example)
private ExampleAppClient AppClient { get; set; }
# region "Construction"
public ExampleApp()
{
// Instantiate the example application
CustomApp = new CustomApplication(
name: "Example Application 1",
applicationType: "Example App",
description: "Example application for Veza integration"
);
// Define an "uncategorized" permission for the example application
// This can be used to group permissions that do not fit into another category,
// or if the application API does not provide permission information
CustomApp.AddCustomPermission(
name: "Uncategorized",
permissions: new List<Permission> { Permission.Uncategorized }
);
// define custom properties for the example application
DefineCustomProperties();
// Set the application icon for the Veza UI
// This should be a base64-encoded SVG or PNG image
LogoBase64 = "abc123...def987654"
}
/// <summary>
/// Define custom properties for the example application.
/// This method is called by the constructor to define any custom properties
/// that the example application entities may have.
/// </summary>
private void DefineCustomProperties()
{
CustomApp.DefinedProperties[typeof(User)].
DefineProperty("locked_out", typeof(bool);
CustomApp.DefinedProperties[typeof(Group)].
DefineProperty("type", typeof(string));
CustomApp.DefinedProperties[typeof(Role)].
DefineProperty("system_id", typeof(int));
CustomApp.DefineResourceProperty("site_id", typeof(int)), "widget");
}
# endregion
# region "Discovery"
// Individual methods for discovering users, groups, roles, and resources.
// Should be scoped as private to ensure that they are called only by the Discover method
// and not directly by the client application.
/// <summary>
/// Discover users for the example application.
/// This method is called by the Discover method to discover users for the example application.
/// </summary>
private void DiscoverUsers()
{
Logger.Info("Discovering users for the example application");
// Get users from the example application client.
// In this example, the GetUsers method returns a list of dictionaries of user data.
List<Dictionary<string, string>> users = AppClient.GetUsers();
// Iterate over the users response and add them to the application payload
foreach (Dictionary<string, string> u in users)
{
// Create a new user object and add it to the application payload
User user = CustomApp.AddUser(name: u["username"]);
user.AddIdentity(u["email"]);
user.IsActive = u["active"];
user.CreatedAt = u["created_at"];
}
Logger.Info("User discovery complete");
}
/// <summary>
/// Discover groups for the example application.
/// This method is called by the Discover method to discover groups for the example application.
/// </summary>
private void DiscoverGroups()
{
Logger.Info("Discovering groups for the example application");
// Get groups from the example application client
// in this example, the GetGroups method returns a list of AppGroup objects
List<AppGroup> groups = AppClient.GetGroups();
// Iterate over the groups response and add them to the application payload
foreach (AppGroup g in groups)
{
// Create a new group object and add it to the application payload
Group group = CustomApp.AddGroup(name: g.Name);
group.CreatedAt = g.CreatedAt;
group.SetProperty(name: "type", value: g.Type);
// Iterate users in the group and add them to the group
foreach (string user in g.Users)
{
CustomApp.Users[user].AddGroup(g.Name);
}
}
Logger.Info("Group discovery complete");
}
/// <summary>
/// Discover resources for the example application.
/// This method is called by the Discover method to discover resources for the example application.
/// </summary>
private void DiscoverResources()
{
Logger.Info("Discovering resources for the example application");
// Get resources from the example application
// In this example, the GetResources method returns a list of AppResource objects
List<AppResource> resources = AppClient.GetResources();
foreach (AppResource r in resources)
{
// Create a resource object and add it to the application payload
Resource resource = CustomApp.AddResource(name: r.Name, resourceType: r.ResourceType);
// Set the `site_id` property for the resource if the resource is a "widget"
// Cstom resource properties are defined per-resource type, so non-"widget" resources will not have this property
if (r.ResourceType == "widget")
{
resource.SetProperty(name: "site_id", value: r.SiteId);
}
}
Logger.Info("Resource discovery complete");
}
/// <summary>
/// Discover roles for the example application.
/// This method is called by the Discover method to discover roles for the example application.
/// </summary>
private void DiscoverRoles()
{
Logger.Info("Discovering roles for the example application");
// get roles from the example application
// in this example, the GetRoles method returns a list of AppRole objects
List<AppRole> roles = AppClient.GetRoles();
foreach (Role role in roles)
{
// create a new role object and add it to the application payload
Role role = CustomApp.AddRole(name: role.Name);
role.CreatedAt = role.CreatedAt;
role.SetProperty(name: "system_id", value: role.SystemId);
// iterate users in the role and add them to the role
// set the applyToApplication flag to true to apply the role to the entire application
foreach (string user in role.Users)
{
CustomApp.Users[user].AddRole(name: role.Name, applyToApplication: true);
}
Application.AddRole(role);
}
Logger.Info("Role discovery complete");
}
# endregion
# region "Execution"
/// <summary>
/// Discover the example application.
/// This method is called by the Run method to discover the example application.
/// It serves as a place to instantiate a client for the example application API
/// and then call each of the discovery methods required to build the application payload.
/// </summary>
/// <param name="exampleApiKey">The API key for the example application API</param>
/// <param name="exampleUrl">The URL for the example application API</param>
/// <returns></returns>
public void Discover(string exampleApiKey, string exampleUrl)
{
// instantiate an API client for the example application
AppClient = new ExampleAppClient(exampleApiKey, exampleUrl);
Logger.Info("Beginning example application discovery");
// discover the example application components
DiscoverUsers();
DiscoverGroups();
DiscoverResources();
DiscoverRoles();
Logger.Info("Example application discovery complete");
}
/// <summary>
/// Handle integration execution.
/// This is separated from the Main method to more easily adapt to changes
/// to application execution (e.g. from Command Prompt, as part of another application, etc.)
/// </summary>
/// <param name="exampleApiKey">The API key for the Example application API</param>
/// <param name="exampleUrl">The URL for the Example application API</param>
/// <param name="vezaApiKey">The API key for the Veza tenant</param>
/// <param name="vezaUrl">The URL for the Veza tenant</param>
/// <returns></returns>
public async Task Run(
string exampleApiKey,
string exampleUrl,
string vezaApiKey,
string vezaUrl
)
{
Logger.Info("Starting example application discovery");
// Instantiate a connection to the Veza API.
// The client will attempt to reach the Veza API and throw an exception if it fails.
// This is to ensure that the Veza API is reachable before attempting to discover and push data.
try
{
OAAClient oaaClient = new(vezaApiKey, vezaUrl);
}
catch (ClientException e)
{
Logger.Error("Failed to connect to Veza API: {0}", e.Message);
return;
}
// Discover the example application
Discover(exampleApiKey, exampleUrl);
// Get or create the Veza provider for the payload
Logger.Debug("Checking Veza for an existing provider for the example application");
Sdk.Client.ApiClient.VezaApiResponse providerResponse = await oaaClient.GetProvider(ProviderName);
// Create the provider if none exists
if (providerResponse is null)
{
Logger.Info("No Veza provider exists; creating provider");
await oaaClient.CreateProvider(ProviderName, "application", base64_icon: LogoBase64);
}
// Push OAA payload to Veza
Logger.Info("Pushing example application metadata to Veza");
RestResponse<Sdk.Client.ApiClient.VezaApiResponse> pushResponse = await oaaClient.PushApplication(ProviderName, Environment.MachineName, CustomApp, save_json: true);
if (pushResponse.IsSuccessful)
{
Logger.Info("Example application discovery complete");
} else
{
Logger.Error($"Push response code: {pushResponse.StatusCode}");
Logger.Error($"Push response content: {pushResponse.Content}");
}
}
# endregion
}
}