From e295b6047a97c0be793cb26996609233943211f7 Mon Sep 17 00:00:00 2001 From: Paul Latzelsperger Date: Thu, 16 Oct 2025 12:33:34 +0200 Subject: [PATCH 1/2] feat: add streaming sample based on NATS --- .dockerignore | 25 + Samples/Streaming/Provider/Dockerfile | 40 + Samples/Streaming/Provider/Extensions.cs | 82 + Samples/Streaming/Provider/Nats/Constants.cs | 8 + .../Provider/Nats/INatsPublisherService.cs | 7 + .../Provider/Nats/NatsDataAddress.cs | 84 + .../Streaming/Provider/Nats/NatsOptions.cs | 6 + .../Provider/Nats/NatsPublisherService.cs | 59 + Samples/Streaming/Provider/Program.cs | 35 + .../Provider/Properties/launchSettings.json | 23 + Samples/Streaming/Provider/Provider.csproj | 26 + .../Postman/dps.postman_collection.json | 109 + .../Provider/Services/DataService.cs | 51 + .../Provider/Services/IDataService.cs | 28 + .../Streaming/Provider/appsettings.Local.json | 20 + Samples/Streaming/Provider/appsettings.json | 36 + .../Keycloak/dataplane-api-realm.json | 2553 +++++++++++++++++ Samples/Streaming/docker-compose.yaml | 62 + compose.yaml | 44 - dataplane-sdk-dotnet.sln | 13 + 20 files changed, 3267 insertions(+), 44 deletions(-) create mode 100644 .dockerignore create mode 100644 Samples/Streaming/Provider/Dockerfile create mode 100644 Samples/Streaming/Provider/Extensions.cs create mode 100644 Samples/Streaming/Provider/Nats/Constants.cs create mode 100644 Samples/Streaming/Provider/Nats/INatsPublisherService.cs create mode 100644 Samples/Streaming/Provider/Nats/NatsDataAddress.cs create mode 100644 Samples/Streaming/Provider/Nats/NatsOptions.cs create mode 100644 Samples/Streaming/Provider/Nats/NatsPublisherService.cs create mode 100644 Samples/Streaming/Provider/Program.cs create mode 100644 Samples/Streaming/Provider/Properties/launchSettings.json create mode 100644 Samples/Streaming/Provider/Provider.csproj create mode 100644 Samples/Streaming/Provider/Resources/Postman/dps.postman_collection.json create mode 100644 Samples/Streaming/Provider/Services/DataService.cs create mode 100644 Samples/Streaming/Provider/Services/IDataService.cs create mode 100644 Samples/Streaming/Provider/appsettings.Local.json create mode 100644 Samples/Streaming/Provider/appsettings.json create mode 100644 Samples/Streaming/Resources/Keycloak/dataplane-api-realm.json create mode 100644 Samples/Streaming/docker-compose.yaml delete mode 100644 compose.yaml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..38bece4 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,25 @@ +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/.idea +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md \ No newline at end of file diff --git a/Samples/Streaming/Provider/Dockerfile b/Samples/Streaming/Provider/Dockerfile new file mode 100644 index 0000000..2e38b3a --- /dev/null +++ b/Samples/Streaming/Provider/Dockerfile @@ -0,0 +1,40 @@ +# syntax=docker/dockerfile:1 + +# Build stage +FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build +WORKDIR /src + +# Copy everything and restore +# (Assumes this Dockerfile sits in the project directory next to the .csproj) +COPY . . + +ARG NUGET_USERNAME +ARG NUGET_PASSWORD + +RUN dotnet nuget add source "https://nuget.pkg.github.com/eclipse-dataplane-core/index.json" \ + --name dcore \ + --username $NUGET_USERNAME \ + --password $NUGET_PASSWORD \ + --store-password-in-clear-text \ + && dotnet restore Provider.csproj \ + && dotnet nuget remove source dcore + +# Publish (framework-dependent) +RUN dotnet publish Provider.csproj -c Release -o /app/publish --no-restore + +# Runtime stage +FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS final +WORKDIR /app + +# Configure ASP.NET Core to listen on port 8080 and set environment +ENV ASPNETCORE_URLS=http://+:8080 \ + ASPNETCORE_ENVIRONMENT=Production + +# Copy published output +COPY --from=build /app/publish ./ + +# Expose HTTP +EXPOSE 8080 + +# Start the app +ENTRYPOINT ["dotnet", "./Provider.dll"] \ No newline at end of file diff --git a/Samples/Streaming/Provider/Extensions.cs b/Samples/Streaming/Provider/Extensions.cs new file mode 100644 index 0000000..09e97fd --- /dev/null +++ b/Samples/Streaming/Provider/Extensions.cs @@ -0,0 +1,82 @@ +using DataPlane.Sdk.Api; +using DataPlane.Sdk.Core; +using DataPlane.Sdk.Core.Domain.Model; +using Microsoft.IdentityModel.Tokens; +using Provider.Nats; +using Provider.Services; +using static DataPlane.Sdk.Core.Data.DataFlowContextFactory; + +namespace Provider; + +public static class Extensions +{ + public static void AddDataPlaneSdk(this IServiceCollection services, IConfiguration configuration) + { + // initialize and configure the DataPlaneSdk + var dataplaneConfig = configuration.GetSection("DataPlaneSdk"); + var config = dataplaneConfig.Get() ?? throw new ArgumentException("Configuration invalid!"); + var dataFlowContext = () => CreatePostgres(configuration, config.RuntimeId); + + + var sdk = new DataPlaneSdk + { + DataFlowStore = dataFlowContext, + RuntimeId = config.RuntimeId, + OnStart = dataFlow => + { + var dataService = services.BuildServiceProvider().GetRequiredService(); + return dataService.ProcessStart(dataFlow); + }, + OnTerminate = df => + { + var dataService = services.BuildServiceProvider().GetRequiredService(); + var task = dataService.ProcessTerminate(df); + task.Wait(); + return task.Result; + }, + OnSuspend = _ => StatusResult.Success(), + OnPrepare = f => + { + f.State = DataFlowState.Prepared; + return StatusResult.Success(f); + }, + OnComplete = _ => StatusResult.Success() + }; + + services.AddSingleton(); + services.AddSingleton(); + + + // add SDK core services + services.AddSdkServices(sdk, dataplaneConfig); + + // Configuration for keycloak. Effectively, this sets the default authentication scheme to "KeycloakJWT", + // foregoing the SDK default authentication scheme and using Keycloak as the identity provider. + // This assumes that Keycloak is running on http://keycloak:8080, which is the default if launched with docker-compose. + + var jwtSettings = configuration.GetSection("JwtSettings"); + + services.AddAuthentication("KeycloakJWT") + .AddJwtBearer("KeycloakJWT", options => + { + // Configure Keycloak as the Identity Provider + options.Authority = jwtSettings["Authority"]; + options.RequireHttpsMetadata = false; // Only for develop + + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + ValidIssuer = jwtSettings["Issuer"], + ValidateAudience = true, + ValidAudience = jwtSettings["Audience"], + ValidateIssuerSigningKey = true, + ValidateLifetime = true, + ValidateActor = false, + ValidateTokenReplay = true + }; + }); + + // wire up ASP.net authorization handlers + services.AddSdkAuthorization(); + } +} \ No newline at end of file diff --git a/Samples/Streaming/Provider/Nats/Constants.cs b/Samples/Streaming/Provider/Nats/Constants.cs new file mode 100644 index 0000000..ade0520 --- /dev/null +++ b/Samples/Streaming/Provider/Nats/Constants.cs @@ -0,0 +1,8 @@ +namespace Provider.Nats; + +public static class Constants +{ + public static readonly string DataAddressType = "NatsStream"; + public static readonly string ForwardChannelSuffix = "forward"; + public static readonly string ReplyChannelSuffix = "reply"; +} \ No newline at end of file diff --git a/Samples/Streaming/Provider/Nats/INatsPublisherService.cs b/Samples/Streaming/Provider/Nats/INatsPublisherService.cs new file mode 100644 index 0000000..23cb4d1 --- /dev/null +++ b/Samples/Streaming/Provider/Nats/INatsPublisherService.cs @@ -0,0 +1,7 @@ +namespace Provider.Nats; + +public interface INatsPublisherService +{ + void StartAsync(string channel); + Task StopAsync(string channel); +} \ No newline at end of file diff --git a/Samples/Streaming/Provider/Nats/NatsDataAddress.cs b/Samples/Streaming/Provider/Nats/NatsDataAddress.cs new file mode 100644 index 0000000..5c104d9 --- /dev/null +++ b/Samples/Streaming/Provider/Nats/NatsDataAddress.cs @@ -0,0 +1,84 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using DataPlane.Sdk.Core.Domain.Model; + +namespace Provider.Nats; + +public class NatsDataAddress : DataAddress +{ + public NatsDataAddress() : base(Constants.DataAddressType) + { + Properties["endpointType"] = "https://example.com/natsdp/v1/nats"; + } + + public string NatsEndpoint + { + init => Properties["endpoint"] = value; + } + + public string Channel + { + get + { + var props = Properties["endpointProperties"]; + + List epp; + if (props is JsonElement) + { + epp = JsonSerializer.Deserialize>(props.ToString()); + } + else + { + epp = props as List; + } + + + var property = epp?.Find(p => p.Key.Equals("channel")); + return (property?.Value ?? null) ?? throw new InvalidOperationException("No 'channel' endpointProperty found"); + } + init => StringEndpointProperty("channel", value); + } + + public string ReplyChannel + { + init => StringEndpointProperty("replyChannel", value); + } + + public static NatsDataAddress Create(DataAddress rawSource) + { + return new NatsDataAddress + { + Properties = rawSource.Properties, + Id = rawSource.Id + }; + } + + private void StringEndpointProperty(string key, string endpointPropertyValue) + { + if (!Properties.TryGetValue("endpointProperties", out var existing)) + { + existing = new List(); + Properties["endpointProperties"] = existing; + } + + var epProps = existing as List; + epProps!.Add(new EndpointProperty + { + Key = key, + Type = "string", + Value = endpointPropertyValue + }); + } + + public class EndpointProperty + { + [JsonPropertyName("key")] + public required string Key { get; init; } + + [JsonPropertyName("type")] + public required string Type { get; init; } + + [JsonPropertyName("value")] + public required string Value { get; init; } + } +} \ No newline at end of file diff --git a/Samples/Streaming/Provider/Nats/NatsOptions.cs b/Samples/Streaming/Provider/Nats/NatsOptions.cs new file mode 100644 index 0000000..6c7400e --- /dev/null +++ b/Samples/Streaming/Provider/Nats/NatsOptions.cs @@ -0,0 +1,6 @@ +namespace Provider.Nats; + +public class NatsOptions +{ + public required string NatsEndpoint { get; set; } +} \ No newline at end of file diff --git a/Samples/Streaming/Provider/Nats/NatsPublisherService.cs b/Samples/Streaming/Provider/Nats/NatsPublisherService.cs new file mode 100644 index 0000000..3979780 --- /dev/null +++ b/Samples/Streaming/Provider/Nats/NatsPublisherService.cs @@ -0,0 +1,59 @@ +using System.Text.Json; +using NATS.Client.Core; + +namespace Provider.Nats; + +public class NatsPublisherService(ILogger logger) : INatsPublisherService +{ + private static readonly IDictionary BackgroundTasks = new Dictionary(); + private static readonly IDictionary CancellationTokenSource = new Dictionary(); + + public void StartAsync(string channel) + { + var ct = new CancellationTokenSource(); + CancellationTokenSource.Add(channel, ct); + + BackgroundTasks.Add(channel, Task.Run(async () => + { + await using var nats = new NatsConnection(); + + var num = 0; + while (!ct.Token.IsCancellationRequested) + { + try + { + var eventData = new { data = $"Event {num++}", num }; + var json = JsonSerializer.Serialize(eventData); + await nats.PublishAsync(channel, json, cancellationToken: ct.Token); + logger.LogInformation("Publish {Json}", json); + await Task.Delay(2000, ct.Token); + } + catch (OperationCanceledException) + { + break; + } + catch (Exception ex) + { + logger.LogError(ex, "Error publishing to NATS"); + } + } + }, ct.Token)); + } + + public async Task StopAsync(string channel) + { + if (CancellationTokenSource.TryGetValue(channel, out var cts)) + { + logger.LogDebug("Stopping {Channel}", channel); + await cts.CancelAsync(); + CancellationTokenSource.Remove(channel); + } + + if (BackgroundTasks.TryGetValue(channel, out var task)) + { + await task; + logger.LogDebug("Stopped {Channel}", channel); + BackgroundTasks.Remove(channel); + } + } +} \ No newline at end of file diff --git a/Samples/Streaming/Provider/Program.cs b/Samples/Streaming/Provider/Program.cs new file mode 100644 index 0000000..17b1bfb --- /dev/null +++ b/Samples/Streaming/Provider/Program.cs @@ -0,0 +1,35 @@ +using Provider; +using Provider.Nats; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi +builder.Services.AddOpenApi(); +builder.Services.AddControllers(); +// SDK: add all services, read configuration etc. +builder.Services.Configure(builder.Configuration.GetSection("Nats")); +builder.Services.AddDataPlaneSdk(builder.Configuration); + +// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi +builder.Services.AddOpenApi(); + + +// Configure the HTTP request pipeline. +var app = builder.Build(); + +if (app.Environment.IsDevelopment()) +{ + app.MapOpenApi(); +} + + +app.UseHttpsRedirection(); + + +app.UseAuthentication(); +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); \ No newline at end of file diff --git a/Samples/Streaming/Provider/Properties/launchSettings.json b/Samples/Streaming/Provider/Properties/launchSettings.json new file mode 100644 index 0000000..3763145 --- /dev/null +++ b/Samples/Streaming/Provider/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:8080", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Local" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:7118;http://localhost:5252", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Samples/Streaming/Provider/Provider.csproj b/Samples/Streaming/Provider/Provider.csproj new file mode 100644 index 0000000..739eb90 --- /dev/null +++ b/Samples/Streaming/Provider/Provider.csproj @@ -0,0 +1,26 @@ + + + + net9.0 + enable + enable + Linux + + + + + + + + + + <_ContentIncludedByDefault Remove="Resources\Keycloak\dataplane-api-realm.json"/> + + + + + + + + + diff --git a/Samples/Streaming/Provider/Resources/Postman/dps.postman_collection.json b/Samples/Streaming/Provider/Resources/Postman/dps.postman_collection.json new file mode 100644 index 0000000..6b8d3e3 --- /dev/null +++ b/Samples/Streaming/Provider/Resources/Postman/dps.postman_collection.json @@ -0,0 +1,109 @@ +{ + "info": { + "_postman_id": "ad21ada1-e71d-45b9-a5f5-9264972b8d62", + "name": "Dataplane Signaling API - Samples", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "647585", + "_collection_link": "https://galactic-star-228409.postman.co/workspace/Dataplane-work~d820e7a7-8273-4d79-986e-96754a31f467/collection/647585-ad21ada1-e71d-45b9-a5f5-9264972b8d62?action=share&source=collection_link&creator=647585" + }, + "item": [ + { + "name": "HttpDataplane", + "item": [] + } + ], + "auth": { + "type": "oauth2", + "oauth2": [ + { + "key": "accessTokenUrl", + "value": "http://localhost:8088/realms/dataplane-signaling-api/protocol/openid-connect/token", + "type": "string" + }, + { + "key": "tokenName", + "value": "dps-token", + "type": "string" + }, + { + "key": "clientId", + "value": "{{CLIENT_ID}}", + "type": "string" + }, + { + "key": "client_authentication", + "value": "body", + "type": "string" + }, + { + "key": "clientSecret", + "value": "{{CLIENT_SECRET}}", + "type": "string" + }, + { + "key": "grant_type", + "value": "client_credentials", + "type": "string" + }, + { + "key": "addTokenTo", + "value": "header", + "type": "string" + } + ] + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "packages": {}, + "requests": {}, + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "packages": {}, + "requests": {}, + "exec": [ + "" + ] + } + } + ], + "variable": [ + { + "key": "CLIENT_SECRET", + "value": "" + }, + { + "key": "CLIENT_ID", + "value": "" + }, + { + "key": "PARTICIPANT_ID", + "value": "" + }, + { + "key": "DATAFLOW_ID", + "value": "" + }, + { + "key": "DATAPLANE_HOST", + "value": "" + }, + { + "key": "PUBLIC_URL", + "value": "" + }, + { + "key": "API_KEY", + "value": "" + } + ] +} \ No newline at end of file diff --git a/Samples/Streaming/Provider/Services/DataService.cs b/Samples/Streaming/Provider/Services/DataService.cs new file mode 100644 index 0000000..46bea5d --- /dev/null +++ b/Samples/Streaming/Provider/Services/DataService.cs @@ -0,0 +1,51 @@ +using DataPlane.Sdk.Core.Domain.Model; +using Microsoft.Extensions.Options; +using Provider.Nats; + +namespace Provider.Services; + +public class DataService(IOptions options, INatsPublisherService publisherService) : IDataService +{ + public Task IsPermitted(string apiKey, DataFlow dataFlow) + { + return Task.FromResult(dataFlow.Destination?.Properties["token"] as string == apiKey); + } + + /// + /// + /// + /// + public StatusResult ProcessStart(DataFlow flow) + { + // create a data address for the NATS endpoint + var channel = flow.Id + "." + Constants.ForwardChannelSuffix; + var replyChannel = flow.Id + "." + Constants.ReplyChannelSuffix; + + + var dataAddress = new NatsDataAddress + { + NatsEndpoint = options.Value.NatsEndpoint, + Channel = channel, + ReplyChannel = replyChannel + }; + + flow.Destination = dataAddress; + + // start publishing events + publisherService.StartAsync(channel); + + return StatusResult.Success(flow); + } + + public async Task ProcessTerminate(DataFlow dataFlow) + { + if (dataFlow.Destination != null) + { + var nda = NatsDataAddress.Create(dataFlow.Destination); + await publisherService.StopAsync(nda.Channel); + return StatusResult.Success(); + } + + return StatusResult.Failed(new StatusFailure { Message = "DataAddress is not a valid NATS DataAddress", Reason = FailureReason.InternalError }); + } +} \ No newline at end of file diff --git a/Samples/Streaming/Provider/Services/IDataService.cs b/Samples/Streaming/Provider/Services/IDataService.cs new file mode 100644 index 0000000..9986d2e --- /dev/null +++ b/Samples/Streaming/Provider/Services/IDataService.cs @@ -0,0 +1,28 @@ +using DataPlane.Sdk.Core.Domain.Model; + +namespace Provider.Services; + +/// +/// Represents a service for managing data flows, permissions, and public endpoint configuration. +/// +public interface IDataService +{ + /// + /// Determines whether the specified API key has permission to access the given data flow. + /// + /// The API key to be checked for permissions. + /// The data flow object for which the permission is being verified. + /// + /// Returns a task representing the asynchronous operation, containing a boolean value that indicates whether the + /// permission is granted. + /// + Task IsPermitted(string apiKey, DataFlow dataFlow); + + /// + /// + /// + /// + StatusResult ProcessStart(DataFlow flow); + + Task ProcessTerminate(DataFlow dataFlow); +} \ No newline at end of file diff --git a/Samples/Streaming/Provider/appsettings.Local.json b/Samples/Streaming/Provider/appsettings.Local.json new file mode 100644 index 0000000..23b16a8 --- /dev/null +++ b/Samples/Streaming/Provider/appsettings.Local.json @@ -0,0 +1,20 @@ +{ + "AllowedHosts": "*", + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "ConnectionStrings": { + "DefaultConnection": "Host=localhost;Port=5432;Database=SdkApi;Username=postgres;Password=postgres" + }, + "JwtSettings": { + "Authority": "http://localhost:8088/realms/dataplane-signaling-api", + "Issuer": "http://keycloak:8088/realms/dataplane-signaling-api", + "Audience": "dataplane-signaling-api" + }, + "Nats": { + "NatsEndpoint": "nats://localhost:4222" + } +} diff --git a/Samples/Streaming/Provider/appsettings.json b/Samples/Streaming/Provider/appsettings.json new file mode 100644 index 0000000..efdffa2 --- /dev/null +++ b/Samples/Streaming/Provider/appsettings.json @@ -0,0 +1,36 @@ +{ + "AllowedHosts": "*", + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "ConnectionStrings": { + "DefaultConnection": "Host=postgres;Port=5432;Database=SdkApi;Username=postgres;Password=postgres" + }, + "Database": { + "AutoMigrate": true + }, + "DataPlaneSdk": { + "ControlApi": { + "BaseUrl": "http://localhost:8083/api/control" + }, + "DataplaneId": "test-dataplane-instance", + "RuntimeId": "httpdataplane-id", + "AllowedSourceTypes": [ + "test-source-type" + ], + "AllowedTransferTypes": [ + "test-transfer-type" + ] + }, + "Nats": { + "NatsEndpoint": "nats://nats:4222" + }, + "JwtSettings": { + "Authority": "http://keycloak:8088/realms/dataplane-signaling-api", + "Issuer": "http://localhost:8088/realms/dataplane-signaling-api", + "Audience": "dataplane-signaling-api" + } +} diff --git a/Samples/Streaming/Resources/Keycloak/dataplane-api-realm.json b/Samples/Streaming/Resources/Keycloak/dataplane-api-realm.json new file mode 100644 index 0000000..be8bcaa --- /dev/null +++ b/Samples/Streaming/Resources/Keycloak/dataplane-api-realm.json @@ -0,0 +1,2553 @@ +{ + "id": "5d1d7e90-1961-49ad-83dc-7c1c0c406acb", + "realm": "dataplane-signaling-api", + "notBefore": 0, + "defaultSignatureAlgorithm": "RS256", + "revokeRefreshToken": false, + "refreshTokenMaxReuse": 0, + "accessTokenLifespan": 300, + "accessTokenLifespanForImplicitFlow": 900, + "ssoSessionIdleTimeout": 1800, + "ssoSessionMaxLifespan": 36000, + "ssoSessionIdleTimeoutRememberMe": 0, + "ssoSessionMaxLifespanRememberMe": 0, + "offlineSessionIdleTimeout": 2592000, + "offlineSessionMaxLifespanEnabled": false, + "offlineSessionMaxLifespan": 5184000, + "clientSessionIdleTimeout": 0, + "clientSessionMaxLifespan": 0, + "clientOfflineSessionIdleTimeout": 0, + "clientOfflineSessionMaxLifespan": 0, + "accessCodeLifespan": 60, + "accessCodeLifespanUserAction": 300, + "accessCodeLifespanLogin": 1800, + "actionTokenGeneratedByAdminLifespan": 43200, + "actionTokenGeneratedByUserLifespan": 300, + "oauth2DeviceCodeLifespan": 600, + "oauth2DevicePollingInterval": 5, + "enabled": true, + "sslRequired": "NONE", + "registrationAllowed": false, + "registrationEmailAsUsername": false, + "rememberMe": false, + "verifyEmail": false, + "loginWithEmailAllowed": true, + "duplicateEmailsAllowed": false, + "resetPasswordAllowed": false, + "editUsernameAllowed": false, + "bruteForceProtected": false, + "permanentLockout": false, + "maxTemporaryLockouts": 0, + "bruteForceStrategy": "MULTIPLE", + "maxFailureWaitSeconds": 900, + "minimumQuickLoginWaitSeconds": 60, + "waitIncrementSeconds": 60, + "quickLoginCheckMilliSeconds": 1000, + "maxDeltaTimeSeconds": 43200, + "failureFactor": 30, + "roles": { + "realm": [ + { + "id": "fbf281d1-19f3-4048-bcf1-4418f68ab92a", + "name": "default-roles-dataplane-signaling-api", + "description": "${role_default-roles}", + "composite": true, + "composites": { + "realm": [ + "offline_access", + "uma_authorization" + ], + "client": { + "account": [ + "view-profile", + "manage-account" + ] + } + }, + "clientRole": false, + "containerId": "5d1d7e90-1961-49ad-83dc-7c1c0c406acb", + "attributes": {} + }, + { + "id": "415ce9bd-ba22-4c92-bff6-b74bd890681e", + "name": "offline_access", + "description": "${role_offline-access}", + "composite": false, + "clientRole": false, + "containerId": "5d1d7e90-1961-49ad-83dc-7c1c0c406acb", + "attributes": {} + }, + { + "id": "06e979b6-df99-4ef6-91cf-8e6f8cfa0e70", + "name": "uma_authorization", + "description": "${role_uma_authorization}", + "composite": false, + "clientRole": false, + "containerId": "5d1d7e90-1961-49ad-83dc-7c1c0c406acb", + "attributes": {} + } + ], + "client": { + "realm-management": [ + { + "id": "b3c3a5da-b11d-42bc-8fc5-b3d07c0f20d8", + "name": "view-clients", + "description": "${role_view-clients}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "query-clients" + ] + } + }, + "clientRole": true, + "containerId": "681c27c6-e429-4c38-9862-709fc873851d", + "attributes": {} + }, + { + "id": "9009f3f4-a171-4864-b545-71ba9ab88ec0", + "name": "view-users", + "description": "${role_view-users}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "query-groups", + "query-users" + ] + } + }, + "clientRole": true, + "containerId": "681c27c6-e429-4c38-9862-709fc873851d", + "attributes": {} + }, + { + "id": "23bf933a-42f7-4926-91af-58a26aa0d4ad", + "name": "query-groups", + "description": "${role_query-groups}", + "composite": false, + "clientRole": true, + "containerId": "681c27c6-e429-4c38-9862-709fc873851d", + "attributes": {} + }, + { + "id": "e8a4364c-50a7-42fe-8736-8cdc7d266235", + "name": "create-client", + "description": "${role_create-client}", + "composite": false, + "clientRole": true, + "containerId": "681c27c6-e429-4c38-9862-709fc873851d", + "attributes": {} + }, + { + "id": "274103e4-24ad-42e1-b75d-dc7eaab4b56e", + "name": "manage-authorization", + "description": "${role_manage-authorization}", + "composite": false, + "clientRole": true, + "containerId": "681c27c6-e429-4c38-9862-709fc873851d", + "attributes": {} + }, + { + "id": "b83766d4-0fbe-40fa-ba4d-547d98650ddc", + "name": "view-identity-providers", + "description": "${role_view-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "681c27c6-e429-4c38-9862-709fc873851d", + "attributes": {} + }, + { + "id": "ea0f0df2-ebae-4e08-a22a-bfc3ed381d52", + "name": "realm-admin", + "description": "${role_realm-admin}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "view-clients", + "view-users", + "query-groups", + "create-client", + "manage-authorization", + "view-identity-providers", + "view-events", + "manage-clients", + "manage-realm", + "query-users", + "query-clients", + "impersonation", + "view-authorization", + "manage-identity-providers", + "manage-users", + "query-realms", + "manage-events", + "view-realm" + ] + } + }, + "clientRole": true, + "containerId": "681c27c6-e429-4c38-9862-709fc873851d", + "attributes": {} + }, + { + "id": "0528515b-e60f-4f42-acc6-cf7394ac0d3d", + "name": "view-events", + "description": "${role_view-events}", + "composite": false, + "clientRole": true, + "containerId": "681c27c6-e429-4c38-9862-709fc873851d", + "attributes": {} + }, + { + "id": "014d8cfe-fe48-4181-85b8-990899d02d2b", + "name": "manage-clients", + "description": "${role_manage-clients}", + "composite": false, + "clientRole": true, + "containerId": "681c27c6-e429-4c38-9862-709fc873851d", + "attributes": {} + }, + { + "id": "6818751f-b287-4b56-9a16-5370b784163e", + "name": "manage-realm", + "description": "${role_manage-realm}", + "composite": false, + "clientRole": true, + "containerId": "681c27c6-e429-4c38-9862-709fc873851d", + "attributes": {} + }, + { + "id": "c1039afd-adb3-4fff-b738-ac1ae72f0c14", + "name": "query-users", + "description": "${role_query-users}", + "composite": false, + "clientRole": true, + "containerId": "681c27c6-e429-4c38-9862-709fc873851d", + "attributes": {} + }, + { + "id": "c44b128a-91da-4937-80d6-7cf2ada2caf1", + "name": "query-clients", + "description": "${role_query-clients}", + "composite": false, + "clientRole": true, + "containerId": "681c27c6-e429-4c38-9862-709fc873851d", + "attributes": {} + }, + { + "id": "430c75c9-bde4-49e6-b8e1-f06e25d04bb2", + "name": "impersonation", + "description": "${role_impersonation}", + "composite": false, + "clientRole": true, + "containerId": "681c27c6-e429-4c38-9862-709fc873851d", + "attributes": {} + }, + { + "id": "e52c3adf-c5fe-48ee-9e5a-945059091392", + "name": "view-authorization", + "description": "${role_view-authorization}", + "composite": false, + "clientRole": true, + "containerId": "681c27c6-e429-4c38-9862-709fc873851d", + "attributes": {} + }, + { + "id": "0b66b24f-e035-4726-b7a3-4ea99fcebd0c", + "name": "manage-identity-providers", + "description": "${role_manage-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "681c27c6-e429-4c38-9862-709fc873851d", + "attributes": {} + }, + { + "id": "43d0363c-0908-4b14-9b52-4f6909b64a7d", + "name": "manage-users", + "description": "${role_manage-users}", + "composite": false, + "clientRole": true, + "containerId": "681c27c6-e429-4c38-9862-709fc873851d", + "attributes": {} + }, + { + "id": "b327aaae-8545-40ac-9938-7422048eb336", + "name": "query-realms", + "description": "${role_query-realms}", + "composite": false, + "clientRole": true, + "containerId": "681c27c6-e429-4c38-9862-709fc873851d", + "attributes": {} + }, + { + "id": "845d32e5-3faa-4054-a6b5-d9dce02f68a2", + "name": "manage-events", + "description": "${role_manage-events}", + "composite": false, + "clientRole": true, + "containerId": "681c27c6-e429-4c38-9862-709fc873851d", + "attributes": {} + }, + { + "id": "66a14daa-97b0-4087-95dd-937131f1f8f7", + "name": "view-realm", + "description": "${role_view-realm}", + "composite": false, + "clientRole": true, + "containerId": "681c27c6-e429-4c38-9862-709fc873851d", + "attributes": {} + } + ], + "dataplane-signaling-api": [ + { + "id": "785871d1-d882-4ae3-9a91-7ea6d8c4eb48", + "name": "uma_protection", + "composite": false, + "clientRole": true, + "containerId": "cc2c64b6-f0f6-4a91-b877-9dd45f88e5a0", + "attributes": {} + } + ], + "security-admin-console": [], + "admin-cli": [], + "account-console": [], + "broker": [ + { + "id": "bdf2113c-3072-4f63-b682-7de5b958a134", + "name": "read-token", + "description": "${role_read-token}", + "composite": false, + "clientRole": true, + "containerId": "8581b577-e99b-4f67-a09c-a5ddf4a112fd", + "attributes": {} + } + ], + "account": [ + { + "id": "bc97e926-36f8-4bbb-8fc5-f50019fd58a4", + "name": "view-groups", + "description": "${role_view-groups}", + "composite": false, + "clientRole": true, + "containerId": "a7aede34-ff0a-455b-9fc0-80f6bd55aa61", + "attributes": {} + }, + { + "id": "e144fa17-49d7-4ca3-a6f5-ea6aace7eb36", + "name": "view-profile", + "description": "${role_view-profile}", + "composite": false, + "clientRole": true, + "containerId": "a7aede34-ff0a-455b-9fc0-80f6bd55aa61", + "attributes": {} + }, + { + "id": "aa45a0b7-b596-4afc-8583-0ad92c8bc4f3", + "name": "view-applications", + "description": "${role_view-applications}", + "composite": false, + "clientRole": true, + "containerId": "a7aede34-ff0a-455b-9fc0-80f6bd55aa61", + "attributes": {} + }, + { + "id": "8dc76cc2-6dd4-4b2a-87b0-4825dccb3dd3", + "name": "manage-account-links", + "description": "${role_manage-account-links}", + "composite": false, + "clientRole": true, + "containerId": "a7aede34-ff0a-455b-9fc0-80f6bd55aa61", + "attributes": {} + }, + { + "id": "790982e6-6c50-4c82-b92d-24e978ba8e91", + "name": "manage-account", + "description": "${role_manage-account}", + "composite": true, + "composites": { + "client": { + "account": [ + "manage-account-links" + ] + } + }, + "clientRole": true, + "containerId": "a7aede34-ff0a-455b-9fc0-80f6bd55aa61", + "attributes": {} + }, + { + "id": "6386e24c-5e1c-4be8-9729-889a0cf1de79", + "name": "manage-consent", + "description": "${role_manage-consent}", + "composite": true, + "composites": { + "client": { + "account": [ + "view-consent" + ] + } + }, + "clientRole": true, + "containerId": "a7aede34-ff0a-455b-9fc0-80f6bd55aa61", + "attributes": {} + }, + { + "id": "d99fa073-7a39-4832-bf2e-d36565c0a165", + "name": "delete-account", + "description": "${role_delete-account}", + "composite": false, + "clientRole": true, + "containerId": "a7aede34-ff0a-455b-9fc0-80f6bd55aa61", + "attributes": {} + }, + { + "id": "fc0c7190-b21f-4e3a-9ada-d18377458950", + "name": "view-consent", + "description": "${role_view-consent}", + "composite": false, + "clientRole": true, + "containerId": "a7aede34-ff0a-455b-9fc0-80f6bd55aa61", + "attributes": {} + } + ] + } + }, + "groups": [], + "defaultRole": { + "id": "fbf281d1-19f3-4048-bcf1-4418f68ab92a", + "name": "default-roles-dataplane-signaling-api", + "description": "${role_default-roles}", + "composite": true, + "clientRole": false, + "containerId": "5d1d7e90-1961-49ad-83dc-7c1c0c406acb" + }, + "requiredCredentials": [ + "password" + ], + "otpPolicyType": "totp", + "otpPolicyAlgorithm": "HmacSHA1", + "otpPolicyInitialCounter": 0, + "otpPolicyDigits": 6, + "otpPolicyLookAheadWindow": 1, + "otpPolicyPeriod": 30, + "otpPolicyCodeReusable": false, + "otpSupportedApplications": [ + "totpAppFreeOTPName", + "totpAppGoogleName", + "totpAppMicrosoftAuthenticatorName" + ], + "localizationTexts": {}, + "webAuthnPolicyRpEntityName": "keycloak", + "webAuthnPolicySignatureAlgorithms": [ + "ES256", + "RS256" + ], + "webAuthnPolicyRpId": "", + "webAuthnPolicyAttestationConveyancePreference": "not specified", + "webAuthnPolicyAuthenticatorAttachment": "not specified", + "webAuthnPolicyRequireResidentKey": "not specified", + "webAuthnPolicyUserVerificationRequirement": "not specified", + "webAuthnPolicyCreateTimeout": 0, + "webAuthnPolicyAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyAcceptableAaguids": [], + "webAuthnPolicyExtraOrigins": [], + "webAuthnPolicyPasswordlessRpEntityName": "keycloak", + "webAuthnPolicyPasswordlessSignatureAlgorithms": [ + "ES256", + "RS256" + ], + "webAuthnPolicyPasswordlessRpId": "", + "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified", + "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified", + "webAuthnPolicyPasswordlessRequireResidentKey": "not specified", + "webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified", + "webAuthnPolicyPasswordlessCreateTimeout": 0, + "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyPasswordlessAcceptableAaguids": [], + "webAuthnPolicyPasswordlessExtraOrigins": [], + "users": [ + { + "id": "4363900f-3451-48a1-b2eb-b92dbc47677e", + "username": "service-account-dataplane-signaling-api", + "emailVerified": false, + "createdTimestamp": 1756373486363, + "enabled": true, + "totp": false, + "serviceAccountClientId": "dataplane-signaling-api", + "disableableCredentialTypes": [], + "requiredActions": [], + "realmRoles": [ + "default-roles-dataplane-signaling-api" + ], + "clientRoles": { + "dataplane-signaling-api": [ + "uma_protection" + ] + }, + "notBefore": 0, + "groups": [] + } + ], + "scopeMappings": [ + { + "clientScope": "offline_access", + "roles": [ + "offline_access" + ] + } + ], + "clientScopeMappings": { + "account": [ + { + "client": "account-console", + "roles": [ + "manage-account", + "view-groups" + ] + } + ] + }, + "clients": [ + { + "id": "a7aede34-ff0a-455b-9fc0-80f6bd55aa61", + "clientId": "account", + "name": "${client_account}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/dataplane-signaling-api/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/realms/dataplane-signaling-api/account/*" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "realm_client": "false", + "post.logout.redirect.uris": "+" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "organization", + "microprofile-jwt" + ] + }, + { + "id": "8cc5d84a-fe7b-494e-84ac-e0014f12d243", + "clientId": "account-console", + "name": "${client_account-console}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/dataplane-signaling-api/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/realms/dataplane-signaling-api/account/*" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "realm_client": "false", + "post.logout.redirect.uris": "+", + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "2d80cc17-73ca-415d-a1bc-14993e449425", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "organization", + "microprofile-jwt" + ] + }, + { + "id": "69374220-beb4-41a5-803e-edb15d80ba5a", + "clientId": "admin-cli", + "name": "${client_admin-cli}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": false, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "realm_client": "false", + "client.use.lightweight.access.token.enabled": "true" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "organization", + "microprofile-jwt" + ] + }, + { + "id": "8581b577-e99b-4f67-a09c-a5ddf4a112fd", + "clientId": "broker", + "name": "${client_broker}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": true, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "realm_client": "true" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "organization", + "microprofile-jwt" + ] + }, + { + "id": "cc2c64b6-f0f6-4a91-b877-9dd45f88e5a0", + "clientId": "dataplane-signaling-api", + "name": "", + "description": "", + "rootUrl": "", + "adminUrl": "", + "baseUrl": "", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "mpoTntIrYjsBqhqo0xuzqRUtCWQCWjG3", + "redirectUris": [ + "/*" + ], + "webOrigins": [ + "/*" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": true, + "authorizationServicesEnabled": true, + "publicClient": false, + "frontchannelLogout": true, + "protocol": "openid-connect", + "attributes": { + "realm_client": "false", + "oidc.ciba.grant.enabled": "false", + "client.secret.creation.time": "1751356476", + "backchannel.logout.session.required": "true", + "standard.token.exchange.enabled": "false", + "oauth2.device.authorization.grant.enabled": "false", + "backchannel.logout.revoke.offline.tokens": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "protocolMappers": [ + { + "id": "c24d369e-f3dc-440f-b85b-afdd72dac3d9", + "name": "audience mapper", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "false", + "lightweight.claim": "false", + "introspection.token.claim": "true", + "access.token.claim": "true", + "included.custom.audience": "dataplane-signaling-api", + "userinfo.token.claim": "false" + } + } + ], + "defaultClientScopes": [ + "web-origins", + "service_account", + "acr", + "roles", + "profile", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "organization", + "microprofile-jwt" + ] + }, + { + "id": "681c27c6-e429-4c38-9862-709fc873851d", + "clientId": "realm-management", + "name": "${client_realm-management}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": true, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "realm_client": "true" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "organization", + "microprofile-jwt" + ] + }, + { + "id": "05f13fb8-9f23-4399-8574-6824d786cd88", + "clientId": "security-admin-console", + "name": "${client_security-admin-console}", + "rootUrl": "${authAdminUrl}", + "baseUrl": "/admin/dataplane-signaling-api/console/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/admin/dataplane-signaling-api/console/*" + ], + "webOrigins": [ + "+" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "realm_client": "false", + "client.use.lightweight.access.token.enabled": "true", + "post.logout.redirect.uris": "+", + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "d34c1faa-5bc9-4d6d-86e7-3474ef20e61b", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "organization", + "microprofile-jwt" + ] + } + ], + "clientScopes": [ + { + "id": "f7241ee8-571a-4d9b-9549-659d62799aa4", + "name": "web-origins", + "description": "OpenID Connect scope for add allowed web origins to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "consent.screen.text": "", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "d9f774d5-b344-4253-b2be-b3f9f8c7e3cb", + "name": "allowed web origins", + "protocol": "openid-connect", + "protocolMapper": "oidc-allowed-origins-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "access.token.claim": "true" + } + } + ] + }, + { + "id": "e86e245a-f543-4f72-bb6c-d1cf84ed7cea", + "name": "address", + "description": "OpenID Connect built-in scope: address", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "consent.screen.text": "${addressScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "4bd47b6f-ee53-48ba-9b84-1865e44f1063", + "name": "address", + "protocol": "openid-connect", + "protocolMapper": "oidc-address-mapper", + "consentRequired": false, + "config": { + "user.attribute.formatted": "formatted", + "user.attribute.country": "country", + "introspection.token.claim": "true", + "user.attribute.postal_code": "postal_code", + "userinfo.token.claim": "true", + "user.attribute.street": "street", + "id.token.claim": "true", + "user.attribute.region": "region", + "access.token.claim": "true", + "user.attribute.locality": "locality" + } + } + ] + }, + { + "id": "a6b5fe2c-f4e3-4fc6-ab0d-a3ac57bf6519", + "name": "service_account", + "description": "Specific scope for a client enabled for service accounts", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "6936ac03-a67c-497a-9fb3-8f49487e9805", + "name": "Client IP Address", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientAddress", + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientAddress", + "jsonType.label": "String" + } + }, + { + "id": "208c538c-db34-46d3-b0ec-bcad1617b995", + "name": "Client ID", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "client_id", + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "client_id", + "jsonType.label": "String" + } + }, + { + "id": "d28762ec-7971-47af-95e7-a417085e21d9", + "name": "Client Host", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientHost", + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientHost", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "a0f91a77-ef05-4540-abaf-6ba183d9ff8e", + "name": "sub-clientid-scope", + "description": "", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "true", + "gui.order": "", + "consent.screen.text": "" + }, + "protocolMappers": [ + { + "id": "57d52720-4f23-495f-b58b-e18921cba698", + "name": "client-id-to-sub", + "protocol": "openid-connect", + "protocolMapper": "oidc-hardcoded-claim-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "claim.value": "4363900f-3451-48a1-b2eb-b92dbc47677e", + "userinfo.token.claim": "true", + "id.token.claim": "true", + "lightweight.claim": "false", + "access.token.claim": "true", + "claim.name": "sub", + "jsonType.label": "String", + "access.tokenResponse.claim": "false" + } + } + ] + }, + { + "id": "6b5625a1-2c54-4bd5-a36e-40ee80c14de9", + "name": "profile", + "description": "OpenID Connect built-in scope: profile", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "consent.screen.text": "${profileScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "ea214f9b-39ed-4f52-bd76-0b35e98c5035", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + }, + { + "id": "43aee751-f7ab-46dc-93ea-416d986ecd96", + "name": "zoneinfo", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "zoneinfo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "zoneinfo", + "jsonType.label": "String" + } + }, + { + "id": "7dc65ce0-c429-4263-b242-07788a9bd1ba", + "name": "family name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "lastName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "family_name", + "jsonType.label": "String" + } + }, + { + "id": "1365ec38-c83a-4ab4-8b52-b0d3b88cb50e", + "name": "website", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "website", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "website", + "jsonType.label": "String" + } + }, + { + "id": "c8826c96-e0dc-4bc4-b0a1-99a5d4a06dfb", + "name": "nickname", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "nickname", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "nickname", + "jsonType.label": "String" + } + }, + { + "id": "6e64c656-d74e-49c5-aff6-813c554efde5", + "name": "picture", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "picture", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "picture", + "jsonType.label": "String" + } + }, + { + "id": "50a63f8d-cf95-4bd3-a323-8178d74231f4", + "name": "middle name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "middleName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "middle_name", + "jsonType.label": "String" + } + }, + { + "id": "e95063d1-d852-4697-83cc-0dae94f8422b", + "name": "updated at", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "updatedAt", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "updated_at", + "jsonType.label": "long" + } + }, + { + "id": "f454b80f-382a-4ea8-8370-5f16c2eebe1a", + "name": "profile", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "profile", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "profile", + "jsonType.label": "String" + } + }, + { + "id": "a7cbcd6b-1dd2-45db-8bb8-e6f7b64e071b", + "name": "gender", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "gender", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "gender", + "jsonType.label": "String" + } + }, + { + "id": "23dd6e5f-6c8e-4883-bff8-8848be743adf", + "name": "birthdate", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "birthdate", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "birthdate", + "jsonType.label": "String" + } + }, + { + "id": "f4be685e-5ec5-4448-b4dc-af00d4830d43", + "name": "full name", + "protocol": "openid-connect", + "protocolMapper": "oidc-full-name-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + }, + { + "id": "21176d65-b342-4f21-9b4e-72b75bdd4422", + "name": "given name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "firstName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "given_name", + "jsonType.label": "String" + } + }, + { + "id": "cf9092e6-b2ae-4e1b-bc76-97a22e862b4a", + "name": "username", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "preferred_username", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "52d0366c-f197-4cdf-a060-5b2fed56f05f", + "name": "basic", + "description": "OpenID Connect scope for add all basic claims to the token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "7484a288-b5aa-4ad5-979d-13fb48894a5b", + "name": "sub", + "protocol": "openid-connect", + "protocolMapper": "oidc-sub-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "access.token.claim": "true" + } + }, + { + "id": "28bfdba4-6599-4f53-9d29-7a431c4fe105", + "name": "auth_time", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "AUTH_TIME", + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "auth_time", + "jsonType.label": "long" + } + } + ] + }, + { + "id": "303d73e7-f8de-4092-879a-04192020153b", + "name": "saml_organization", + "description": "Organization Membership", + "protocol": "saml", + "attributes": { + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "1b1680fb-3fd6-40a1-9c4b-4ac3aea1f08d", + "name": "organization", + "protocol": "saml", + "protocolMapper": "saml-organization-membership-mapper", + "consentRequired": false, + "config": {} + } + ] + }, + { + "id": "11622d4f-a665-4364-ab2e-d459c1f787e7", + "name": "microprofile-jwt", + "description": "Microprofile - JWT built-in scope", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "b555408c-59a6-4f63-a354-c48a9d61dd57", + "name": "upn", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "upn", + "jsonType.label": "String" + } + }, + { + "id": "82746c3f-5674-4876-b4ad-9a69e8db2223", + "name": "groups", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "multivalued": "true", + "user.attribute": "foo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "groups", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "ebe8d3e9-9320-4cc3-92ff-fb2735d8b0e1", + "name": "organization", + "description": "Additional claims about the organization a subject belongs to", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "consent.screen.text": "${organizationScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "cc50de7f-9dd2-4e02-8026-100e1b9f14f2", + "name": "organization", + "protocol": "openid-connect", + "protocolMapper": "oidc-organization-membership-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "organization", + "jsonType.label": "String", + "multivalued": "true" + } + } + ] + }, + { + "id": "540e11a8-0f9f-43c7-b437-55434fb3b295", + "name": "roles", + "description": "OpenID Connect scope for add user roles to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "consent.screen.text": "${rolesScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "5aee1dca-2127-40b3-b821-5885eb1eccd9", + "name": "realm roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "realm_access.roles", + "jsonType.label": "String", + "multivalued": "true" + } + }, + { + "id": "3388ee40-df0e-49f8-828e-a09d4cd301eb", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "access.token.claim": "true" + } + }, + { + "id": "5c6a1763-73b6-4202-8862-43bbdcf81814", + "name": "client roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-client-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "resource_access.${client_id}.roles", + "jsonType.label": "String", + "multivalued": "true" + } + } + ] + }, + { + "id": "d6a27161-c68d-437f-9356-70c667ca54a1", + "name": "offline_access", + "description": "OpenID Connect built-in scope: offline_access", + "protocol": "openid-connect", + "attributes": { + "consent.screen.text": "${offlineAccessScopeConsentText}", + "display.on.consent.screen": "true" + } + }, + { + "id": "e5bc9a75-67f3-4cd7-a0a9-a3d5869bc678", + "name": "acr", + "description": "OpenID Connect scope for add acr (authentication context class reference) to the token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "65d172e9-6fd9-4017-89e6-d0187e1329a7", + "name": "acr loa level", + "protocol": "openid-connect", + "protocolMapper": "oidc-acr-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true" + } + } + ] + }, + { + "id": "7f2a85ad-b039-442d-b0ab-736b4b329268", + "name": "phone", + "description": "OpenID Connect built-in scope: phone", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "consent.screen.text": "${phoneScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "ab984d8e-f002-4e51-80d9-3333cba9d34d", + "name": "phone number verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "phoneNumberVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number_verified", + "jsonType.label": "boolean" + } + }, + { + "id": "5c9340e5-1737-4a4d-88f5-af3243e8a932", + "name": "phone number", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "phoneNumber", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "f440bb0a-6802-4327-9ff8-59306beecc53", + "name": "role_list", + "description": "SAML role list", + "protocol": "saml", + "attributes": { + "consent.screen.text": "${samlRoleListScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "8b182426-4dfa-42ea-913e-5a863ea868af", + "name": "role list", + "protocol": "saml", + "protocolMapper": "saml-role-list-mapper", + "consentRequired": false, + "config": { + "single": "false", + "attribute.nameformat": "Basic", + "attribute.name": "Role" + } + } + ] + }, + { + "id": "8b65c105-fd98-41e5-8573-1a4f9629fcff", + "name": "email", + "description": "OpenID Connect built-in scope: email", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "consent.screen.text": "${emailScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "4286bfd1-f709-4329-b9fe-e400773c44cd", + "name": "email verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "emailVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email_verified", + "jsonType.label": "boolean" + } + }, + { + "id": "cea71b50-ac2f-49d3-8cf7-8bad3a32b992", + "name": "email", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "email", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email", + "jsonType.label": "String" + } + } + ] + } + ], + "defaultDefaultClientScopes": [ + "role_list", + "saml_organization", + "profile", + "email", + "roles", + "web-origins", + "acr", + "basic" + ], + "defaultOptionalClientScopes": [ + "offline_access", + "address", + "phone", + "microprofile-jwt", + "organization" + ], + "browserSecurityHeaders": { + "contentSecurityPolicyReportOnly": "", + "xContentTypeOptions": "nosniff", + "referrerPolicy": "no-referrer", + "xRobotsTag": "none", + "xFrameOptions": "SAMEORIGIN", + "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "strictTransportSecurity": "max-age=31536000; includeSubDomains" + }, + "smtpServer": {}, + "eventsEnabled": false, + "eventsListeners": [ + "jboss-logging" + ], + "enabledEventTypes": [], + "adminEventsEnabled": false, + "adminEventsDetailsEnabled": false, + "identityProviders": [], + "identityProviderMappers": [], + "components": { + "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [ + { + "id": "c3e4b6dc-af5b-4298-bbe7-47795b47542d", + "name": "Trusted Hosts", + "providerId": "trusted-hosts", + "subType": "anonymous", + "subComponents": {}, + "config": { + "host-sending-registration-request-must-match": [ + "true" + ], + "client-uris-must-match": [ + "true" + ] + } + }, + { + "id": "b21ea286-879b-42ac-8233-65ebd03dcafb", + "name": "Consent Required", + "providerId": "consent-required", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "1d15c63f-4132-4afa-9d9a-a6f698e2781b", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "saml-role-list-mapper", + "oidc-sha256-pairwise-sub-mapper", + "oidc-address-mapper", + "oidc-usermodel-attribute-mapper", + "oidc-full-name-mapper", + "oidc-usermodel-property-mapper", + "saml-user-attribute-mapper", + "saml-user-property-mapper" + ] + } + }, + { + "id": "659d9881-2437-4d23-9bfc-b2c58e543fdf", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "oidc-full-name-mapper", + "oidc-usermodel-property-mapper", + "oidc-sha256-pairwise-sub-mapper", + "saml-user-property-mapper", + "saml-user-attribute-mapper", + "oidc-address-mapper", + "oidc-usermodel-attribute-mapper", + "saml-role-list-mapper" + ] + } + }, + { + "id": "4e57c4bc-a48b-4c4a-ac0f-033af8ec5193", + "name": "Full Scope Disabled", + "providerId": "scope", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "bb178118-fed2-4163-a431-e36ffd1dd72a", + "name": "Max Clients Limit", + "providerId": "max-clients", + "subType": "anonymous", + "subComponents": {}, + "config": { + "max-clients": [ + "200" + ] + } + }, + { + "id": "7ac7bf14-f181-4913-80b1-a672c33dafc4", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + }, + { + "id": "4cb5d288-0f7a-4597-9272-9822243a6507", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + } + ], + "org.keycloak.keys.KeyProvider": [ + { + "id": "23a38bd4-901e-4922-ad7d-9f913cfbb6a3", + "name": "rsa-generated", + "providerId": "rsa-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ] + } + }, + { + "id": "ae79441c-d444-47f9-a3fb-a4570e8c0bfe", + "name": "rsa-enc-generated", + "providerId": "rsa-enc-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ], + "algorithm": [ + "RSA-OAEP" + ] + } + }, + { + "id": "812c12b2-f1c0-41be-a184-9db0db6f73a2", + "name": "aes-generated", + "providerId": "aes-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ] + } + }, + { + "id": "2007f44d-fb3d-4ce6-b7f5-e7a48def705c", + "name": "hmac-generated-hs512", + "providerId": "hmac-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ], + "algorithm": [ + "HS512" + ] + } + } + ] + }, + "internationalizationEnabled": false, + "supportedLocales": [], + "authenticationFlows": [ + { + "id": "40e877dd-4b50-4be9-898e-20224d64da8c", + "alias": "Account verification options", + "description": "Method with which to verity the existing account", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-email-verification", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Verify Existing Account by Re-authentication", + "userSetupAllowed": false + } + ] + }, + { + "id": "27d92516-ad33-48a9-8e8d-eeeb5618552b", + "alias": "Browser - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-otp-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "4743da93-d772-4c69-a5a8-2c3f7a17c161", + "alias": "Browser - Conditional Organization", + "description": "Flow to determine if the organization identity-first login is to be used", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "organization", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "9acb8ad1-4326-465e-8658-038a9e8403b7", + "alias": "Direct Grant - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "direct-grant-validate-otp", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "23f43398-8df6-4045-8602-dbeaa97635d2", + "alias": "First Broker Login - Conditional Organization", + "description": "Flow to determine if the authenticator that adds organization members is to be used", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "idp-add-organization-member", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "793d9754-edb6-4ecf-a772-accf047b701d", + "alias": "First broker login - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-otp-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "bb4ed01c-323c-42a7-8c3f-a51a521017c7", + "alias": "Handle Existing Account", + "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-confirm-link", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Account verification options", + "userSetupAllowed": false + } + ] + }, + { + "id": "9e307086-9642-4b8c-b9b9-c10801c911c0", + "alias": "Organization", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 10, + "autheticatorFlow": true, + "flowAlias": "Browser - Conditional Organization", + "userSetupAllowed": false + } + ] + }, + { + "id": "f0aa3fac-1029-47a2-89e1-b251de47b07f", + "alias": "Reset - Conditional OTP", + "description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-otp", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "216b281f-f3c5-49c0-8212-0fcbc1cd0eea", + "alias": "User creation or linking", + "description": "Flow for the existing/non-existing user alternatives", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "create unique user config", + "authenticator": "idp-create-user-if-unique", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Handle Existing Account", + "userSetupAllowed": false + } + ] + }, + { + "id": "5f981e15-6f9a-434a-980f-2002d3f7cf98", + "alias": "Verify Existing Account by Re-authentication", + "description": "Reauthentication of existing account", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-username-password-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "First broker login - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "04a37523-5837-4b0c-8799-6e08af277814", + "alias": "browser", + "description": "Browser based authentication", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-cookie", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-spnego", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "identity-provider-redirector", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 25, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 26, + "autheticatorFlow": true, + "flowAlias": "Organization", + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 30, + "autheticatorFlow": true, + "flowAlias": "forms", + "userSetupAllowed": false + } + ] + }, + { + "id": "9ea5ee33-be35-49fa-b10d-1322e1e6538c", + "alias": "clients", + "description": "Base authentication for clients", + "providerId": "client-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "client-secret", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-jwt", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-secret-jwt", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 30, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-x509", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 40, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "70165b4a-34c7-4c4d-85aa-56442cc01d08", + "alias": "direct grant", + "description": "OpenID Connect Resource Owner Grant", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "direct-grant-validate-username", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "direct-grant-validate-password", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 30, + "autheticatorFlow": true, + "flowAlias": "Direct Grant - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "5acbfb24-530c-40d6-89ab-1a8175b111dd", + "alias": "docker auth", + "description": "Used by Docker clients to authenticate against the IDP", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "docker-http-basic-authenticator", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "85aa471e-104c-4b94-85af-24b93620bfbc", + "alias": "first broker login", + "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "review profile config", + "authenticator": "idp-review-profile", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "User creation or linking", + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 50, + "autheticatorFlow": true, + "flowAlias": "First Broker Login - Conditional Organization", + "userSetupAllowed": false + } + ] + }, + { + "id": "8cdf50fc-9e15-4739-9837-6aad51e23d1d", + "alias": "forms", + "description": "Username, password, otp and other auth forms.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-username-password-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Browser - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "bbf5be69-f0d6-4204-8878-7371a2addb50", + "alias": "registration", + "description": "Registration flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-page-form", + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": true, + "flowAlias": "registration form", + "userSetupAllowed": false + } + ] + }, + { + "id": "f6fc4443-8c44-4736-87a2-357e81e8fda7", + "alias": "registration form", + "description": "Registration form", + "providerId": "form-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-user-creation", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-password-action", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 50, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-recaptcha-action", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 60, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-terms-and-conditions", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 70, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "e1aac4d3-7482-420c-b2cc-87fd01ec7f60", + "alias": "reset credentials", + "description": "Reset credentials for a user if they forgot their password or something", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "reset-credentials-choose-user", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-credential-email", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-password", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 30, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 40, + "autheticatorFlow": true, + "flowAlias": "Reset - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "19b83646-bb26-447a-9f91-9b9914744174", + "alias": "saml ecp", + "description": "SAML ECP Profile Authentication Flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "http-basic-authenticator", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + } + ], + "authenticatorConfig": [ + { + "id": "9bf41a41-e73e-48f8-935b-205ea7b39922", + "alias": "create unique user config", + "config": { + "require.password.update.after.registration": "false" + } + }, + { + "id": "14e18262-b5e1-4743-b5da-e2c48a55b8ce", + "alias": "review profile config", + "config": { + "update.profile.on.first.login": "missing" + } + } + ], + "requiredActions": [ + { + "alias": "CONFIGURE_TOTP", + "name": "Configure OTP", + "providerId": "CONFIGURE_TOTP", + "enabled": true, + "defaultAction": false, + "priority": 10, + "config": {} + }, + { + "alias": "TERMS_AND_CONDITIONS", + "name": "Terms and Conditions", + "providerId": "TERMS_AND_CONDITIONS", + "enabled": false, + "defaultAction": false, + "priority": 20, + "config": {} + }, + { + "alias": "UPDATE_PASSWORD", + "name": "Update Password", + "providerId": "UPDATE_PASSWORD", + "enabled": true, + "defaultAction": false, + "priority": 30, + "config": {} + }, + { + "alias": "UPDATE_PROFILE", + "name": "Update Profile", + "providerId": "UPDATE_PROFILE", + "enabled": true, + "defaultAction": false, + "priority": 40, + "config": {} + }, + { + "alias": "VERIFY_EMAIL", + "name": "Verify Email", + "providerId": "VERIFY_EMAIL", + "enabled": true, + "defaultAction": false, + "priority": 50, + "config": {} + }, + { + "alias": "delete_account", + "name": "Delete Account", + "providerId": "delete_account", + "enabled": false, + "defaultAction": false, + "priority": 60, + "config": {} + }, + { + "alias": "webauthn-register", + "name": "Webauthn Register", + "providerId": "webauthn-register", + "enabled": true, + "defaultAction": false, + "priority": 70, + "config": {} + }, + { + "alias": "webauthn-register-passwordless", + "name": "Webauthn Register Passwordless", + "providerId": "webauthn-register-passwordless", + "enabled": true, + "defaultAction": false, + "priority": 80, + "config": {} + }, + { + "alias": "VERIFY_PROFILE", + "name": "Verify Profile", + "providerId": "VERIFY_PROFILE", + "enabled": true, + "defaultAction": false, + "priority": 90, + "config": {} + }, + { + "alias": "delete_credential", + "name": "Delete Credential", + "providerId": "delete_credential", + "enabled": true, + "defaultAction": false, + "priority": 100, + "config": {} + }, + { + "alias": "update_user_locale", + "name": "Update User Locale", + "providerId": "update_user_locale", + "enabled": true, + "defaultAction": false, + "priority": 1000, + "config": {} + } + ], + "browserFlow": "browser", + "registrationFlow": "registration", + "directGrantFlow": "direct grant", + "resetCredentialsFlow": "reset credentials", + "clientAuthenticationFlow": "clients", + "dockerAuthenticationFlow": "docker auth", + "firstBrokerLoginFlow": "first broker login", + "attributes": { + "cibaBackchannelTokenDeliveryMode": "poll", + "cibaExpiresIn": "120", + "cibaAuthRequestedUserHint": "login_hint", + "oauth2DeviceCodeLifespan": "600", + "oauth2DevicePollingInterval": "5", + "parRequestUriLifespan": "60", + "cibaInterval": "5", + "realmReusableOtpCode": "false" + }, + "keycloakVersion": "26.2.5", + "userManagedAccessAllowed": false, + "organizationsEnabled": false, + "verifiableCredentialsEnabled": false, + "adminPermissionsEnabled": false, + "clientProfiles": { + "profiles": [] + }, + "clientPolicies": { + "policies": [] + } +} \ No newline at end of file diff --git a/Samples/Streaming/docker-compose.yaml b/Samples/Streaming/docker-compose.yaml new file mode 100644 index 0000000..ce5b71f --- /dev/null +++ b/Samples/Streaming/docker-compose.yaml @@ -0,0 +1,62 @@ +services: + + provider-dataplane: + build: + context: ./Provider + dockerfile: Dockerfile + args: + NUGET_USERNAME: ${NUGET_USERNAME} + NUGET_PASSWORD: ${NUGET_PASSWORD} + + ports: + - "8080:8080" + depends_on: + postgres: + condition: service_healthy + networks: + - sample-network + + postgres: + image: postgres:17-alpine + environment: + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=postgres + - POSTGRES_DB=SdkApi + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + healthcheck: + test: [ "CMD-SHELL", "pg_isready -U postgres -d SdkApi" ] + interval: 5s + timeout: 5s + retries: 5 + networks: + - sample-network + + keycloak: + image: quay.io/keycloak/keycloak:latest + environment: + KC_BOOTSTRAP_ADMIN_USERNAME: admin + KC_BOOTSTRAP_ADMIN_PASSWORD: admin + KC_HEALTH_ENABLED: "true" + KC_LOG_LEVEL: info + command: [ "start-dev", "--http-port", "8088", "--import-realm" ] + ports: + - "8088:8088" + networks: + - sample-network + volumes: + - ./Resources/Keycloak/dataplane-api-realm.json:/opt/keycloak/data/import/realm-export.json + + nats: + image: nats:latest + ports: + - "4222:4222" + +volumes: + postgres_data: + +networks: + sample-network: + driver: bridge diff --git a/compose.yaml b/compose.yaml deleted file mode 100644 index 323351b..0000000 --- a/compose.yaml +++ /dev/null @@ -1,44 +0,0 @@ -services: - sdk.example: - image: sdk.example - build: - context: . - dockerfile: Sdk.Example/Dockerfile - environment: - - DOTNET_ENVIRONMENT=test # contains configuration for docker compose setup - depends_on: - controlplane: - condition: service_healthy - postgres: - condition: service_healthy - links: - - postgres - - controlplane - - - - postgres: - image: postgres:15 - environment: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - POSTGRES_DB: SdkApi - ports: - - "15432:5432" - healthcheck: - test: [ "CMD-SHELL", "pg_isready -U postgres -d SdkApi" ] - interval: 10s - timeout: 5s - retries: 5 - - controlplane: - image: "mvd-controlplane" - volumes: - - ./Sdk.Example/res/configuration.properties:/app/configuration.properties - - ./Sdk.Example/res/deployment/assets/participants/participants.local.json:/app/deployment/assets/participants/participants.local.json - - healthcheck: - test: [ "CMD", "curl", "-f", "http://localhost:8080/check/readiness" ] - interval: 11s - timeout: 5s - retries: 5 diff --git a/dataplane-sdk-dotnet.sln b/dataplane-sdk-dotnet.sln index b1b46db..0850eef 100644 --- a/dataplane-sdk-dotnet.sln +++ b/dataplane-sdk-dotnet.sln @@ -22,6 +22,13 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HttpDataplane-Keycloak", "S EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataPlane.Sdk.Test.Utils", "DataPlane.Sdk.Test.Utils\DataPlane.Sdk.Test.Utils.csproj", "{391E2C8C-7806-40CC-A847-2ECD25FE73CC}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Streaming", "Streaming", "{9B7B836B-426D-4F48-8467-98E24CE4FCB1}" + ProjectSection(SolutionItems) = preProject + Samples\Streaming\docker-compose.yaml = Samples\Streaming\docker-compose.yaml + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Provider", "Samples\Streaming\Provider\Provider.csproj", "{C1B9F93E-4499-4506-A654-9B19601D8FA2}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -55,8 +62,14 @@ Global {391E2C8C-7806-40CC-A847-2ECD25FE73CC}.Debug|Any CPU.Build.0 = Debug|Any CPU {391E2C8C-7806-40CC-A847-2ECD25FE73CC}.Release|Any CPU.ActiveCfg = Release|Any CPU {391E2C8C-7806-40CC-A847-2ECD25FE73CC}.Release|Any CPU.Build.0 = Release|Any CPU + {C1B9F93E-4499-4506-A654-9B19601D8FA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C1B9F93E-4499-4506-A654-9B19601D8FA2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C1B9F93E-4499-4506-A654-9B19601D8FA2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C1B9F93E-4499-4506-A654-9B19601D8FA2}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {534284F6-8E17-4774-95FA-7E133AF346F7} = {32055AC6-C4BE-4B7C-A829-F268AB12E577} + {9B7B836B-426D-4F48-8467-98E24CE4FCB1} = {32055AC6-C4BE-4B7C-A829-F268AB12E577} + {C1B9F93E-4499-4506-A654-9B19601D8FA2} = {9B7B836B-426D-4F48-8467-98E24CE4FCB1} EndGlobalSection EndGlobal From 808a8dfec06595fa3938746f7a04b93c529b0ac2 Mon Sep 17 00:00:00 2001 From: Paul Latzelsperger Date: Thu, 16 Oct 2025 17:04:51 +0200 Subject: [PATCH 2/2] feat: Add Streaming sample based on NATS --- .../HttpDataplane-Keycloak.csproj | 2 +- Samples/Streaming/Consumer/Consumer.csproj | 20 + Samples/Streaming/Consumer/Dockerfile | 40 ++ Samples/Streaming/Consumer/Extensions.cs | 81 +++++ Samples/Streaming/Consumer/Nats/Constants.cs | 8 + .../Consumer/Nats/NatsDataAddress.cs | 91 +++++ .../Streaming/Consumer/Nats/NatsSubscriber.cs | 54 +++ Samples/Streaming/Consumer/Program.cs | 33 ++ .../Consumer/Properties/launchSettings.json | 23 ++ .../Streaming/Consumer/appsettings.Local.json | 20 + Samples/Streaming/Consumer/appsettings.json | 36 ++ Samples/Streaming/Provider/Extensions.cs | 6 +- .../Provider/Nats/INatsPublisherService.cs | 2 +- .../Provider/Nats/NatsDataAddress.cs | 35 +- .../Provider/Nats/NatsPublisherService.cs | 10 +- Samples/Streaming/Provider/Provider.csproj | 8 +- .../Provider/Services/DataService.cs | 2 +- .../Streaming/Provider/appsettings.Local.json | 2 +- Samples/Streaming/Provider/appsettings.json | 6 +- .../StreamingPull.postman_collection.json | 342 ++++++++++++++++++ Samples/Streaming/docker-compose.yaml | 22 ++ dataplane-sdk-dotnet.sln | 225 ++++++++---- 22 files changed, 958 insertions(+), 110 deletions(-) create mode 100644 Samples/Streaming/Consumer/Consumer.csproj create mode 100644 Samples/Streaming/Consumer/Dockerfile create mode 100644 Samples/Streaming/Consumer/Extensions.cs create mode 100644 Samples/Streaming/Consumer/Nats/Constants.cs create mode 100644 Samples/Streaming/Consumer/Nats/NatsDataAddress.cs create mode 100644 Samples/Streaming/Consumer/Nats/NatsSubscriber.cs create mode 100644 Samples/Streaming/Consumer/Program.cs create mode 100644 Samples/Streaming/Consumer/Properties/launchSettings.json create mode 100644 Samples/Streaming/Consumer/appsettings.Local.json create mode 100644 Samples/Streaming/Consumer/appsettings.json create mode 100644 Samples/Streaming/Resources/Postman/StreamingPull.postman_collection.json diff --git a/Samples/HttpDataplane-Keycloak/HttpDataplane-Keycloak.csproj b/Samples/HttpDataplane-Keycloak/HttpDataplane-Keycloak.csproj index c1960a5..bc691a2 100644 --- a/Samples/HttpDataplane-Keycloak/HttpDataplane-Keycloak.csproj +++ b/Samples/HttpDataplane-Keycloak/HttpDataplane-Keycloak.csproj @@ -8,7 +8,7 @@ - + diff --git a/Samples/Streaming/Consumer/Consumer.csproj b/Samples/Streaming/Consumer/Consumer.csproj new file mode 100644 index 0000000..2da95bc --- /dev/null +++ b/Samples/Streaming/Consumer/Consumer.csproj @@ -0,0 +1,20 @@ + + + + net9.0 + enable + enable + Linux + + + + + + + + + + + + + diff --git a/Samples/Streaming/Consumer/Dockerfile b/Samples/Streaming/Consumer/Dockerfile new file mode 100644 index 0000000..97510c7 --- /dev/null +++ b/Samples/Streaming/Consumer/Dockerfile @@ -0,0 +1,40 @@ +# syntax=docker/dockerfile:1 + +# Build stage +FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build +WORKDIR /src + +# Copy everything and restore +# (Assumes this Dockerfile sits in the project directory next to the .csproj) +COPY . . + +ARG NUGET_USERNAME +ARG NUGET_PASSWORD + +RUN dotnet nuget add source "https://nuget.pkg.github.com/eclipse-dataplane-core/index.json" \ + --name dcore \ + --username $NUGET_USERNAME \ + --password $NUGET_PASSWORD \ + --store-password-in-clear-text \ + && dotnet restore Consumer.csproj \ + && dotnet nuget remove source dcore + +# Publish (framework-dependent) +RUN dotnet publish Consumer.csproj -c Release -o /app/publish --no-restore + +# Runtime stage +FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS final +WORKDIR /app + +# Configure ASP.NET Core to listen on port 8080 and set environment +ENV ASPNETCORE_URLS=http://+:8080 \ + ASPNETCORE_ENVIRONMENT=Production + +# Copy published output +COPY --from=build /app/publish ./ + +# Expose HTTP +EXPOSE 8080 + +# Start the app +ENTRYPOINT ["dotnet", "./Consumer.dll"] \ No newline at end of file diff --git a/Samples/Streaming/Consumer/Extensions.cs b/Samples/Streaming/Consumer/Extensions.cs new file mode 100644 index 0000000..8fd15b7 --- /dev/null +++ b/Samples/Streaming/Consumer/Extensions.cs @@ -0,0 +1,81 @@ +using Consumer.Nats; +using DataPlane.Sdk.Api; +using DataPlane.Sdk.Core; +using DataPlane.Sdk.Core.Domain.Model; +using Microsoft.IdentityModel.Tokens; +using static DataPlane.Sdk.Core.Data.DataFlowContextFactory; + +namespace Consumer; + +public static class Extensions +{ + public static void AddDataPlaneSdk(this IServiceCollection services, IConfiguration configuration) + { + // initialize and configure the DataPlaneSdk + var dataplaneConfig = configuration.GetSection("DataPlaneSdk"); + var config = dataplaneConfig.Get() ?? throw new ArgumentException("Configuration invalid!"); + var dataFlowContext = () => CreatePostgres(configuration, config.RuntimeId); + + + var sdk = new DataPlaneSdk + { + DataFlowStore = dataFlowContext, + RuntimeId = config.RuntimeId, + OnStart = dataFlow => + { + if (dataFlow.Destination == null) + { + return StatusResult.BadRequest("DataFlow.Destination cannot be null"); + } + + var dataService = services.BuildServiceProvider().GetRequiredService(); + dataService.Start(NatsDataAddress.Create(dataFlow.Destination)).Wait(); + return StatusResult.Success(dataFlow); + }, + OnTerminate = df => StatusResult.Success(), + OnSuspend = _ => StatusResult.Success(), + OnPrepare = f => + { + f.IsConsumer = true; + f.State = DataFlowState.Prepared; + return StatusResult.Success(f); + }, + OnComplete = _ => StatusResult.Success() + }; + + services.AddSingleton(); + + + // add SDK core services + services.AddSdkServices(sdk, dataplaneConfig); + + // Configuration for keycloak. Effectively, this sets the default authentication scheme to "KeycloakJWT", + // foregoing the SDK default authentication scheme and using Keycloak as the identity provider. + // This assumes that Keycloak is running on http://keycloak:8080, which is the default if launched with docker-compose. + + var jwtSettings = configuration.GetSection("JwtSettings"); + + services.AddAuthentication("KeycloakJWT") + .AddJwtBearer("KeycloakJWT", options => + { + // Configure Keycloak as the Identity Provider + options.Authority = jwtSettings["Authority"]; + options.RequireHttpsMetadata = false; // Only for develop + + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + ValidIssuer = jwtSettings["Issuer"], + ValidateAudience = true, + ValidAudience = jwtSettings["Audience"], + ValidateIssuerSigningKey = true, + ValidateLifetime = true, + ValidateActor = false, + ValidateTokenReplay = true + }; + }); + + // wire up ASP.net authorization handlers + services.AddSdkAuthorization(); + } +} \ No newline at end of file diff --git a/Samples/Streaming/Consumer/Nats/Constants.cs b/Samples/Streaming/Consumer/Nats/Constants.cs new file mode 100644 index 0000000..c135755 --- /dev/null +++ b/Samples/Streaming/Consumer/Nats/Constants.cs @@ -0,0 +1,8 @@ +namespace Consumer.Nats; + +public static class Constants +{ + public static readonly string DataAddressType = "NatsStream"; + public static readonly string ForwardChannelSuffix = "forward"; + public static readonly string ReplyChannelSuffix = "reply"; +} \ No newline at end of file diff --git a/Samples/Streaming/Consumer/Nats/NatsDataAddress.cs b/Samples/Streaming/Consumer/Nats/NatsDataAddress.cs new file mode 100644 index 0000000..316257d --- /dev/null +++ b/Samples/Streaming/Consumer/Nats/NatsDataAddress.cs @@ -0,0 +1,91 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using DataPlane.Sdk.Core.Domain.Model; + +namespace Consumer.Nats; + +public class NatsDataAddress : DataAddress +{ + public NatsDataAddress() : base(Constants.DataAddressType) + { + Properties["endpointType"] = "https://example.com/natsdp/v1/nats"; + } + + public string NatsEndpoint + { + init => Properties["endpoint"] = value; + get => Properties["endpoint"].ToString() ?? throw new InvalidOperationException("No 'endpoint' endpointProperty found"); + } + + public string Channel + { + get + { + var property = GetEndpointProperty("channel"); + return (property?.Value ?? null) ?? throw new InvalidOperationException("No 'channel' endpointProperty found"); + } + init => StringEndpointProperty("channel", value); + } + + public string ReplyChannel + { + init => StringEndpointProperty("replyChannel", value); + } + + private EndpointProperty? GetEndpointProperty(string key) + { + var props = Properties["endpointProperties"]; + + List epp; + if (props is JsonElement) + { + epp = JsonSerializer.Deserialize>(props.ToString()); + } + else + { + epp = props as List; + } + + + var property = epp?.Find(p => p.Key.Equals(key)); + return property; + } + + public static NatsDataAddress Create(DataAddress rawSource) + { + return new NatsDataAddress + { + Properties = rawSource.Properties, + Id = rawSource.Id + }; + } + + private void StringEndpointProperty(string key, string endpointPropertyValue) + { + if (!Properties.TryGetValue("endpointProperties", out var existing)) + { + existing = new List(); + Properties["endpointProperties"] = existing; + } + + var epProps = existing as List; + epProps!.Add(new EndpointProperty + { + Key = key, + Type = "string", + Value = endpointPropertyValue + }); + } + + public class EndpointProperty + { + [JsonPropertyName("key")] + public required string Key { get; init; } + + [JsonPropertyName("type")] + public required string Type { get; init; } + + [JsonPropertyName("value")] + public required string Value { get; init; } + } +} \ No newline at end of file diff --git a/Samples/Streaming/Consumer/Nats/NatsSubscriber.cs b/Samples/Streaming/Consumer/Nats/NatsSubscriber.cs new file mode 100644 index 0000000..d73854b --- /dev/null +++ b/Samples/Streaming/Consumer/Nats/NatsSubscriber.cs @@ -0,0 +1,54 @@ +using NATS.Client.Core; + +namespace Consumer.Nats; + +public class NatsSubscriber(ILogger logger) +{ + private static readonly IDictionary BackgroundTasks = new Dictionary(); + private static readonly IDictionary CancellationTokens = new Dictionary(); + + public async Task Start(NatsDataAddress nats) + { + var channel = nats.Channel; + var cts = new CancellationTokenSource(); + + BackgroundTasks.Add(channel, Task.Run(async () => + { + await using var conn = new NatsConnection(new NatsOpts + { + Url = nats.NatsEndpoint + }); + + while (!cts.Token.IsCancellationRequested) + { + await foreach (var mesg in conn.SubscribeAsync(channel, cancellationToken: cts.Token)) + { + var data = mesg.Data; + logger.LogInformation("Received {Data}", data); + } + } + }, cts.Token)); + + CancellationTokens.Add(channel, cts); + } + + + public async Task Stop(NatsDataAddress nats) + { + var channel = nats.Channel; + + if (CancellationTokens.TryGetValue(channel, out var ct)) + { + await ct.CancelAsync(); + logger.LogDebug("Stopping {Channel}", channel); + CancellationTokens.Remove(channel); + } + + if (BackgroundTasks.TryGetValue(channel, out var task)) + { + await task; + BackgroundTasks.Remove(channel); + logger.LogDebug("Stopped {Channel}", channel); + } + } +} \ No newline at end of file diff --git a/Samples/Streaming/Consumer/Program.cs b/Samples/Streaming/Consumer/Program.cs new file mode 100644 index 0000000..9e6309a --- /dev/null +++ b/Samples/Streaming/Consumer/Program.cs @@ -0,0 +1,33 @@ +using Consumer; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi +builder.Services.AddOpenApi(); +builder.Services.AddControllers(); +// SDK: add all services, read configuration etc. +builder.Services.AddDataPlaneSdk(builder.Configuration); + +// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi +builder.Services.AddOpenApi(); + + +// Configure the HTTP request pipeline. +var app = builder.Build(); + +if (app.Environment.IsDevelopment()) +{ + app.MapOpenApi(); +} + + +app.UseHttpsRedirection(); + + +app.UseAuthentication(); +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); \ No newline at end of file diff --git a/Samples/Streaming/Consumer/Properties/launchSettings.json b/Samples/Streaming/Consumer/Properties/launchSettings.json new file mode 100644 index 0000000..b8d5e1a --- /dev/null +++ b/Samples/Streaming/Consumer/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:8081", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Local" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:7118;http://localhost:5252", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Samples/Streaming/Consumer/appsettings.Local.json b/Samples/Streaming/Consumer/appsettings.Local.json new file mode 100644 index 0000000..f074503 --- /dev/null +++ b/Samples/Streaming/Consumer/appsettings.Local.json @@ -0,0 +1,20 @@ +{ + "AllowedHosts": "*", + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "ConnectionStrings": { + "DefaultConnection": "Host=localhost;Port=5432;Database=Consumer;Username=postgres;Password=postgres" + }, + "JwtSettings": { + "Authority": "http://localhost:8088/realms/dataplane-signaling-api", + "Issuer": "http://keycloak:8088/realms/dataplane-signaling-api", + "Audience": "dataplane-signaling-api" + }, + "Nats": { + "NatsEndpoint": "nats://localhost:4222" + } +} diff --git a/Samples/Streaming/Consumer/appsettings.json b/Samples/Streaming/Consumer/appsettings.json new file mode 100644 index 0000000..0ae73ef --- /dev/null +++ b/Samples/Streaming/Consumer/appsettings.json @@ -0,0 +1,36 @@ +{ + "AllowedHosts": "*", + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "ConnectionStrings": { + "DefaultConnection": "Host=postgres;Port=5432;Database=Consumer;Username=postgres;Password=postgres" + }, + "Database": { + "AutoMigrate": true + }, + "DataPlaneSdk": { + "ControlApi": { + "BaseUrl": "http://localhost:8083/api/control" + }, + "DataplaneId": "consumer-1", + "RuntimeId": "consumer-dataplane-1", + "AllowedSourceTypes": [ + "test-source-type" + ], + "AllowedTransferTypes": [ + "test-transfer-type" + ] + }, + "Nats": { + "NatsEndpoint": "nats://nats:4222" + }, + "JwtSettings": { + "Authority": "http://keycloak:8088/realms/dataplane-signaling-api", + "Issuer": "http://localhost:8088/realms/dataplane-signaling-api", + "Audience": "dataplane-signaling-api" + } +} diff --git a/Samples/Streaming/Provider/Extensions.cs b/Samples/Streaming/Provider/Extensions.cs index 09e97fd..06ebda5 100644 --- a/Samples/Streaming/Provider/Extensions.cs +++ b/Samples/Streaming/Provider/Extensions.cs @@ -35,11 +35,7 @@ public static void AddDataPlaneSdk(this IServiceCollection services, IConfigurat return task.Result; }, OnSuspend = _ => StatusResult.Success(), - OnPrepare = f => - { - f.State = DataFlowState.Prepared; - return StatusResult.Success(f); - }, + OnPrepare = _ => throw new NotImplementedException("Cannot call /prepare on a provider data plane"), OnComplete = _ => StatusResult.Success() }; diff --git a/Samples/Streaming/Provider/Nats/INatsPublisherService.cs b/Samples/Streaming/Provider/Nats/INatsPublisherService.cs index 23cb4d1..e1054d9 100644 --- a/Samples/Streaming/Provider/Nats/INatsPublisherService.cs +++ b/Samples/Streaming/Provider/Nats/INatsPublisherService.cs @@ -2,6 +2,6 @@ namespace Provider.Nats; public interface INatsPublisherService { - void StartAsync(string channel); + void Start(string channel); Task StopAsync(string channel); } \ No newline at end of file diff --git a/Samples/Streaming/Provider/Nats/NatsDataAddress.cs b/Samples/Streaming/Provider/Nats/NatsDataAddress.cs index 5c104d9..be203bc 100644 --- a/Samples/Streaming/Provider/Nats/NatsDataAddress.cs +++ b/Samples/Streaming/Provider/Nats/NatsDataAddress.cs @@ -14,26 +14,14 @@ public NatsDataAddress() : base(Constants.DataAddressType) public string NatsEndpoint { init => Properties["endpoint"] = value; + get => Properties["endpoint"] as string ?? throw new InvalidOperationException("No 'endpoint' endpointProperty found"); } public string Channel { get { - var props = Properties["endpointProperties"]; - - List epp; - if (props is JsonElement) - { - epp = JsonSerializer.Deserialize>(props.ToString()); - } - else - { - epp = props as List; - } - - - var property = epp?.Find(p => p.Key.Equals("channel")); + var property = GetEndpointProperty("channel"); return (property?.Value ?? null) ?? throw new InvalidOperationException("No 'channel' endpointProperty found"); } init => StringEndpointProperty("channel", value); @@ -44,6 +32,25 @@ public string ReplyChannel init => StringEndpointProperty("replyChannel", value); } + private EndpointProperty? GetEndpointProperty(string key) + { + var props = Properties["endpointProperties"]; + + List epp; + if (props is JsonElement) + { + epp = JsonSerializer.Deserialize>(props.ToString()); + } + else + { + epp = props as List; + } + + + var property = epp?.Find(p => p.Key.Equals(key)); + return property; + } + public static NatsDataAddress Create(DataAddress rawSource) { return new NatsDataAddress diff --git a/Samples/Streaming/Provider/Nats/NatsPublisherService.cs b/Samples/Streaming/Provider/Nats/NatsPublisherService.cs index 3979780..6d35219 100644 --- a/Samples/Streaming/Provider/Nats/NatsPublisherService.cs +++ b/Samples/Streaming/Provider/Nats/NatsPublisherService.cs @@ -1,21 +1,25 @@ using System.Text.Json; +using Microsoft.Extensions.Options; using NATS.Client.Core; namespace Provider.Nats; -public class NatsPublisherService(ILogger logger) : INatsPublisherService +public class NatsPublisherService(ILogger logger, IOptions options) : INatsPublisherService { private static readonly IDictionary BackgroundTasks = new Dictionary(); private static readonly IDictionary CancellationTokenSource = new Dictionary(); - public void StartAsync(string channel) + public void Start(string channel) { var ct = new CancellationTokenSource(); CancellationTokenSource.Add(channel, ct); BackgroundTasks.Add(channel, Task.Run(async () => { - await using var nats = new NatsConnection(); + await using var nats = new NatsConnection(new NatsOpts + { + Url = options.Value.NatsEndpoint + }); var num = 0; while (!ct.Token.IsCancellationRequested) diff --git a/Samples/Streaming/Provider/Provider.csproj b/Samples/Streaming/Provider/Provider.csproj index 739eb90..44e4c07 100644 --- a/Samples/Streaming/Provider/Provider.csproj +++ b/Samples/Streaming/Provider/Provider.csproj @@ -8,19 +8,15 @@ - + - - <_ContentIncludedByDefault Remove="Resources\Keycloak\dataplane-api-realm.json"/> - - - + diff --git a/Samples/Streaming/Provider/Services/DataService.cs b/Samples/Streaming/Provider/Services/DataService.cs index 46bea5d..7d11e3a 100644 --- a/Samples/Streaming/Provider/Services/DataService.cs +++ b/Samples/Streaming/Provider/Services/DataService.cs @@ -32,7 +32,7 @@ public StatusResult ProcessStart(DataFlow flow) flow.Destination = dataAddress; // start publishing events - publisherService.StartAsync(channel); + publisherService.Start(channel); return StatusResult.Success(flow); } diff --git a/Samples/Streaming/Provider/appsettings.Local.json b/Samples/Streaming/Provider/appsettings.Local.json index 23b16a8..d205a4a 100644 --- a/Samples/Streaming/Provider/appsettings.Local.json +++ b/Samples/Streaming/Provider/appsettings.Local.json @@ -7,7 +7,7 @@ } }, "ConnectionStrings": { - "DefaultConnection": "Host=localhost;Port=5432;Database=SdkApi;Username=postgres;Password=postgres" + "DefaultConnection": "Host=localhost;Port=5432;Database=Provider;Username=postgres;Password=postgres" }, "JwtSettings": { "Authority": "http://localhost:8088/realms/dataplane-signaling-api", diff --git a/Samples/Streaming/Provider/appsettings.json b/Samples/Streaming/Provider/appsettings.json index efdffa2..2610a9c 100644 --- a/Samples/Streaming/Provider/appsettings.json +++ b/Samples/Streaming/Provider/appsettings.json @@ -7,7 +7,7 @@ } }, "ConnectionStrings": { - "DefaultConnection": "Host=postgres;Port=5432;Database=SdkApi;Username=postgres;Password=postgres" + "DefaultConnection": "Host=postgres;Port=5432;Database=Provider;Username=postgres;Password=postgres" }, "Database": { "AutoMigrate": true @@ -16,8 +16,8 @@ "ControlApi": { "BaseUrl": "http://localhost:8083/api/control" }, - "DataplaneId": "test-dataplane-instance", - "RuntimeId": "httpdataplane-id", + "DataplaneId": "provider-1", + "RuntimeId": "provider-dataplane-1", "AllowedSourceTypes": [ "test-source-type" ], diff --git a/Samples/Streaming/Resources/Postman/StreamingPull.postman_collection.json b/Samples/Streaming/Resources/Postman/StreamingPull.postman_collection.json new file mode 100644 index 0000000..eabf6dc --- /dev/null +++ b/Samples/Streaming/Resources/Postman/StreamingPull.postman_collection.json @@ -0,0 +1,342 @@ +{ + "info": { + "_postman_id": "84fd9da2-29f2-405d-9f51-f28fdf4c532b", + "name": "Streaming Pull", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "647585", + "_collection_link": "https://galactic-star-228409.postman.co/workspace/Dataplane-work~d820e7a7-8273-4d79-986e-96754a31f467/collection/647585-84fd9da2-29f2-405d-9f51-f28fdf4c532b?action=share&source=collection_link&creator=647585" + }, + "item": [ + { + "name": "Consumer - Prepare", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"processId\": \"{{DATAFLOW_ID}}\",\n \"datasetId\": \"test-asset\",\n \"participantId\": \"{{PARTICIPANT_ID}}\",\n \"agreementId\": \"test-agreement\",\n \"transferType\": {\n \"flowType\": \"pull\",\n \"destinationType\": \"NatsStream\"\n }\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8081/api/v1/{{PARTICIPANT_ID}}/dataflows/prepare", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8081", + "path": [ + "api", + "v1", + "{{PARTICIPANT_ID}}", + "dataflows", + "prepare" + ] + } + }, + "response": [] + }, + { + "name": "Provider - Start Dataflow", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"processId\": \"{{DATAFLOW_ID}}\",\n \"datasetId\": \"test-asset\",\n \"participantId\": \"{{PARTICIPANT_ID}}\",\n \"agreementId\": \"test-agreement\",\n \"dataAddress\": {\n \"@type\": \"DataAddress\",\n \"properties\":{\n \n }\n },\n \"transferType\": {\n \"flowType\": \"pull\",\n \"destinationType\": \"NatsStream\"\n }\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{DATAPLANE_HOST}}/api/v1/{{PARTICIPANT_ID}}/dataflows/start", + "host": [ + "{{DATAPLANE_HOST}}" + ], + "path": [ + "api", + "v1", + "{{PARTICIPANT_ID}}", + "dataflows", + "start" + ] + } + }, + "response": [] + }, + { + "name": "Consumer - Notify Started", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "if (pm.response.code < 300 && pm.response.code >= 200) {", + " const responseJson = pm.response.json();", + " pm.expect(responseJson).to.have.property('dataAddress');", + " pm.expect(responseJson.dataAddress).to.have.property('properties');", + " pm.expect(responseJson.dataAddress.properties).to.have.property('url');", + " pm.expect(responseJson.dataAddress.properties.url).to.exist;", + "", + " const url = responseJson.dataAddress.properties.url;", + " pm.collectionVariables.set(\"PUBLIC_URL\", url);", + "", + " pm.expect(responseJson.dataAddress.properties).to.have.property('token');", + " const token = responseJson.dataAddress.properties.token;", + " pm.collectionVariables.set(\"API_KEY\", token);", + "}", + "", + "pm.test(\"Status code is >=200 and <300\", function () {", + " pm.expect(pm.response.code < 300 && pm.response.code >= 200).to.be.true", + "});", + "pm.test(\"Public URL is set\", function(){", + " pm.expect(pm.collectionVariables.get(\"PUBLIC_URL\")).not.to.be.undefined", + "})", + "", + "", + "pm.test(\"API is set\", function(){", + " pm.expect(pm.collectionVariables.get(\"API_KEY\")).not.to.be.undefined", + "})", + "" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"processId\": \"{{DATAFLOW_ID}}\",\n \"datasetId\": \"test-asset\",\n \"participantId\": \"{{PARTICIPANT_ID}}\",\n \"agreementId\": \"test-agreement\",\n \"dataAddress\": {\n \"@type\": \"HttpData\",\n \"properties\":{\n \"baseUrl\": \"https://jsonplaceholder.typicode.com/comments/22\"\n }\n },\n \"transferType\": {\n \"flowType\": \"pull\",\n \"destinationType\": \"HttpData\"\n }\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{DATAPLANE_HOST}}/api/v1/{{PARTICIPANT_ID}}/dataflows/{{CONSUMER_DATAFLOW_ID}}//started", + "host": [ + "{{DATAPLANE_HOST}}" + ], + "path": [ + "api", + "v1", + "{{PARTICIPANT_ID}}", + "dataflows", + "{{CONSUMER_DATAFLOW_ID}}", + "", + "started" + ] + } + }, + "response": [] + }, + { + "name": "Access Public API", + "request": { + "auth": { + "type": "apikey", + "apikey": [ + { + "key": "value", + "value": "{{API_KEY}}", + "type": "string" + }, + { + "key": "key", + "value": "x-api-key", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{PUBLIC_URL}}", + "host": [ + "{{PUBLIC_URL}}" + ] + } + }, + "response": [] + }, + { + "name": "Complete Dataflow", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{DATAPLANE_HOST}}/api/v1/{{PARTICIPANT_ID}}/dataflows/{{DATAFLOW_ID}}/completed", + "host": [ + "{{DATAPLANE_HOST}}" + ], + "path": [ + "api", + "v1", + "{{PARTICIPANT_ID}}", + "dataflows", + "{{DATAFLOW_ID}}", + "completed" + ] + } + }, + "response": [] + }, + { + "name": "Terminate Dataflow", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"reason\": \"done\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{DATAPLANE_HOST}}/api/v1/{{PARTICIPANT_ID}}/dataflows/{{DATAFLOW_ID}}/terminate", + "host": [ + "{{DATAPLANE_HOST}}" + ], + "path": [ + "api", + "v1", + "{{PARTICIPANT_ID}}", + "dataflows", + "{{DATAFLOW_ID}}", + "terminate" + ] + } + }, + "response": [] + } + ] +} \ No newline at end of file diff --git a/Samples/Streaming/docker-compose.yaml b/Samples/Streaming/docker-compose.yaml index ce5b71f..6c4a029 100644 --- a/Samples/Streaming/docker-compose.yaml +++ b/Samples/Streaming/docker-compose.yaml @@ -13,6 +13,26 @@ services: depends_on: postgres: condition: service_healthy + nats: + condition: service_started + networks: + - sample-network + + consumer-dataplane: + build: + context: ./Consumer + dockerfile: Dockerfile + args: + NUGET_USERNAME: ${NUGET_USERNAME} + NUGET_PASSWORD: ${NUGET_PASSWORD} + + ports: + - "8081:8080" + depends_on: + postgres: + condition: service_healthy + nats: + condition: service_started networks: - sample-network @@ -53,6 +73,8 @@ services: image: nats:latest ports: - "4222:4222" + networks: + - sample-network volumes: postgres_data: diff --git a/dataplane-sdk-dotnet.sln b/dataplane-sdk-dotnet.sln index 0850eef..4c3313b 100644 --- a/dataplane-sdk-dotnet.sln +++ b/dataplane-sdk-dotnet.sln @@ -1,75 +1,150 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31903.59 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataPlane.Sdk.Api", "DataPlane.Sdk.Api\DataPlane.Sdk.Api.csproj", "{0E3D4901-133A-46D3-B1CB-7400C65BED32}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataPlane.Sdk.Core", "DataPlane.Sdk.Core\DataPlane.Sdk.Core.csproj", "{8B194066-A67B-44D9-945F-88A4EC1EBEBF}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataPlane.Sdk.Core.Test", "DataPlane.Sdk.Core.Test\DataPlane.Sdk.Core.Test.csproj", "{C9F8CEE9-F96C-41B5-B58D-3D0B82B87258}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A4B4E17A-8E91-45A0-BD5B-70EA9CA1B76D}" - ProjectSection(SolutionItems) = preProject - compose.yaml = compose.yaml - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataPlane.Sdk.Api.Test", "DataPlane.Sdk.Api.Test\DataPlane.Sdk.Api.Test.csproj", "{C95272D3-3046-4C26-A422-C19EC45C7800}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{32055AC6-C4BE-4B7C-A829-F268AB12E577}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HttpDataplane-Keycloak", "Samples\HttpDataplane-Keycloak\HttpDataplane-Keycloak.csproj", "{534284F6-8E17-4774-95FA-7E133AF346F7}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataPlane.Sdk.Test.Utils", "DataPlane.Sdk.Test.Utils\DataPlane.Sdk.Test.Utils.csproj", "{391E2C8C-7806-40CC-A847-2ECD25FE73CC}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Streaming", "Streaming", "{9B7B836B-426D-4F48-8467-98E24CE4FCB1}" - ProjectSection(SolutionItems) = preProject - Samples\Streaming\docker-compose.yaml = Samples\Streaming\docker-compose.yaml - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Provider", "Samples\Streaming\Provider\Provider.csproj", "{C1B9F93E-4499-4506-A654-9B19601D8FA2}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {0E3D4901-133A-46D3-B1CB-7400C65BED32}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0E3D4901-133A-46D3-B1CB-7400C65BED32}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0E3D4901-133A-46D3-B1CB-7400C65BED32}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0E3D4901-133A-46D3-B1CB-7400C65BED32}.Release|Any CPU.Build.0 = Release|Any CPU - {8B194066-A67B-44D9-945F-88A4EC1EBEBF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8B194066-A67B-44D9-945F-88A4EC1EBEBF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8B194066-A67B-44D9-945F-88A4EC1EBEBF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8B194066-A67B-44D9-945F-88A4EC1EBEBF}.Release|Any CPU.Build.0 = Release|Any CPU - {C9F8CEE9-F96C-41B5-B58D-3D0B82B87258}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C9F8CEE9-F96C-41B5-B58D-3D0B82B87258}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C9F8CEE9-F96C-41B5-B58D-3D0B82B87258}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C9F8CEE9-F96C-41B5-B58D-3D0B82B87258}.Release|Any CPU.Build.0 = Release|Any CPU - {C95272D3-3046-4C26-A422-C19EC45C7800}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C95272D3-3046-4C26-A422-C19EC45C7800}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C95272D3-3046-4C26-A422-C19EC45C7800}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C95272D3-3046-4C26-A422-C19EC45C7800}.Release|Any CPU.Build.0 = Release|Any CPU - {534284F6-8E17-4774-95FA-7E133AF346F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {534284F6-8E17-4774-95FA-7E133AF346F7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {534284F6-8E17-4774-95FA-7E133AF346F7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {534284F6-8E17-4774-95FA-7E133AF346F7}.Release|Any CPU.Build.0 = Release|Any CPU - {391E2C8C-7806-40CC-A847-2ECD25FE73CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {391E2C8C-7806-40CC-A847-2ECD25FE73CC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {391E2C8C-7806-40CC-A847-2ECD25FE73CC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {391E2C8C-7806-40CC-A847-2ECD25FE73CC}.Release|Any CPU.Build.0 = Release|Any CPU - {C1B9F93E-4499-4506-A654-9B19601D8FA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C1B9F93E-4499-4506-A654-9B19601D8FA2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C1B9F93E-4499-4506-A654-9B19601D8FA2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C1B9F93E-4499-4506-A654-9B19601D8FA2}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {534284F6-8E17-4774-95FA-7E133AF346F7} = {32055AC6-C4BE-4B7C-A829-F268AB12E577} - {9B7B836B-426D-4F48-8467-98E24CE4FCB1} = {32055AC6-C4BE-4B7C-A829-F268AB12E577} - {C1B9F93E-4499-4506-A654-9B19601D8FA2} = {9B7B836B-426D-4F48-8467-98E24CE4FCB1} - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataPlane.Sdk.Api", "DataPlane.Sdk.Api\DataPlane.Sdk.Api.csproj", "{0E3D4901-133A-46D3-B1CB-7400C65BED32}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataPlane.Sdk.Core", "DataPlane.Sdk.Core\DataPlane.Sdk.Core.csproj", "{8B194066-A67B-44D9-945F-88A4EC1EBEBF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataPlane.Sdk.Core.Test", "DataPlane.Sdk.Core.Test\DataPlane.Sdk.Core.Test.csproj", "{C9F8CEE9-F96C-41B5-B58D-3D0B82B87258}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A4B4E17A-8E91-45A0-BD5B-70EA9CA1B76D}" + ProjectSection(SolutionItems) = preProject + compose.yaml = compose.yaml + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataPlane.Sdk.Api.Test", "DataPlane.Sdk.Api.Test\DataPlane.Sdk.Api.Test.csproj", "{C95272D3-3046-4C26-A422-C19EC45C7800}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{32055AC6-C4BE-4B7C-A829-F268AB12E577}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HttpDataplane-Keycloak", "Samples\HttpDataplane-Keycloak\HttpDataplane-Keycloak.csproj", "{534284F6-8E17-4774-95FA-7E133AF346F7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataPlane.Sdk.Test.Utils", "DataPlane.Sdk.Test.Utils\DataPlane.Sdk.Test.Utils.csproj", "{391E2C8C-7806-40CC-A847-2ECD25FE73CC}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Streaming", "Streaming", "{9B7B836B-426D-4F48-8467-98E24CE4FCB1}" + ProjectSection(SolutionItems) = preProject + Samples\Streaming\docker-compose.yaml = Samples\Streaming\docker-compose.yaml + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Provider", "Samples\Streaming\Provider\Provider.csproj", "{C1B9F93E-4499-4506-A654-9B19601D8FA2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Consumer", "Samples\Streaming\Consumer\Consumer.csproj", "{0219D903-F6B5-4E22-BF08-BAFF5EB1895D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0E3D4901-133A-46D3-B1CB-7400C65BED32}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0E3D4901-133A-46D3-B1CB-7400C65BED32}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0E3D4901-133A-46D3-B1CB-7400C65BED32}.Debug|x64.ActiveCfg = Debug|Any CPU + {0E3D4901-133A-46D3-B1CB-7400C65BED32}.Debug|x64.Build.0 = Debug|Any CPU + {0E3D4901-133A-46D3-B1CB-7400C65BED32}.Debug|x86.ActiveCfg = Debug|Any CPU + {0E3D4901-133A-46D3-B1CB-7400C65BED32}.Debug|x86.Build.0 = Debug|Any CPU + {0E3D4901-133A-46D3-B1CB-7400C65BED32}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0E3D4901-133A-46D3-B1CB-7400C65BED32}.Release|Any CPU.Build.0 = Release|Any CPU + {0E3D4901-133A-46D3-B1CB-7400C65BED32}.Release|x64.ActiveCfg = Release|Any CPU + {0E3D4901-133A-46D3-B1CB-7400C65BED32}.Release|x64.Build.0 = Release|Any CPU + {0E3D4901-133A-46D3-B1CB-7400C65BED32}.Release|x86.ActiveCfg = Release|Any CPU + {0E3D4901-133A-46D3-B1CB-7400C65BED32}.Release|x86.Build.0 = Release|Any CPU + {8B194066-A67B-44D9-945F-88A4EC1EBEBF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8B194066-A67B-44D9-945F-88A4EC1EBEBF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8B194066-A67B-44D9-945F-88A4EC1EBEBF}.Debug|x64.ActiveCfg = Debug|Any CPU + {8B194066-A67B-44D9-945F-88A4EC1EBEBF}.Debug|x64.Build.0 = Debug|Any CPU + {8B194066-A67B-44D9-945F-88A4EC1EBEBF}.Debug|x86.ActiveCfg = Debug|Any CPU + {8B194066-A67B-44D9-945F-88A4EC1EBEBF}.Debug|x86.Build.0 = Debug|Any CPU + {8B194066-A67B-44D9-945F-88A4EC1EBEBF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8B194066-A67B-44D9-945F-88A4EC1EBEBF}.Release|Any CPU.Build.0 = Release|Any CPU + {8B194066-A67B-44D9-945F-88A4EC1EBEBF}.Release|x64.ActiveCfg = Release|Any CPU + {8B194066-A67B-44D9-945F-88A4EC1EBEBF}.Release|x64.Build.0 = Release|Any CPU + {8B194066-A67B-44D9-945F-88A4EC1EBEBF}.Release|x86.ActiveCfg = Release|Any CPU + {8B194066-A67B-44D9-945F-88A4EC1EBEBF}.Release|x86.Build.0 = Release|Any CPU + {C9F8CEE9-F96C-41B5-B58D-3D0B82B87258}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C9F8CEE9-F96C-41B5-B58D-3D0B82B87258}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C9F8CEE9-F96C-41B5-B58D-3D0B82B87258}.Debug|x64.ActiveCfg = Debug|Any CPU + {C9F8CEE9-F96C-41B5-B58D-3D0B82B87258}.Debug|x64.Build.0 = Debug|Any CPU + {C9F8CEE9-F96C-41B5-B58D-3D0B82B87258}.Debug|x86.ActiveCfg = Debug|Any CPU + {C9F8CEE9-F96C-41B5-B58D-3D0B82B87258}.Debug|x86.Build.0 = Debug|Any CPU + {C9F8CEE9-F96C-41B5-B58D-3D0B82B87258}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C9F8CEE9-F96C-41B5-B58D-3D0B82B87258}.Release|Any CPU.Build.0 = Release|Any CPU + {C9F8CEE9-F96C-41B5-B58D-3D0B82B87258}.Release|x64.ActiveCfg = Release|Any CPU + {C9F8CEE9-F96C-41B5-B58D-3D0B82B87258}.Release|x64.Build.0 = Release|Any CPU + {C9F8CEE9-F96C-41B5-B58D-3D0B82B87258}.Release|x86.ActiveCfg = Release|Any CPU + {C9F8CEE9-F96C-41B5-B58D-3D0B82B87258}.Release|x86.Build.0 = Release|Any CPU + {C95272D3-3046-4C26-A422-C19EC45C7800}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C95272D3-3046-4C26-A422-C19EC45C7800}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C95272D3-3046-4C26-A422-C19EC45C7800}.Debug|x64.ActiveCfg = Debug|Any CPU + {C95272D3-3046-4C26-A422-C19EC45C7800}.Debug|x64.Build.0 = Debug|Any CPU + {C95272D3-3046-4C26-A422-C19EC45C7800}.Debug|x86.ActiveCfg = Debug|Any CPU + {C95272D3-3046-4C26-A422-C19EC45C7800}.Debug|x86.Build.0 = Debug|Any CPU + {C95272D3-3046-4C26-A422-C19EC45C7800}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C95272D3-3046-4C26-A422-C19EC45C7800}.Release|Any CPU.Build.0 = Release|Any CPU + {C95272D3-3046-4C26-A422-C19EC45C7800}.Release|x64.ActiveCfg = Release|Any CPU + {C95272D3-3046-4C26-A422-C19EC45C7800}.Release|x64.Build.0 = Release|Any CPU + {C95272D3-3046-4C26-A422-C19EC45C7800}.Release|x86.ActiveCfg = Release|Any CPU + {C95272D3-3046-4C26-A422-C19EC45C7800}.Release|x86.Build.0 = Release|Any CPU + {534284F6-8E17-4774-95FA-7E133AF346F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {534284F6-8E17-4774-95FA-7E133AF346F7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {534284F6-8E17-4774-95FA-7E133AF346F7}.Debug|x64.ActiveCfg = Debug|Any CPU + {534284F6-8E17-4774-95FA-7E133AF346F7}.Debug|x64.Build.0 = Debug|Any CPU + {534284F6-8E17-4774-95FA-7E133AF346F7}.Debug|x86.ActiveCfg = Debug|Any CPU + {534284F6-8E17-4774-95FA-7E133AF346F7}.Debug|x86.Build.0 = Debug|Any CPU + {534284F6-8E17-4774-95FA-7E133AF346F7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {534284F6-8E17-4774-95FA-7E133AF346F7}.Release|Any CPU.Build.0 = Release|Any CPU + {534284F6-8E17-4774-95FA-7E133AF346F7}.Release|x64.ActiveCfg = Release|Any CPU + {534284F6-8E17-4774-95FA-7E133AF346F7}.Release|x64.Build.0 = Release|Any CPU + {534284F6-8E17-4774-95FA-7E133AF346F7}.Release|x86.ActiveCfg = Release|Any CPU + {534284F6-8E17-4774-95FA-7E133AF346F7}.Release|x86.Build.0 = Release|Any CPU + {391E2C8C-7806-40CC-A847-2ECD25FE73CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {391E2C8C-7806-40CC-A847-2ECD25FE73CC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {391E2C8C-7806-40CC-A847-2ECD25FE73CC}.Debug|x64.ActiveCfg = Debug|Any CPU + {391E2C8C-7806-40CC-A847-2ECD25FE73CC}.Debug|x64.Build.0 = Debug|Any CPU + {391E2C8C-7806-40CC-A847-2ECD25FE73CC}.Debug|x86.ActiveCfg = Debug|Any CPU + {391E2C8C-7806-40CC-A847-2ECD25FE73CC}.Debug|x86.Build.0 = Debug|Any CPU + {391E2C8C-7806-40CC-A847-2ECD25FE73CC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {391E2C8C-7806-40CC-A847-2ECD25FE73CC}.Release|Any CPU.Build.0 = Release|Any CPU + {391E2C8C-7806-40CC-A847-2ECD25FE73CC}.Release|x64.ActiveCfg = Release|Any CPU + {391E2C8C-7806-40CC-A847-2ECD25FE73CC}.Release|x64.Build.0 = Release|Any CPU + {391E2C8C-7806-40CC-A847-2ECD25FE73CC}.Release|x86.ActiveCfg = Release|Any CPU + {391E2C8C-7806-40CC-A847-2ECD25FE73CC}.Release|x86.Build.0 = Release|Any CPU + {C1B9F93E-4499-4506-A654-9B19601D8FA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C1B9F93E-4499-4506-A654-9B19601D8FA2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C1B9F93E-4499-4506-A654-9B19601D8FA2}.Debug|x64.ActiveCfg = Debug|Any CPU + {C1B9F93E-4499-4506-A654-9B19601D8FA2}.Debug|x64.Build.0 = Debug|Any CPU + {C1B9F93E-4499-4506-A654-9B19601D8FA2}.Debug|x86.ActiveCfg = Debug|Any CPU + {C1B9F93E-4499-4506-A654-9B19601D8FA2}.Debug|x86.Build.0 = Debug|Any CPU + {C1B9F93E-4499-4506-A654-9B19601D8FA2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C1B9F93E-4499-4506-A654-9B19601D8FA2}.Release|Any CPU.Build.0 = Release|Any CPU + {C1B9F93E-4499-4506-A654-9B19601D8FA2}.Release|x64.ActiveCfg = Release|Any CPU + {C1B9F93E-4499-4506-A654-9B19601D8FA2}.Release|x64.Build.0 = Release|Any CPU + {C1B9F93E-4499-4506-A654-9B19601D8FA2}.Release|x86.ActiveCfg = Release|Any CPU + {C1B9F93E-4499-4506-A654-9B19601D8FA2}.Release|x86.Build.0 = Release|Any CPU + {0219D903-F6B5-4E22-BF08-BAFF5EB1895D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0219D903-F6B5-4E22-BF08-BAFF5EB1895D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0219D903-F6B5-4E22-BF08-BAFF5EB1895D}.Debug|x64.ActiveCfg = Debug|Any CPU + {0219D903-F6B5-4E22-BF08-BAFF5EB1895D}.Debug|x64.Build.0 = Debug|Any CPU + {0219D903-F6B5-4E22-BF08-BAFF5EB1895D}.Debug|x86.ActiveCfg = Debug|Any CPU + {0219D903-F6B5-4E22-BF08-BAFF5EB1895D}.Debug|x86.Build.0 = Debug|Any CPU + {0219D903-F6B5-4E22-BF08-BAFF5EB1895D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0219D903-F6B5-4E22-BF08-BAFF5EB1895D}.Release|Any CPU.Build.0 = Release|Any CPU + {0219D903-F6B5-4E22-BF08-BAFF5EB1895D}.Release|x64.ActiveCfg = Release|Any CPU + {0219D903-F6B5-4E22-BF08-BAFF5EB1895D}.Release|x64.Build.0 = Release|Any CPU + {0219D903-F6B5-4E22-BF08-BAFF5EB1895D}.Release|x86.ActiveCfg = Release|Any CPU + {0219D903-F6B5-4E22-BF08-BAFF5EB1895D}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {534284F6-8E17-4774-95FA-7E133AF346F7} = {32055AC6-C4BE-4B7C-A829-F268AB12E577} + {9B7B836B-426D-4F48-8467-98E24CE4FCB1} = {32055AC6-C4BE-4B7C-A829-F268AB12E577} + {C1B9F93E-4499-4506-A654-9B19601D8FA2} = {9B7B836B-426D-4F48-8467-98E24CE4FCB1} + {0219D903-F6B5-4E22-BF08-BAFF5EB1895D} = {9B7B836B-426D-4F48-8467-98E24CE4FCB1} + EndGlobalSection +EndGlobal