Skip to content

Commit

Permalink
functionality for sbl bridge (#547)
Browse files Browse the repository at this point in the history
* functionality for sbl bridge

* fix db migration

* add empty value to appsettings

* dont initialize httpClient in dev service

* test with client instead of singleton

* log more info

* cleanup

* fix missing comma

* send to sblbridge when published

* cleanup

* big cleanup

---------

Co-authored-by: Hammerbeck <andreas.hammerbeck@digdir.no>
  • Loading branch information
Andreass2 and Hammerbeck authored Jan 8, 2025
1 parent 828f26f commit f57743f
Show file tree
Hide file tree
Showing 30 changed files with 809 additions and 11 deletions.
3 changes: 3 additions & 0 deletions .azure/applications/api/main.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ param keyVaultUrl string
param namePrefix string
@secure()
param storageAccountName string
@secure()
param sblBridgeBaseUrl string
@minLength(3)
param maskinporten_environment string
param correspondenceBaseUrl string
Expand Down Expand Up @@ -99,6 +101,7 @@ module containerApp '../../modules/containerApp/main.bicep' = {
contactReservationRegistryBaseUrl: contactReservationRegistryBaseUrl
idportenIssuer: idportenIssuer
dialogportenIssuer: dialogportenIssuer
sblBridgeBaseUrl: sblBridgeBaseUrl
maskinporten_token_exchange_environment: maskinporten_token_exchange_environment
}
}
Expand Down
1 change: 1 addition & 0 deletions .azure/applications/api/params.bicepparam
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ param location = 'norwayeast'
param imageTag = readEnvironmentVariable('IMAGE_TAG')
param platform_base_url = readEnvironmentVariable('PLATFORM_BASE_URL')
param correspondenceBaseUrl = readEnvironmentVariable('CORRESPONDENCE_BASE_URL')
param sblBridgeBaseUrl = readEnvironmentVariable('SBL_BRIDGE_BASE_URL')
param contactReservationRegistryBaseUrl = readEnvironmentVariable('CONTACT_RESERVATION_REGISTRY_BASE_URL')
param environment = readEnvironmentVariable('ENVIRONMENT')
param maskinporten_environment = 'test'
Expand Down
4 changes: 4 additions & 0 deletions .azure/modules/containerApp/main.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ param idportenIssuer string
param dialogportenIssuer string
param maskinporten_token_exchange_environment string

@secure()
param sblBridgeBaseUrl string

