diff --git a/src/AasxOpenidClient/AasxOpenidClient.csproj b/src/AasxOpenidClient/AasxOpenidClient.csproj
index b402be530..ad0a38eaa 100644
--- a/src/AasxOpenidClient/AasxOpenidClient.csproj
+++ b/src/AasxOpenidClient/AasxOpenidClient.csproj
@@ -25,7 +25,7 @@
-
+
diff --git a/src/AasxPackageExplorer/options-debug.MIHO.json b/src/AasxPackageExplorer/options-debug.MIHO.json
index cc0a0aad3..3a2fbd744 100644
--- a/src/AasxPackageExplorer/options-debug.MIHO.json
+++ b/src/AasxPackageExplorer/options-debug.MIHO.json
@@ -53,6 +53,12 @@
"AccessInfo": {
"Method": "None" // None, Ask, Basic, CertificateStore, File, InteractiveEntry, Secret
}
+ },
+ {
+ "BaseAddress": "https://leo-discovery.admin-shell-io.com/api/v2/",
+ "AccessInfo": {
+ "Method": "None" // None, Ask, Basic, CertificateStore, File, InteractiveEntry, Secret
+ }
},
{
"BaseAddress": "https://aasx-server-pcf.dev.pxcio.net/",
diff --git a/src/AasxPackageLogic/AasxPackageLogic.csproj b/src/AasxPackageLogic/AasxPackageLogic.csproj
index f33343909..851868824 100644
--- a/src/AasxPackageLogic/AasxPackageLogic.csproj
+++ b/src/AasxPackageLogic/AasxPackageLogic.csproj
@@ -43,7 +43,7 @@
-
+
diff --git a/src/AasxPackageLogic/DispEditHelperEntities.cs b/src/AasxPackageLogic/DispEditHelperEntities.cs
index 28e0c3ee9..0440eebe5 100644
--- a/src/AasxPackageLogic/DispEditHelperEntities.cs
+++ b/src/AasxPackageLogic/DispEditHelperEntities.cs
@@ -1557,6 +1557,7 @@ public static async Task ExecuteUiForFetchOfElements(
"Error building location from fetch selection. Aborting.");
return false;
}
+ Options.Curr.AutoAuthenticateAsk = fetchContext.Record.AutoAuthenticate;
// more details into container options
var containerOptions = new PackageContainerHttpRepoSubset.
diff --git a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs
index 0cc18260e..2ca52d82a 100644
--- a/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs
+++ b/src/AasxPackageLogic/PackageCentral/PackageContainerHttpRepoSubset.cs
@@ -9,6 +9,7 @@ This source code may use other Open Source software components (see LICENSE.txt)
using AasxIntegrationBase;
using AdminShellNS;
+using AdminShellNS.Extensions;
using AngleSharp.Dom;
using AnyUi;
using Extensions;
@@ -303,14 +304,14 @@ public static bool IsValidUriForRegistryAasByAssetIds(string location)
public static bool IsValidUriForRegOfRegAasByAssetId(string location)
{
- var m = Regex.Match(location, @"^(http(|s))://(.*?)/registry-descriptors/([-A-Za-z0-9_]{1,999})$",
+ var m = Regex.Match(location, @"^(http(|s))://(.*?)/(?:registry-descriptors/|companies\?asset_id=)([-A-Za-z0-9_]{1,999})$",
RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
return m.Success;
}
public static string IsValidAndDecodeUriForRegOfRegAasByAssetId(string location)
{
- var m = Regex.Match(location, @"^(http(|s))://(.*?)/registry-descriptors/([-A-Za-z0-9_]{1,999})$",
+ var m = Regex.Match(location, @"^(http(|s))://(.*?)/(?:registry-descriptors/|companies\?asset_id=)([-A-Za-z0-9_]{1,999})$",
RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
if (!m.Success)
return null;
@@ -353,7 +354,7 @@ public static Uri GetBaseUri(string location)
// try an explicit search for known parts of ressources
// (preserves scheme, host and leading pathes)
var m = Regex.Match(location, @"^(.*?)(/shells|/submodel|/concept-description|/lookup|/description"
- + "|/shell-descriptor|/submodel-descriptor|/bulk|/serialization|/package)");
+ + "|/shell-descriptor|/submodel-descriptor|/bulk|/serialization|/package|/registry-descriptors|/companies)");
if (m.Success)
return new Uri(m.Groups[1].ToString() + "/");
@@ -366,6 +367,10 @@ public static Uri GetBaseUri(string location)
{
return new Uri(location.Substring(0, p) + "/");
}
+ else
+ {
+ return new Uri(location);
+ }
}
// go to error
@@ -732,10 +737,17 @@ public static Uri BuildUriForRegOfRegAasByAssetId(Uri baseUri, string id, bool e
// access
if (id?.HasContent() != true)
return null;
+ var route = "registry-descriptors/";
- // try combine
var assenc = encryptIds ? AdminShellUtil.Base64UrlEncode(id) : id;
- return CombineUri(baseUri, $"registry-descriptors/{assenc}");
+
+ if (baseUri.Host.Contains("leo-discovery"))
+ {
+ route = "companies";
+ assenc = $"?asset_id={assenc}";
+ }
+
+ return CombineUri(baseUri, $"{route}{assenc}");
}
//
@@ -819,7 +831,10 @@ private static async Task FromRegistryGetAasAndSubmodels(
{
// strictly check IFC
var aasIfc = "" + ep["interface"];
- if (aasIfc != "AAS-1.0")
+ if (!(aasIfc == "AAS-1.0"
+ || aasIfc == "AAS-2.0"
+ || aasIfc == "AAS-3.0"
+ || aasIfc == "AAS-3.1"))
continue;
// direct access HREF
@@ -843,8 +858,11 @@ private static async Task FromRegistryGetAasAndSubmodels(
{
// strictly check IFC
var smIfc = "" + smep["interface"];
- if (smIfc != "SUBMODEL-1.0")
- continue;
+ if (!(aasIfc == "SUBMODEL-1.0"
+ || aasIfc == "SUBMODEL-2.0"
+ || aasIfc == "SUBMODEL-3.0"
+ || aasIfc == "SUBMODEL-3.1"))
+ continue;
// ok
string href = smep.protocolInformation.href;
@@ -969,7 +987,7 @@ private static async Task FromRegistryGetAasAndSubmodels(
return true;
}
- private static async Task FromRegOfRegGetAasAndSubmodels(
+ private static async Task FromRegOfRegGetAasAndSubmodels(
OnDemandListIdentifiable prepAas,
OnDemandListIdentifiable prepSM,
ConnectExtendedRecord record,
@@ -980,82 +998,141 @@ private static async Task FromRegOfRegGetAasAndSubmodels(
List trackNewIdentifiables = null,
List trackLoadedIdentifiables = null,
Action lambdaReportProgress = null,
- bool compatOldAasxServer = false)
+ bool compatOldAasxServer = false,
+ bool isLeoDiscoveryServer = false)
{
// access
if (record == null || regDescriptor == null || assetId?.HasContent() != true)
- return false;
+ return;
- // The format is:
- // {
- // "Url": "http://example.com/6789",
- // "Security": "",
- // "Match": "LIKE",
- // "Pattern": "%6789%",
- // "Domain": "example.com",
- // "Id": "xxx",
- // "Info": "xxx"
- // }
- // However, only Url and Id are currently useful
-
- string regUrl = "" + regDescriptor["url"];
- string regInfo = "" + regDescriptor["info"];
- if (regInfo == "")
- regInfo = "";
- // valid url?
- if (regUrl == "")
- return false;
- var basicUri = GetBaseUri(regUrl);
- if (basicUri == null)
- return false;
+ List regUrls = new List();
- // build again a set of baseUris, but only one pattern set
- var baseUris = new BaseUriDict(key: "AAS-REG", value: basicUri.ToString());
+ if (isLeoDiscoveryServer)
+ {
+ //See: https://leo-discovery.admin-shell-io.com/
- // translate to a list of AAS-Ids ..
- var uriGetListOfAids = BuildUriForRegistryAasByAssetId(baseUris.GetBaseUriForAasReg(), assetId);
- if (compatOldAasxServer)
- uriGetListOfAids = BuildUriForRegistryAasByAssetLinkDeprecated(baseUris.GetBaseUriForAasReg(), assetId);
- var listOfAids = await PackageHttpDownloadUtil.DownloadEntityToDynamicObject(
- uriGetListOfAids, runtimeOptions, allowFakeResponses);
+ var endpoints = regDescriptor?.endpoints;
- if (listOfAids == null || !(listOfAids is JArray) || (listOfAids as JArray).Count < 1)
+ if (endpoints != null)
+ {
+ foreach (var endpoint in endpoints)
+ {
+ if (endpoint["interface"] == "AAS-REGISTRY-3.1"
+ || endpoint["interface"] == "AAS-REGISTRY-3.0")
+ {
+ var regUrl = endpoint.protocolInformation["href"];
+ regUrls.Add("" + regUrl);
+ }
+ }
+ }
+ }
+ else
+ {
+ // The format is:
+ // {
+ // "Url": "http://example.com/6789",
+ // "Security": "",
+ // "Match": "LIKE",
+ // "Pattern": "%6789%",
+ // "Domain": "example.com",
+ // "Id": "xxx",
+ // "Info": "xxx"
+ // }
+ // However, only Url and Id are currently useful
+
+ regUrls.Add("" + regDescriptor["url"]);
+ }
+
+
+ // valid url?
+ if (regUrls.IsNullOrEmpty())
+ return;
+
+ List basicUris = new List();
+
+ foreach (var regUrl in regUrls)
{
- runtimeOptions?.Log?.Info("Registry {0} did not translate glopbalAssetId={1} to any AAS Ids. " +
- "Aborting! URi was: {2}",
- basicUri, assetId, uriGetListOfAids);
- return false;
+ var basicUri = GetBaseUri(regUrl);
+ if (basicUri != null)
+ {
+ basicUris.Add(basicUri);
+ }
+ }
+
+ if(basicUris.IsNullOrEmpty())
+ {
+ return;
}
- // take the individual AAS ids
- foreach (var aid in listOfAids)
+ foreach (var basicUri in basicUris)
{
- // prepare receiving the descriptor
- var uriGetAasDescr = BuildUriForRegistrySingleAAS(baseUris.GetBaseUriForAasReg(), aid.ToString());
- var resAasDescr = await PackageHttpDownloadUtil.DownloadEntityToDynamicObject(
- uriGetAasDescr, runtimeOptions, allowFakeResponses);
+ // build again a set of baseUris, but only one pattern set
+ var baseUris = new BaseUriDict(key: "AAS-REG", value: basicUri.ToString());
- // have directly a single descriptor?!
- if (!(resAasDescr is JObject))
+ // translate to a list of AAS-Ids ..
+ var uriGetListOfAids = BuildUriForRegistryAasByAssetId(baseUris.GetBaseUriForAasReg(), assetId);
+ if (compatOldAasxServer)
+ uriGetListOfAids = BuildUriForRegistryAasByAssetLinkDeprecated(baseUris.GetBaseUriForAasReg(), assetId);
+
+ try
{
- runtimeOptions?.Log?.Info("Registry did not return a single AAS descriptor! Aborting. URI was: {0}",
- uriGetAasDescr);
- return false;
- }
+ var listOfAids = await PackageHttpDownloadUtil.DownloadEntityToDynamicObject(
+ uriGetListOfAids, runtimeOptions, allowFakeResponses);
- lambdaReportProgress?.Invoke(0, 0, 0, 1);
+ if (listOfAids == null || !(listOfAids is JArray) || (listOfAids as JArray).Count < 1)
+ {
+ runtimeOptions?.Log?.Info("Registry {0} did not translate globalAssetId={1} to any AAS Ids. " +
+ "Aborting! URi was: {2}",
+ basicUri, assetId, uriGetListOfAids);
+ }
+ else
+ {
+ // take the individual AAS ids
+ foreach (var aid in listOfAids)
+ {
+ // prepare receiving the descriptor
+ var uriGetAasDescr = BuildUriForRegistrySingleAAS(baseUris.GetBaseUriForAasReg(), aid.ToString());
+ var resAasDescr = await PackageHttpDownloadUtil.DownloadEntityToDynamicObject(
+ uriGetAasDescr, runtimeOptions, allowFakeResponses);
+
+ // have directly a single descriptor?!
+ if (!(resAasDescr is JObject))
+ {
+ runtimeOptions?.Log?.Info("Registry did not return a single AAS descriptor! Aborting. URI was: {0}",
+ uriGetAasDescr);
+ }
- // refer to dedicated function
- await FromRegistryGetAasAndSubmodels(
- prepAas, prepSM, record, runtimeOptions, allowFakeResponses, resAasDescr,
- trackNewIdentifiables, trackLoadedIdentifiables,
- lambdaReportProgress: lambdaReportProgress);
+ lambdaReportProgress?.Invoke(0, 0, 0, 1);
+
+ try
+ {
+ // refer to dedicated function
+ await FromRegistryGetAasAndSubmodels(
+ prepAas, prepSM, record, runtimeOptions, allowFakeResponses, resAasDescr,
+ trackNewIdentifiables, trackLoadedIdentifiables,
+ lambdaReportProgress: lambdaReportProgress);
+ }
+ catch (Exception e)
+ {
+ runtimeOptions?.Log?.Info("Download aas and submodels from registry {0} failed globalAssetId={1} to any AAS Ids. " +
+ "Aborting! AAS id was: {2}, message was {3}",
+ basicUri, assetId, aid, e.Message);
+ }
+
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ runtimeOptions?.Log?.Info("Download descriptors from registry {0} failed globalAssetId={1} to any AAS Ids. " +
+ "Aborting! URi was: {2}, message was {3}",
+ basicUri, assetId, uriGetListOfAids, e.Message);
+ }
}
- // OK?
- return true;
+ return;
}
public override async Task LoadFromSourceAsync(
@@ -1221,6 +1298,12 @@ protected static async Task LoadFromSourceInternalAsyn
}
else
{
+ bool isFXLeoDiscoveryServer = fullItemLocation.Contains("leo-discovery");
+ if (isFXLeoDiscoveryServer)
+ {
+ resObj = resObj["data"];
+ }
+
foreach (var res in resObj)
{
// refer to dedicated function
@@ -1230,7 +1313,8 @@ await FromRegOfRegGetAasAndSubmodels(
trackNewIdentifiables, trackLoadedIdentifiables,
lambdaReportProgress: lambdaReportAasSm,
// TODO: check!!
- compatOldAasxServer: true);
+ compatOldAasxServer: isFXLeoDiscoveryServer ? false : true,
+ isLeoDiscoveryServer : isFXLeoDiscoveryServer);
}
}
}
diff --git a/src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs b/src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs
index 928ab7f9a..f5e6d4f64 100644
--- a/src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs
+++ b/src/AasxPackageLogic/PackageCentral/PackageHttpDownloadUtil.cs
@@ -7,10 +7,6 @@ This source code is licensed under the Apache License 2.0 (see LICENSE.txt).
This source code may use other Open Source software components (see LICENSE.txt).
*/
-using AasxOpenIdClient;
-using AdminShellNS;
-using Namotion.Reflection;
-using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
@@ -22,6 +18,10 @@ This source code may use other Open Source software components (see LICENSE.txt)
using System.Text.Json.Nodes;
using System.Threading;
using System.Threading.Tasks;
+using AasxOpenIdClient;
+using AdminShellNS;
+using Namotion.Reflection;
+using Newtonsoft.Json;
using Aas = AasCore.Aas3_1;
namespace AasxPackageLogic.PackageCentral
@@ -290,10 +290,25 @@ await memStream.WriteAsync(buffer, 0, bytesRead,
// prepare request
var requestContent = new StringContent(requestBody, Encoding.UTF8, requestContentType);
- // retrieve response
- var response = await client.PostAsync(requestPath, requestContent);
+ // make a request
+ HttpResponseMessage response = null;
+ using (var requestMessage =
+ new HttpRequestMessage(HttpMethod.Post, requestPath))
+ {
+ // assume headers to be for authorization
+ if (runtimeOptions?.HttpHeaderData?.Headers != null)
+ foreach (var header in runtimeOptions.HttpHeaderData?.Headers)
+ {
+ requestMessage.Headers.Add(header.Key, header.Value);
+ }
+
+ requestMessage.Content = requestContent;
+ response = await client.SendAsync(requestMessage,
+ HttpCompletionOption.ResponseHeadersRead);
+
+ }
- if (response.IsSuccessStatusCode)
+ if (response != null && response.IsSuccessStatusCode)
{
//
// this portion of the code is prepared to receive large sets of content data
@@ -304,7 +319,7 @@ await memStream.WriteAsync(buffer, 0, bytesRead,
// log
if (runtimeOptions?.ExtendedConnectionDebug == true)
- runtimeOptions .Log?.Info($".. response with header-content-len {contentLength} " +
+ runtimeOptions.Log?.Info($".. response with header-content-len {contentLength} " +
$"and file-name {contentFn} ..");
var contentStream = await response?.Content?.ReadAsStreamAsync();
@@ -318,7 +333,7 @@ await memStream.WriteAsync(buffer, 0, bytesRead,
if (contentFn != null)
givenFn = contentFn;
if (runtimeOptions?.ExtendedConnectionDebug == true)
- runtimeOptions .Log?.Info($".. downloading and scanning by proxy/firewall {client.BaseAddress} " +
+ runtimeOptions.Log?.Info($".. downloading and scanning by proxy/firewall {client.BaseAddress} " +
$"and request {requestPath} .. ");
using (var memStream = new MemoryStream())
@@ -372,7 +387,7 @@ await memStream.WriteAsync(buffer, 0, bytesRead,
else
{
//
- // assume smaller conent data
+ // assume smaller content data
//
var responseContents = await response.Content.ReadAsStringAsync();
@@ -474,6 +489,13 @@ public static async Task> HttpPutPostFromMemoryStr
// var data = new StringContent("1.2345", Encoding.UTF8, "application/json");
}
+ // assume headers to be for authorization
+ if (runtimeOptions?.HttpHeaderData?.Headers != null)
+ foreach (var header in runtimeOptions.HttpHeaderData?.Headers)
+ {
+ overallContent.Headers.Add(header.Key, header.Value);
+ }
+
// get response?
using (var response = (!usePost)
? await client.PutAsync(requestPath, overallContent)
@@ -510,18 +532,35 @@ public static async Task> HttpDeleteUri(
runtimeOptions.Log?.Info($"HttpClient DELETE() with base-address {client.BaseAddress} " +
$"and request {requestPath} .. ");
+
+ // make a request
+ HttpResponseMessage response = null;
+ using (var requestMessage =
+ new HttpRequestMessage(HttpMethod.Delete, requestPath))
+ {
+ // assume headers to be for authorization
+ if (runtimeOptions?.HttpHeaderData?.Headers != null)
+ foreach (var header in runtimeOptions.HttpHeaderData?.Headers)
+ {
+ requestMessage.Headers.Add(header.Key, header.Value);
+ }
+
+ response = await client.SendAsync(requestMessage);
+ }
+
+ var content = "";
+
// get response?
- using (var response = await client.DeleteAsync(requestPath))
+ if (response != null)
{
// try read in any case, despite:
// https://stackoverflow.com/questions/17640666/httpclient-deleteasync-and-content-readadstringasync-always-return-null
- var content = "";
if (response.StatusCode != HttpStatusCode.NoContent)
await response.Content.ReadAsStringAsync();
-
- // ok!
- return new Tuple(response.StatusCode, content);
}
+ // ok!
+ return new Tuple(response.StatusCode, content);
+
}
public static async Task> HttpPutPostIdentifiable(
diff --git a/src/AasxPackageLogic/PackageCentral/SecurityAccessHandlerLogicBase.cs b/src/AasxPackageLogic/PackageCentral/SecurityAccessHandlerLogicBase.cs
index da5ad1887..0b1c0a27e 100644
--- a/src/AasxPackageLogic/PackageCentral/SecurityAccessHandlerLogicBase.cs
+++ b/src/AasxPackageLogic/PackageCentral/SecurityAccessHandlerLogicBase.cs
@@ -10,34 +10,27 @@ This source code is licensed under the Apache License 2.0 (see LICENSE.txt).
This source code may use other Open Source software components (see LICENSE.txt).
*/
-using AasxIntegrationBase;
-using AasxPackageLogic;
-using AasxPackageLogic.PackageCentral;
-using AdminShellNS;
-using AnyUi;
-using Extensions;
-using Microsoft.Identity.Client;
-using Microsoft.IdentityModel.Tokens;
-using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
-using System.ComponentModel;
using System.IdentityModel.Tokens.Jwt;
-using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
-using System.Reflection;
using System.Security.Claims;
using System.Security.Cryptography.X509Certificates;
-using System.Security.RightsManagement;
using System.Text;
-using System.Text.RegularExpressions;
-using System.Threading;
+using System.Text.Json;
using System.Threading.Tasks;
-using System.Windows;
-using Aas = AasCore.Aas3_1;
+using AasxIntegrationBase;
+using AasxPackageLogic;
+using AasxPackageLogic.PackageCentral;
+using AdminShellNS;
+using AnyUi;
+using Microsoft.Identity.Client;
+using Microsoft.IdentityModel.JsonWebTokens;
+using Microsoft.IdentityModel.Tokens;
+using Newtonsoft.Json.Linq;
namespace AasxPackageExplorer
{
@@ -430,6 +423,264 @@ protected virtual async Task AskForAuthServerUri(string baseAddress, str
return null;
}
+ private async Task DoTokenExchange(string[] tokenExchangeParams, string secretAccessToken)
+ {
+ var handler = new HttpClientHandler { DefaultProxyCredentials = CredentialCache.DefaultCredentials };
+ using var client = new HttpClient(handler);
+
+ var exchangedToken = String.Empty;
+
+ var configUrl = tokenExchangeParams[0];
+ var target = tokenExchangeParams[1];
+
+ var d = new Dictionary
+ {
+ { "grant_type", "urn:ietf:params:oauth:grant-type:token-exchange" },
+ { "subject_token_type", "urn:ietf:params:oauth:token-type:jwt" },
+ { "requested_token_type", "urn:ietf:params:oauth:token-type:access_token" },
+ { "subject_token", secretAccessToken },
+ };
+
+ if (target != "")
+ {
+ d.Add("audience", target);
+ }
+
+ string jwksUri = $"{configUrl}/jwks";
+ string tokenEndpoint = $"{configUrl}/token";
+
+ //Get Well-Known
+ try
+ {
+ var openIdConfig = await client.GetStringAsync($"{configUrl}/.well-known/openid-configuration");
+ var openIdConfigJson = JsonDocument.Parse(openIdConfig);
+
+ if (openIdConfigJson.RootElement.TryGetProperty("jwks_uri", out var propJwksUri))
+ {
+ jwksUri = propJwksUri.GetString();
+ }
+
+ if (openIdConfigJson.RootElement.TryGetProperty("token_endpoint", out var propTokenUrl))
+ {
+ tokenEndpoint = propTokenUrl.GetString();
+ }
+ }
+ catch (Exception ex)
+ {
+ Log.Singleton.Info($"Could not access well-known from {configUrl}");
+ }
+
+ var request = new HttpRequestMessage(HttpMethod.Post, tokenEndpoint)
+ {
+ Content = new FormUrlEncodedContent(d)
+ };
+ request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
+
+ var response = await client.SendAsync(request);
+ var content = await response.Content.ReadAsStringAsync();
+
+ var accessToken = "";
+ var doc = JsonDocument.Parse(content);
+ if (doc.RootElement.TryGetProperty("access_token", out var tokenElement))
+ {
+ accessToken = tokenElement.GetString();
+ Console.WriteLine("Access Token: " + accessToken);
+
+ var jwksJson = await client.GetStringAsync(jwksUri);
+ var jwks = JObject.Parse(jwksJson)["keys"];
+
+ // 1) Handler
+ var jsonWebTokenHandler = new JsonWebTokenHandler();
+
+ // 2) JWK aus JWKS direkt verwenden (hier exemplarisch LINQ auf Dein jwks-Array)
+ var jsonWebToken = new JsonWebToken(accessToken);
+ var jwtHeaderKid = jsonWebToken.Kid; // liest 'kid' robust
+ var jwkJson = jwks.First(k => k["kid"].ToString() == jwtHeaderKid).ToString(); // k ist i. d. R. ein JObject
+ var jwk = new JsonWebKey(jwkJson);
+
+ // 3) Validierungsparameter
+ var validationParams = new TokenValidationParameters
+ {
+ // Signaturprüfung
+ IssuerSigningKey = jwk, // kein manuelles RSAParameters nötig
+ ValidateIssuerSigningKey = true,
+
+ // Lebenszeit
+ ValidateLifetime = true,
+ RequireExpirationTime = true,
+ ClockSkew = TimeSpan.FromMinutes(5), // bei UTC+0 gut, ggf. 2–5 Minuten
+
+ // Issuer/Audience je nach Bedarf (bei Tests oft aus)
+ ValidateIssuer = false, // später auf true + ValidIssuer setzen
+ ValidateAudience = false, // später auf true + ValidAudience setzen
+
+ // Keine Legacy-Claim-Mappings
+ // MapInboundClaims = false,
+
+ // Optional: Name-/Rollen-Claims aus dem JWT
+ NameClaimType = "name",
+ RoleClaimType = "role"
+ };
+
+ try
+ {
+ var result = jsonWebTokenHandler.ValidateToken(accessToken, validationParams);
+ if (!result.IsValid)
+ {
+ Console.WriteLine($"Validation failed: {result.Exception?.Message}");
+ return null;
+ }
+ else
+ {
+ Console.WriteLine("Token is valid");
+ exchangedToken = accessToken;
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Validation failed: {ex.Message}");
+ return null;
+ }
+ }
+
+ return exchangedToken;
+ }
+
+ protected virtual async Task> AskForTokenExchange()
+ {
+ bool needsTokenExchange1 = false;
+ bool needsTokenExchange2 = false;
+ string[] tokenExchange1Params = ["https://iam-security-training.com/consumer/sts", "factory-x",];
+ string[] tokenExchange2Params = ["https://iam-security-training.com/provider/sts", "factory-x",];
+
+ var ucJob = new AnyUiDialogueDataModalPanel("With token exchange?");
+
+ ucJob.ActivateRenderPanel(null,
+ disableScrollArea: false,
+ dialogButtons: AnyUiMessageBoxButton.OK,
+ renderPanel: (uci) =>
+ {
+ // create panel
+ var panel = new AnyUiStackPanel();
+ var helper = new AnyUiSmallWidgetToolkit();
+
+ var g = helper.AddSmallGrid(25, 2, new[] { "200:", "*" },
+ padding: new AnyUiThickness(0, 5, 0, 5),
+ margin: new AnyUiThickness(10, 0, 30, 0));
+
+ panel.Add(g);
+
+ // dynamic rows
+ int row = 0;
+
+ // Info
+ helper.Set(
+ helper.AddSmallLabelTo(g, row, 0, content: "For some authentification methods, you will need to do a token exchange", wrapping: AnyUiTextWrapping.Wrap),
+
+ colSpan: 2);
+ row++;
+
+ // separation
+ helper.AddSmallBorderTo(g, row, 0,
+ borderThickness: new AnyUiThickness(0.5), borderBrush: AnyUiBrushes.White,
+ colSpan: 2,
+ margin: new AnyUiThickness(0, 0, 0, 20));
+ row++;
+
+ AnyUiUIElement.SetBoolFromControl(
+ helper.AddSmallCheckBoxTo(g, row, 0,
+ content: "Do token exchange 1",
+ isChecked: needsTokenExchange1,
+ verticalContentAlignment: AnyUiVerticalAlignment.Center),
+ (b) => { needsTokenExchange1 = b; });
+
+ row++;
+
+ // Content URL
+
+ helper.AddSmallLabelTo(g, row, 0, content: "Content URL:",
+ verticalAlignment: AnyUiVerticalAlignment.Center,
+ verticalContentAlignment: AnyUiVerticalAlignment.Center);
+
+ AnyUiUIElement.SetStringFromControl(
+ helper.Set(
+ helper.AddSmallTextBoxTo(g, row, 1,
+ text: $"{tokenExchange1Params[0]}",
+ verticalAlignment: AnyUiVerticalAlignment.Center,
+ verticalContentAlignment: AnyUiVerticalAlignment.Center),
+ horizontalAlignment: AnyUiHorizontalAlignment.Stretch),
+ (s) => { tokenExchange1Params[0] = s; });
+
+ row++;
+
+ // Target (Audience)
+
+ helper.AddSmallLabelTo(g, row, 0, content: "Target (Audience):",
+ verticalAlignment: AnyUiVerticalAlignment.Center,
+ verticalContentAlignment: AnyUiVerticalAlignment.Center);
+
+ AnyUiUIElement.SetStringFromControl(
+ helper.Set(
+ helper.AddSmallTextBoxTo(g, row, 1,
+ text: $"{tokenExchange1Params[1]}",
+ verticalAlignment: AnyUiVerticalAlignment.Center,
+ verticalContentAlignment: AnyUiVerticalAlignment.Center),
+ horizontalAlignment: AnyUiHorizontalAlignment.Stretch),
+ (s) => { tokenExchange1Params[1] = s; });
+
+ row++;
+
+
+ AnyUiUIElement.SetBoolFromControl(
+ helper.AddSmallCheckBoxTo(g, row, 0,
+ content: "Do token exchange 2",
+ isChecked: needsTokenExchange2,
+ verticalContentAlignment: AnyUiVerticalAlignment.Center),
+ (b) => { needsTokenExchange2 = b; });
+
+ row++;
+
+ // Content URL
+
+ helper.AddSmallLabelTo(g, row, 0, content: "Content URL:",
+ verticalAlignment: AnyUiVerticalAlignment.Center,
+ verticalContentAlignment: AnyUiVerticalAlignment.Center);
+
+ AnyUiUIElement.SetStringFromControl(
+ helper.Set(
+ helper.AddSmallTextBoxTo(g, row, 1,
+ text: $"{tokenExchange2Params[0]}",
+ verticalAlignment: AnyUiVerticalAlignment.Center,
+ verticalContentAlignment: AnyUiVerticalAlignment.Center),
+ horizontalAlignment: AnyUiHorizontalAlignment.Stretch),
+ (s) => { tokenExchange2Params[0] = s; });
+
+ row++;
+
+ // Target (Audience)
+
+ helper.AddSmallLabelTo(g, row, 0, content: "Target (Audience):",
+ verticalAlignment: AnyUiVerticalAlignment.Center,
+ verticalContentAlignment: AnyUiVerticalAlignment.Center);
+
+ AnyUiUIElement.SetStringFromControl(
+ helper.Set(
+ helper.AddSmallTextBoxTo(g, row, 1,
+ text: $"{tokenExchange2Params[1]}",
+ verticalAlignment: AnyUiVerticalAlignment.Center,
+ verticalContentAlignment: AnyUiVerticalAlignment.Center),
+ horizontalAlignment: AnyUiHorizontalAlignment.Stretch),
+ (s) => { tokenExchange2Params[1] = s; });
+
+ // give back
+ return g;
+ });
+
+ if (_displayContext != null && await _displayContext.StartFlyoverModalAsync(ucJob))
+ return new Tuple(needsTokenExchange1, tokenExchange1Params, needsTokenExchange2, tokenExchange2Params);
+ return new Tuple(needsTokenExchange1, tokenExchange1Params, needsTokenExchange2, tokenExchange2Params); ;
+ }
+
protected virtual async Task AskForSelectFromCertCollection(
string baseAddress,
X509Certificate2Collection fcollection)
@@ -692,6 +943,20 @@ protected virtual async Task DetermineAuthenticateHeaderForE
var secretAccessToken = secretContentJson["access_token"].ToString();
Log.Singleton.Info($"Security access handler: Found 'access_token': {secretAccessToken}");
+ var tokenExchanges = await AskForTokenExchange();
+
+ if (tokenExchanges != null && tokenExchanges.Item1)
+ {
+ secretAccessToken = await DoTokenExchange(tokenExchanges.Item2, secretAccessToken);
+
+ if (tokenExchanges.Item3)
+ {
+ secretAccessToken = await DoTokenExchange(tokenExchanges.Item4, secretAccessToken);
+ }
+ }
+
+ Log.Singleton.Info($"Security access handler: Exchanged token to 'access_token': {secretAccessToken}");
+
// build correct header key, remember, return
endpoint.LastRenewed = DateTime.UtcNow;
endpoint.LastHeaderItem = new HttpHeaderDataItem("Authorization", $"Bearer {secretAccessToken.ToString()}");
@@ -878,7 +1143,8 @@ protected virtual async Task DetermineAuthenticateHeaderForE
chain.Import(certFn, PW);
x5c = chain.Cast().Reverse().Select(c => Convert.ToBase64String(c.RawData)).ToArray();
- } catch (Exception ex)
+ }
+ catch (Exception ex)
{
Log.Singleton.Error(ex, $"when building certificate chain for certificate file {certFn}. " +
$"Aborting. Using no security!");
@@ -887,8 +1153,8 @@ protected virtual async Task DetermineAuthenticateHeaderForE
}
else if (preferredMethod == SecurityAccessMethod.InteractiveEntry)
- {
-
+ {
+
// test tenant for ENTRA id
// TODO: configure clientId
var tenant = "common"; // Damit auch externe Konten wie @live.de funktionieren
@@ -1008,9 +1274,18 @@ protected virtual async Task DetermineAuthenticateHeaderForE
var accessToken = contentJson["access_token"];
Log.Singleton.Info($"Security access handler: Found 'access_token': {accessToken}");
+ var accessTokenString = accessToken.ToString();
+
+ //ToDo: Need token exchange here?
+ //var tokenExchanges = await AskForTokenExchange();
+
+ //if (tokenExchanges != null && !String.IsNullOrEmpty(tokenExchanges[0]))
+ //{
+ //}
+
// build correct header key, remember, return
endpoint.LastRenewed = DateTime.UtcNow;
- endpoint.LastHeaderItem = new HttpHeaderDataItem("Authorization", $"Bearer {accessToken.ToString()}");
+ endpoint.LastHeaderItem = new HttpHeaderDataItem("Authorization", $"Bearer {accessTokenString}");
return endpoint.LastHeaderItem;
}
}