diff --git a/src/Dan.Plugin.Tilda/Config/KeyVault.cs b/src/Dan.Plugin.Tilda/Config/KeyVault.cs new file mode 100644 index 0000000..d40e301 --- /dev/null +++ b/src/Dan.Plugin.Tilda/Config/KeyVault.cs @@ -0,0 +1,47 @@ +using System; +using System.Security.Cryptography.X509Certificates; +using System.Threading.Tasks; +using Azure.Identity; +using Azure.Security.KeyVault.Secrets; + +namespace Dan.Plugin.Tilda.Config; + +public class KeyVault +{ + private SecretClient SecretClient { get; } + + /// + /// Key Vault for Core + /// + /// Name of the Key Vault + public KeyVault(string vaultName) + { + SecretClient = new SecretClient(new Uri($"https://{vaultName}.vault.azure.net/"), new DefaultAzureCredential()); + } + + /// + /// Get a secret from the key vault + /// + /// Secret name + /// The secret value + public async Task Get(string key) + { + var secret = await SecretClient.GetSecretAsync(key); + return secret.Value.Value; + } + + /// + /// Get a certificate from the key vault + /// + /// Certificate name + /// The certificate + public async Task GetCertificate(string key) + { + var base64Certificate = await Get(key); + var certBytes = Convert.FromBase64String(base64Certificate); + + var cert = new X509Certificate2(certBytes, string.Empty, X509KeyStorageFlags.MachineKeySet); + + return await Task.FromResult(cert); + } +} diff --git a/src/Dan.Plugin.Tilda/Config/Settings.cs b/src/Dan.Plugin.Tilda/Config/Settings.cs index f865126..c278ac9 100644 --- a/src/Dan.Plugin.Tilda/Config/Settings.cs +++ b/src/Dan.Plugin.Tilda/Config/Settings.cs @@ -1,4 +1,5 @@ using System; +using System.Security.Cryptography.X509Certificates; namespace Dan.Plugin.Tilda.Config { @@ -10,12 +11,32 @@ public string GetClassBaseUri(string className) } public string RedisConnectionString { get; set; } - + public bool IsTest { get; set; } public bool IsLocalDevelopment { get; set; } public string Breaker_RetryWaitTime { get; set; } public string Breaker_OpenCircuitTime { get; set; } + + public string KofuviEndpoint { get; set; } + public string KvName { get; set; } + public string KvKofuviCertificateName { get; set; } + + private static string KeyVaultName => Environment.GetEnvironmentVariable("KvName"); + private static string KofuviCertificateName => Environment.GetEnvironmentVariable("KvKofuviCertificateName"); + + private static X509Certificate2 _altinnCertificate { get; set; } + public static X509Certificate2 Certificate + { + get + { + return _altinnCertificate ?? new KeyVault(KeyVaultName).GetCertificate(KofuviCertificateName).Result; + } + set + { + _altinnCertificate = value; + } + } } } diff --git a/src/Dan.Plugin.Tilda/Dan.Plugin.Tilda.csproj b/src/Dan.Plugin.Tilda/Dan.Plugin.Tilda.csproj index 2dbc492..7714e9d 100644 --- a/src/Dan.Plugin.Tilda/Dan.Plugin.Tilda.csproj +++ b/src/Dan.Plugin.Tilda/Dan.Plugin.Tilda.csproj @@ -23,6 +23,8 @@ + + diff --git a/src/Dan.Plugin.Tilda/Models/KofuviResponse.cs b/src/Dan.Plugin.Tilda/Models/KofuviResponse.cs new file mode 100644 index 0000000..bd12a6d --- /dev/null +++ b/src/Dan.Plugin.Tilda/Models/KofuviResponse.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Dan.Plugin.Tilda.Models; + +[Serializable] +public class KofuviResponse +{ + [JsonProperty("_embedded")] + public Embedded Embedded { get; set; } +} + +[Serializable] +public class Embedded +{ + [JsonProperty("varsling")] + public Notification Notification { get; set; } +} + +[Serializable] +public class Notification +{ + [JsonProperty("varslingsadresser")] + public List NotificationAddresses { get; set; } +} + +[Serializable] +public class NotificationAddress +{ + [JsonProperty("kontaktinformasjon")] + public ContactInformation ContactInformation { get; set; } +} + +[Serializable] +public class ContactInformation +{ + [JsonProperty("digitalVarslingsinformasjon")] + public DigitalNotificationInformation DigitalNotificationInformation { get; set; } +} + +[Serializable] +public class DigitalNotificationInformation +{ + [JsonProperty("epostadresse")] + public NotificationEmail NotificationEmail { get; set; } +} + +[Serializable] +public class NotificationEmail +{ + [JsonProperty("fullstendigAdresse")] + public string CompleteEmail { get; set; } +} diff --git a/src/Dan.Plugin.Tilda/Models/TildaRegistryEntry.cs b/src/Dan.Plugin.Tilda/Models/TildaRegistryEntry.cs index 24577a2..b53179e 100644 --- a/src/Dan.Plugin.Tilda/Models/TildaRegistryEntry.cs +++ b/src/Dan.Plugin.Tilda/Models/TildaRegistryEntry.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Text; using Newtonsoft.Json; namespace Dan.Plugin.Tilda.Models @@ -14,6 +13,9 @@ public class TildaRegistryEntry [JsonProperty("tildaenhetNavn", Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Ignore)] public string Name; + [JsonProperty("epostaddresser", Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Ignore)] + public List Emails; + [JsonProperty("tildaenhetHovedenhet", Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Ignore)] public string ControlObjectParent; @@ -31,7 +33,6 @@ public class TildaRegistryEntry [JsonProperty("driftsstatus", Required = Required.Default, DefaultValueHandling = DefaultValueHandling.Ignore)] public OperationStatus OperationalStatus; - } public class AccountsInformation diff --git a/src/Dan.Plugin.Tilda/Program.cs b/src/Dan.Plugin.Tilda/Program.cs index 014e11f..7c386ce 100644 --- a/src/Dan.Plugin.Tilda/Program.cs +++ b/src/Dan.Plugin.Tilda/Program.cs @@ -12,6 +12,7 @@ using Polly.Extensions.Http; using Polly.Registry; using System; +using System.Net.Http; using Settings = Dan.Plugin.Tilda.Config.Settings; var host = new HostBuilder() @@ -20,7 +21,7 @@ { configuration .AddJsonFile("worker-logging.json", optional:true); - }) + }) .ConfigureServices((context, services) => { // This makes IOption available in the DI container. @@ -47,8 +48,18 @@ services.AddHttpClient("ERHttpClient", client => { client.Timeout = new TimeSpan(0, 0, 5); - }); + }); + services.AddHttpClient("KofuviClient", _ => + { + + }) + .ConfigurePrimaryHttpMessageHandler(() => + { + var handler = new HttpClientHandler(); + handler.ClientCertificates.Add(Settings.Certificate); + return handler; + });; }) .Build(); diff --git a/src/Dan.Plugin.Tilda/Tilda.cs b/src/Dan.Plugin.Tilda/Tilda.cs index aca9fb4..0a65308 100644 --- a/src/Dan.Plugin.Tilda/Tilda.cs +++ b/src/Dan.Plugin.Tilda/Tilda.cs @@ -30,6 +30,7 @@ public class Tilda private ILogger _logger; private HttpClient _client; private HttpClient _erClient; + private HttpClient _kofuviClient; private Settings _settings; private readonly IEntityRegistryService _entityRegistryService; private readonly IEvidenceSourceMetadata _metadata; @@ -41,6 +42,7 @@ public Tilda(IHttpClientFactory httpClientFactory, IOptions settings, _policyRegistry = policyRegistry; _client = httpClientFactory.CreateClient("SafeHttpClient"); _erClient = httpClientFactory.CreateClient("ERHttpClient"); + _kofuviClient = httpClientFactory.CreateClient("KofuviClient"); _settings = settings.Value; _entityRegistryService = entityRegistry; _entityRegistryService.AllowTestCcrLookup = _settings.IsLocalDevelopment || _settings.IsLocalDevelopment; @@ -440,6 +442,7 @@ private async Task> GetEvidenceValuesStorulykkevirksomhet(Ev return eb.GetEvidenceValues(); } + // TODO: use IncludeSubunits-param in Tildaparameters? And then we can reduce duplicate code between the overloads private async Task> GetOrganizationsFromBR(string organizationNumber, TildaParameters param) { var result = new List(); @@ -450,19 +453,32 @@ private async Task> GetOrganizationsFromBR(string organ accountsInformation = await Helpers.GetAnnualTurnoverFromBR(organizationNumber, _client, _policyRegistry); } - result.Add(await ConvertBRtoTilda(brResult.First(), accountsInformation)); + var kofuviAddresses = await Helpers.GetKofuviAddresses(_settings.KofuviEndpoint, organizationNumber, _kofuviClient, _logger); + + var organization = await ConvertBRtoTilda(brResult.First(), accountsInformation); + if (kofuviAddresses.Count > 0) + { + organization.Emails = kofuviAddresses; + } + result.Add(organization); return result; } private async Task GetOrganizationFromBR(string organizationNumber) { - var brResultTask = Helpers.GetFromBR(organizationNumber, _erClient, false, _policyRegistry); - var accountsInformationTask = Helpers.GetAnnualTurnoverFromBR(organizationNumber, _client, _policyRegistry); + var brResultTask = await Helpers.GetFromBR(organizationNumber, _erClient, false, _policyRegistry); + var accountsInformationTask = await Helpers.GetAnnualTurnoverFromBR(organizationNumber, _client, _policyRegistry); - await Task.WhenAll(brResultTask, accountsInformationTask); + var kofuviAddresses = await Helpers.GetKofuviAddresses(_settings.KofuviEndpoint, organizationNumber, _kofuviClient, _logger); + + var organization = await ConvertBRtoTilda(brResultTask.First(), accountsInformationTask); + if (kofuviAddresses.Count > 0) + { + organization.Emails = kofuviAddresses; + } - return await ConvertBRtoTilda(brResultTask.Result.First(), accountsInformationTask.Result); + return organization; } private async Task> GetEvidenceValuesTilsynskoordinering(EvidenceHarvesterRequest req, TildaParameters param) diff --git a/src/Dan.Plugin.Tilda/Utils/Helpers.cs b/src/Dan.Plugin.Tilda/Utils/Helpers.cs index 4a5fa18..3807805 100644 --- a/src/Dan.Plugin.Tilda/Utils/Helpers.cs +++ b/src/Dan.Plugin.Tilda/Utils/Helpers.cs @@ -176,7 +176,7 @@ private static async Task> GetAllUnitsFromBR(string string rawResult; try { - var response = await client.GetAsync($"http://data.brreg.no/enhetsregisteret/api/enheter/?overordnetEnhet={organizationNumber}"); + var response = await client.GetAsync($"https://data.brreg.no/enhetsregisteret/api/enheter/?overordnetEnhet={organizationNumber}"); if (response.StatusCode == HttpStatusCode.NotFound) { throw new EvidenceSourcePermanentClientException( @@ -208,7 +208,7 @@ public static async Task> GetFromBR(string organizat { List result = new List(); - if (organization == "111111111") + if (organization is "111111111" or "811105562") { result.Add(new BREntityRegisterEntry() { @@ -560,5 +560,56 @@ public static async Task GetPdfreport(string url, string sourceOrgNo, Ht return result; } + + // Returns empty list on any error + public static async Task> GetKofuviAddresses(string baseEndpoint, string organizationNumber, HttpClient client, ILogger logger) + { + var targetUrl = $"{baseEndpoint}/api/varslingsadresser/{organizationNumber}"; + string responseString; + try + { + var response = await client.GetAsync(targetUrl); + if (!response.IsSuccessStatusCode) + { + logger.LogError( + "Failed to get kofuvi addresses for org={organizationNumber} on url={targetUrl} with unsuccessful response status={statusCode}", + organizationNumber, targetUrl, response.StatusCode + ); + return new List(); + } + responseString = await response.Content.ReadAsStringAsync(); + } + catch (Exception ex) + { + logger.LogError( + "Failed to get kofuvi addresses for org={organizationNumber} on url={targetUrl} with exception ex={ex} message={message} status={status}", + organizationNumber, targetUrl, ex.GetType().Name, ex.Message, "hardfail" + ); + return new List(); + } + + try + { + var kofuviResponse = JsonConvert.DeserializeObject(responseString); + var addresses = kofuviResponse.Embedded.Notification.NotificationAddresses; + if (addresses is null || addresses.Count == 0) + { + return new List(); + } + + return addresses + .Select(a => a.ContactInformation?.DigitalNotificationInformation?.NotificationEmail?.CompleteEmail) + .Where(a => a is not null) + .ToList(); + } + catch (Exception ex) + { + logger.LogError( + "Failed to deserialize kofuvi response for org={organizationNumber} with exception ex={ex} message={message} status={status}", + organizationNumber, ex.GetType().Name, ex.Message, "hardfail" + ); + return new List(); + } + } } }