@secure()
param subscription_id string
@secure()
Expand Down Expand Up @@ -78,6 +81,7 @@ var containerAppEnvVarsdefault = [
{ name: 'GeneralSettings__CorrespondenceBaseUrl', value: correspondenceBaseUrl }
{ name: 'GeneralSettings__ContactReservationRegistryBaseUrl', value: contactReservationRegistryBaseUrl}
{ name: 'GeneralSettings__SlackUrl', secretRef: 'slack-url' }
{ name: 'GeneralSettings__AltinnSblBridgeBaseUrl', value: sblBridgeBaseUrl }
{ name: 'DialogportenSettings__Issuer', value: dialogportenIssuer }
{ name: 'IdportenSettings__Issuer', value: idportenIssuer }
{ name: 'IdportenSettings__ClientId', secretRef: 'idporten-client-id' }
Expand Down
4 changes: 4 additions & 0 deletions .github/actions/deploy-to-environment/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ inputs:
SLACK_URL:
description: "Slack URL"
required: true
SBL_BRIDGE_BASE_URL:
description: "SBL Bridge Base URL"
required: true
MASKINPORTEN_TOKEN_EXCHANGE_ENVIRONMENT:
description: "Maskinporten Token Exchange Environment"
required: false
Expand Down Expand Up @@ -126,4 +129,5 @@ runs:
IDPORTEN_ISSUER: ${{ inputs.IDPORTEN_ISSUER }}
PLATFORM_BASE_URL: ${{ inputs.PLATFORM_BASE_URL }}
STORAGE_ACCOUNT_NAME: ${{ inputs.AZURE_STORAGE_ACCOUNT_NAME }}
SBL_BRIDGE_BASE_URL: ${{ inputs.SBL_BRIDGE_BASE_URL }}
MASKINPORTEN_TOKEN_EXCHANGE_ENVIRONMENT: ${{ inputs.MASKINPORTEN_TOKEN_EXCHANGE_ENVIRONMENT }}
4 changes: 4 additions & 0 deletions .github/actions/release-version/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ inputs:
IDPORTEN_ISSUER:
description: "Issuer for Maskinporten integration used for IDPorten auth"
required: true
SBL_BRIDGE_BASE_URL:
description: "Base url for SBL Bridge"
required: true
MASKINPORTEN_TOKEN_EXCHANGE_ENVIRONMENT:
description: "Environment for Maskinporten token exchange"
required: false
Expand Down Expand Up @@ -77,6 +80,7 @@ runs:
CONTACT_RESERVATION_REGISTRY_BASE_URL: ${{ inputs.CONTACT_RESERVATION_REGISTRY_BASE_URL }}
DIALOGPORTEN_ISSUER: ${{ inputs.DIALOGPORTEN_ISSUER }}
IDPORTEN_ISSUER: ${{ inputs.IDPORTEN_ISSUER }}
SBL_BRIDGE_BASE_URL: ${{ inputs.SBL_BRIDGE_BASE_URL }}
MASKINPORTEN_TOKEN_EXCHANGE_ENVIRONMENT: ${{ inputs.MASKINPORTEN_TOKEN_EXCHANGE_ENVIRONMENT }}
with:
scope: subscription
Expand Down
43 changes: 43 additions & 0 deletions .github/workflows/ci-cd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,48 @@ jobs:
PLATFORM_BASE_URL: ${{ secrets.PLATFORM_BASE_URL }}
PLATFORM_SUBSCRIPTION_KEY: ${{ secrets.PLATFORM_SUBSCRIPTION_KEY }}
SLACK_URL: ${{ secrets.SLACK_URL }}
SBL_BRIDGE_BASE_URL: ${{ secrets.SBL_BRIDGE_BASE_URL }}

deploy-at22:
name: deploy at22
runs-on: ubuntu-latest
environment: test
if: always() && !failure() && !cancelled()
needs: [get-version, publish, test]
permissions:
id-token: write
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Deploy to environment
uses: ./.github/actions/deploy-to-environment
with:
environment: at22
imageTag: ${{ needs.get-version.outputs.imageTag }}
ACCESS_MANAGEMENT_SUBSCRIPTION_KEY: ${{ secrets.ACCESS_MANAGEMENT_SUBSCRIPTION_KEY }}
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
AZURE_ENVIRONMENT_KEY_VAULT_NAME: ${{ secrets.AZURE_ENVIRONMENT_KEY_VAULT_NAME }}
AZURE_NAME_PREFIX: ${{ secrets.AZURE_NAME_PREFIX }}
AZURE_STORAGE_ACCOUNT_NAME: ${{ secrets.AZURE_STORAGE_ACCOUNT_NAME }}
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
AZURE_TEST_ACCESS_CLIENT_ID: ${{ secrets.AZURE_TEST_ACCESS_CLIENT_ID }}
CORRESPONDENCE_BASE_URL: ${{ secrets.CORRESPONDENCE_BASE_URL }}
DIALOGPORTEN_ISSUER: ${{ secrets.DIALOGPORTEN_ISSUER }}
IDPORTEN_CLIENT_ID: ${{ secrets.IDPORTEN_CLIENT_ID }}
IDPORTEN_CLIENT_SECRET: ${{ secrets.IDPORTEN_CLIENT_SECRET }}
IDPORTEN_ISSUER: ${{ secrets.IDPORTEN_ISSUER }}
MASKINPORTEN_CLIENT_ID: ${{ secrets.MASKINPORTEN_CLIENT_ID }}
MASKINPORTEN_JWK: ${{ secrets.MASKINPORTEN_JWK }}
PLATFORM_BASE_URL: ${{ secrets.PLATFORM_BASE_URL }}
PLATFORM_SUBSCRIPTION_KEY: ${{ secrets.PLATFORM_SUBSCRIPTION_KEY }}
SLACK_URL: ${{ secrets.SLACK_URL }}
SBL_BRIDGE_BASE_URL: ${{ secrets.SBL_BRIDGE_BASE_URL }}
MASKINPORTEN_TOKEN_EXCHANGE_ENVIRONMENT: ${{ secrets.MASKINPORTEN_TOKEN_EXCHANGE_ENVIRONMENT }}
CONTACT_RESERVATION_REGISTRY_BASE_URL: ${{ secrets.CONTACT_RESERVATION_REGISTRY_BASE_URL }}

deploy-staging:
name: Internal staging
Expand Down Expand Up @@ -121,6 +162,7 @@ jobs:
PLATFORM_BASE_URL: ${{ secrets.PLATFORM_BASE_URL }}
PLATFORM_SUBSCRIPTION_KEY: ${{ secrets.PLATFORM_SUBSCRIPTION_KEY }}
SLACK_URL: ${{ secrets.SLACK_URL }}
SBL_BRIDGE_BASE_URL: ${{ secrets.SBL_BRIDGE_BASE_URL }}
MASKINPORTEN_TOKEN_EXCHANGE_ENVIRONMENT: ${{ secrets.MASKINPORTEN_TOKEN_EXCHANGE_ENVIRONMENT }}

deploy-production:
Expand Down Expand Up @@ -164,6 +206,7 @@ jobs:
PLATFORM_BASE_URL: ${{ secrets.PLATFORM_BASE_URL }}
PLATFORM_SUBSCRIPTION_KEY: ${{ secrets.PLATFORM_SUBSCRIPTION_KEY }}
SLACK_URL: ${{ secrets.SLACK_URL }}
SBL_BRIDGE_BASE_URL: ${{ secrets.SBL_BRIDGE_BASE_URL }}
MASKINPORTEN_TOKEN_EXCHANGE_ENVIRONMENT: ${{ secrets.MASKINPORTEN_TOKEN_EXCHANGE_ENVIRONMENT }}

release-to-git:
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/deploy-to-environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,5 @@ jobs:
PLATFORM_BASE_URL: ${{ secrets.PLATFORM_BASE_URL }}
PLATFORM_SUBSCRIPTION_KEY: ${{ secrets.PLATFORM_SUBSCRIPTION_KEY }}
SLACK_URL: ${{ secrets.SLACK_URL }}
MASKINPORTEN_TOKEN_EXCHANGE_ENVIRONMENT: ${{ secrets.MASKINPORTEN_TOKEN_EXCHANGE_ENVIRONMENT }}
SBL_BRIDGE_BASE_URL: ${{ secrets.SBL_BRIDGE_BASE_URL }}
MASKINPORTEN_TOKEN_EXCHANGE_ENVIRONMENT: ${{ secrets.MASKINPORTEN_TOKEN_EXCHANGE_ENVIRONMENT }}
1 change: 1 addition & 0 deletions .github/workflows/publish-branch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,6 @@ jobs:
STORAGE_ACCOUNT_NAME: ${{ secrets.AZURE_STORAGE_ACCOUNT_NAME }}
DIALOGPORTEN_ISSUER: ${{ secrets.DIALOGPORTEN_ISSUER }}
IDPORTEN_ISSUER: ${{ secrets.IDPORTEN_ISSUER }}
SBL_BRIDGE_BASE_URL: ${{ secrets.SBL_BRIDGE_BASE_URL }}
MASKINPORTEN_TOKEN_EXCHANGE_ENVIRONMENT: ${{ secrets.MASKINPORTEN_TOKEN_EXCHANGE_ENVIRONMENT }}

1 change: 1 addition & 0 deletions src/Altinn.Correspondence.API/appsettings.Development.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"GeneralSettings": {
"SlackUrl": "",
"CorrespondenceBaseUrl": "https://localhost:7241/",
"AltinnSblBridgeBaseUrl": "",
"ContactReservationRegistryBaseUrl": "https://test.kontaktregisteret.no"
}
}
2 changes: 1 addition & 1 deletion src/Altinn.Correspondence.API/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@
}
},
"AllowedHosts": "*"
}
}
2 changes: 2 additions & 0 deletions src/Altinn.Correspondence.Application/DependencyInjection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Altinn.Correspondence.Application.InitializeAttachment;
using Altinn.Correspondence.Application.InitializeCorrespondence;
using Altinn.Correspondence.Application.InitializeCorrespondences;
using Altinn.Correspondence.Application.ProcessLegacyParty;
using Altinn.Correspondence.Application.PublishCorrespondence;
using Altinn.Correspondence.Application.PurgeAttachment;
using Altinn.Correspondence.Application.PurgeCorrespondence;
Expand Down Expand Up @@ -47,6 +48,7 @@ public static void AddApplicationHandlers(this IServiceCollection services)
// Integrations
services.AddScoped<MalwareScanResultHandler>();
services.AddScoped<CheckNotificationHandler>();
services.AddScoped<ProcessLegacyPartyHandler>();

// Helpers
services.AddScoped<AttachmentHelper>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using Altinn.Correspondence.Core.Repositories;
using Altinn.Correspondence.Core.Services;
using Microsoft.Extensions.Logging;
using OneOf;
using System.Security.Claims;

namespace Altinn.Correspondence.Application.ProcessLegacyParty;

public class ProcessLegacyPartyHandler(
ILogger<ProcessLegacyPartyHandler> logger,
IAltinnRegisterService altinnRegisterService,
IAltinnSblBridgeService sblBridgeService,
ILegacyPartyRepository legacyPartyRepository)
{
public async Task Process(string recipient, ClaimsPrincipal? user, CancellationToken cancellationToken)
{
logger.LogInformation("Process legacy party {recipient}", recipient);
var partyId = await altinnRegisterService.LookUpPartyId(recipient, cancellationToken);
if (partyId is null)
{
throw new Exception("Failed to look up party in Altinn Register");
}
var exists = await legacyPartyRepository.PartyAlreadyExists((int)partyId, cancellationToken);
if (!exists)
{
var success = await sblBridgeService.AddPartyToSblBridge((int)partyId, cancellationToken);
if (!success)
{
throw new Exception("Failed to send party to SBL");
}
logger.Log(LogLevel.Information, "Party {partyId} added to SBL", partyId);
await legacyPartyRepository.AddLegacyPartyId((int)partyId, cancellationToken);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Altinn.Correspondence.Application.CancelNotification;
using Altinn.Correspondence.Application.Helpers;
using Altinn.Correspondence.Application.ProcessLegacyParty;
using Altinn.Correspondence.Common.Helpers;
using Altinn.Correspondence.Core.Models.Entities;
using Altinn.Correspondence.Core.Models.Enums;
Expand Down Expand Up @@ -93,6 +94,7 @@ public async Task<OneOf<Task, Error>> Process(Guid correspondenceId, ClaimsPrinc
PartyUuid = partyUuid
};
await correspondenceRepository.UpdatePublished(correspondenceId, status.StatusChanged, cancellationToken);
backgroundJobClient.Enqueue<ProcessLegacyPartyHandler>((handler) => handler.Process(correspondence.Recipient, null, cancellationToken));
backgroundJobClient.Enqueue<IDialogportenService>((dialogportenService) => dialogportenService.CreateInformationActivity(correspondenceId, DialogportenActorType.ServiceOwner, DialogportenTextType.CorrespondencePublished));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Altinn.Correspondence.Core.Models.Enums;

namespace Altinn.Correspondence.Core.Models.Entities
{
public class LegacyPartyEntity
{
[Key]
public Guid Id { get; set; }
public int PartyId { get; set; }
}
}
2 changes: 2 additions & 0 deletions src/Altinn.Correspondence.Core/Options/GeneralSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ public class GeneralSettings
public string SlackUrl { get; set; } = string.Empty;

public string CorrespondenceBaseUrl { get; set; } = string.Empty;

public string AltinnSblBridgeBaseUrl { get; set; } = string.Empty;
public string RedisConnectionString { get; set; } = string.Empty;
public string ContactReservationRegistryBaseUrl { get; set; } = string.Empty;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Altinn.Correspondence.Core.Models.Entities;

namespace Altinn.Correspondence.Core.Repositories
{
public interface ILegacyPartyRepository
{
Task AddLegacyPartyId(int id, CancellationToken cancellationToken);
Task<bool> PartyAlreadyExists(int id, CancellationToken cancellationToken);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
namespace Altinn.Correspondence.Core.Services;
public interface IAltinnRegisterService
{
Task<string?> LookUpPartyId(string identificationId, CancellationToken cancellationToken);
Task<int?> LookUpPartyId(string identificationId, CancellationToken cancellationToken);
Task<string?> LookUpName(string identificationId, CancellationToken cancellationToken);
Task<Party?> LookUpPartyByPartyId(int partyId, CancellationToken cancellationToken);
Task<Party?> LookUpPartyById(string identificationId, CancellationToken cancellationToken);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using Altinn.Correspondence.Core.Models.Entities;

namespace Altinn.Correspondence.Core.Services;
public interface IAltinnSblBridgeService
{
Task<bool> AddPartyToSblBridge(int partyId, CancellationToken cancellationToken);

}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public async Task Publish(AltinnEventType type, string resourceId, string itemId
string? partyId = null;
if (recipientId != null)
{
partyId = await _altinnRegisterService.LookUpPartyId(recipientId, cancellationToken);
partyId = (await _altinnRegisterService.LookUpPartyId(recipientId, cancellationToken))?.ToString();
}

var cloudEvent = CreateCloudEvent(type, resourceId, itemId, partyId, recipientId, eventSource);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ public class AltinnRegisterDevService : IAltinnRegisterService
private const string _identificationIDPattern = @"^(?:\d{11}|\d{9}|0192:\d{9})$";
private static readonly Regex IdentificationIDRegex = new(_identificationIDPattern);
private readonly int _digdirPartyId = 50952483;
public Task<string?> LookUpPartyId(string identificationId, CancellationToken cancellationToken)
public Task<int?> LookUpPartyId(string identificationId, CancellationToken cancellationToken)
{
if (IdentificationIDRegex.IsMatch(identificationId))
{
return Task.FromResult<string?>(_digdirPartyId.ToString());
return Task.FromResult<int?>(_digdirPartyId);
}
return Task.FromResult<string?>(null);
return Task.FromResult<int?>(null);
}
public Task<string?> LookUpName(string identificationId, CancellationToken cancellationToken)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ public AltinnRegisterService(HttpClient httpClient, IOptions<AltinnOptions> alti
_logger = logger;
}

public async Task<string?> LookUpPartyId(string identificationId, CancellationToken cancellationToken = default)
public async Task<int?> LookUpPartyId(string identificationId, CancellationToken cancellationToken = default)
{
var party = await LookUpPartyById(identificationId, cancellationToken);
return party?.PartyId.ToString();
return party?.PartyId;
}

public async Task<string?> LookUpName(string identificationId, CancellationToken cancellationToken = default)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Altinn.Correspondence.Core.Services;

namespace Altinn.Correspondence.Integrations.Altinn.SblBridge;
public class AltinnSblBridgeDevService : IAltinnSblBridgeService
{
private readonly string _baseUrl;
public AltinnSblBridgeDevService(string baseUrl)
{
_baseUrl = baseUrl;
}

public async Task<bool> AddPartyToSblBridge(int partyId, CancellationToken cancellationToken = default)
{
if (partyId <= 0)
{
return false;
}
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System.Net.Http.Headers;
using System.Text;
using Altinn.Correspondence.Core.Services;


namespace Altinn.Correspondence.Integrations.Altinn.SblBridge;
public class AltinnSblBridgeService : IAltinnSblBridgeService
{
private readonly HttpClient _httpClient;

public AltinnSblBridgeService(HttpClient httpClient)
{
_httpClient = httpClient;
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}

public async Task<bool> AddPartyToSblBridge(int partyId, CancellationToken cancellationToken = default)
{
if (partyId <= 0)
{
return false;
}
StringContent content = new StringContent(partyId.ToString(), Encoding.UTF8, "application/json");
using var response = await _httpClient.PostAsync($"authorization/api/partieswithmessages", content, cancellationToken);
if (!response.IsSuccessStatusCode)
{
var statusCode = response.StatusCode;
var errorContent = await response.Content.ReadAsStringAsync(cancellationToken);
throw new Exception($"Error when adding party to SBL Bridge. Statuscode was: ${statusCode}, error was: ${errorContent}");
}
return true;
}
}
Loading

0 comments on commit f57743f

Please sign in to comment.