├── version.txt ├── Test ├── Altinn.Correspondence.Tests │ ├── Data │ │ ├── test.txt │ │ ├── WebHookSubscriptionValidationTest.json │ │ ├── MalwareScanResult_NoThreatFound.json │ │ └── MalwareScanResult_Malicious.json │ ├── Fixtures │ │ ├── CustomWebApplicationTestsCollection.cs │ │ └── TestApplicationDbContext.cs │ ├── Helpers │ │ └── UnitWebApplicationFactory.cs │ ├── TestingUtility │ │ └── LegacyHttpContextAccessor.cs │ └── Factories │ │ └── MalwareScanFactory.cs ├── Altinn.Correspondence.UseCaseTests │ ├── fixtures │ │ └── attachment.txt │ └── helpers │ │ ├── commonUtils.js │ │ └── cryptoUtils.js ├── Altinn.Correspondence.LoadTests │ ├── data │ │ ├── serviceowners-test.csv │ │ └── serviceowners-yt01.csv │ └── common │ │ └── testimports.js └── Altinn.Correspondence.LoadTests.DatabasePopulater │ ├── appsettings.json │ ├── PostgresSettings.cs │ ├── BatchingOptions.cs │ └── populate_test_database.sql ├── .bruno ├── Attachment │ ├── folder.bru │ ├── Overview.bru │ ├── Details.bru │ ├── Download attachment.bru │ ├── Purge.bru │ ├── Upload attachment.bru │ └── Initialize batch attachment.bru ├── Authentication │ ├── folder.bru │ ├── End User System │ │ ├── folder.bru │ │ └── Exchange recipient token for Altinn token.bru │ └── Service Owner │ │ ├── folder.bru │ │ └── Exchange Maskinporten token to Altinn token.bru ├── Correspondence │ ├── folder.bru │ ├── {correspondenceId} │ │ ├── folder.bru │ │ ├── Details.bru │ │ ├── Purge (as end user system).bru │ │ ├── Confirm (as end user system).bru │ │ ├── Read Content (as end user system).bru │ │ ├── Download attachment (as end user system).bru │ │ └── Overview.bru │ └── Search (as end user).bru ├── bruno.json ├── Events Integration │ ├── folder.bru │ ├── Get subscriptions.bru │ ├── Delete subscription.bru │ ├── Create subscription (as serviceowner).bru │ └── Create subscription (as end user system).bru ├── Configure End User System │ ├── folder.bru │ ├── System overview.bru │ ├── Exchange systemprovider token for Altinn token.bru │ └── Create recipient systemuser request.bru ├── test-file.txt └── environments │ ├── example.bru │ └── default.bru ├── start.bat ├── .github ├── CODEOWNERS ├── workflows │ ├── test-use-cases-TT02.yml │ ├── test-use-cases-PROD.yml │ ├── validate-formatting.yml │ └── check-label-for-pr.yml ├── release.yml ├── pull_request_template.md └── actions │ ├── publish-image │ └── action.yml │ └── get-current-version │ └── action.yml ├── flux ├── syncroot │ ├── at22 │ │ └── kustomization.yaml │ ├── at23 │ │ └── kustomization.yaml │ ├── at24 │ │ └── kustomization.yaml │ ├── tt02 │ │ └── kustomization.yaml │ ├── yt01 │ │ └── kustomization.yaml │ ├── base │ │ ├── broker-namespace.yaml │ │ ├── correspondence-namespace.yaml │ │ ├── broker-oci-repository.yaml │ │ ├── kustomization.yaml │ │ ├── correspondence-oci-repository.yaml │ │ ├── broker-flux-kustomize.yaml │ │ └── correspondence-flux-kustomize.yaml │ └── prod │ │ └── kustomization.yaml └── correspondence │ ├── application-identity.yaml │ └── kustomization.yaml ├── src ├── Altinn.Correspondence.Core │ ├── Options │ │ ├── DatabaseOptions.cs │ │ ├── AttachmentStorageOptions.cs │ │ ├── DialogportenSettings.cs │ │ ├── IdPortenSettings.cs │ │ ├── SlackSettings.cs │ │ ├── AltinnOptions.cs │ │ └── GeneralSettings.cs │ ├── Models │ │ ├── Enums │ │ │ ├── EmailContentType.cs │ │ │ ├── StorageProviderType.cs │ │ │ ├── ResourceAccessLevel.cs │ │ │ ├── CorrespondenceSyncType.cs │ │ │ ├── NotificationType.cs │ │ │ ├── SyncEventType.cs │ │ │ ├── DialogPortenSystemLabel.cs │ │ │ ├── NotificationTemplate.cs │ │ │ ├── AltinnVersion.cs │ │ │ ├── AttachmentDataLocationType.cs │ │ │ ├── RecipientType.cs │ │ │ ├── CorrespondencesRoleType.cs │ │ │ ├── IdempotencyType.cs │ │ │ ├── NotificationChannel.cs │ │ │ ├── PartyType.cs │ │ │ ├── AttachmentStatus.cs │ │ │ └── CorrespondenceDeleteEventType.cs │ │ ├── Notifications │ │ │ ├── NotificationStatusSummary.cs │ │ │ ├── NotificationResourceLinks.cs │ │ │ ├── SmsTemplate.cs │ │ │ ├── Recipient.cs │ │ │ ├── NotificationStatusDetailsResponse.cs │ │ │ ├── StatusExt.cs │ │ │ ├── EmailTemplate.cs │ │ │ ├── SmsNotificationWithResult.cs │ │ │ ├── EmailNotificationWithResult.cs │ │ │ ├── NotificationStatus.cs │ │ │ ├── SmsNotificationSummary.cs │ │ │ ├── NotificationOrderRequestResponseV2.cs │ │ │ ├── EmailNotificationSummary.cs │ │ │ └── RecipientLookupResult.cs │ │ ├── Entities │ │ │ ├── LegacyPartyEntity.cs │ │ │ ├── PartyWithSubUnits.cs │ │ │ ├── StorageProviderEntity.cs │ │ │ ├── ServiceOwnerEntity.cs │ │ │ ├── CorrespondenceReplyOptionsEntity.cs │ │ │ ├── ExternalReferenceEntity.cs │ │ │ ├── CorrespondenceMigrationStatusEntity.cs │ │ │ ├── AttachmentStatusEntity.cs │ │ │ ├── CorrespondenceAttachmentEntity.cs │ │ │ ├── CorrespondenceContentEntity.cs │ │ │ ├── IdempotencyKeyEntity.cs │ │ │ ├── NotificationTemplateEntity.cs │ │ │ ├── CorrespondenceDeleteEventEntity.cs │ │ │ └── CorrespondenceStatusEntity.cs │ │ └── Register │ │ │ ├── MainUnits.cs │ │ │ └── Roles.cs │ ├── Services │ │ ├── Enums │ │ │ ├── DialogportenActorType.cs │ │ │ ├── DialogportenTextType.cs │ │ │ └── AltinnEventType.cs │ │ ├── IContactReservationRegistryService.cs │ │ ├── IEventBus.cs │ │ ├── IAltinnStorageService.cs │ │ ├── IResourceManager.cs │ │ └── IAltinnRegisterService.cs │ ├── Repositories │ │ ├── ILegacyPartyRepository.cs │ │ ├── IAttachmentStatusRepository.cs │ │ ├── IAltinnAccessManangementService.cs │ │ ├── IMigrationRepostitory.cs │ │ ├── ICorrespondenceForwardingEventRepository.cs │ │ ├── INotificationTemplateRepository.cs │ │ ├── ICorrespondenceStatusRepository.cs │ │ ├── ICorrespondenceDeleteEventRepository.cs │ │ ├── IServiceOwnerRepository.cs │ │ ├── IAltinnNotificationService.cs │ │ └── IStorageRepository.cs │ └── Exceptions.cs ├── Altinn.Correspondence.API │ ├── Auth │ │ └── MaskinportenSecurityTokenException.cs │ ├── appsettings.json │ ├── Models │ │ ├── Enums │ │ │ ├── TransmissionTypeExt.cs │ │ │ ├── NotificationTemplateExt.cs │ │ │ ├── AttachmentDataLocationTypeExt.cs │ │ │ ├── InitializeAttachmentDataLocationTypeExt.cs │ │ │ ├── NotificationChannelExt.cs │ │ │ └── AttachmentStatus.cs │ │ ├── CorrespondencesExt.cs │ │ ├── LegacyCorrespondenceAttachmentExt.cs │ │ ├── CorrespondenceContentExt.cs │ │ ├── AttachmentDetailsExt.cs │ │ ├── PaginationExt.cs │ │ ├── LegacyUserExt.cs │ │ ├── AttachmentMigrationStatusExt.cs │ │ ├── SyncCorrespondenceStatusEventRequestExt.cs │ │ ├── NotificationDetailsSummary.cs │ │ ├── SyncCorrespondenceForwardingEventRequestExt.cs │ │ ├── SyncCorrespondenceNotificationRequestExt.cs │ │ ├── InitializeCorrespondencesResponseExt.cs │ │ ├── ExternalReferenceExt.cs │ │ ├── MakeCorrespondenceAvailableResponseExt.cs │ │ ├── PaginationMetaDataExt.cs │ │ ├── NotificationProcessStatusExt.cs │ │ ├── CorrespondenceReplyOptionExt.cs │ │ ├── MigrateInitializeAttachmentExt.cs │ │ ├── NotificationStatusExt.cs │ │ ├── MakeCorrespondenceAvailableRequestExt.cs │ │ ├── InitializeCorrespondenceAttachmentExt.cs │ │ ├── AtachmentStatusEvent.cs │ │ ├── CorrespondenceStatusEventExt.cs │ │ ├── NotificationDetailsExt.cs │ │ └── LegacyNotificationExt.cs │ ├── Helpers │ │ ├── SecurityHeadersMiddleware.cs │ │ └── TokenHelper.cs │ ├── Filters │ │ └── SetMigrationJobOriginAttribute.cs │ ├── ValidationAttributes │ │ ├── OptionalEnumAttribute.cs │ │ ├── RequiredEnumAttribute.cs │ │ ├── ResourceIdentifierAttribute.cs │ │ ├── OrganizationNumberAttribute.cs │ │ ├── MD5ChecksumAttribute.cs │ │ └── OrganizationNumberOptionalAttribute.cs │ └── Mappers │ │ ├── AttachmentStatusMapper.cs │ │ └── LegacyGetCorrespondencesMapper.cs ├── Altinn.Correspondence.Integrations │ ├── Dialogporten │ │ ├── Enums │ │ │ └── DialogportenLanguageCode.cs │ │ ├── Helpers │ │ │ └── TransmissionValidator.cs │ │ └── Models │ │ │ └── SetDialogSystemLabelRequest.cs │ ├── Altinn │ │ ├── Storage │ │ │ ├── SblBridgeParty.cs │ │ │ └── AltinnStorageDevService.cs │ │ ├── Events │ │ │ ├── Helpers │ │ │ │ └── LowerCaseNamingPolicy.cs │ │ │ ├── CloudEvent.cs │ │ │ └── ConsoleLogEventBus.cs │ │ ├── ContactReservationRegistry │ │ │ ├── ContactReservationPersonRequest.cs │ │ │ └── ContactReservationRegistryDevService.cs │ │ └── AccessManagement │ │ │ └── AltinnAccessManagementDevService.cs │ ├── Hangfire │ │ ├── HangfireQueues.cs │ │ ├── HangfireConnectionFactory.cs │ │ ├── BackgroundJobContext.cs │ │ ├── BackgroundJobServerFilter.cs │ │ └── BackgroundJobClientFilter.cs │ ├── Azure │ │ ├── AzureResourceManagerOptions.cs │ │ └── MalwareScanConfiguration.cs │ └── Slack │ │ └── SlackNotificationService.cs ├── Altinn.Correspondence.Application │ ├── DownloadAttachment │ │ └── DownloadAttachmentRequest.cs │ ├── CheckNotification │ │ └── CheckNotificationResponse.cs │ ├── PurgeCorrespondence │ │ └── PurgeCorrespondenceRequest.cs │ ├── ConfirmCorrespondence │ │ └── ConfirmCorrespondenceRequest.cs │ ├── GetCorrespondenceDetails │ │ └── GetCorrespondenceDetailsRequest.cs │ ├── MarkCorrespondenceAsRead │ │ └── MarkCorrespondenceAsReadRequest.cs │ ├── GetCorespondences │ │ ├── GetCorrespondencesResponse.cs │ │ ├── GetCorrespondencesRequest.cs │ │ └── LegacyGetCorrespondencesRequest.cs │ ├── CleanupOrphanedDialogs │ │ ├── CleanupOrphanedDialogsResponse.cs │ │ └── CleanupOrphanedDialogsRequest.cs │ ├── CleanupPerishingDialogs │ │ ├── CleanupPerishingDialogsResponse.cs │ │ └── CleanupPerishingDialogsRequest.cs │ ├── RestoreSoftDeletedDialogs │ │ ├── RestoreSoftDeletedDialogsResponse.cs │ │ └── RestoreSoftDeletedDialogsRequest.cs │ ├── InitializeAttachment │ │ └── InitializeAttachmentRequest.cs │ ├── CleanupMarkdownAndHTMLInSummary │ │ ├── CleanupMarkdownAndHTMLInSummaryResponse.cs │ │ └── CleanupMarkdownAndHTMLInSummaryRequest.cs │ ├── MalwareScanResult │ │ ├── MalwareScanResultRequest.cs │ │ └── Models │ │ │ └── ScanResultData.cs │ ├── GetCorrespondenceOverview │ │ └── GetCorrespondenceOverviewRequest.cs │ ├── InitializeServiceOwner │ │ └── InitializeServiceOwnerRequest.cs │ ├── CleanupConfirmedMigratedCorrespondences │ │ ├── CleanupConfirmedMigratedCorrespondencesResponse.cs │ │ └── CleanupConfirmedMigratedCorrespondencesRequest.cs │ ├── DownloadCorrespondenceAttachment │ │ ├── DownloadCorrespondenceAttachmentRequest.cs │ │ └── DownloadCorrespondenceAttachmentResponse.cs │ ├── MigrateCorrespondence │ │ ├── AttachmentMigrationStatus.cs │ │ ├── MigrateCorrespondenceResponse.cs │ │ ├── MigrateCorrespondenceRequest.cs │ │ └── MakeCorrespondenceAvailableRequest.cs │ ├── AttachmentStatusText.cs │ ├── PublishCorrespondence │ │ └── PublishCorrespondenceRequest.cs │ ├── UploadAttachment │ │ └── UploadAttachmentRequest.cs │ ├── LegacyUpdateCorrespondenceStatus │ │ └── LegacyUpdateCorrespondenceStatusRequest.cs │ ├── SyncCorrespondenceEvent │ │ ├── SyncCorrespondenceForwardingEventRequest.cs │ │ ├── SyncCorrespondenceNotificationEventRequest.cs │ │ ├── SyncCorrespondenceStatusEventRequest.cs │ │ └── SyncEventDateTimeOffsetExtensions.cs │ ├── CreateNotificationOrder │ │ └── CreateNotificationOrderRequest.cs │ ├── IHandler.cs │ ├── CleanupBruksmonster │ │ └── CleanupBruksmonsterResponse.cs │ ├── MigrateCorrespondenceAttachment │ │ └── MigrateAttachmentRequest.cs │ ├── GenerateReport │ │ └── GenerateDailySummaryReportRequest.cs │ ├── InitializeCorrespondences │ │ └── InitializeCorrespondencesRequest.cs │ └── Helpers │ │ └── AttachmentExtensions.cs ├── Altinn.Correspondence.Common │ ├── Helpers │ │ └── Models │ │ │ ├── TokenConsumer.cs │ │ │ └── SystemUserAuthorizationDetails.cs │ └── Altinn.Correspondence.Common.csproj └── Altinn.Correspondence.Persistence │ ├── Helpers │ └── DateTimeOffsetConverter.cs │ ├── Repositories │ ├── AttachmentStatusRepository.cs │ ├── NotificationTemplateRepository.cs │ ├── LegacyPartyRepository.cs │ └── CorrespondenceForwardingEventRepository.cs │ └── Migrations │ ├── 20250619102557_AddLegacyIndices.cs │ ├── 20241014091946_AddedPublish.cs │ ├── 20251203120630_IdempotencyBasedOnEndUser.cs │ ├── 20250514112454_AddIsMigratingIndexdotnet.cs │ ├── 20250117073451_RemoveDataType.cs │ ├── 20241008095547_MigIgnoreReservation.cs │ ├── 20250214073107_Add attachmentSize.cs │ ├── 20251118141328_AddResourceIdIndexForAttachments.cs │ ├── 20250221124036_Notification_Order_Request.cs │ ├── 20241010081929_MigrationRequestPublishTime.cs │ ├── 20241029141456_Remove_RestrictionNameFromAttachmentEntity.cs │ ├── 20241111104028_Remove_MarkedUnreadFromCorrespondenceEntity.cs │ ├── 20250527111747_AddIsConfidentialFlag.cs │ ├── 20250507210753_CorrespondenceNotification_ShipmentId.cs │ ├── 20250227132312_Add_Attachment_DisplayName.cs │ ├── 20251027140102_AddExpirationTimeToAttachments.cs │ ├── 20240926100009_Notification_isReminder.cs │ ├── 20241024103535_AddedIsConfirmationNeeded.cs │ ├── 20250220130047_AddIsMigratingFlagToCorrespondences.cs │ ├── 20250220090415_Remove_Attachment_Logical_Name.cs │ ├── 20250526122446_AddRequestInitializeCorrespondence.cs │ └── 20241127134045_Add_PartyUuidToAttachmentStatusEntity.cs ├── .azure ├── modules │ ├── keyvault │ │ ├── upsertSecret.bicep │ │ ├── upsertSecrets.bicep │ │ ├── create.bicep │ │ ├── addSecretsOfficerRole.bicep │ │ └── addReaderRoles.bicep │ ├── identity │ │ ├── addContributorAccess.bicep │ │ └── addStorageBlobDataContributorRole.bicep │ ├── subscription │ │ └── addMonitoringReaderRole.bicep │ ├── postgreSql │ │ └── addAdminAccess.bicep │ └── containerApp │ │ └── fetchEventGridIps.bicep └── applications │ └── migration │ └── params.bicepparam ├── .dockerignore └── renovate.json /version.txt: -------------------------------------------------------------------------------- 1 | 1.3.0 2 | -------------------------------------------------------------------------------- /Test/Altinn.Correspondence.Tests/Data/test.txt: -------------------------------------------------------------------------------- 1 | test -------------------------------------------------------------------------------- /.bruno/Attachment/folder.bru: -------------------------------------------------------------------------------- 1 | meta { 2 | name: Attachment 3 | seq: 4 4 | } 5 | -------------------------------------------------------------------------------- /start.bat: -------------------------------------------------------------------------------- 1 | docker compose up -d 2 | dotnet watch --project ./src/Altinn.Correspondence.API -------------------------------------------------------------------------------- /.bruno/Authentication/folder.bru: -------------------------------------------------------------------------------- 1 | meta { 2 | name: Authentication 3 | seq: 3 4 | } 5 | -------------------------------------------------------------------------------- /.bruno/Correspondence/folder.bru: -------------------------------------------------------------------------------- 1 | meta { 2 | name: Correspondence 3 | seq: 5 4 | } 5 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | /.github/CODEOWNERS @altinn/team-messaging 2 | -------------------------------------------------------------------------------- /Test/Altinn.Correspondence.UseCaseTests/fixtures/attachment.txt: -------------------------------------------------------------------------------- 1 | This is a small test attachment. -------------------------------------------------------------------------------- /.bruno/Correspondence/{correspondenceId}/folder.bru: -------------------------------------------------------------------------------- 1 | meta { 2 | name: {correspondenceId} 3 | seq: 2 4 | } 5 | -------------------------------------------------------------------------------- /.bruno/bruno.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1", 3 | "name": "Altinn.Correspondence", 4 | "type": "collection" 5 | } 6 | 7 | -------------------------------------------------------------------------------- /.bruno/Events Integration/folder.bru: -------------------------------------------------------------------------------- 1 | meta { 2 | name: Events Integration 3 | seq: 6 4 | } 5 | 6 | auth { 7 | mode: inherit 8 | } 9 | -------------------------------------------------------------------------------- /flux/syncroot/at22/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - ../base 5 | -------------------------------------------------------------------------------- /flux/syncroot/at23/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - ../base 5 | -------------------------------------------------------------------------------- /flux/syncroot/at24/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - ../base 5 | -------------------------------------------------------------------------------- /flux/syncroot/tt02/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - ../base 5 | -------------------------------------------------------------------------------- /flux/syncroot/yt01/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - ../base 5 | -------------------------------------------------------------------------------- /.bruno/Authentication/End User System/folder.bru: -------------------------------------------------------------------------------- 1 | meta { 2 | name: End User System 3 | seq: 4 4 | } 5 | 6 | auth { 7 | mode: inherit 8 | } 9 | -------------------------------------------------------------------------------- /.bruno/Authentication/Service Owner/folder.bru: -------------------------------------------------------------------------------- 1 | meta { 2 | name: Service Owner 3 | seq: 1 4 | } 5 | 6 | auth { 7 | mode: inherit 8 | } 9 | -------------------------------------------------------------------------------- /.bruno/Configure End User System/folder.bru: -------------------------------------------------------------------------------- 1 | meta { 2 | name: Configure End User System 3 | seq: 7 4 | } 5 | 6 | auth { 7 | mode: inherit 8 | } 9 | -------------------------------------------------------------------------------- /flux/syncroot/base/broker-namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: broker 5 | annotations: 6 | "linkerd.io/inject": "enabled" 7 | -------------------------------------------------------------------------------- /Test/Altinn.Correspondence.LoadTests/data/serviceowners-test.csv: -------------------------------------------------------------------------------- 1 | org,orgno,scopes,resource,ssn 2 | digdir,313154599,altinn:correspondence.write,dagl-correspondence,20827199746 -------------------------------------------------------------------------------- /flux/correspondence/application-identity.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: application.dis.altinn.cloud/v1alpha1 2 | kind: ApplicationIdentity 3 | metadata: 4 | name: app-identity 5 | spec: {} 6 | -------------------------------------------------------------------------------- /flux/correspondence/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - application-identity.yaml 5 | - application.yaml -------------------------------------------------------------------------------- /flux/syncroot/base/correspondence-namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: correspondence 5 | annotations: 6 | "linkerd.io/inject": "enabled" 7 | -------------------------------------------------------------------------------- /Test/Altinn.Correspondence.LoadTests.DatabasePopulater/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "PostgresConnectionString": "Host=localhost:5432;Username=postgres;Password=postgres;Database=correspondence" 3 | } -------------------------------------------------------------------------------- /Test/Altinn.Correspondence.LoadTests/data/serviceowners-yt01.csv: -------------------------------------------------------------------------------- 1 | org,orgno,scopes,resource,ssn 2 | digdir,713431400,altinn:correspondence.write altinn:correspondence.read,ttd-dialogporten-performance-test-10,27080618048 -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Options/DatabaseOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Core.Options; 2 | 3 | public class DatabaseOptions 4 | { 5 | public required string ConnectionString { get; set; } 6 | } 7 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.API/Auth/MaskinportenSecurityTokenException.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.API.Auth 2 | { 3 | public sealed class MaskinportenSecurityTokenException : Exception 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Models/Enums/EmailContentType.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Core.Models.Enums; 2 | 3 | public enum EmailContentType 4 | { 5 | Plain = 0, 6 | 7 | Html = 1, 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Models/Enums/StorageProviderType.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Core.Models.Enums; 2 | 3 | public enum StorageProviderType 4 | { 5 | Altinn3Azure, 6 | Altinn3AzureWithoutVirusScan 7 | } 8 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Models/Enums/ResourceAccessLevel.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Core.Models.Enums 2 | { 3 | public enum ResourceAccessLevel 4 | { 5 | Write = 0, 6 | Read = 1, 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Options/AttachmentStorageOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Core.Options; 2 | 3 | public class AttachmentStorageOptions 4 | { 5 | public required string ConnectionString { get; set; } 6 | } 7 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.API/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning", 6 | "Microsoft.EntityFrameworkCore": "Warning" 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Options/DialogportenSettings.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Core.Options 2 | { 3 | public class DialogportenSettings 4 | { 5 | public string Issuer { get; set; } = string.Empty; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Integrations/Dialogporten/Enums/DialogportenLanguageCode.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Integrations.Dialogporten.Enums; 2 | 3 | public enum DialogportenLanguageCode 4 | { 5 | NB, 6 | NN, 7 | EN 8 | } 9 | -------------------------------------------------------------------------------- /Test/Altinn.Correspondence.LoadTests.DatabasePopulater/PostgresSettings.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.LoadTests.DatabasePopulater; 2 | 3 | public class PostgresSettings 4 | { 5 | public string PostgresConnectionString { get; set; } 6 | } 7 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Application/DownloadAttachment/DownloadAttachmentRequest.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Application.DownloadAttachment; 2 | 3 | public class DownloadAttachmentRequest 4 | { 5 | public Guid AttachmentId { get; set; } 6 | } 7 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Application/CheckNotification/CheckNotificationResponse.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Application.CheckNotification; 2 | public class CheckNotificationResponse 3 | { 4 | public required bool SendNotification { get; set; } 5 | } 6 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Application/PurgeCorrespondence/PurgeCorrespondenceRequest.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Application.PurgeCorrespondence; 2 | 3 | public class PurgeCorrespondenceRequest 4 | { 5 | public required Guid CorrespondenceId { get; set; } 6 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Application/ConfirmCorrespondence/ConfirmCorrespondenceRequest.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Application.ConfirmCorrespondence; 2 | 3 | public class ConfirmCorrespondenceRequest 4 | { 5 | public required Guid CorrespondenceId { get; set; } 6 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Application/GetCorrespondenceDetails/GetCorrespondenceDetailsRequest.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Application.GetCorrespondenceDetails; 2 | 3 | public class GetCorrespondenceDetailsRequest 4 | { 5 | public required Guid CorrespondenceId { get; set; } 6 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Application/MarkCorrespondenceAsRead/MarkCorrespondenceAsReadRequest.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Application.MarkCorrespondenceAsRead; 2 | 3 | public class MarkCorrespondenceAsReadRequest 4 | { 5 | public required Guid CorrespondenceId { get; set; } 6 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Application/GetCorespondences/GetCorrespondencesResponse.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Application.GetCorrespondences 2 | { 3 | public class GetCorrespondencesResponse 4 | { 5 | public List Ids { get; set; } = new List(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Models/Enums/CorrespondenceSyncType.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Core.Models.Enums 2 | { 3 | public enum CorrespondenceSyncType 4 | { 5 | StatusEvents = 0, 6 | NotificationEvents = 1, 7 | ForwardingEvents = 2 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Services/Enums/DialogportenActorType.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Core.Services.Enums 2 | { 3 | public enum DialogportenActorType 4 | { 5 | ServiceOwner, 6 | Sender, 7 | Recipient, 8 | PartyRepresentative 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Application/CleanupOrphanedDialogs/CleanupOrphanedDialogsResponse.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Application.CleanupOrphanedDialogs; 2 | 3 | public class CleanupOrphanedDialogsResponse 4 | { 5 | public string? JobId { get; set; } 6 | public string? Message { get; set; } 7 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Models/Enums/NotificationType.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Core.Models.Enums; 2 | 3 | /// 4 | /// Types of notifications supported by the Altinn Notification Service 5 | /// 6 | public enum NotificationType 7 | { 8 | SMS, 9 | Email 10 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Application/CleanupPerishingDialogs/CleanupPerishingDialogsResponse.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Application.CleanupPerishingDialogs; 2 | 3 | public class CleanupPerishingDialogsResponse 4 | { 5 | public string? JobId { get; set; } 6 | public string? Message { get; set; } 7 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Models/Notifications/NotificationStatusSummary.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Core.Models.Notifications; 2 | 3 | public class NotificationsStatusSummary 4 | { 5 | public EmailNotificationStatus? Email { get; set; } 6 | 7 | public SmsNotificationStatus? Sms { get; set; } 8 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Application/RestoreSoftDeletedDialogs/RestoreSoftDeletedDialogsResponse.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Application.RestoreSoftDeletedDialogs; 2 | 3 | public class RestoreSoftDeletedDialogsResponse 4 | { 5 | public string? JobId { get; set; } 6 | public string? Message { get; set; } 7 | } -------------------------------------------------------------------------------- /.bruno/Attachment/Overview.bru: -------------------------------------------------------------------------------- 1 | meta { 2 | name: Overview 3 | type: http 4 | seq: 3 5 | } 6 | 7 | get { 8 | url: {{correspondence_base_url}}/correspondence/api/v1/attachment/{{attachmentId}} 9 | body: none 10 | auth: bearer 11 | } 12 | 13 | auth:bearer { 14 | token: {{serviceowner_altinn_token}} 15 | } 16 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Models/Enums/SyncEventType.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Core.Models.Enums; 2 | 3 | /// 4 | /// This Enum is used when syncing specific events from Altinn 3 to Altinn 2. 5 | /// 6 | public enum SyncEventType 7 | { 8 | Read, 9 | Confirm, 10 | Delete 11 | } -------------------------------------------------------------------------------- /.bruno/Attachment/Details.bru: -------------------------------------------------------------------------------- 1 | meta { 2 | name: Details 3 | type: http 4 | seq: 4 5 | } 6 | 7 | get { 8 | url: {{correspondence_base_url}}/correspondence/api/v1/attachment/{{attachmentId}}/details 9 | body: none 10 | auth: bearer 11 | } 12 | 13 | auth:bearer { 14 | token: {{serviceowner_altinn_token}} 15 | } 16 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Models/Enums/DialogPortenSystemLabel.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Core.Models.Enums 2 | { 3 | public enum DialogPortenSystemLabel 4 | { 5 | Default = 1, 6 | Bin = 2, 7 | Archive = 3, 8 | MarkedAsUnopened = 4, 9 | Sent = 5 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Models/Notifications/NotificationResourceLinks.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Altinn.Correspondence.Core.Models.Notifications; 4 | 5 | public class NotificationResourceLinks 6 | { 7 | [JsonPropertyName("self")] 8 | public string Self { get; set; } = string.Empty; 9 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Models/Notifications/SmsTemplate.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace Altinn.Correspondence.Core.Models.Notifications 3 | { 4 | public class SmsTemplate 5 | { 6 | public string SenderNumber { get; set; } = string.Empty; 7 | 8 | public string Body { get; set; } = string.Empty; 9 | } 10 | } -------------------------------------------------------------------------------- /flux/syncroot/base/broker-oci-repository.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: source.toolkit.fluxcd.io/v1 2 | kind: OCIRepository 3 | metadata: 4 | name: broker-app-sync 5 | namespace: broker 6 | spec: 7 | interval: 5m0s 8 | provider: azure 9 | ref: 10 | tag: main 11 | timeout: 5m0s 12 | url: oci://altinncr.azurecr.io/corr/broker-sync 13 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Application/InitializeAttachment/InitializeAttachmentRequest.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Correspondence.Core.Models.Entities; 2 | 3 | namespace Altinn.Correspondence.Application.InitializeAttachment; 4 | 5 | public class InitializeAttachmentRequest 6 | { 7 | public required AttachmentEntity Attachment { get; set; } 8 | } 9 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Integrations/Altinn/Storage/SblBridgeParty.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Altinn.Correspondence.Integrations.Altinn.Storage 4 | { 5 | public class SblBridgeParty 6 | { 7 | [JsonPropertyName("partyId")] 8 | public int PartyId { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Integrations/Hangfire/HangfireQueues.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Integrations.Hangfire; 2 | 3 | public static class HangfireQueues 4 | { 5 | public const string Default = "default"; 6 | public const string LiveMigration = "live-migration"; 7 | public const string Migration = "migration"; 8 | } 9 | -------------------------------------------------------------------------------- /.github/workflows/test-use-cases-TT02.yml: -------------------------------------------------------------------------------- 1 | name: Test Use Cases - TT02 2 | 3 | on: 4 | schedule: 5 | - cron: '*/15 * * * *' 6 | 7 | jobs: 8 | test-staging: 9 | name: Run use case tests on staging 10 | uses: ./.github/workflows/test-use-cases.yml 11 | with: 12 | environment: use-case-staging 13 | secrets: inherit -------------------------------------------------------------------------------- /Test/Altinn.Correspondence.UseCaseTests/helpers/commonUtils.js: -------------------------------------------------------------------------------- 1 | export function toUrn(id) { 2 | if (!id) return ''; 3 | if (id.includes(':')) return id; 4 | if (/^\d{11}$/.test(id)) return `urn:altinn:person:identifier-no:${id}`; 5 | if (/^\d{9}$/.test(id)) return `urn:altinn:organization:identifier-no:${id}`; 6 | return id; 7 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Application/CleanupMarkdownAndHTMLInSummary/CleanupMarkdownAndHTMLInSummaryResponse.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Application.CleanupMarkdownAndHTMLInSummary; 2 | 3 | public class CleanupMarkdownAndHTMLInSummaryResponse 4 | { 5 | public string? JobId { get; set; } 6 | public string? Message { get; set; } 7 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Application/MalwareScanResult/MalwareScanResultRequest.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Correspondence.Application.MalwareScanResult.Models; 2 | 3 | namespace Altinn.Correspondence.Application.MalwareScanResult; 4 | 5 | public class MalwareScanResultRequest 6 | { 7 | public required ScanResultData ScanResultData { get; set; } 8 | } 9 | -------------------------------------------------------------------------------- /.github/workflows/test-use-cases-PROD.yml: -------------------------------------------------------------------------------- 1 | name: Test Use Cases - PROD 2 | 3 | on: 4 | schedule: 5 | - cron: '*/15 * * * *' 6 | 7 | jobs: 8 | test-prod: 9 | name: Run use case tests on production 10 | uses: ./.github/workflows/test-use-cases.yml 11 | with: 12 | environment: use-case-production 13 | secrets: inherit -------------------------------------------------------------------------------- /flux/syncroot/base/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - correspondence-namespace.yaml 5 | - correspondence-oci-repository.yaml 6 | - correspondence-flux-kustomize.yaml 7 | - broker-namespace.yaml 8 | - broker-oci-repository.yaml 9 | - broker-flux-kustomize.yaml 10 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Application/GetCorrespondenceOverview/GetCorrespondenceOverviewRequest.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Application.GetCorrespondenceOverview; 2 | 3 | public class GetCorrespondenceOverviewRequest 4 | { 5 | public required Guid CorrespondenceId { get; set; } 6 | 7 | public bool OnlyGettingContent { get; set; } 8 | } -------------------------------------------------------------------------------- /.bruno/Attachment/Download attachment.bru: -------------------------------------------------------------------------------- 1 | meta { 2 | name: Download attachment 3 | type: http 4 | seq: 5 5 | } 6 | 7 | get { 8 | url: {{correspondence_base_url}}/correspondence/api/v1/attachment/{{attachmentId}}/download 9 | body: none 10 | auth: bearer 11 | } 12 | 13 | auth:bearer { 14 | token: {{serviceowner_altinn_token}} 15 | } 16 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Models/Entities/LegacyPartyEntity.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Altinn.Correspondence.Core.Models.Entities 4 | { 5 | public class LegacyPartyEntity 6 | { 7 | [Key] 8 | public Guid Id { get; set; } 9 | public int PartyId { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Repositories/ILegacyPartyRepository.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Core.Repositories 2 | { 3 | public interface ILegacyPartyRepository 4 | { 5 | Task AddLegacyPartyId(int id, CancellationToken cancellationToken); 6 | Task PartyAlreadyExists(int id, CancellationToken cancellationToken); 7 | } 8 | } -------------------------------------------------------------------------------- /.bruno/Attachment/Purge.bru: -------------------------------------------------------------------------------- 1 | meta { 2 | name: Purge 3 | type: http 4 | seq: 6 5 | } 6 | 7 | delete { 8 | url: {{correspondence_base_url}}/correspondence/api/v1/attachment/{{attachmentId}} 9 | body: json 10 | auth: bearer 11 | } 12 | 13 | auth:bearer { 14 | token: {{serviceowner_altinn_token}} 15 | } 16 | 17 | body:json { 18 | {} 19 | } 20 | -------------------------------------------------------------------------------- /.bruno/Correspondence/{correspondenceId}/Details.bru: -------------------------------------------------------------------------------- 1 | meta { 2 | name: Details 3 | type: http 4 | seq: 2 5 | } 6 | 7 | get { 8 | url: {{correspondence_base_url}}/correspondence/api/v1/correspondence/{{correspondenceId}}/details 9 | body: none 10 | auth: bearer 11 | } 12 | 13 | auth:bearer { 14 | token: {{serviceowner_altinn_token}} 15 | } 16 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Application/CleanupPerishingDialogs/CleanupPerishingDialogsRequest.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Altinn.Correspondence.Application.CleanupPerishingDialogs; 4 | 5 | public class CleanupPerishingDialogsRequest 6 | { 7 | [Range(100, int.MaxValue)] 8 | public int WindowSize { get; set; } = 10000; 9 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Application/InitializeServiceOwner/InitializeServiceOwnerRequest.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Application.InitializeServiceOwner; 2 | 3 | public class InitializeServiceOwnerRequest 4 | { 5 | public string ServiceOwnerId { get; set; } = string.Empty; 6 | 7 | public string ServiceOwnerName { get; set; } = string.Empty; 8 | } 9 | -------------------------------------------------------------------------------- /flux/syncroot/base/correspondence-oci-repository.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: source.toolkit.fluxcd.io/v1 2 | kind: OCIRepository 3 | metadata: 4 | name: correspondence-sync 5 | namespace: correspondence 6 | spec: 7 | interval: 5m0s 8 | provider: azure 9 | ref: 10 | tag: main 11 | timeout: 5m0s 12 | url: oci://altinncr.azurecr.io/corr/correspondence-sync 13 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Application/RestoreSoftDeletedDialogs/RestoreSoftDeletedDialogsRequest.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Altinn.Correspondence.Application.RestoreSoftDeletedDialogs; 4 | 5 | public class RestoreSoftDeletedDialogsRequest 6 | { 7 | [Range(100, int.MaxValue)] 8 | public int WindowSize { get; set; } = 1000; 9 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Application/CleanupConfirmedMigratedCorrespondences/CleanupConfirmedMigratedCorrespondencesResponse.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Application.CleanupConfirmedMigratedCorrespondences; 2 | 3 | public class CleanupConfirmedMigratedCorrespondencesResponse 4 | { 5 | public string? JobId { get; set; } 6 | public string? Message { get; set; } 7 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Repositories/IAttachmentStatusRepository.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Correspondence.Core.Models.Entities; 2 | 3 | namespace Altinn.Correspondence.Core.Repositories 4 | { 5 | public interface IAttachmentStatusRepository 6 | { 7 | Task AddAttachmentStatus(AttachmentStatusEntity attachment, CancellationToken cancellationToken); 8 | } 9 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Services/IContactReservationRegistryService.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Core.Services 2 | { 3 | public interface IContactReservationRegistryService 4 | { 5 | public Task IsPersonReserved(string ssn); 6 | 7 | public Task> GetReservedRecipients(List recipients); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Services/IEventBus.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Correspondence.Core.Services.Enums; 2 | 3 | namespace Altinn.Correspondence.Core.Services; 4 | 5 | public interface IEventBus 6 | { 7 | Task Publish(AltinnEventType type, string resourceId, string itemId, string eventSource, string recipientId = null, CancellationToken cancellationToken = default); 8 | } 9 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Application/DownloadCorrespondenceAttachment/DownloadCorrespondenceAttachmentRequest.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Application.DownloadCorrespondenceAttachment; 2 | 3 | public class DownloadCorrespondenceAttachmentRequest 4 | { 5 | public required Guid CorrespondenceId { get; set; } 6 | public required Guid AttachmentId { get; set; } 7 | } 8 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Application/DownloadCorrespondenceAttachment/DownloadCorrespondenceAttachmentResponse.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Application.DownloadCorrespondenceAttachment 2 | { 3 | public class DownloadCorrespondenceAttachmentResponse 4 | { 5 | public Stream Stream { get; set; } 6 | public string FileName { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Options/IdPortenSettings.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Core.Options 2 | { 3 | public class IdportenSettings 4 | { 5 | public string Issuer { get; set; } = string.Empty; 6 | public string ClientId { get; set; } = string.Empty; 7 | public string ClientSecret { get; set; } = string.Empty; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Application/CleanupOrphanedDialogs/CleanupOrphanedDialogsRequest.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Altinn.Correspondence.Application.CleanupOrphanedDialogs; 4 | 5 | public class CleanupOrphanedDialogsRequest 6 | { 7 | [Range(100, int.MaxValue)] 8 | public int WindowSize { get; set; } = 10000; 9 | } 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Application/MigrateCorrespondence/AttachmentMigrationStatus.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Correspondence.Core.Models.Enums; 2 | 3 | namespace Altinn.Correspondence.Application.MigrateCorrespondence; 4 | 5 | public class AttachmentMigrationStatus 6 | { 7 | public Guid AttachmentId {get;set;} 8 | public AttachmentStatus AttachmentStatus {get;set;} 9 | } 10 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Integrations/Altinn/Events/Helpers/LowerCaseNamingPolicy.cs: -------------------------------------------------------------------------------- 1 | 2 | using System.Text.Json; 3 | 4 | namespace Altinn.Correspondence.Integrations.Altinn.Events.Helpers; 5 | internal class LowerCaseNamingPolicy : JsonNamingPolicy 6 | { 7 | public override string ConvertName(string name) 8 | { 9 | return name.ToLower(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Common/Helpers/Models/TokenConsumer.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Altinn.Correspondence.Common.Helpers.Models; 4 | 5 | public class TokenConsumer 6 | { 7 | [JsonPropertyName("authority")] 8 | public string Authority { get; set; } 9 | 10 | [JsonPropertyName("ID")] 11 | public string ID { get; set; } 12 | } 13 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Models/Entities/PartyWithSubUnits.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Core.Models.Entities 2 | { 3 | /// 4 | /// Class representing a party 5 | /// 6 | public class PartyWithSubUnits : Party 7 | { 8 | public List SubUnits { get; set; } = new List(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Repositories/IAltinnAccessManangementService.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Correspondence.Core.Models.Entities; 2 | 3 | namespace Altinn.Correspondence.Core.Repositories; 4 | 5 | public interface IAltinnAccessManagementService 6 | { 7 | Task> GetAuthorizedParties(Party partyToRequestFor, string? userId, CancellationToken cancellationToken = default); 8 | } 9 | -------------------------------------------------------------------------------- /.bruno/Configure End User System/System overview.bru: -------------------------------------------------------------------------------- 1 | meta { 2 | name: System overview 3 | type: http 4 | seq: 4 5 | } 6 | 7 | get { 8 | url: {{platform_base_url}}/authentication/api/v1/systemregister/vendor/{{serviceowner_orgnumber}}_{{resource_id}}_correspondence 9 | body: none 10 | auth: bearer 11 | } 12 | 13 | auth:bearer { 14 | token: {{systemprovider_token}} 15 | } 16 | -------------------------------------------------------------------------------- /.bruno/Events Integration/Get subscriptions.bru: -------------------------------------------------------------------------------- 1 | meta { 2 | name: Get subscriptions 3 | type: http 4 | seq: 1 5 | } 6 | 7 | get { 8 | url: {{platform_base_url}}/events/api/v1/subscriptions/ 9 | body: none 10 | auth: bearer 11 | } 12 | 13 | auth:bearer { 14 | token: {{serviceowner_altinn_token}} 15 | } 16 | 17 | settings { 18 | encodeUrl: true 19 | timeout: 0 20 | } 21 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Application/CleanupMarkdownAndHTMLInSummary/CleanupMarkdownAndHTMLInSummaryRequest.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Altinn.Correspondence.Application.CleanupMarkdownAndHTMLInSummary; 4 | 5 | public class CleanupMarkdownAndHTMLInSummaryRequest 6 | { 7 | [Range(100, int.MaxValue)] 8 | public int WindowSize { get; set; } = 10000; 9 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Repositories/IMigrationRepostitory.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Correspondence.Core.Models.Entities; 2 | 3 | namespace Altinn.Correspondence.Core.Repositories 4 | { 5 | public interface IMigrationRepository 6 | { 7 | Task GetCorrespondenceMigrationStatus(Guid correspondenceId, CancellationToken cancellationToken); 8 | } 9 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Integrations/Hangfire/HangfireConnectionFactory.cs: -------------------------------------------------------------------------------- 1 | using Hangfire.PostgreSql; 2 | using Npgsql; 3 | 4 | namespace Altinn.Correspondence.Integrations.Hangfire; 5 | 6 | public class HangfireConnectionFactory(NpgsqlDataSource dataSource) : IConnectionFactory 7 | { 8 | public NpgsqlConnection GetOrCreateConnection() => dataSource.CreateConnection(); 9 | } 10 | -------------------------------------------------------------------------------- /.bruno/Correspondence/{correspondenceId}/Purge (as end user system).bru: -------------------------------------------------------------------------------- 1 | meta { 2 | name: Purge (as end user system) 3 | type: http 4 | seq: 6 5 | } 6 | 7 | delete { 8 | url: {{correspondence_base_url}}/correspondence/api/v1/correspondence/{{correspondenceId}}/purge 9 | body: none 10 | auth: bearer 11 | } 12 | 13 | auth:bearer { 14 | token: {{recipient_altinn_token}} 15 | } 16 | -------------------------------------------------------------------------------- /Test/Altinn.Correspondence.Tests/Fixtures/CustomWebApplicationTestsCollection.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Correspondence.Tests.Helpers; 2 | 3 | namespace Altinn.Correspondence.Tests.Fixtures 4 | { 5 | [CollectionDefinition(nameof(CustomWebApplicationTestsCollection))] 6 | public class CustomWebApplicationTestsCollection : ICollectionFixture 7 | { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.azure/modules/keyvault/upsertSecret.bicep: -------------------------------------------------------------------------------- 1 | param destKeyVaultName string 2 | param secretName string 3 | @secure() 4 | param secretValue string 5 | 6 | resource secret 'Microsoft.KeyVault/vaults/secrets@2024-11-01' = { 7 | name: '${destKeyVaultName}/${secretName}' 8 | properties: { 9 | value: secretValue 10 | } 11 | } 12 | 13 | output secretUri string = secret.properties.secretUri 14 | -------------------------------------------------------------------------------- /.bruno/Correspondence/{correspondenceId}/Confirm (as end user system).bru: -------------------------------------------------------------------------------- 1 | meta { 2 | name: Confirm (as end user system) 3 | type: http 4 | seq: 4 5 | } 6 | 7 | post { 8 | url: {{correspondence_base_url}}/correspondence/api/v1/correspondence/{{correspondenceId}}/confirm 9 | body: none 10 | auth: bearer 11 | } 12 | 13 | auth:bearer { 14 | token: {{recipient_altinn_token}} 15 | } 16 | -------------------------------------------------------------------------------- /Test/Altinn.Correspondence.LoadTests.DatabasePopulater/BatchingOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.LoadTests.DatabasePopulater 2 | { 3 | public class BatchingOptions 4 | { 5 | public int BatchSize { get; set; } = 50000; 6 | public int MaxDegreeOfParallelism { get; set; } = 4; 7 | public Action Logger { get; set; } = Console.WriteLine; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Application/AttachmentStatusText.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Application 2 | { 3 | public static class AttachmentStatusText 4 | { 5 | public const string InvalidLocationUrl = "Could not get data location url"; 6 | public const string ChecksumMismatch = "Checksum mismatch"; 7 | public const string UploadFailed = "Upload failed"; 8 | } 9 | } -------------------------------------------------------------------------------- /.bruno/Correspondence/{correspondenceId}/Read Content (as end user system).bru: -------------------------------------------------------------------------------- 1 | meta { 2 | name: Read Content (as end user system) 3 | type: http 4 | seq: 3 5 | } 6 | 7 | get { 8 | url: {{correspondence_base_url}}/correspondence/api/v1/correspondence/{{correspondenceId}}/content 9 | body: none 10 | auth: bearer 11 | } 12 | 13 | auth:bearer { 14 | token: {{recipient_altinn_token}} 15 | } 16 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Integrations/Hangfire/BackgroundJobContext.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Integrations.Hangfire; 2 | 3 | public static class BackgroundJobContext 4 | { 5 | private static readonly AsyncLocal _origin = new(); 6 | 7 | public static string? Origin 8 | { 9 | get => _origin.Value; 10 | set => _origin.Value = value; 11 | } 12 | } 13 | 14 | 15 | -------------------------------------------------------------------------------- /.bruno/Events Integration/Delete subscription.bru: -------------------------------------------------------------------------------- 1 | meta { 2 | name: Delete subscription 3 | type: http 4 | seq: 4 5 | } 6 | 7 | delete { 8 | url: {{platform_base_url}}/events/api/v1/subscriptions/{{event_subscription_id}} 9 | body: json 10 | auth: bearer 11 | } 12 | 13 | auth:bearer { 14 | token: {{serviceowner_altinn_token}} 15 | } 16 | 17 | settings { 18 | encodeUrl: true 19 | timeout: 0 20 | } 21 | -------------------------------------------------------------------------------- /.bruno/test-file.txt: -------------------------------------------------------------------------------- 1 | This is a test file for Altinn Correspondence API testing. 2 | It can be used to test file upload functionality in the Bruno collection. 3 | 4 | Content: Lorem ipsum dolor sit amet, consectetur adipiscing elit. 5 | Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 6 | Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris. 7 | 8 | Test data for correspondence attachments. 9 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Integrations/Altinn/ContactReservationRegistry/ContactReservationPersonRequest.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Altinn.Correspondence.Integrations.Altinn.ContactReservationRegistry; 4 | 5 | public class ContactReservationPersonRequest 6 | { 7 | [JsonPropertyName("personidentifikatorer")] 8 | public List Personidentifikatorer { get; set; } 9 | } 10 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Application/CleanupConfirmedMigratedCorrespondences/CleanupConfirmedMigratedCorrespondencesRequest.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Altinn.Correspondence.Application.CleanupConfirmedMigratedCorrespondences; 4 | 5 | public class CleanupConfirmedMigratedCorrespondencesRequest 6 | { 7 | [Range(100, int.MaxValue)] 8 | public int WindowSize { get; set; } = 10000; 9 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Application/PublishCorrespondence/PublishCorrespondenceRequest.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Correspondence.Application.InitializeCorrespondences; 2 | 3 | namespace Altinn.Correspondence.Application.PublishCorrespondence; 4 | 5 | public class PublishCorrespondenceRequest 6 | { 7 | public required Guid CorrespondenceId { get; set; } 8 | public required NotificationRequest? NotificationRequest { get; set; } 9 | } -------------------------------------------------------------------------------- /Test/Altinn.Correspondence.LoadTests/common/testimports.js: -------------------------------------------------------------------------------- 1 | export { uuidv4 } from 'https://jslib.k6.io/k6-utils/1.4.0/index.js'; 2 | export { getEnterpriseToken, getPersonalToken } from './token.js'; 3 | export { randomItem, randomIntBetween } from 'https://jslib.k6.io/k6-utils/1.4.0/index.js'; 4 | export { URL } from 'https://jslib.k6.io/url/1.0.0/index.js'; 5 | export { describe, expect } from 'https://jslib.k6.io/k6chaijs/4.3.4.3/index.js'; -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Application/UploadAttachment/UploadAttachmentRequest.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Application.UploadAttachment 2 | { 3 | public class UploadAttachmentRequest 4 | { 5 | public Guid AttachmentId { get; set; } 6 | public required Stream UploadStream { get; set; } 7 | public long? ContentLength { get; set; } 8 | public Guid? SenderPartyUuid { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.bruno/Correspondence/{correspondenceId}/Download attachment (as end user system).bru: -------------------------------------------------------------------------------- 1 | meta { 2 | name: Download attachment (as end user system) 3 | type: http 4 | seq: 5 5 | } 6 | 7 | get { 8 | url: {{correspondence_base_url}}/correspondence/api/v1/correspondence/{{correspondenceId}}/attachment/{{attachment_id}}/download 9 | body: none 10 | auth: bearer 11 | } 12 | 13 | auth:bearer { 14 | token: {{recipient_altinn_token}} 15 | } 16 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Application/LegacyUpdateCorrespondenceStatus/LegacyUpdateCorrespondenceStatusRequest.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Correspondence.Core.Models.Enums; 2 | 3 | namespace Altinn.Correspondence.Application.LegacyUpdateCorrespondenceStatus; 4 | 5 | public class LegacyUpdateCorrespondenceStatusRequest 6 | { 7 | public required Guid CorrespondenceId { get; set; } 8 | public required CorrespondenceStatus Status { get; set; } 9 | } 10 | -------------------------------------------------------------------------------- /Test/Altinn.Correspondence.Tests/Helpers/UnitWebApplicationFactory.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | 3 | namespace Altinn.Correspondence.Tests.Helpers 4 | { 5 | internal class UnitWebApplicationFactory : CustomWebApplicationFactory 6 | { 7 | public UnitWebApplicationFactory(Action customServices) 8 | { 9 | CustomServices = customServices; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Models/Notifications/Recipient.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace Altinn.Correspondence.Core.Models.Notifications; 3 | 4 | public class Recipient 5 | { 6 | public string? EmailAddress { get; set; } 7 | 8 | public string? MobileNumber { get; set; } 9 | 10 | public string? OrganizationNumber { get; set; } 11 | 12 | public string? NationalIdentityNumber { get; set; } 13 | 14 | public bool? IsReserved { get; set; } 15 | } -------------------------------------------------------------------------------- /.bruno/Correspondence/Search (as end user).bru: -------------------------------------------------------------------------------- 1 | meta { 2 | name: Search (as end user) 3 | type: http 4 | seq: 4 5 | } 6 | 7 | get { 8 | url: {{correspondence_base_url}}/correspondence/api/v1/correspondence?role=Recipient&resourceId={{resource_id}} 9 | body: none 10 | auth: bearer 11 | } 12 | 13 | params:query { 14 | role: Recipient 15 | resourceId: {{resource_id}} 16 | } 17 | 18 | auth:bearer { 19 | token: {{recipient_altinn_token}} 20 | } 21 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Application/SyncCorrespondenceEvent/SyncCorrespondenceForwardingEventRequest.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Correspondence.Core.Models.Entities; 2 | 3 | namespace Altinn.Correspondence.Application.SyncCorrespondenceEvent; 4 | 5 | public class SyncCorrespondenceForwardingEventRequest 6 | { 7 | public required Guid CorrespondenceId { get; set; } 8 | public required List SyncedEvents { get; set; } 9 | } 10 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Application/SyncCorrespondenceEvent/SyncCorrespondenceNotificationEventRequest.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Correspondence.Core.Models.Entities; 2 | 3 | namespace Altinn.Correspondence.Application.SyncCorrespondenceEvent; 4 | 5 | public class SyncCorrespondenceNotificationEventRequest 6 | { 7 | public required Guid CorrespondenceId { get; set; } 8 | public required List SyncedEvents { get; set; } 9 | } 10 | -------------------------------------------------------------------------------- /flux/syncroot/base/broker-flux-kustomize.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.toolkit.fluxcd.io/v1 2 | kind: Kustomization 3 | metadata: 4 | name: broker-app-sync 5 | namespace: broker 6 | spec: 7 | force: false 8 | interval: 5m0s 9 | path: ./ 10 | prune: false 11 | retryInterval: 5m0s 12 | sourceRef: 13 | kind: OCIRepository 14 | name: broker-app-sync 15 | namespace: broker 16 | targetNamespace: broker 17 | timeout: 5m0s 18 | wait: true 19 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Models/Enums/NotificationTemplate.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Core.Models.Enums 2 | { 3 | public enum NotificationTemplate 4 | { 5 | CustomMessage = 0, 6 | GenericAltinnMessage = 1, 7 | 8 | /// 9 | /// Notification was sent from Altinn 2 and then exported to Altinn 3 along with the Altinn 2 message. 10 | /// 11 | Altinn2Message = 2, 12 | } 13 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Repositories/ICorrespondenceForwardingEventRepository.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Correspondence.Core.Models.Entities; 2 | 3 | namespace Altinn.Correspondence.Core.Repositories 4 | { 5 | public interface ICorrespondenceForwardingEventRepository 6 | { 7 | Task> AddForwardingEvents(List correspondenceForwardingEventEntities, CancellationToken cancellationToken); 8 | } 9 | } -------------------------------------------------------------------------------- /.azure/applications/migration/params.bicepparam: -------------------------------------------------------------------------------- 1 | using './main.bicep' 2 | 3 | param location = 'norwayeast' 4 | param environment = readEnvironmentVariable('ENVIRONMENT') 5 | param keyVaultName = readEnvironmentVariable('KEY_VAULT_NAME') 6 | param keyVaultUrl = readEnvironmentVariable('KEY_VAULT_URL') 7 | param namePrefix = readEnvironmentVariable('NAME_PREFIX') 8 | param appVersion = readEnvironmentVariable('APP_VERSION') 9 | param apimIp = readEnvironmentVariable('APIM_IP') 10 | -------------------------------------------------------------------------------- /.bruno/Authentication/Service Owner/Exchange Maskinporten token to Altinn token.bru: -------------------------------------------------------------------------------- 1 | meta { 2 | name: Exchange Maskinporten token to Altinn token 3 | type: http 4 | seq: 2 5 | } 6 | 7 | get { 8 | url: {{platform_base_url}}/authentication/api/v1/exchange/maskinporten 9 | auth: bearer 10 | } 11 | 12 | auth:bearer { 13 | token: {{serviceowner_maskinporten_token}} 14 | } 15 | 16 | script:post-response { 17 | bru.setVar("serviceowner_altinn_token", res.body); 18 | } 19 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Models/Notifications/NotificationStatusDetailsResponse.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Core.Models.Notifications; 2 | 3 | public class NotificationsStatusDetails 4 | { 5 | public EmailNotificationWithResult? Email { get; set; } 6 | 7 | public SmsNotificationWithResult? Sms { get; set; } 8 | 9 | public List? Emails { get; set; } 10 | 11 | public List? Smses { get; set; } 12 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Services/Enums/DialogportenTextType.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Core.Services.Enums 2 | { 3 | public enum DialogportenTextType 4 | { 5 | NotificationOrderCreated, 6 | NotificationOrderCancelled, 7 | NotificationSent, 8 | NotificationReminderSent, 9 | DownloadStarted, 10 | CorrespondencePublished, 11 | CorrespondenceConfirmed, 12 | CorrespondencePurged 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.bruno/Authentication/End User System/Exchange recipient token for Altinn token.bru: -------------------------------------------------------------------------------- 1 | meta { 2 | name: Exchange recipient token for Altinn token 3 | type: http 4 | seq: 2 5 | } 6 | 7 | get { 8 | url: {{platform_base_url}}/authentication/api/v1/exchange/maskinporten 9 | body: none 10 | auth: bearer 11 | } 12 | 13 | auth:bearer { 14 | token: {{recipient_maskinporten_token}} 15 | } 16 | 17 | script:post-response { 18 | bru.setVar("recipient_altinn_token", res.body); 19 | } 20 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Application/CreateNotificationOrder/CreateNotificationOrderRequest.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Correspondence.Application.InitializeCorrespondences; 2 | 3 | namespace Altinn.Correspondence.Application.CreateNotificationOrder; 4 | 5 | public class CreateNotificationOrderRequest 6 | { 7 | public required NotificationRequest NotificationRequest { get; set; } 8 | public required Guid CorrespondenceId { get; set; } 9 | public string? Language { get; set; } = null; 10 | } -------------------------------------------------------------------------------- /.bruno/Configure End User System/Exchange systemprovider token for Altinn token.bru: -------------------------------------------------------------------------------- 1 | meta { 2 | name: Exchange systemprovider token for Altinn token 3 | type: http 4 | seq: 2 5 | } 6 | 7 | get { 8 | url: {{platform_base_url}}/authentication/api/v1/exchange/maskinporten 9 | body: none 10 | auth: bearer 11 | } 12 | 13 | auth:bearer { 14 | token: {{systemprovider_maskinporten_token}} 15 | } 16 | 17 | script:post-response { 18 | bru.setVar("systemprovider_token", res.body); 19 | } 20 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Repositories/INotificationTemplateRepository.cs: -------------------------------------------------------------------------------- 1 | 2 | using Altinn.Correspondence.Core.Models.Entities; 3 | using Altinn.Correspondence.Core.Models.Enums; 4 | 5 | namespace Altinn.Correspondence.Core.Repositories 6 | { 7 | public interface INotificationTemplateRepository 8 | { 9 | Task> GetNotificationTemplates(NotificationTemplate template, CancellationToken cancellationToken, string? language = null); 10 | } 11 | } -------------------------------------------------------------------------------- /.bruno/Correspondence/{correspondenceId}/Overview.bru: -------------------------------------------------------------------------------- 1 | meta { 2 | name: Overview 3 | type: http 4 | seq: 1 5 | } 6 | 7 | get { 8 | url: {{correspondence_base_url}}/correspondence/api/v1/correspondence/{{correspondenceId}} 9 | body: none 10 | auth: bearer 11 | } 12 | 13 | auth:bearer { 14 | token: {{serviceowner_altinn_token}} 15 | } 16 | 17 | script:post-response { 18 | const responseData = res.body; 19 | bru.setVar("attachment_id", responseData.content.attachments[0].id); 20 | } 21 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.API/Models/Enums/TransmissionTypeExt.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.API.Models.Enums 2 | { 3 | /// 4 | /// Defines the type of a transmission. 5 | /// 6 | public enum TransmissionTypeExt : int 7 | { 8 | Information = 1, 9 | Acceptance = 2, 10 | Rejection = 3, 11 | Request = 4, 12 | Alert = 5, 13 | Decision = 6, 14 | Submission = 7, 15 | Correction = 8 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Models/Notifications/StatusExt.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Altinn.Correspondence.Core.Models.Notifications; 4 | 5 | public class StatusExt 6 | { 7 | [JsonPropertyName("status")] 8 | public string Status { get; set; } = string.Empty; 9 | 10 | [JsonPropertyName("description")] 11 | public string? StatusDescription { get; set; } 12 | 13 | [JsonPropertyName("lastUpdate")] 14 | public DateTime LastUpdate { get; set; } 15 | } -------------------------------------------------------------------------------- /flux/syncroot/base/correspondence-flux-kustomize.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.toolkit.fluxcd.io/v1 2 | kind: Kustomization 3 | metadata: 4 | name: correspondence-app-sync 5 | namespace: correspondence 6 | spec: 7 | force: false 8 | interval: 5m0s 9 | path: ./ 10 | prune: false 11 | retryInterval: 5m0s 12 | sourceRef: 13 | kind: OCIRepository 14 | name: correspondence-app-sync 15 | namespace: correspondence 16 | targetNamespace: correspondence 17 | timeout: 5m0s 18 | wait: true 19 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Application/IHandler.cs: -------------------------------------------------------------------------------- 1 | using OneOf; 2 | using System.Security.Claims; 3 | 4 | namespace Altinn.Correspondence.Application; 5 | 6 | internal interface IHandler 7 | { 8 | Task> Process(TRequest request, ClaimsPrincipal? user, CancellationToken cancellationToken); 9 | } 10 | 11 | internal interface IHandler 12 | { 13 | Task> Process(ClaimsPrincipal? user, CancellationToken cancellationToken); 14 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Models/Enums/AltinnVersion.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Core.Models.Enums; 2 | 3 | /// 4 | /// Specifies the Altinn version for a correspondence 5 | /// 6 | public enum AltinnVersion 7 | { 8 | /// 9 | /// Correspondence from Altinn 2 (legacy system) 10 | /// 11 | Altinn2 = 0, 12 | 13 | /// 14 | /// Correspondence from Altinn 3 (current system) 15 | /// 16 | Altinn3 = 1 17 | } 18 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Models/Notifications/EmailTemplate.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Correspondence.Core.Models.Enums; 2 | 3 | namespace Altinn.Correspondence.Core.Models.Notifications; 4 | 5 | public class EmailTemplate 6 | { 7 | public string FromAddress { get; set; } = string.Empty; 8 | 9 | public string Subject { get; set; } = string.Empty; 10 | 11 | public string Body { get; set; } = string.Empty; 12 | 13 | public EmailContentType ContentType { get; set; } = EmailContentType.Plain; 14 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Services/IAltinnStorageService.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Correspondence.Core.Models.Enums; 2 | 3 | namespace Altinn.Correspondence.Core.Services; 4 | 5 | public interface IAltinnStorageService 6 | { 7 | Task AddPartyToSblBridge(int partyId, CancellationToken cancellationToken); 8 | Task SyncCorrespondenceEventToSblBridge(int altinn2CorrespondenceId, int partyId, DateTimeOffset utcEventTimestamp, SyncEventType eventType, CancellationToken cancellationToken); 9 | } 10 | -------------------------------------------------------------------------------- /flux/syncroot/prod/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - ../base 5 | patches: 6 | - target: 7 | kind: OCIRepository 8 | name: broker-app-sync 9 | patch: |- 10 | - op: replace 11 | path: /spec/ref/tag 12 | value: prod 13 | - target: 14 | kind: OCIRepository 15 | name: correspondence-app-sync 16 | patch: |- 17 | - op: replace 18 | path: /spec/ref/tag 19 | value: prod 20 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Persistence/Helpers/DateTimeOffsetConverter.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 2 | 3 | namespace Altinn.Correspondence.Persistence.Helpers 4 | { 5 | internal class DateTimeOffsetConverter : ValueConverter 6 | { 7 | public DateTimeOffsetConverter() 8 | : base( 9 | d => d.ToUniversalTime(), 10 | d => d.ToUniversalTime()) 11 | { 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Application/SyncCorrespondenceEvent/SyncCorrespondenceStatusEventRequest.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Correspondence.Core.Models.Entities; 2 | 3 | namespace Altinn.Correspondence.Application.SyncCorrespondenceEvent; 4 | 5 | public class SyncCorrespondenceStatusEventRequest 6 | { 7 | public required Guid CorrespondenceId { get; set; } 8 | public List? SyncedEvents { get; set; } 9 | public List? SyncedDeleteEvents { get; set; } 10 | } 11 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Options/SlackSettings.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Hosting; 2 | 3 | namespace Altinn.Correspondence.Core.Options; 4 | 5 | public class SlackSettings 6 | { 7 | private readonly IHostEnvironment _hostEnvironment; 8 | 9 | public SlackSettings(IHostEnvironment hostEnvironment) 10 | { 11 | _hostEnvironment = hostEnvironment; 12 | } 13 | 14 | public string NotificationChannel => _hostEnvironment.IsProduction() ? "#mf-varsling-critical" : "#test-varslinger"; 15 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.API/Models/CorrespondencesExt.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Altinn.Correspondence.API.Models 4 | { 5 | /// 6 | /// An entity representing a a list of Correspondences 7 | /// 8 | public class CorrespondencesExt 9 | { 10 | /// 11 | /// Correspondence ids 12 | /// 13 | [JsonPropertyName("ids")] 14 | public List Ids { get; set; } = new List(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | **/.classpath 2 | **/.dockerignore 3 | **/.env 4 | **/.git 5 | **/.gitignore 6 | **/.project 7 | **/.settings 8 | **/.toolstarget 9 | **/.vs 10 | **/.vscode 11 | **/*.*proj.user 12 | **/*.dbmdl 13 | **/*.jfm 14 | **/azds.yaml 15 | **/bin 16 | **/charts 17 | **/docker-compose* 18 | **/Dockerfile* 19 | **/node_modules 20 | **/npm-debug.log 21 | **/obj 22 | **/secrets.dev.yaml 23 | **/values.dev.yaml 24 | LICENSE 25 | README.md 26 | !**/.gitignore 27 | !.git/HEAD 28 | !.git/config 29 | !.git/packed-refs 30 | !.git/refs/heads/** -------------------------------------------------------------------------------- /Test/Altinn.Correspondence.Tests/Fixtures/TestApplicationDbContext.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Correspondence.Persistence; 2 | using Microsoft.EntityFrameworkCore; 3 | 4 | namespace Altinn.Correspondence.Tests.Fixtures 5 | { 6 | public class TestApplicationDbContext : ApplicationDbContext 7 | { 8 | public TestApplicationDbContext(DbContextOptions options) 9 | : base(options) 10 | { 11 | } 12 | 13 | protected new bool IsAccessTokenValid() => false; 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Application/CleanupBruksmonster/CleanupBruksmonsterResponse.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Application.CleanupBruksmonster; 2 | 3 | public class CleanupBruksmonsterResponse 4 | { 5 | public required string ResourceId { get; set; } 6 | public required int CorrespondencesFound { get; set; } 7 | public required int AttachmentsFound { get; set; } 8 | public required string DeleteDialogsJobId { get; set; } 9 | public required string DeleteCorrespondencesJobId { get; set; } 10 | } 11 | 12 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base" 5 | ], 6 | "labels": [ 7 | "kind/dependencies" 8 | ], 9 | "schedule": [ 10 | "before 7am on Monday", 11 | "before 7am on Friday" 12 | ], 13 | "packageRules": [ 14 | { 15 | "matchPaths": [ "**/*.bicep" ], 16 | "enabled": false 17 | }, 18 | { 19 | "groupName": "All dependencies", 20 | "matchUpdateTypes": [ "major", "minor", "patch" ] 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Application/MigrateCorrespondence/MigrateCorrespondenceResponse.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Application.MigrateCorrespondence; 2 | 3 | public class MigrateCorrespondenceResponse 4 | { 5 | public Guid CorrespondenceId { get; set; } 6 | 7 | public int Altinn2CorrespondenceId { get; set; } 8 | 9 | public List? AttachmentMigrationStatuses { get; set; } 10 | public bool IsAlreadyMigrated { get; set; } = false; 11 | 12 | public string? DialogId { get; set; } 13 | } 14 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.API/Models/LegacyCorrespondenceAttachmentExt.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Altinn.Correspondence.API.Models 4 | { 5 | /// 6 | /// Represents a binary attachment to a Correspondence 7 | /// 8 | public class LegacyCorrespondenceAttachmentExt : CorrespondenceAttachmentExt 9 | { 10 | /// 11 | /// The name of the attachment 12 | /// 13 | [JsonPropertyName("name")] 14 | public string? Name { get; set; } 15 | } 16 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Application/MigrateCorrespondenceAttachment/MigrateAttachmentRequest.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Correspondence.Core.Models.Entities; 2 | 3 | namespace Altinn.Correspondence.Application.InitializeAttachment; 4 | 5 | public class MigrateAttachmentRequest 6 | { 7 | public required AttachmentEntity Attachment { get; set; } 8 | public required Guid SenderPartyUuid { get; set; } 9 | public required Stream UploadStream { get; set; } 10 | public long ContentLength { get; set; } 11 | public string Altinn2AttachmentId { get; set; } 12 | } 13 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Models/Entities/StorageProviderEntity.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Correspondence.Core.Models.Enums; 2 | 3 | namespace Altinn.Correspondence.Core.Models.Entities; 4 | 5 | public class StorageProviderEntity 6 | { 7 | public long Id { get; set; } 8 | public DateTimeOffset Created { get; set; } 9 | 10 | public StorageProviderType Type { get; set; } 11 | 12 | public required string StorageResourceName { get; set; } 13 | public required string ServiceOwnerId { get; set; } 14 | public required bool Active { get; set; } 15 | } 16 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.API/Models/Enums/NotificationTemplateExt.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.API.Models.Enums 2 | { 3 | /// 4 | /// Enum describing available notification templates. 5 | /// 6 | public enum NotificationTemplateExt 7 | { 8 | /// 9 | /// Fully customizable template. 10 | /// 11 | CustomMessage, 12 | 13 | /// 14 | /// Standard Altinn notification template. 15 | /// 16 | GenericAltinnMessage, 17 | 18 | } 19 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Repositories/ICorrespondenceStatusRepository.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Correspondence.Core.Models.Entities; 2 | 3 | namespace Altinn.Correspondence.Core.Repositories 4 | { 5 | public interface ICorrespondenceStatusRepository 6 | { 7 | Task AddCorrespondenceStatus(CorrespondenceStatusEntity correspondenceStatusEntity, CancellationToken cancellationToken); 8 | Task> AddCorrespondenceStatuses(List correspondenceStatusEntities, CancellationToken cancellationToken); 9 | } 10 | } -------------------------------------------------------------------------------- /Test/Altinn.Correspondence.Tests/Data/WebHookSubscriptionValidationTest.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "2d1781af-3a4c-4d7c-bd0c-e34b19da4e66", 4 | "topic": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 5 | "subject": "", 6 | "data": { 7 | "validationCode": "512d38b6-c7b8-40c8-89fe-f46f9e9622b6", 8 | "validationUrl": "https://www.contoso.com/" 9 | }, 10 | "eventType": "Microsoft.EventGrid.SubscriptionValidationEvent", 11 | "eventTime": "2018-01-25T22:12:19.4556811Z", 12 | "metadataVersion": "1", 13 | "dataVersion": "1" 14 | } 15 | ] 16 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Repositories/ICorrespondenceDeleteEventRepository.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Correspondence.Core.Models.Entities; 2 | 3 | namespace Altinn.Correspondence.Core.Repositories 4 | { 5 | public interface ICorrespondenceDeleteEventRepository 6 | { 7 | Task AddDeleteEvent(CorrespondenceDeleteEventEntity correspondenceDeleteEventEntity, CancellationToken cancellationToken); 8 | 9 | Task> GetDeleteEventsForCorrespondenceId(Guid correspondenceId, CancellationToken cancellationToken); 10 | } 11 | } -------------------------------------------------------------------------------- /.bruno/environments/example.bru: -------------------------------------------------------------------------------- 1 | vars { 2 | platform_base_url: https://platform.tt02.altinn.no 3 | correspondence_base_url: https://platform.tt02.altinn.no 4 | resource_id: your-resource-id 5 | serviceowner_orgnumber: 123456789 6 | recipient_orgnumber: 987654321 7 | recipient_ssn: 12345678901 8 | client_id: your-maskinporten-integration-id 9 | client_kid: your-maskinporten-key-id 10 | client_pem: -----BEGIN PRIVATE KEY-----\nYOUR_PRIVATE_KEY_HERE\n-----END PRIVATE KEY----- 11 | } 12 | 13 | vars:secret [ 14 | client_pem 15 | serviceowner_altinn_token 16 | recipient_altinn_token 17 | ] 18 | -------------------------------------------------------------------------------- /.github/workflows/validate-formatting.yml: -------------------------------------------------------------------------------- 1 | name: Validate formatting 2 | 3 | on: 4 | workflow_call: 5 | workflow_dispatch: 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | check-formatting: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | 15 | - name: Setup .NET 16 | uses: actions/setup-dotnet@v4 17 | with: 18 | dotnet-version: | 19 | 9.0.x 20 | 21 | - name: Format 22 | run: | 23 | dotnet format --verify-no-changes --verbosity diagnostic --include ./src/** 24 | -------------------------------------------------------------------------------- /.azure/modules/keyvault/upsertSecrets.bicep: -------------------------------------------------------------------------------- 1 | param sourceKeyvaultName string 2 | param secrets { name: string, value: string }[] 3 | 4 | var baseName = 'secret${uniqueString(resourceGroup().id)}' 5 | 6 | module keyvaultSecret './upsertSecret.bicep' = [for i in range(0, length(secrets)): { 7 | name: '${i}deploy${baseName}' 8 | params: { 9 | destKeyVaultName: sourceKeyvaultName 10 | secretName: secrets[i].name 11 | secretValue: secrets[i].value 12 | } 13 | }] 14 | 15 | output keyvaultUris array = [for i in range(0, length(secrets)): { 16 | endpoint: keyvaultSecret[i].outputs.secretUri 17 | }] 18 | -------------------------------------------------------------------------------- /.bruno/Attachment/Upload attachment.bru: -------------------------------------------------------------------------------- 1 | meta { 2 | name: Upload attachment 3 | type: http 4 | seq: 2 5 | } 6 | 7 | post { 8 | url: {{correspondence_base_url}}/correspondence/api/v1/attachment/{{attachment_id}}/upload 9 | body: file 10 | auth: bearer 11 | } 12 | 13 | headers { 14 | Content-Type: application/octet-stream 15 | } 16 | 17 | auth:bearer { 18 | token: {{serviceowner_altinn_token}} 19 | } 20 | 21 | body:file { 22 | file: @file(collection.bru) 23 | } 24 | 25 | script:post-response { 26 | const responseData = res.body; 27 | bru.setVar("attachmentId", responseData.attachmentId); 28 | } 29 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Application/GenerateReport/GenerateDailySummaryReportRequest.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Application.GenerateReport; 2 | 3 | /// 4 | /// Request parameters for generating daily summary report 5 | /// 6 | public class GenerateDailySummaryReportRequest 7 | { 8 | /// 9 | /// Whether to include Altinn2 correspondences in the report. 10 | /// If false, only Altinn3 correspondences will be included. 11 | /// Default is true (include both Altinn2 and Altinn3). 12 | /// 13 | public bool Altinn2Included { get; set; } = true; 14 | } 15 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.API/Models/CorrespondenceContentExt.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Altinn.Correspondence.API.Models 4 | { 5 | /// 6 | /// Represents the content of a reportee element of the type correspondence. 7 | /// 8 | public class CorrespondenceContentExt : InitializeCorrespondenceContentExt 9 | { 10 | /// 11 | /// Gets or sets a list of attachments. 12 | /// 13 | [JsonPropertyName("attachments")] 14 | public required new List Attachments { get; set; } 15 | } 16 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Models/Entities/ServiceOwnerEntity.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Core.Models.Entities; 2 | 3 | public class ServiceOwnerEntity 4 | { 5 | public required string Id { get; set; } 6 | public required string Name { get; set; } 7 | public required List StorageProviders { get; set; } 8 | public StorageProviderEntity? GetStorageProvider(bool withVirusScan) 9 | { 10 | return StorageProviders.FirstOrDefault(sp => sp.Type == (withVirusScan ? Enums.StorageProviderType.Altinn3Azure : Enums.StorageProviderType.Altinn3AzureWithoutVirusScan)); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Integrations/Altinn/ContactReservationRegistry/ContactReservationRegistryDevService.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Correspondence.Core.Services; 2 | 3 | namespace Altinn.Correspondence.Integrations.Altinn.ContactReservationRegistry; 4 | 5 | internal class ContactReservationRegistryDevService : IContactReservationRegistryService 6 | { 7 | public Task IsPersonReserved(string ssn) 8 | { 9 | return Task.FromResult(false); 10 | } 11 | 12 | public Task> GetReservedRecipients(List recipients) 13 | { 14 | return Task.FromResult(new List()); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.bruno/environments/default.bru: -------------------------------------------------------------------------------- 1 | vars { 2 | platform_base_url: {{process.env.PLATFORM_BASE_URL}} 3 | correspondence_base_url: {{process.env.CORRESPONDENCE_BASE_URL}} 4 | resource_id: {{process.env.RESOURCE_ID}} 5 | serviceowner_orgnumber: {{process.env.SERVICEOWNER_ORGNUMBER}} 6 | recipient_orgnumber: {{process.env.RECIPIENT_ORGNUMBER}} 7 | recipient_ssn: {{process.env.RECIPIENT_SSN}} 8 | client_id: {{process.env.CLIENT_ID}} 9 | client_kid: {{process.env.CLIENT_KID}} 10 | client_pem: {{process.env.CLIENT_PEM}} 11 | } 12 | 13 | vars:secret [ 14 | client_pem 15 | serviceowner_altinn_token 16 | recipient_altinn_token 17 | ] 18 | -------------------------------------------------------------------------------- /.azure/modules/identity/addContributorAccess.bicep: -------------------------------------------------------------------------------- 1 | targetScope = 'subscription' 2 | 3 | param userAssignedIdentityPrincipalId string 4 | 5 | var roleDefinitionResourceId = 'b24988ac-6180-42a0-ab88-20f7382dd24c' // Contributor role 6 | resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { 7 | name: guid(subscription().id, userAssignedIdentityPrincipalId, roleDefinitionResourceId) 8 | properties: { 9 | roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitionResourceId) 10 | principalId: userAssignedIdentityPrincipalId 11 | principalType: 'ServicePrincipal' 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.azure/modules/subscription/addMonitoringReaderRole.bicep: -------------------------------------------------------------------------------- 1 | targetScope = 'subscription' 2 | 3 | param grafanaPrincipalId string 4 | 5 | var monitoringReaderRoleDefinitionId = '43d0d8ad-25c7-4714-9337-8ba259a9fe05' 6 | 7 | resource monitoringReaderRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { 8 | name: guid(subscription().id, grafanaPrincipalId, monitoringReaderRoleDefinitionId) 9 | properties: { 10 | roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', monitoringReaderRoleDefinitionId) 11 | principalId: grafanaPrincipalId 12 | principalType: 'ServicePrincipal' 13 | } 14 | } 15 | 16 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.API/Models/AttachmentDetailsExt.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Altinn.Correspondence.API.Models 4 | { 5 | /// 6 | /// Represents an overview of a shared attachment that can be used by multiple correspondences 7 | /// 8 | public class AttachmentDetailsExt : AttachmentOverviewExt 9 | { 10 | /// 11 | /// The Status history for the Attachment 12 | /// 13 | [JsonPropertyName("statusHistory")] 14 | public List StatusHistory { get; set; } = new List(); 15 | } 16 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.API/Models/PaginationExt.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Altinn.Correspondence.API.Models 4 | { 5 | /// 6 | /// An entity representing pagination 7 | /// 8 | public class PaginationExt 9 | { 10 | /// 11 | /// pagination offset 12 | /// 13 | [JsonPropertyName("offset")] 14 | public int Offset { get; set; } = 0; 15 | 16 | /// 17 | /// pagination limit 18 | /// 19 | [JsonPropertyName("limit")] 20 | public int Limit { get; set; } = 20; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Models/Notifications/SmsNotificationWithResult.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | 4 | namespace Altinn.Correspondence.Core.Models.Notifications 5 | { 6 | public class SmsNotificationWithResult 7 | { 8 | [JsonPropertyName("id")] 9 | public Guid Id { get; set; } 10 | 11 | [JsonPropertyName("succeeded")] 12 | public bool Succeeded { get; set; } 13 | 14 | [JsonPropertyName("recipient")] 15 | public Recipient Recipient { get; set; } = new(); 16 | 17 | [JsonPropertyName("sendStatus")] 18 | public StatusExt SendStatus { get; set; } = new(); 19 | } 20 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Models/Notifications/EmailNotificationWithResult.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | 4 | namespace Altinn.Correspondence.Core.Models.Notifications 5 | { 6 | public class EmailNotificationWithResult 7 | { 8 | [JsonPropertyName("id")] 9 | public Guid? Id { get; set; } 10 | 11 | [JsonPropertyName("succeeded")] 12 | public bool Succeeded { get; set; } 13 | 14 | [JsonPropertyName("recipient")] 15 | public Recipient Recipient { get; set; } = new(); 16 | 17 | [JsonPropertyName("sendStatus")] 18 | public StatusExt SendStatus { get; set; } = new(); 19 | } 20 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Application/MigrateCorrespondence/MigrateCorrespondenceRequest.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Correspondence.Core.Models.Entities; 2 | 3 | namespace Altinn.Correspondence.Application.MigrateCorrespondence; 4 | 5 | public class MigrateCorrespondenceRequest 6 | { 7 | public required int Altinn2CorrespondenceId { get; set; } 8 | public required CorrespondenceEntity CorrespondenceEntity { get; set; } 9 | public List ExistingAttachments { get; set; } 10 | public bool MakeAvailable { get; set; } = false; 11 | public List DeleteEventEntities { get; set; } = new List(); 12 | } 13 | -------------------------------------------------------------------------------- /.azure/modules/identity/addStorageBlobDataContributorRole.bicep: -------------------------------------------------------------------------------- 1 | targetScope = 'subscription' 2 | param userAssignedIdentityPrincipalId string 3 | 4 | var roleDefinitionResourceId = 'ba92f5b4-2d11-453d-a403-e96b0029c9fe' // Storage blob data contrubotur role 5 | resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { 6 | name: guid(subscription().id, userAssignedIdentityPrincipalId, roleDefinitionResourceId) 7 | properties: { 8 | roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitionResourceId) 9 | principalId: userAssignedIdentityPrincipalId 10 | principalType: 'ServicePrincipal' 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.azure/modules/keyvault/create.bicep: -------------------------------------------------------------------------------- 1 | param vaultName string 2 | param location string 3 | @secure() 4 | param tenant_id string 5 | 6 | resource keyVault 'Microsoft.KeyVault/vaults@2024-11-01' = { 7 | name: vaultName 8 | location: location 9 | properties: { 10 | enabledForTemplateDeployment: true 11 | enabledForDiskEncryption: true 12 | enabledForDeployment: true 13 | enableSoftDelete: true 14 | enablePurgeProtection: true 15 | sku: { 16 | name: 'standard' 17 | family: 'A' 18 | } 19 | tenantId: tenant_id 20 | enableRbacAuthorization: true 21 | accessPolicies:[] 22 | } 23 | } 24 | 25 | output name string = keyVault.name 26 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Models/Entities/CorrespondenceReplyOptionsEntity.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.ComponentModel.DataAnnotations.Schema; 3 | 4 | namespace Altinn.Correspondence.Core.Models.Entities 5 | { 6 | public class CorrespondenceReplyOptionEntity 7 | { 8 | [Key] 9 | public Guid Id { get; set; } 10 | 11 | public required string LinkURL { get; set; } 12 | 13 | public string? LinkText { get; set; } 14 | 15 | public Guid CorrespondenceId { get; set; } 16 | [ForeignKey("CorrespondenceId")] 17 | public CorrespondenceEntity? Correspondence { get; set; } 18 | } 19 | } -------------------------------------------------------------------------------- /.azure/modules/postgreSql/addAdminAccess.bicep: -------------------------------------------------------------------------------- 1 | param principalId string 2 | type PrincipalType = 'Group' | 'ServicePrincipal' | 'Unknown' | 'User' 3 | param principalType PrincipalType 4 | param tenantId string 5 | param appName string 6 | param namePrefix string 7 | 8 | resource database 'Microsoft.DBforPostgreSQL/flexibleServers@2024-08-01' existing = { 9 | name: '${namePrefix}-dbserver' 10 | } 11 | resource databaseAccess 'Microsoft.DBforPostgreSQL/flexibleServers/administrators@2024-08-01' = { 12 | name: principalId 13 | parent: database 14 | properties: { 15 | principalType: principalType 16 | tenantId: tenantId 17 | principalName: appName 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.API/Helpers/SecurityHeadersMiddleware.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Primitives; 2 | 3 | namespace Altinn.Correspondence.Helpers; 4 | public class SecurityHeadersMiddleware 5 | { 6 | private readonly RequestDelegate _next; 7 | 8 | public SecurityHeadersMiddleware(RequestDelegate next) 9 | { 10 | _next = next; 11 | } 12 | 13 | public async Task InvokeAsync(HttpContext context) 14 | { 15 | context.Response.Headers.Append("X-Content-Type-Options", new StringValues("nosniff")); 16 | context.Response.Headers.Append("Cache-Control", new StringValues("no-store")); 17 | 18 | await _next(context); 19 | } 20 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.API/Filters/SetMigrationJobOriginAttribute.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Correspondence.Integrations.Hangfire; 2 | using Microsoft.AspNetCore.Mvc.Filters; 3 | 4 | namespace Altinn.Correspondence.API.Filters; 5 | 6 | public sealed class SetJobOriginAttribute(string origin) : ActionFilterAttribute 7 | { 8 | private readonly string _origin = origin; 9 | 10 | public override void OnActionExecuting(ActionExecutingContext context) 11 | { 12 | BackgroundJobContext.Origin = _origin; 13 | } 14 | 15 | public override void OnActionExecuted(ActionExecutedContext context) 16 | { 17 | BackgroundJobContext.Origin = null; 18 | } 19 | } 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.API/Models/LegacyUserExt.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Altinn.Correspondence.API.Models 4 | { 5 | /// 6 | /// Represents a user for use by the Legacy implementation 7 | /// 8 | public class LegacyUserExt 9 | { 10 | /// 11 | /// The partyId of the user 12 | /// 13 | [JsonPropertyName("partyId")] 14 | public int? PartyId { get; set; } 15 | 16 | /// 17 | /// The name of the user 18 | /// 19 | [JsonPropertyName("name")] 20 | public string? Name { get; set; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Models/Notifications/NotificationStatus.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Altinn.Correspondence.Core.Models.Notifications; 4 | 5 | public abstract class NotificationStatus 6 | { 7 | 8 | [JsonPropertyName("links")] 9 | public NotificationResourceLinks Links { get; set; } = new(); 10 | 11 | 12 | [JsonPropertyName("generated")] 13 | public int Generated { get; set; } 14 | 15 | [JsonPropertyName("succeeded")] 16 | public int Succeeded { get; set; } 17 | } 18 | 19 | public class EmailNotificationStatus : NotificationStatus 20 | { 21 | } 22 | 23 | public class SmsNotificationStatus : NotificationStatus 24 | { 25 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Options/AltinnOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Core.Options; 2 | 3 | public class AltinnOptions 4 | { 5 | public string OpenIdWellKnown { get; set; } = string.Empty; 6 | public string PlatformGatewayUrl { get; set; } = string.Empty; 7 | public string PlatformSubscriptionKey { get; set; } = string.Empty; 8 | public string AccessManagementSubscriptionKey { get; set; } = string.Empty; 9 | public string OverrideAuthorizationUrl { get; set; } = string.Empty; 10 | public string AuthorizationCertificateThumbprint { get; set; } = string.Empty; 11 | public string ArbeidsflateOriginsCommaSeparated { get; set; } = string.Empty; 12 | } 13 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Integrations/Altinn/Events/CloudEvent.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Integrations.Altinn.Events; 2 | 3 | public class CloudEvent 4 | { 5 | public string SpecVersion { get; set; } = "1.0"; 6 | public Guid Id { get; set; } 7 | public string Type { get; set; } = null!; 8 | public DateTimeOffset Time { get; set; } 9 | public string Resource { get; set; } = null!; 10 | public string ResourceInstance { get; set; } = null!; 11 | public string? Subject { get; set; } 12 | public string? AlternativeSubject { get; set; } 13 | public string Source { get; set; } = null!; 14 | public Dictionary? Data { get; set; } 15 | } 16 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.API/Models/AttachmentMigrationStatusExt.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | using Altinn.Correspondence.API.Models.Enums; 3 | 4 | namespace Altinn.Correspondence.API.Models 5 | { 6 | public class AttachmentMigrationStatusExt 7 | { 8 | /// 9 | /// Unique Id for this attachment 10 | /// 11 | [JsonPropertyName("attachmentId")] 12 | public required Guid AttachmentId { get; set; } 13 | 14 | /// 15 | /// Current attachment status 16 | /// 17 | [JsonPropertyName("status")] 18 | public required AttachmentStatusExt Status { get; set; } 19 | } 20 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.API/Models/Enums/AttachmentDataLocationTypeExt.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.API.Models.Enums 2 | { 3 | /// 4 | /// Defines the location of the attachment data 5 | /// 6 | public enum AttachmentDataLocationTypeExt : int 7 | { 8 | /// 9 | /// Specifies that the attachment data is stored in the Altinn Correspondence Storage 10 | /// 11 | AltinnCorrespondenceAttachment = 0, 12 | 13 | /// 14 | /// Specifies that the attachment data is stored in an external storage controlled by the sender 15 | /// 16 | ExternalStorage = 1 17 | } 18 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Models/Enums/AttachmentDataLocationType.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Core.Models.Enums 2 | { 3 | /// 4 | /// Defines the location of the attachment data 5 | /// 6 | public enum AttachmentDataLocationType : int 7 | { 8 | /// 9 | /// Specifies that the attachment data is stored in the Altinn Correspondence Storage 10 | /// 11 | AltinnCorrespondenceAttachment = 0, 12 | 13 | /// 14 | /// Specifies that the attachment data is stored in an external storage controlled by the sender 15 | /// 16 | ExternalStorage = 1 17 | } 18 | } -------------------------------------------------------------------------------- /Test/Altinn.Correspondence.UseCaseTests/helpers/cryptoUtils.js: -------------------------------------------------------------------------------- 1 | import encoding from 'k6/encoding'; 2 | 3 | export function pemToBinary(pem) { 4 | const base64 = (pem || '') 5 | .replace(/-----BEGIN[\s\S]*?-----/g, '') 6 | .replace(/-----END[\s\S]*?-----/g, '') 7 | .replace(/\s+/g, ''); 8 | return encoding.b64decode(base64, 'std'); 9 | } 10 | 11 | // Converts a string to bytes (Uint8Array) 12 | // Note: This assumes ASCII-only input (works for base64url JWT strings) 13 | export function stringToBytes(str) { 14 | const arr = new Uint8Array(str.length); 15 | for (let i = 0; i < str.length; i++) { 16 | arr[i] = str.charCodeAt(i); 17 | } 18 | return arr; 19 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Exceptions.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Core.Exceptions; 2 | public class HashMismatchException : InvalidOperationException 3 | { 4 | public HashMismatchException(string message) : base(message) { } 5 | } 6 | 7 | public class DataLocationUrlException : InvalidOperationException 8 | { 9 | public DataLocationUrlException(string message) : base(message) { } 10 | } 11 | 12 | public class DialogNotFoundException : Exception 13 | { 14 | public string DialogId { get; } 15 | 16 | public DialogNotFoundException(string dialogId) 17 | : base($"Dialog with id '{dialogId}' was not found in Dialogporten") 18 | { 19 | DialogId = dialogId; 20 | } 21 | } -------------------------------------------------------------------------------- /Test/Altinn.Correspondence.Tests/TestingUtility/LegacyHttpContextAccessor.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Claims; 2 | using Microsoft.AspNetCore.Http; 3 | 4 | public class LegacyHttpContextAccessor : IHttpContextAccessor 5 | { 6 | HttpContext IHttpContextAccessor.HttpContext 7 | { 8 | get 9 | { 10 | var httpContext = new DefaultHttpContext() 11 | { 12 | User = new ClaimsPrincipal(new ClaimsIdentity(new Claim[] 13 | { 14 | new Claim("urn:altinn:partyid", "123456789") 15 | })) 16 | }; 17 | return httpContext; 18 | } 19 | set => throw new NotImplementedException(); 20 | } 21 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Models/Enums/RecipientType.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Core.Models.Enums 2 | { 3 | /// 4 | /// Defines the type of recipients 5 | /// 6 | public enum RecipientType : int 7 | { 8 | /// 9 | /// Specifies that the recipient is a person 10 | /// 11 | Person = 0, 12 | 13 | /// 14 | /// Specifies that the recipient is an organization 15 | /// 16 | Organization = 1, 17 | 18 | /// 19 | /// Specifies that the recipient type is unknown or could not be determined 20 | /// 21 | Unknown = 2, 22 | } 23 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Application/GetCorespondences/GetCorrespondencesRequest.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Correspondence.Core.Models.Enums; 2 | 3 | namespace Altinn.Correspondence.Application.GetCorrespondences; 4 | 5 | public class GetCorrespondencesRequest 6 | { 7 | public required string ResourceId { get; set; } 8 | 9 | public DateTimeOffset? From { get; set; } 10 | 11 | public DateTimeOffset? To { get; set; } 12 | 13 | public CorrespondenceStatus? Status { get; set; } 14 | 15 | public required CorrespondencesRoleType Role { get; set; } 16 | 17 | public string? OnBehalfOf { get; set; } 18 | 19 | public string? SendersReference { get; set; } 20 | 21 | public Guid? IdempotentKey { get; set; } 22 | } 23 | -------------------------------------------------------------------------------- /.bruno/Events Integration/Create subscription (as serviceowner).bru: -------------------------------------------------------------------------------- 1 | meta { 2 | name: Create subscription (as serviceowner) 3 | type: http 4 | seq: 2 5 | } 6 | 7 | post { 8 | url: {{platform_base_url}}/events/api/v1/subscriptions/ 9 | body: json 10 | auth: bearer 11 | } 12 | 13 | auth:bearer { 14 | token: {{serviceowner_altinn_token}} 15 | } 16 | 17 | body:json { 18 | { 19 | "endpoint": "your-http-endpoint-that-returns-200", 20 | "resourceFilter": "urn:altinn:resource:{{resource_id}}" 21 | } 22 | } 23 | 24 | script:post-response { 25 | const responseData = res.body; 26 | bru.setVar("event_subscription_id", responseData.id); 27 | } 28 | 29 | settings { 30 | encodeUrl: true 31 | timeout: 0 32 | } 33 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.API/Models/SyncCorrespondenceStatusEventRequestExt.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace Altinn.Correspondence.API.Models; 5 | 6 | /// 7 | /// Request model for synchronizing correspondence status events from Altinn 2 8 | /// 9 | public class SyncCorrespondenceStatusEventRequestExt 10 | { 11 | [JsonPropertyName("correspondenceId")] 12 | public required Guid CorrespondenceId { get; set; } 13 | [JsonPropertyName("syncedStatusEvents")] 14 | [MinLength(1, ErrorMessage = "At least one status event is required.")] 15 | public required List SyncedEvents { get; set; } 16 | } 17 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Application/InitializeCorrespondences/InitializeCorrespondencesRequest.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Correspondence.Core.Models.Entities; 2 | using Microsoft.AspNetCore.Http; 3 | 4 | namespace Altinn.Correspondence.Application.InitializeCorrespondences; 5 | 6 | public class InitializeCorrespondencesRequest 7 | { 8 | public required CorrespondenceEntity Correspondence { get; set; } 9 | 10 | public List Attachments { get; set; } = new List(); 11 | 12 | public NotificationRequest? Notification { get; set; } 13 | 14 | public List ExistingAttachments { get; set; } 15 | 16 | public List Recipients { get; set; } 17 | 18 | public Guid? IdempotentKey { get; set; } 19 | } 20 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Models/Entities/ExternalReferenceEntity.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.ComponentModel.DataAnnotations.Schema; 3 | using Altinn.Correspondence.Core.Models.Enums; 4 | 5 | namespace Altinn.Correspondence.Core.Models.Entities 6 | { 7 | public class ExternalReferenceEntity 8 | { 9 | [Key] 10 | public Guid Id { get; set; } 11 | 12 | public required string ReferenceValue { get; set; } 13 | 14 | public required ReferenceType ReferenceType { get; set; } 15 | 16 | public Guid CorrespondenceId { get; set; } 17 | [ForeignKey("CorrespondenceId")] 18 | public CorrespondenceEntity? Correspondence { get; set; } 19 | } 20 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Application/MigrateCorrespondence/MakeCorrespondenceAvailableRequest.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Application.MigrateCorrespondence; 2 | 3 | public class MakeCorrespondenceAvailableRequest 4 | { 5 | public Guid? CorrespondenceId { get; set; } 6 | public bool CreateEvents { get; set; } 7 | public List? CorrespondenceIds { get; set; } 8 | public bool AsyncProcessing { get; set; } 9 | public int? BatchSize { get; set; } 10 | public int? BatchOffset { get; set; } 11 | public DateTimeOffset? CursorCreated { get; set; } 12 | public Guid? CursorId { get; set; } 13 | public DateTimeOffset? CreatedFrom { get; set; } 14 | public DateTimeOffset? CreatedTo { get; set; } 15 | } 16 | -------------------------------------------------------------------------------- /.azure/modules/keyvault/addSecretsOfficerRole.bicep: -------------------------------------------------------------------------------- 1 | param keyvaultName string 2 | param principalType string = 'Group' 3 | param principalObjectId string 4 | 5 | var secretsOfficerRoleId = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b86a8fe4-44ce-4948-aee5-eccb2c155cd7') 6 | 7 | resource kv 'Microsoft.KeyVault/vaults@2024-11-01' existing = { 8 | name: keyvaultName 9 | } 10 | 11 | resource secretsOfficer 'Microsoft.Authorization/roleAssignments@2022-04-01' = { 12 | name: guid(subscription().id, kv.id, principalObjectId, 'kv-secrets-officer') 13 | scope: kv 14 | properties: { 15 | roleDefinitionId: secretsOfficerRoleId 16 | principalId: principalObjectId 17 | principalType: principalType 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.API/Models/NotificationDetailsSummary.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Altinn.Correspondence.API.Models; 4 | 5 | /// 6 | /// A class representing a summary of status overviews of all notification channels 7 | /// 8 | public class NotificationStatusDetailsExt 9 | { 10 | [JsonPropertyName("email")] 11 | public NotificationDetailsExt? Email { get; set; } 12 | 13 | [JsonPropertyName("sms")] 14 | public NotificationDetailsExt? Sms { get; set; } 15 | 16 | [JsonPropertyName("emails")] 17 | public List? Emails { get; set; } 18 | 19 | [JsonPropertyName("smses")] 20 | public List? Smses { get; set; } 21 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Application/Helpers/AttachmentExtensions.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Correspondence.Core.Models.Entities; 2 | using Altinn.Correspondence.Core.Models.Enums; 3 | namespace Altinn.Correspondence.Application.Helpers; 4 | public static class AttachmentStatusExtensions 5 | { 6 | public static AttachmentStatusEntity? GetLatestStatus(this AttachmentEntity attachment) 7 | { 8 | var statusEntity = attachment.Statuses 9 | .OrderByDescending(s => s.StatusChanged).FirstOrDefault(); 10 | return statusEntity; 11 | } 12 | public static bool StatusHasBeen(this AttachmentEntity attachment, AttachmentStatus status) 13 | { 14 | return attachment.Statuses.Any(s => s.Status == status); 15 | } 16 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.API/Models/SyncCorrespondenceForwardingEventRequestExt.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace Altinn.Correspondence.API.Models; 5 | 6 | /// 7 | /// Request model for synchronizing correspondence forwarding events from Altinn 2 8 | /// 9 | public class SyncCorrespondenceForwardingEventRequestExt 10 | { 11 | [JsonPropertyName("correspondenceId")] 12 | public required Guid CorrespondenceId { get; set; } 13 | [JsonPropertyName("syncedForwardingEvents")] 14 | [MinLength(1, ErrorMessage = "At least one forwarding event is required.")] 15 | public required List SyncedEvents { get; set; } 16 | } 17 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.API/Models/SyncCorrespondenceNotificationRequestExt.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace Altinn.Correspondence.API.Models; 5 | 6 | /// 7 | /// Request model for synchronizing correspondence notification events from Altinn 2 8 | /// 9 | public class SyncCorrespondenceNotificationEventRequestExt 10 | { 11 | [JsonPropertyName("correspondenceId")] 12 | public required Guid CorrespondenceId { get; set; } 13 | [JsonPropertyName("syncedNotificationEvents")] 14 | [MinLength(1, ErrorMessage = "At least one notification event is required.")] 15 | public required List SyncedEvents { get; set; } 16 | } 17 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Repositories/IServiceOwnerRepository.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Correspondence.Core.Models.Entities; 2 | using Altinn.Correspondence.Core.Models.Enums; 3 | 4 | namespace Altinn.Correspondence.Core.Repositories 5 | { 6 | public interface IServiceOwnerRepository 7 | { 8 | Task InitializeNewServiceOwner(string orgNo, string name, CancellationToken cancellationToken); 9 | Task GetServiceOwnerByOrgNo(string orgNo, CancellationToken cancellationToken); 10 | Task GetServiceOwnerByOrgCode(string orgCode, CancellationToken cancellationToken); 11 | Task InitializeStorageProvider(string orgNo, string storageAccountName, StorageProviderType storageType); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Test/Altinn.Correspondence.Tests/Factories/MalwareScanFactory.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Correspondence.Application.MalwareScanResult.Models; 2 | 3 | namespace Altinn.Correspondence.Tests.Factories 4 | { 5 | internal class MalwareScanFactory 6 | { 7 | internal static ScanResultData GetSuccessfulScanResult() => new ScanResultData() 8 | { 9 | BlobUri = "https://corrtestmigrations.blob.core.windows.net/attachments/arm-ttk.zip", 10 | CorrelationId = "77e1c215-d59d-43e0-a1d9-c479370998a8", 11 | ETag = "0x8DC83A7AD27459B", 12 | ScanResultDetails = null, 13 | ScanResultType = "No threats found", 14 | ScanFinishedTimeUtc = DateTime.Parse("2024-06-03T08:32:23.7483045Z") 15 | }; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Persistence/Repositories/AttachmentStatusRepository.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Correspondence.Core.Models.Entities; 2 | using Altinn.Correspondence.Core.Repositories; 3 | 4 | namespace Altinn.Correspondence.Persistence.Repositories 5 | { 6 | public class AttachmentStatusRepository(ApplicationDbContext context) : IAttachmentStatusRepository 7 | { 8 | private readonly ApplicationDbContext _context = context; 9 | 10 | public async Task AddAttachmentStatus(AttachmentStatusEntity status, CancellationToken cancellationToken) 11 | { 12 | await _context.AttachmentStatuses.AddAsync(status, cancellationToken); 13 | await _context.SaveChangesAsync(); 14 | return status.Id; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Repositories/IAltinnNotificationService.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Correspondence.Core.Models.Notifications; 2 | 3 | namespace Altinn.Correspondence.Core.Repositories; 4 | 5 | public interface IAltinnNotificationService 6 | { 7 | Task CreateNotificationV2(NotificationOrderRequestV2 notificationRequest, CancellationToken cancellationToken = default); 8 | Task CancelNotification(string orderId, CancellationToken cancellationToken = default); 9 | Task GetNotificationDetails(string orderId, CancellationToken cancellationToken = default); 10 | Task GetNotificationDetailsV2(string shipmentId, CancellationToken cancellationToken = default); 11 | } 12 | 13 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Integrations/Dialogporten/Helpers/TransmissionValidator.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Correspondence.Core.Models.Entities; 2 | using Altinn.Correspondence.Core.Models.Enums; 3 | 4 | namespace Altinn.Correspondence.Integrations.Dialogporten.Helpers 5 | { 6 | internal class TransmissionValidator 7 | { 8 | internal static bool IsTransmission(CorrespondenceEntity correspondence) 9 | { 10 | var transmissionId = correspondence.ExternalReferences.FirstOrDefault(reference => reference.ReferenceType == ReferenceType.DialogportenTransmissionId)?.ReferenceValue; 11 | if (transmissionId == null) 12 | { 13 | return false; 14 | } 15 | 16 | return true; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Models/Notifications/SmsNotificationSummary.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Altinn.Correspondence.Core.Models.Notifications 4 | { 5 | public class SmsNotificationSummary 6 | { 7 | [JsonPropertyName("orderId")] 8 | public Guid OrderId { get; set; } 9 | 10 | [JsonPropertyName("sendersReference")] 11 | public string? SendersReference { get; set; } 12 | 13 | [JsonPropertyName("generated")] 14 | public int Generated { get; set; } 15 | 16 | [JsonPropertyName("succeeded")] 17 | public int Succeeded { get; set; } 18 | 19 | [JsonPropertyName("notifications")] 20 | public List Notifications { get; set; } = []; 21 | } 22 | } -------------------------------------------------------------------------------- /.github/workflows/check-label-for-pr.yml: -------------------------------------------------------------------------------- 1 | name: Checking label for PR 2 | on: 3 | pull_request: 4 | types: [opened, reopened, ready_for_review, labeled, unlabeled] 5 | jobs: 6 | label: 7 | runs-on: ubuntu-latest 8 | permissions: 9 | pull-requests: write 10 | steps: 11 | - uses: mheap/github-action-required-labels@v5 12 | with: 13 | mode: minimum 14 | count: 1 15 | labels: | 16 | kind/breaking-change 17 | kind/feature 18 | kind/feature-request 19 | kind/bug 20 | kind/other 21 | kind/user-story 22 | kind/dependencies 23 | kind/documentation 24 | kind/enhancement 25 | kind/incident 26 | kind/chore 27 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.API/ValidationAttributes/OptionalEnumAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Altinn.Correspondence.API.ValidationAttributes; 4 | 5 | public class OptionalEnumAttribute : ValidationAttribute 6 | { 7 | protected override ValidationResult? IsValid(object? value, ValidationContext validationContext) 8 | { 9 | if (value is null) 10 | { 11 | return ValidationResult.Success; 12 | } 13 | 14 | if (!Enum.IsDefined(value.GetType(), value)) 15 | { 16 | var fieldName = validationContext.DisplayName; 17 | return new ValidationResult($"The {fieldName} field must be a defined value."); 18 | } 19 | 20 | return ValidationResult.Success; 21 | } 22 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Models/Entities/CorrespondenceMigrationStatusEntity.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using Altinn.Correspondence.Core.Models.Enums; 3 | 4 | namespace Altinn.Correspondence.Core.Models.Entities 5 | { 6 | /// 7 | /// Class used to describe current migration status for an Altinn 2 correspondence. 8 | /// 9 | public class CorrespondenceMigrationStatusEntity 10 | { 11 | [Key] 12 | public Guid? CorrespondenceId { get; set; } 13 | 14 | public int? Altinn2CorrespondenceId { get; set; } 15 | 16 | public CorrespondenceStatus? Status { get; set; } 17 | 18 | public List AttachmentStatus { get; set; } = new List(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.API/Models/InitializeCorrespondencesResponseExt.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Altinn.Correspondence.API.Models; 4 | 5 | /// 6 | /// Contains information about the created correspondences and their attachments. 7 | /// 8 | public class InitializeCorrespondencesResponseExt 9 | { 10 | /// 11 | /// The initialized correspondences 12 | /// 13 | [JsonPropertyName("correspondences")] 14 | public List Correspondences { get; set; } 15 | 16 | /// 17 | /// The IDs of the attachments that is included in the correspondences 18 | /// 19 | [JsonPropertyName("attachmentIds")] 20 | public List AttachmentIds { get; set; } 21 | } 22 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Common/Altinn.Correspondence.Common.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | exclude: 3 | labels: 4 | - ignore-for-release 5 | categories: 6 | - title: Breaking Changes 🛠 7 | labels: 8 | - kind/breaking-change 9 | - title: New Features 🎉 10 | labels: 11 | - kind/feature 12 | - kind/feature-request 13 | - title: Bugfixes 🐛 14 | labels: 15 | - kind/bug 16 | - title: Other Changes 17 | labels: 18 | - kind/other 19 | - kind/user-story 20 | - title: Dependency Upgrades 📦 21 | labels: 22 | - kind/dependencies 23 | - title: Enhancements 24 | labels: 25 | - kind/enhancement 26 | - title: Incident 27 | labels: 28 | - kind/incident 29 | - title: Uncategorized changes 30 | labels: 31 | - "*" 32 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.API/Models/ExternalReferenceExt.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Correspondence.API.Models.Enums; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace Altinn.Correspondence.API.Models 5 | { 6 | /// 7 | /// Represents a reference to another item in the Altinn ecosystem 8 | /// 9 | public class ExternalReferenceExt 10 | { 11 | /// 12 | /// The Reference Value 13 | /// 14 | [JsonPropertyName("referenceValue")] 15 | public required string ReferenceValue { get; set; } 16 | 17 | /// 18 | /// The Type of reference 19 | /// 20 | [JsonPropertyName("referenceType")] 21 | public required ReferenceTypeExt ReferenceType { get; set; } 22 | } 23 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.API/Models/MakeCorrespondenceAvailableResponseExt.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.API.Models; 2 | public class MakeCorrespondenceAvailableResponseExt 3 | { 4 | public List? Statuses { get; set; } 5 | } 6 | 7 | public class MakeCorrespondenceAvailableStatusExt 8 | { 9 | public MakeCorrespondenceAvailableStatusExt(Guid correspondenceId, string? error = null, string? dialogId = null, bool ok = false) 10 | { 11 | CorrespondenceId = correspondenceId; 12 | DialogId = dialogId; 13 | Ok = ok; 14 | Error = error; 15 | } 16 | public Guid CorrespondenceId { get; set; } 17 | public string? DialogId { get; set; } 18 | public string? Error { get; set; } 19 | public bool Ok { get; set; } = false; 20 | } 21 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Models/Enums/CorrespondencesRoleType.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Core.Models.Enums; 2 | /// 3 | /// Defines how to filter the correspondences returned by the GetCorrespondences endpoint 4 | /// 5 | public enum CorrespondencesRoleType 6 | { 7 | /// 8 | /// Only return the correspondences where the consumer is the recipient of the correspondence 9 | /// 10 | Recipient, 11 | 12 | /// 13 | /// Only return the correspondences where the consumer is the sender of the correspondence 14 | /// 15 | Sender, 16 | 17 | /// 18 | /// Only return the correspondences where the consumer is the recipient or sender of the correspondence 19 | /// 20 | RecipientAndSender, 21 | } -------------------------------------------------------------------------------- /.bruno/Attachment/Initialize batch attachment.bru: -------------------------------------------------------------------------------- 1 | meta { 2 | name: Initialize batch attachment 3 | type: http 4 | seq: 1 5 | } 6 | 7 | post { 8 | url: {{correspondence_base_url}}/correspondence/api/v1/attachment 9 | body: json 10 | auth: bearer 11 | } 12 | 13 | auth:bearer { 14 | token: {{serviceowner_altinn_token}} 15 | } 16 | 17 | body:json { 18 | { 19 | "resourceId": "{{resource_id}}", 20 | "sendersReference": "attachment-1", 21 | "fileName": "test-document.pdf", 22 | "dataType": "application/pdf", 23 | "intendedPresentation": "HumanReadable", 24 | "name": "Test Document", 25 | "restrictionName": "None", 26 | "isEncrypted": false 27 | } 28 | } 29 | 30 | script:post-response { 31 | const responseData = res.body; 32 | bru.setVar("attachment_id", responseData); 33 | } 34 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.API/ValidationAttributes/RequiredEnumAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Altinn.Correspondence.API.ValidationAttributes; 4 | public class RequiredEnumAttribute : ValidationAttribute 5 | { 6 | protected override ValidationResult? IsValid(object? value, ValidationContext validationContext) 7 | { 8 | var fieldName = validationContext.DisplayName; 9 | 10 | if (value is null) 11 | { 12 | return new ValidationResult($"The {fieldName} field is required."); 13 | } 14 | 15 | if (!Enum.IsDefined(value.GetType(), value)) 16 | { 17 | return new ValidationResult($"The {fieldName} field must be a defined value."); 18 | } 19 | 20 | return ValidationResult.Success; 21 | } 22 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Models/Notifications/NotificationOrderRequestResponseV2.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Core.Models.Notifications 2 | { 3 | public class NotificationOrderRequestResponseV2 4 | { 5 | public Guid NotificationOrderId { get; set; } 6 | 7 | public NotificationResponseV2 Notification { get; set; } = null!; 8 | } 9 | 10 | public class NotificationResponseV2 11 | { 12 | public List Reminders { get; set; } = new(); 13 | 14 | public Guid ShipmentId { get; set; } 15 | 16 | public string SendersReference { get; set; } = null!; 17 | } 18 | 19 | public class ReminderResponse 20 | { 21 | public Guid ShipmentId { get; set; } 22 | 23 | public string SendersReference { get; set; } = null!; 24 | } 25 | } -------------------------------------------------------------------------------- /.bruno/Events Integration/Create subscription (as end user system).bru: -------------------------------------------------------------------------------- 1 | meta { 2 | name: Create subscription (as end user system) 3 | type: http 4 | seq: 3 5 | } 6 | 7 | post { 8 | url: {{platform_base_url}}/events/api/v1/subscriptions/ 9 | body: json 10 | auth: bearer 11 | } 12 | 13 | auth:bearer { 14 | token: {{recipient_altinn_token}} 15 | } 16 | 17 | body:json { 18 | { 19 | "endpoint": "your-http-endpoint-that-returns-200", 20 | "resourceFilter": "urn:altinn:resource:{{resource_id}}", 21 | "subjectFilter": "urn:altinn:organization:identifier-no:{{recipient_orgnumber}}" 22 | } 23 | } 24 | 25 | script:post-response { 26 | const responseData = res.body; 27 | bru.setVar("event_subscription_id", responseData.id); 28 | } 29 | 30 | settings { 31 | encodeUrl: true 32 | timeout: 0 33 | } 34 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Models/Notifications/EmailNotificationSummary.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Altinn.Correspondence.Core.Models.Notifications 4 | { 5 | public class EmailNotificationSummary 6 | { 7 | [JsonPropertyName("orderId")] 8 | public Guid OrderId { get; set; } 9 | 10 | [JsonPropertyName("sendersReference")] 11 | public string? SendersReference { get; set; } 12 | 13 | [JsonPropertyName("generated")] 14 | public int Generated { get; set; } 15 | 16 | [JsonPropertyName("succeeded")] 17 | public int Succeeded { get; set; } 18 | 19 | [JsonPropertyName("notifications")] 20 | public List Notifications { get; set; } = new List(); 21 | } 22 | } -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Description 4 | 5 | 6 | ## Related Issue(s) 7 | - #{issue number} 8 | 9 | ## Verification 10 | - [ ] **Your** code builds clean without any errors or warnings 11 | - [ ] Manual testing done (required) 12 | - [ ] Relevant automated test added (if you find this hard, leave it and we'll help out) 13 | - [ ] All tests run green 14 | - [ ] If pre- or post-deploy actions (including database migrations) are needed, add a description, include a "Pre/Post-deploy actions" section below, and mark the PR title with ⚠️ 15 | 16 | ## Documentation 17 | - [ ] User documentation is updated with a separate linked PR in [altinn-studio-docs.](https://github.com/Altinn/altinn-studio-docs) (if applicable) -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Services/IResourceManager.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Correspondence.Core.Models.Entities; 2 | using Azure.ResourceManager.Network.Models; 3 | 4 | namespace Altinn.Correspondence.Core.Services; 5 | 6 | public interface IResourceManager 7 | { 8 | Task DeployStorageAccount(ServiceOwnerEntity serviceOwnerEntity, bool virusScan, CancellationToken cancellationToken); 9 | void DeployStorageAccountsForServiceOwner(ServiceOwnerEntity serviceOwnerEntity, CancellationToken cancellationToken); 10 | Task UpdateContainerAppIpRestrictionsAsync(Dictionary newIps, CancellationToken cancellationToken); 11 | Task RetrieveServiceTags(CancellationToken cancellationToken); 12 | Task> RetrieveCurrentIpRanges(CancellationToken cancellationToken); 13 | } 14 | -------------------------------------------------------------------------------- /Test/Altinn.Correspondence.LoadTests.DatabasePopulater/populate_test_database.sql: -------------------------------------------------------------------------------- 1 | CREATE OR REPLACE PROCEDURE populate_test_database(total_records bigint) 2 | LANGUAGE plpgsql 3 | AS $$ 4 | DECLARE 5 | batches int; 6 | remaining_records int; 7 | i int; 8 | BEGIN 9 | -- SET session_replication_role = replica; // Must be set in Azure DbServer Server Parameters 10 | batches := total_records / 1000; 11 | remaining_records := total_records % 1000; 12 | 13 | FOR i IN 1..batches LOOP 14 | RAISE NOTICE 'Processing batch % of %', i, batches; 15 | PERFORM generate_test_data(1000); 16 | END LOOP; 17 | 18 | IF remaining_records > 0 THEN 19 | PERFORM generate_test_data(remaining_records); 20 | END IF; 21 | -- SET session_replication_role = DEFAULT; 22 | END; 23 | $$; 24 | -------------------------------------------------------------------------------- /.bruno/Configure End User System/Create recipient systemuser request.bru: -------------------------------------------------------------------------------- 1 | meta { 2 | name: Create recipient systemuser request 3 | type: http 4 | seq: 5 5 | } 6 | 7 | post { 8 | url: {{platform_base_url}}/authentication/api/v1/systemuser/request/vendor 9 | body: json 10 | auth: bearer 11 | } 12 | 13 | auth:bearer { 14 | token: {{systemprovider_token}} 15 | } 16 | 17 | body:json { 18 | { 19 | "externalReference": "dev-test-create_01", 20 | "systemId": "{{serviceowner_orgnumber}}_{{resource_id}}_correspondence", 21 | "partyOrgNo": "{{recipient_orgnumber}}", 22 | "rights": [ 23 | { 24 | "Resource": [ 25 | { 26 | "id": "urn:altinn:resource", 27 | "value": "{{resource_id}}" 28 | } 29 | ] 30 | } 31 | ], 32 | "redirectUrl": "" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Models/Enums/IdempotencyType.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Core.Models.Enums; 2 | 3 | /// 4 | /// Represents different types of idempotency keys that can be used for a correspondence. 5 | /// This enum is used to support creating idempotent requests at the correspondence level. 6 | /// 7 | public enum IdempotencyType 8 | { 9 | /// 10 | /// Indicates that this is a key for activity on Dialogporten 11 | /// 12 | DialogportenActivity = 0, 13 | 14 | /// 15 | /// Indicates that this is a idempotency key for a correspondence 16 | /// 17 | Correspondence = 1, 18 | 19 | /// 20 | /// Indicates that this is a idempotency key for a notification order 21 | /// 22 | NotificationOrder = 2, 23 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Application/GetCorespondences/LegacyGetCorrespondencesRequest.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Correspondence.Core.Models.Enums; 2 | 3 | namespace Altinn.Correspondence.Application.GetCorrespondences; 4 | 5 | public class LegacyGetCorrespondencesRequest 6 | { 7 | public required int[] InstanceOwnerPartyIdList { get; set; } 8 | 9 | public bool IncludeActive { get; set; } 10 | 11 | public bool IncludeArchived { get; set; } 12 | 13 | public bool IncludeDeleted { get; set; } 14 | 15 | public bool FilterMigrated { get; set; } = true; 16 | 17 | public DateTimeOffset? From { get; set; } 18 | 19 | public DateTimeOffset? To { get; set; } 20 | 21 | public string? SearchString { get; set; } 22 | 23 | public string? Language { get; set; } 24 | 25 | public CorrespondenceStatus? Status { get; set; } 26 | } 27 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Models/Register/MainUnits.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Core.Models.Register 2 | { 3 | public class MainUnitsRequest 4 | { 5 | public string Data { get; set; } = string.Empty; 6 | } 7 | 8 | public class MainUnitsResponse 9 | { 10 | public List Data { get; set; } = new List(); 11 | } 12 | 13 | public class MainUnitItem 14 | { 15 | public string PartyType { get; set; } = string.Empty; 16 | public string OrganizationIdentifier { get; set; } = string.Empty; 17 | public Guid PartyUuid { get; set; } 18 | public long VersionId { get; set; } 19 | public string Urn { get; set; } = string.Empty; 20 | public int? PartyId { get; set; } 21 | public string DisplayName { get; set; } = string.Empty; 22 | } 23 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Integrations/Azure/AzureResourceManagerOptions.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Altinn.Correspondence.Integrations.Azure; 4 | 5 | public class AzureResourceManagerOptions 6 | { 7 | public string Location { get; set; } = string.Empty; 8 | [StringLength(7, ErrorMessage = "The environment can only be 7 characters long because of constraint on length of Azure storage account name")] 9 | public string Environment { get; set; } = string.Empty; 10 | public string SubscriptionId { get; set; } = string.Empty; 11 | public string ApplicationResourceGroupName { get; set; } = string.Empty; 12 | public string MalwareScanEventGridTopicName { get; set; } = string.Empty; 13 | public string ContainerAppName { get; set; } = string.Empty; 14 | public string ApimIP { get; set; } = string.Empty; 15 | } 16 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Models/Entities/AttachmentStatusEntity.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.ComponentModel.DataAnnotations.Schema; 3 | using Altinn.Correspondence.Core.Models.Enums; 4 | 5 | namespace Altinn.Correspondence.Core.Models.Entities 6 | { 7 | public class AttachmentStatusEntity 8 | { 9 | [Key] 10 | public Guid Id { get; set; } 11 | 12 | [Required] 13 | public AttachmentStatus Status { get; set; } 14 | 15 | public string StatusText { get; set; } = string.Empty; 16 | 17 | [Required] 18 | public DateTimeOffset StatusChanged { get; set; } 19 | 20 | public Guid AttachmentId { get; set; } 21 | [ForeignKey("AttachmentId")] 22 | public AttachmentEntity? Attachment { get; set; } 23 | public Guid PartyUuid { get; set; } 24 | 25 | } 26 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Services/Enums/AltinnEventType.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Core.Services.Enums; 2 | 3 | public enum AltinnEventType 4 | { 5 | AttachmentInitialized, 6 | AttachmentUploadProcessing, 7 | AttachmentPublished, 8 | AttachmentUploadFailed, 9 | AttachmentPurged, 10 | AttachmentDownloaded, 11 | AttachmentExpired, 12 | 13 | CorrespondenceInitialized, 14 | CorrespondencePublished, 15 | CorrespondenceArchived, 16 | CorrespondencePurged, 17 | CorrespondencePublishFailed, 18 | 19 | CorrespondenceReceiverRead, 20 | CorrespondenceReceiverConfirmed, 21 | CorrespondenceReceiverReplied, 22 | CorrespondenceReceiverNeverConfirmed, 23 | CorrespondenceReceiverReserved, 24 | CorrespondenceReceiverNeverRead, 25 | NotificationCreated, 26 | CorrespondenceNotificationCreationFailed, 27 | } 28 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Integrations/Hangfire/BackgroundJobServerFilter.cs: -------------------------------------------------------------------------------- 1 | using Hangfire.Common; 2 | using Hangfire.Server; 3 | 4 | namespace Altinn.Correspondence.Integrations.Hangfire; 5 | 6 | public class BackgroundJobServerFilter : JobFilterAttribute, IServerFilter 7 | { 8 | /// 9 | /// Set the background job context origin if the job has an Origin parameter. 10 | /// 11 | public void OnPerforming(PerformingContext context) 12 | { 13 | var origin = context.GetJobParameter("Origin"); 14 | if (!string.IsNullOrEmpty(origin)) 15 | { 16 | BackgroundJobContext.Origin = origin; 17 | } 18 | } 19 | 20 | public void OnPerformed(PerformedContext context) 21 | { 22 | // Clear after job completes 23 | BackgroundJobContext.Origin = null; 24 | } 25 | } 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Options/GeneralSettings.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Core.Options; 2 | 3 | public class GeneralSettings 4 | { 5 | public string SlackUrl { get; set; } = string.Empty; 6 | 7 | public string CorrespondenceBaseUrl { get; set; } = string.Empty; 8 | 9 | public string AltinnSblBridgeBaseUrl { get; set; } = string.Empty; 10 | public string RedisConnectionString { get; set; } = string.Empty; 11 | public string ContactReservationRegistryBaseUrl { get; set; } = string.Empty; 12 | public string BrregBaseUrl { get; set; } = string.Empty; 13 | public string ApplicationInsightsConnectionString { get; set; } = string.Empty; 14 | public bool DisableTelemetryForMigration { get; set; } = true; 15 | public bool DisableTelemetryForSync { get; set; } = false; 16 | public int MigrationWorkerCountPerReplica { get; set; } = 2; 17 | } 18 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Integrations/Hangfire/BackgroundJobClientFilter.cs: -------------------------------------------------------------------------------- 1 | using Hangfire.Client; 2 | using Hangfire.Common; 3 | 4 | namespace Altinn.Correspondence.Integrations.Hangfire; 5 | 6 | public class BackgroundJobClientFilter : JobFilterAttribute, IClientFilter 7 | { 8 | /// 9 | /// Set the Origin parameter on the new job if the background job context has an origin 10 | /// 11 | public void OnCreating(CreatingContext filterContext) 12 | { 13 | var origin = BackgroundJobContext.Origin; 14 | if (!string.IsNullOrEmpty(origin)) 15 | { 16 | // Set Origin parameter on new job if not present 17 | filterContext.SetJobParameter("Origin", origin); 18 | } 19 | } 20 | 21 | public void OnCreated(CreatedContext filterContext) 22 | { 23 | // no-op 24 | } 25 | } 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Models/Entities/CorrespondenceAttachmentEntity.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.ComponentModel.DataAnnotations.Schema; 3 | 4 | namespace Altinn.Correspondence.Core.Models.Entities 5 | { 6 | public class CorrespondenceAttachmentEntity 7 | { 8 | [Key] 9 | public Guid Id { get; set; } 10 | 11 | public DateTimeOffset Created { get; set; } 12 | 13 | [Required] 14 | public DateTimeOffset ExpirationTime { get; set; } 15 | 16 | public Guid CorrespondenceContentId { get; set; } 17 | [ForeignKey("CorrespondenceContentId")] 18 | public CorrespondenceContentEntity? CorrespondenceContent { get; set; } 19 | 20 | public Guid AttachmentId { get; set; } 21 | [ForeignKey("AttachmentId")] 22 | public AttachmentEntity? Attachment { get; set; } 23 | } 24 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Models/Register/Roles.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Core.Models.Register 2 | { 3 | public class Roles 4 | { 5 | public List Data { get; set; } = new List(); 6 | } 7 | 8 | public class RoleItem 9 | { 10 | public RoleDescriptor Role { get; set; } = default!; 11 | public RoleParty To { get; set; } = default!; 12 | public RoleParty From { get; set; } = default!; 13 | } 14 | 15 | public class RoleDescriptor 16 | { 17 | public string Source { get; set; } = string.Empty; 18 | public string Identifier { get; set; } = string.Empty; 19 | public string Urn { get; set; } = string.Empty; 20 | } 21 | 22 | public class RoleParty 23 | { 24 | public Guid PartyUuid { get; set; } 25 | public string Urn { get; set; } = string.Empty; 26 | } 27 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Models/Enums/NotificationChannel.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Core.Models.Enums; 2 | 3 | public enum NotificationChannel 4 | { 5 | /// 6 | /// The selected channel for the notification is only email. 7 | /// 8 | Email = 0, 9 | 10 | /// 11 | /// The selected channel for the notification is only sms. 12 | /// 13 | Sms = 1, 14 | 15 | /// 16 | /// The selected channel for the notification is email preferred. 17 | /// 18 | EmailPreferred = 2, 19 | 20 | /// 21 | /// The selected channel for the notification is sms preferred. 22 | /// 23 | SmsPreferred = 3, 24 | 25 | /// 26 | /// The selected channel for the notification is both email and sms. 27 | /// 28 | EmailAndSms = 4, 29 | } 30 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.API/Models/PaginationMetaDataExt.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Altinn.Correspondence.API.Models 4 | { 5 | /// 6 | /// An entity representing a list of Correspondences 7 | /// 8 | public class PaginationMetaDataExt 9 | { 10 | /// 11 | /// Total number of Correspondences 12 | /// 13 | [JsonPropertyName("totalItems")] 14 | public int TotalItems { get; set; } = 0; 15 | 16 | /// 17 | /// The given page number 18 | /// 19 | [JsonPropertyName("page")] 20 | public int Page { get; set; } = 1; 21 | 22 | /// 23 | /// The number of pages 24 | /// 25 | [JsonPropertyName("pages")] 26 | public int TotalPages { get; set; } = 1; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Models/Entities/CorrespondenceContentEntity.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.ComponentModel.DataAnnotations.Schema; 3 | 4 | namespace Altinn.Correspondence.Core.Models.Entities 5 | { 6 | public class CorrespondenceContentEntity 7 | { 8 | [Key] 9 | public Guid Id { get; set; } 10 | 11 | public required string Language { get; set; } 12 | 13 | public required string MessageTitle { get; set; } 14 | 15 | public required string MessageSummary { get; set; } 16 | 17 | public required string MessageBody { get; set; } 18 | 19 | public required List Attachments { get; set; } 20 | 21 | public Guid CorrespondenceId { get; set; } 22 | [ForeignKey("CorrespondenceId")] 23 | public CorrespondenceEntity? Correspondence { get; set; } 24 | } 25 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.API/Models/NotificationProcessStatusExt.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Altinn.Correspondence.API.Models; 4 | 5 | /// 6 | /// An abstract class representing a status overview of a notification channels 7 | /// 8 | public class NotificationProcessStatusExt 9 | { 10 | /// 11 | /// The actual status of the notification 12 | /// 13 | [JsonPropertyName("status")] 14 | public string Status { get; set; } = string.Empty; 15 | 16 | /// 17 | /// The description of the status 18 | /// 19 | [JsonPropertyName("description")] 20 | public string? StatusDescription { get; set; } 21 | 22 | /// 23 | /// The date time of when the status was last updated 24 | /// 25 | [JsonPropertyName("lastUpdate")] 26 | public DateTime LastUpdate { get; set; } 27 | } -------------------------------------------------------------------------------- /Test/Altinn.Correspondence.Tests/Data/MalwareScanResult_NoThreatFound.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "blobUri": "http://127.0.0.1:10000/devstoreaccount1/attachments/--FILEID--", 4 | "correlationId": "21c48159-e5ef-4376-ba96-4f8d6e0f1c7f", 5 | "eTag": "--ETAGID--", 6 | "scanFinishedTimeUtc": "2023-12-08T08:11:44.9457492Z", 7 | "scanResultDetails": null, 8 | "scanResultType": "No threats found" 9 | }, 10 | "dataVersion": "1.0", 11 | "eventTime": "2023-12-08T08:11:44.9464641Z", 12 | "eventType": "Microsoft.Security.MalwareScanningResult", 13 | "id": "21c48159-e5ef-4376-ba96-4f8d6e0f1c7f", 14 | "metadataVersion": "1", 15 | "subject": "storageAccounts/devstoreaccount1/containers/attachments/blobs/--FILEID--", 16 | "topic": "/subscriptions/81cc3a6b-dfdf-49c7-96f0-3efddb159356/resourceGroups/serviceowner-test-0192-991825827-rg/providers/Microsoft.EventGrid/topics/test-broker-defenderresults" 17 | } 18 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.API/Models/CorrespondenceReplyOptionExt.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Altinn.Correspondence.API.Models 4 | { 5 | /// 6 | /// Represents a ReplyOption with information provided by the sender. 7 | /// A reply option is a way for recipients to respond to a correspondence in addition to the normal Read and Confirm operations 8 | /// 9 | public class CorrespondenceReplyOptionExt 10 | { 11 | /// 12 | /// Gets or sets the URL to be used as a reply/response to a correspondence. 13 | /// 14 | [JsonPropertyName("linkURL")] 15 | public required string LinkURL { get; set; } 16 | 17 | /// 18 | /// Gets or sets the url text. 19 | /// 20 | [JsonPropertyName("linkText")] 21 | public string? LinkText { get; set; } 22 | } 23 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Models/Entities/IdempotencyKeyEntity.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.ComponentModel.DataAnnotations.Schema; 3 | using Altinn.Correspondence.Core.Models.Enums; 4 | 5 | 6 | namespace Altinn.Correspondence.Core.Models.Entities; 7 | 8 | public class IdempotencyKeyEntity 9 | { 10 | [Key] 11 | public Guid Id { get; set; } 12 | 13 | public Guid? CorrespondenceId { get; set; } 14 | 15 | [ForeignKey(nameof(CorrespondenceId))] 16 | public CorrespondenceEntity? Correspondence { get; set; } 17 | 18 | public Guid? AttachmentId { get; set; } 19 | 20 | [ForeignKey(nameof(AttachmentId))] 21 | public AttachmentEntity? Attachment { get; set; } 22 | 23 | public string? PartyUrn { get; set; } 24 | 25 | public StatusAction? StatusAction { get; set; } 26 | 27 | [Required] 28 | public IdempotencyType IdempotencyType { get; set; } 29 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Models/Entities/NotificationTemplateEntity.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using Altinn.Correspondence.Core.Models.Enums; 3 | 4 | namespace Altinn.Correspondence.Core.Models.Entities 5 | { 6 | public class NotificationTemplateEntity 7 | { 8 | [Key] 9 | public int Id { get; set; } 10 | 11 | public required NotificationTemplate Template { get; set; } 12 | public RecipientType? RecipientType { get; set; } 13 | public required string EmailSubject { get; set; } 14 | public required string EmailBody { get; set; } 15 | public required string SmsBody { get; set; } 16 | public required string ReminderEmailBody { get; set; } 17 | public required string ReminderEmailSubject { get; set; } 18 | public required string ReminderSmsBody { get; set; } 19 | public string? Language { get; set; } 20 | } 21 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Models/Enums/PartyType.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Core.Models.Enums 2 | { 3 | /// 4 | /// Enum containing values for the different types of parties 5 | /// 6 | public enum PartyType 7 | { 8 | /// 9 | /// Party Type is Person 10 | /// 11 | Person = 1, 12 | 13 | /// 14 | /// Party Type is Organization 15 | /// 16 | Organization = 2, 17 | 18 | /// 19 | /// Party Type is Self Identified user 20 | /// 21 | SelfIdentified = 3, 22 | 23 | /// 24 | /// Party Type is sub unit 25 | /// 26 | SubUnit = 4, 27 | 28 | /// 29 | /// Party Type is bankruptcy estate 30 | /// 31 | BankruptcyEstate = 5 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Integrations/Slack/SlackNotificationService.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Correspondence.Core.Options; 2 | using Microsoft.Extensions.Configuration; 3 | using Microsoft.Extensions.Hosting; 4 | using Microsoft.Extensions.Logging; 5 | using Slack.Webhooks; 6 | 7 | public class SlackNotificationService(IConfiguration configuration, 8 | ISlackClient slackClient, 9 | IHostEnvironment hostEnvironment, 10 | SlackSettings slackSettings, 11 | ILogger logger) 12 | { 13 | public async Task SendSlackMessage(string message) 14 | { 15 | logger.LogInformation($"Posting to Slack: {message}"); 16 | var slackMessage = new SlackMessage 17 | { 18 | Text = $"Slack alert from {hostEnvironment.EnvironmentName}: {message}", 19 | Channel = slackSettings.NotificationChannel, 20 | }; 21 | await slackClient.PostAsync(slackMessage); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Integrations/Dialogporten/Models/SetDialogSystemLabelRequest.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Altinn.Correspondence.Integrations.Dialogporten.Models; 4 | 5 | public class SetDialogSystemLabelRequest 6 | { 7 | [JsonPropertyName("dialogId")] 8 | public Guid DialogId { get; set; } 9 | 10 | [JsonPropertyName("enduserId")] 11 | public string EnduserId { get; set; } = string.Empty; 12 | 13 | [JsonPropertyName("addLabels")] 14 | public IReadOnlyCollection AddLabels { get; set; } = Array.Empty(); 15 | 16 | [JsonPropertyName("removeLabels")] 17 | public IReadOnlyCollection RemoveLabels { get; set; } = Array.Empty(); 18 | } 19 | 20 | // Enum for system labels (match API contract) 21 | public enum SystemLabel 22 | { 23 | Default = 1, 24 | Bin = 2, 25 | Archive = 3, 26 | MarkedAsUnopened = 4, 27 | Sent = 5 28 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.API/Models/MigrateInitializeAttachmentExt.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Altinn.Correspondence.API.Models; 4 | 5 | /// 6 | /// Represents a container object for attachments used when initiating a shared attachment 7 | /// 8 | public class MigrateInitializeAttachmentExt : InitializeAttachmentExt 9 | { 10 | [JsonPropertyName("senderPartyUuid")] 11 | public required Guid SenderPartyUuid { get; set; } 12 | 13 | [JsonPropertyName("altinn2AttachmentId")] 14 | public required string Altinn2AttachmentId { get; set; } 15 | 16 | [JsonPropertyName("created")] 17 | public required DateTimeOffset Created { get; set; } 18 | 19 | /// 20 | /// A reference value given to the attachment by the creator. 21 | /// 22 | [JsonPropertyName("altinn2sendersReference")] 23 | public string? Altinn2SendersReference { get; set; } = string.Empty; 24 | } 25 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Persistence/Repositories/NotificationTemplateRepository.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Correspondence.Core.Models.Entities; 2 | using Altinn.Correspondence.Core.Models.Enums; 3 | using Altinn.Correspondence.Core.Repositories; 4 | using Microsoft.EntityFrameworkCore; 5 | 6 | namespace Altinn.Correspondence.Persistence.Repositories 7 | { 8 | public class NotificationTemplateRepository(ApplicationDbContext context) : INotificationTemplateRepository 9 | { 10 | private readonly ApplicationDbContext _context = context; 11 | 12 | public async Task> GetNotificationTemplates(NotificationTemplate template, CancellationToken cancellationToken, string? language = null) 13 | { 14 | return await _context.NotificationTemplates.Where(a => a.Template == template && (a.Language == null || a.Language.ToLower() == language.ToLower())).ToListAsync(cancellationToken); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.API/Models/NotificationStatusExt.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Altinn.Correspondence.API.Models; 4 | 5 | /// 6 | /// A class representing a status summary 7 | /// 8 | /// 9 | /// External representation to be used in the API. 10 | /// 11 | public class NotificationStatusExt 12 | { 13 | /// 14 | /// The actual status of the notification 15 | /// 16 | [JsonPropertyName("status")] 17 | public string Status { get; set; } = string.Empty; 18 | 19 | /// 20 | /// The description of the status 21 | /// 22 | [JsonPropertyName("description")] 23 | public string? StatusDescription { get; set; } 24 | 25 | /// 26 | /// The date time of when the status was last updated 27 | /// 28 | [JsonPropertyName("lastUpdate")] 29 | public DateTime LastUpdate { get; set; } 30 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Repositories/IStorageRepository.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Correspondence.Core.Models.Entities; 2 | 3 | namespace Altinn.Correspondence.Core.Repositories 4 | { 5 | public interface IStorageRepository 6 | { 7 | Task<(string locationUrl, string hash, long size)> UploadAttachment(AttachmentEntity attachment, Stream stream, StorageProviderEntity? storageProviderEntity, CancellationToken cancellationToken); 8 | Task DownloadAttachment(Guid attachmentId, StorageProviderEntity? storageProviderEntity, CancellationToken cancellationToken); 9 | Task PurgeAttachment(Guid attachmentId, StorageProviderEntity? storageProviderEntity, CancellationToken cancellationToken); 10 | Task<(string locationUrl, string hash, long size)> UploadReportFile(string fileName, Stream stream, CancellationToken cancellationToken); 11 | Task DownloadReportFile(string fileName, CancellationToken cancellationToken); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Integrations/Altinn/Events/ConsoleLogEventBus.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Correspondence.Core.Services; 2 | using Altinn.Correspondence.Core.Services.Enums; 3 | 4 | using Microsoft.Extensions.Logging; 5 | 6 | namespace Altinn.Correspondence.Integrations.Altinn.Events; 7 | public class ConsoleLogEventBus : IEventBus 8 | { 9 | private readonly ILogger _logger; 10 | 11 | public ConsoleLogEventBus(ILogger logger) 12 | { 13 | _logger = logger; 14 | } 15 | 16 | public Task Publish(AltinnEventType type, string resourceId, string itemId, string eventSource, string? recipientId = null, CancellationToken cancellationToken = default) 17 | { 18 | _logger.LogInformation("{CloudEventType} event raised on instance {eventSource} {itemId} for party with organization number or ssn: {recipientId}", type.ToString(), eventSource, itemId, recipientId); 19 | return Task.CompletedTask; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.API/Models/MakeCorrespondenceAvailableRequestExt.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Altinn.Correspondence.API.Models; 4 | 5 | public class MakeCorrespondenceAvailableRequestExt 6 | { 7 | [JsonPropertyName("correspondenceId")] 8 | public Guid? CorrespondenceId { get; set; } 9 | 10 | [JsonPropertyName("createEvents")] 11 | public bool CreateEvents { get; set; } = false; 12 | 13 | [JsonPropertyName("correspondenceIds")] 14 | public List? CorrespondenceIds { get; set; } 15 | 16 | [JsonPropertyName("asyncProcessing")] 17 | public bool AsyncProcessing { get; set; } = false; 18 | 19 | [JsonPropertyName("batchSize")] 20 | public int? BatchSize { get; set; } 21 | 22 | [JsonPropertyName("createdFrom")] // inclusive 23 | public DateTimeOffset? CreatedFrom { get; set; } 24 | 25 | [JsonPropertyName("createdTo")] // exclusive 26 | public DateTimeOffset? CreatedTo { get; set; } 27 | } 28 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.API/Models/Enums/InitializeAttachmentDataLocationTypeExt.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.API.Models.Enums 2 | { 3 | /// 4 | /// Defines the location of the attachment data during the Initialize Correspondence Operation 5 | /// 6 | public enum InitializeAttachmentDataLocationTypeExt : int 7 | { 8 | /// 9 | /// Specifies that the attachment data will need to be uploaded to Altinn Correspondence via the Upload Attachment operation 10 | /// 11 | NewCorrespondenceAttachment = 0, 12 | 13 | /// 14 | /// Specifies that the attachment already exist in Altinn Correspondence storage 15 | /// 16 | ExistingCorrespondenceAttachment = 1, 17 | 18 | /// 19 | /// Specifies that the attachment data already exist in an external storage 20 | /// 21 | ExistingExternalStorage = 2 22 | } 23 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.API/Models/InitializeCorrespondenceAttachmentExt.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.Text.Json.Serialization; 3 | using Altinn.Correspondence.API.Models.Enums; 4 | 5 | namespace Altinn.Correspondence.API.Models 6 | { 7 | /// 8 | /// Represents an attachment to a specific correspondence as part of Initialize Correspondence Operation 9 | /// 10 | public class InitializeCorrespondenceAttachmentExt : BaseAttachmentExt 11 | { 12 | /// 13 | /// A unique id for the correspondence attachment. 14 | /// 15 | [JsonPropertyName("id")] 16 | public Guid Id { get; set; } 17 | 18 | /// 19 | /// Specifies the location type of the attachment data 20 | /// 21 | [JsonPropertyName("dataLocationType")] 22 | [Required] 23 | public InitializeAttachmentDataLocationTypeExt DataLocationType { get; set; } 24 | } 25 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Models/Enums/AttachmentStatus.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Core.Models.Enums 2 | { 3 | /// 4 | /// Represents the important statuses for an attachment 5 | /// 6 | public enum AttachmentStatus 7 | { 8 | /// 9 | /// Attachment has been Initialized. 10 | /// 11 | Initialized = 0, 12 | 13 | /// 14 | /// Awaiting processing of upload 15 | /// 16 | UploadProcessing = 1, 17 | 18 | // 19 | /// Published and available for download 20 | /// 21 | Published = 2, 22 | 23 | /// 24 | /// Purged 25 | /// 26 | Purged = 3, 27 | 28 | /// 29 | /// Failed 30 | /// 31 | Failed = 4, 32 | 33 | /// 34 | /// Expired 35 | /// 36 | Expired = 5 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Models/Notifications/RecipientLookupResult.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Altinn.Correspondence.Core.Models.Notifications; 4 | 5 | public class RecipientLookupResult 6 | { 7 | [JsonConverter(typeof(JsonStringEnumConverter))] 8 | public RecipientLookupStatus Status { get; set; } 9 | 10 | public List? IsReserved { get; set; } 11 | 12 | public List? MissingContact { get; set; } 13 | } 14 | 15 | /// 16 | /// Enum describing the success rate for recipient lookup 17 | /// 18 | public enum RecipientLookupStatus 19 | { 20 | /// 21 | /// The recipient lookup was successful for all recipients 22 | /// 23 | Success, 24 | 25 | /// 26 | /// The recipient lookup was successful for some recipients 27 | /// 28 | PartialSuccess, 29 | 30 | /// 31 | /// The recipient lookup failed for all recipients 32 | /// 33 | Failed 34 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Persistence/Repositories/LegacyPartyRepository.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Correspondence.Core.Models.Entities; 2 | using Altinn.Correspondence.Core.Repositories; 3 | using Microsoft.EntityFrameworkCore; 4 | 5 | namespace Altinn.Correspondence.Persistence.Repositories 6 | { 7 | public class LegacyPartyRepository(ApplicationDbContext context) : ILegacyPartyRepository 8 | { 9 | private readonly ApplicationDbContext _context = context; 10 | 11 | public async Task AddLegacyPartyId(int id, CancellationToken cancellationToken) 12 | { 13 | await _context.LegacyParties.AddAsync(new LegacyPartyEntity { PartyId = id }, cancellationToken); 14 | await _context.SaveChangesAsync(cancellationToken); 15 | } 16 | 17 | public async Task PartyAlreadyExists(int partyId, CancellationToken cancellationToken) 18 | { 19 | return await _context.LegacyParties.AnyAsync(p => p.PartyId == partyId, cancellationToken); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.API/Models/AtachmentStatusEvent.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Correspondence.API.Models.Enums; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace Altinn.Correspondence.API.Models 5 | { 6 | /// 7 | /// An entity representing a Attachment Status Event 8 | /// 9 | public class AttachmentStatusEvent 10 | { 11 | /// 12 | /// The attachment status 13 | /// 14 | [JsonPropertyName("status")] 15 | public AttachmentStatusExt Status { get; set; } 16 | 17 | /// 18 | /// Attachment status text description 19 | /// 20 | [JsonPropertyName("statusText")] 21 | public string StatusText { get; set; } = string.Empty; 22 | 23 | /// 24 | /// Timestamp for when the Attachment Status occurred 25 | /// 26 | [JsonPropertyName("statusChanged")] 27 | public DateTimeOffset StatusChanged { get; set; } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Integrations/Azure/MalwareScanConfiguration.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Integrations.Azure; 2 | 3 | internal class MalwareScanConfiguration 4 | { 5 | public required Properties Properties { get; set; } 6 | } 7 | 8 | internal class MalwareScanning 9 | { 10 | public required OnUpload OnUpload { get; set; } 11 | public required string ScanResultsEventGridTopicResourceId { get; set; } 12 | } 13 | 14 | internal class OnUpload 15 | { 16 | public required bool IsEnabled { get; set; } 17 | public required int CapGBPerMonth { get; set; } 18 | } 19 | 20 | internal class Properties 21 | { 22 | public required bool IsEnabled { get; set; } 23 | public required MalwareScanning MalwareScanning { get; set; } 24 | public required SensitiveDataDiscovery SensitiveDataDiscovery { get; set; } 25 | public required bool OverrideSubscriptionLevelSettings { get; set; } 26 | } 27 | 28 | internal class SensitiveDataDiscovery 29 | { 30 | public required bool IsEnabled { get; set; } 31 | } 32 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Persistence/Repositories/CorrespondenceForwardingEventRepository.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Correspondence.Core.Models.Entities; 2 | using Altinn.Correspondence.Core.Repositories; 3 | using Microsoft.Extensions.Logging; 4 | 5 | namespace Altinn.Correspondence.Persistence.Repositories; 6 | 7 | public class CorrespondenceForwardingEventRepository(ApplicationDbContext context, ILogger logger) : ICorrespondenceForwardingEventRepository 8 | { 9 | private readonly ApplicationDbContext _context = context; 10 | 11 | public async Task> AddForwardingEvents(List correspondenceForwardingEventEntities, CancellationToken cancellationToken) 12 | { 13 | await _context.CorrespondenceForwardingEvents.AddRangeAsync(correspondenceForwardingEventEntities, cancellationToken); 14 | await _context.SaveChangesAsync(cancellationToken); 15 | return correspondenceForwardingEventEntities; 16 | } 17 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Common/Helpers/Models/SystemUserAuthorizationDetails.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Altinn.Correspondence.Common.Helpers.Models; 4 | 5 | public class SystemUserAuthorizationDetails 6 | { 7 | [JsonPropertyName("type")] 8 | public string Type { get; set; } 9 | 10 | [JsonPropertyName("systemuser_id")] 11 | public List SystemUserId { get; set; } 12 | 13 | [JsonPropertyName("systemuser_org")] 14 | public SystemUserOrg SystemUserOrg { get; set; } 15 | 16 | [JsonPropertyName("system_id")] 17 | public string SystemId { get; set; } 18 | } 19 | 20 | public class SystemUserAuthorization 21 | { 22 | [JsonPropertyName("authorization_details")] 23 | public List AuthorizationDetails { get; set; } 24 | } 25 | 26 | public class SystemUserOrg 27 | { 28 | [JsonPropertyName("authority")] 29 | public string Authority { get; set; } 30 | 31 | [JsonPropertyName("ID")] 32 | public string ID { get; set; } 33 | } 34 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Models/Enums/CorrespondenceDeleteEventType.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Core.Models.Enums 2 | { 3 | /// 4 | /// Represents the important statuses for an Correspondence 5 | /// 6 | public enum CorrespondenceDeleteEventType : int 7 | { 8 | /// 9 | /// The correspondence was hard deleted by the sender/serviceOwner/Altinn, equivalent to purge 10 | /// 11 | HardDeletedByServiceOwner = 1, 12 | /// 13 | /// The correspondence was hard deleted by the recipient, equivalent to purge 14 | /// 15 | HardDeletedByRecipient = 2, 16 | /// 17 | /// The correspondence was soft deleted by the recipient 18 | /// 19 | SoftDeletedByRecipient = 3, 20 | /// 21 | /// The correspondence was restored by the recipient from soft delete 22 | /// 23 | RestoredByRecipient = 4 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Models/Entities/CorrespondenceDeleteEventEntity.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.ComponentModel.DataAnnotations.Schema; 3 | using Altinn.Correspondence.Core.Models.Enums; 4 | using Microsoft.EntityFrameworkCore; 5 | 6 | namespace Altinn.Correspondence.Core.Models.Entities 7 | { 8 | [Index(nameof(EventType))] 9 | public class CorrespondenceDeleteEventEntity 10 | { 11 | [Key] 12 | public Guid Id { get; set; } 13 | 14 | [Required] 15 | public CorrespondenceDeleteEventType EventType { get; set; } 16 | 17 | [Required] 18 | public DateTimeOffset EventOccurred { get; set; } 19 | 20 | [Required] 21 | public Guid CorrespondenceId { get; set; } 22 | 23 | [ForeignKey("CorrespondenceId")] 24 | public CorrespondenceEntity? Correspondence { get; set; } 25 | 26 | public Guid PartyUuid { get; set; } 27 | 28 | public DateTimeOffset? SyncedFromAltinn2 { get; set; } 29 | } 30 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Persistence/Migrations/20250619102557_AddLegacyIndices.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace Altinn.Correspondence.Persistence.Migrations 6 | { 7 | /// 8 | public partial class AddLegacyIndices : Migration 9 | { 10 | /// 11 | protected override void Up(MigrationBuilder migrationBuilder) 12 | { 13 | migrationBuilder.CreateIndex( 14 | name: "IX_Attachments_DataLocationUrl", 15 | schema: "correspondence", 16 | table: "Attachments", 17 | column: "DataLocationUrl"); 18 | } 19 | 20 | /// 21 | protected override void Down(MigrationBuilder migrationBuilder) 22 | { 23 | migrationBuilder.DropIndex( 24 | name: "IX_Attachments_DataLocationUrl", 25 | schema: "correspondence", 26 | table: "Attachments"); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.API/ValidationAttributes/ResourceIdentifierAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.Text.RegularExpressions; 3 | using Altinn.Correspondence.Common.Constants; 4 | 5 | namespace Altinn.Correspondence.API.Models; 6 | 7 | public class ResourceIdentifierAttribute : ValidationAttribute 8 | { 9 | private static readonly string Pattern = $@"^(?:{UrnConstants.Resource}:)?[^:]{{1,255}}$"; 10 | private static readonly Regex Regex = new(Pattern); 11 | protected override ValidationResult? IsValid(object? value, ValidationContext validationContext) 12 | { 13 | if (value is not string stringValue || !IsValidResourceFormat(stringValue)) 14 | { 15 | return new ValidationResult(ErrorMessage ?? "Invalid Resource identifier format"); 16 | } 17 | 18 | return ValidationResult.Success; 19 | } 20 | 21 | public static bool IsValidResourceFormat(string value) 22 | { 23 | return string.IsNullOrEmpty(value) || Regex.IsMatch(value); 24 | } 25 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.API/ValidationAttributes/OrganizationNumberAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.Text.RegularExpressions; 3 | using Altinn.Correspondence.Common.Constants; 4 | 5 | namespace Altinn.Correspondence.API.Models; 6 | public class OrganizationNumberAttribute : ValidationAttribute 7 | { 8 | private static readonly string Pattern = $@"^(?:0192:|{UrnConstants.OrganizationNumberAttribute}:)\d{{9}}$"; 9 | private static readonly Regex Regex = new(Pattern); 10 | protected override ValidationResult? IsValid(object? value, ValidationContext validationContext) 11 | { 12 | if (value is string stringValue && IsValidOrganizationNumber(stringValue)) 13 | { 14 | return ValidationResult.Success; 15 | } 16 | return new ValidationResult(ErrorMessage ?? "Invalid organization number format"); 17 | } 18 | public static bool IsValidOrganizationNumber(string value) 19 | { 20 | return !string.IsNullOrEmpty(value) && Regex.IsMatch(value); 21 | } 22 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Persistence/Migrations/20241014091946_AddedPublish.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace Altinn.Correspondence.Persistence.Migrations 6 | { 7 | /// 8 | public partial class AddedPublish : Migration 9 | { 10 | /// 11 | protected override void Up(MigrationBuilder migrationBuilder) 12 | { 13 | migrationBuilder.AddColumn( 14 | name: "Published", 15 | schema: "correspondence", 16 | table: "Correspondences", 17 | type: "timestamp with time zone", 18 | nullable: true); 19 | } 20 | 21 | /// 22 | protected override void Down(MigrationBuilder migrationBuilder) 23 | { 24 | migrationBuilder.DropColumn( 25 | name: "Published", 26 | schema: "correspondence", 27 | table: "Correspondences"); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Persistence/Migrations/20251203120630_IdempotencyBasedOnEndUser.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace Altinn.Correspondence.Persistence.Migrations 6 | { 7 | /// 8 | public partial class IdempotencyBasedOnEndUser : Migration 9 | { 10 | /// 11 | protected override void Up(MigrationBuilder migrationBuilder) 12 | { 13 | migrationBuilder.AddColumn( 14 | name: "PartyUrn", 15 | schema: "correspondence", 16 | table: "IdempotencyKeys", 17 | type: "text", 18 | nullable: true); 19 | } 20 | 21 | /// 22 | protected override void Down(MigrationBuilder migrationBuilder) 23 | { 24 | migrationBuilder.DropColumn( 25 | name: "PartyUrn", 26 | schema: "correspondence", 27 | table: "IdempotencyKeys"); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Persistence/Migrations/20250514112454_AddIsMigratingIndexdotnet.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace Altinn.Correspondence.Persistence.Migrations 6 | { 7 | /// 8 | public partial class AddIsMigratingIndexdotnet : Migration 9 | { 10 | /// 11 | protected override void Up(MigrationBuilder migrationBuilder) 12 | { 13 | migrationBuilder.CreateIndex( 14 | name: "IX_Correspondences_IsMigrating", 15 | schema: "correspondence", 16 | table: "Correspondences", 17 | column: "IsMigrating"); 18 | } 19 | 20 | /// 21 | protected override void Down(MigrationBuilder migrationBuilder) 22 | { 23 | migrationBuilder.DropIndex( 24 | name: "IX_Correspondences_IsMigrating", 25 | schema: "correspondence", 26 | table: "Correspondences"); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.github/actions/publish-image/action.yml: -------------------------------------------------------------------------------- 1 | name: Publish image 2 | 3 | description: "Publishes a docker image to GitHub Container Registry" 4 | 5 | inputs: 6 | dockerImageBaseName: 7 | description: "Base image name for docker images" 8 | required: true 9 | imageTag: 10 | description: "Version image tag" 11 | required: true 12 | GITHUB_TOKEN: 13 | description: "GitHub token" 14 | required: true 15 | default: ${{ github.token }} 16 | 17 | runs: 18 | using: "composite" 19 | steps: 20 | - uses: actions/checkout@v4 21 | 22 | - name: Login to GitHub Container Registry 23 | uses: docker/login-action@v3 24 | with: 25 | registry: ghcr.io 26 | username: ${{ github.actor }} 27 | password: ${{ inputs.GITHUB_TOKEN }} 28 | 29 | - name: Push image to registry 30 | shell: bash 31 | run: | 32 | # Construct the image tag using the Git hash 33 | IMAGE="${{ inputs.dockerImageBaseName }}:${{ inputs.imageTag }}" 34 | docker build . --tag $IMAGE 35 | docker push $IMAGE 36 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.API/Models/CorrespondenceStatusEventExt.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Correspondence.API.Models.Enums; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace Altinn.Correspondence.API.Models 5 | { 6 | /// 7 | /// An entity representing a Correspondence Status Event 8 | /// 9 | public class CorrespondenceStatusEventExt 10 | { 11 | /// 12 | /// Correspondence Status Event 13 | /// 14 | [JsonPropertyName("status")] 15 | public CorrespondenceStatusExt Status { get; set; } 16 | 17 | /// 18 | /// Correspondence Status Text description 19 | /// 20 | [JsonPropertyName("statusText")] 21 | public string StatusText { get; set; } = string.Empty; 22 | 23 | /// 24 | /// Timestamp for when this Correspondence Status Event occurred 25 | /// 26 | [JsonPropertyName("statusChanged")] 27 | public DateTimeOffset StatusChanged { get; set; } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Services/IAltinnRegisterService.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Correspondence.Core.Models.Entities; 2 | using Altinn.Correspondence.Core.Models.Register; 3 | 4 | namespace Altinn.Correspondence.Core.Services; 5 | public interface IAltinnRegisterService 6 | { 7 | Task LookUpPartyId(string identificationId, CancellationToken cancellationToken); 8 | Task LookUpName(string identificationId, CancellationToken cancellationToken); 9 | Task LookUpPartyByPartyId(int partyId, CancellationToken cancellationToken); 10 | Task LookUpPartyByPartyUuid(Guid partyUuid, CancellationToken cancellationToken); 11 | Task LookUpPartyById(string identificationId, CancellationToken cancellationToken); 12 | Task?> LookUpPartiesByIds(List identificationIds, CancellationToken cancellationToken); 13 | Task> LookUpPartyRoles(string partyUuid, CancellationToken cancellationToken); 14 | Task> LookUpMainUnits(string urn, CancellationToken cancellationToken); 15 | } 16 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Integrations/Altinn/Storage/AltinnStorageDevService.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Correspondence.Core.Models.Enums; 2 | using Altinn.Correspondence.Core.Services; 3 | 4 | namespace Altinn.Correspondence.Integrations.Altinn.Storage 5 | { 6 | public class AltinnStorageDevService : IAltinnStorageService 7 | { 8 | public async Task AddPartyToSblBridge(int partyId, CancellationToken cancellationToken = default) 9 | { 10 | if (partyId <= 0) 11 | { 12 | return false; 13 | } 14 | return true; 15 | } 16 | 17 | public async Task SyncCorrespondenceEventToSblBridge(int altinn2CorrespondenceId, int partyId, DateTimeOffset utcEventTimeStamp, SyncEventType eventType, CancellationToken cancellationToken) 18 | { 19 | if (partyId <= 0 || altinn2CorrespondenceId <= 0 || utcEventTimeStamp == DateTimeOffset.MinValue) 20 | { 21 | return false; 22 | } 23 | return true; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Persistence/Migrations/20250117073451_RemoveDataType.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace Altinn.Correspondence.Persistence.Migrations 6 | { 7 | /// 8 | public partial class RemoveDataType : Migration 9 | { 10 | /// 11 | protected override void Up(MigrationBuilder migrationBuilder) 12 | { 13 | migrationBuilder.DropColumn( 14 | name: "DataType", 15 | schema: "correspondence", 16 | table: "Attachments"); 17 | } 18 | 19 | /// 20 | protected override void Down(MigrationBuilder migrationBuilder) 21 | { 22 | migrationBuilder.AddColumn( 23 | name: "DataType", 24 | schema: "correspondence", 25 | table: "Attachments", 26 | type: "text", 27 | nullable: false, 28 | defaultValue: ""); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Integrations/Altinn/AccessManagement/AltinnAccessManagementDevService.cs: -------------------------------------------------------------------------------- 1 | 2 | using Altinn.Correspondence.Core.Models.Entities; 3 | using Altinn.Correspondence.Core.Models.Enums; 4 | using Altinn.Correspondence.Core.Repositories; 5 | 6 | namespace Altinn.Correspondence.Integrations.Altinn.AccessManagement; 7 | 8 | public class AltinnAccessManagementDevService : IAltinnAccessManagementService 9 | { 10 | private readonly int _digdirPartyId = 50952483; 11 | public Task> GetAuthorizedParties(Party partyToRequestFor, string? userId, CancellationToken cancellationToken = default) 12 | { 13 | PartyWithSubUnits party = new() 14 | { 15 | PartyId = _digdirPartyId, 16 | OrgNumber = "991825827", 17 | SSN = "", 18 | Resources = new List(), 19 | PartyTypeName = PartyType.Organization, 20 | UnitType = "Virksomhet", 21 | Name = "Digitaliseringsdirektoratet", 22 | }; 23 | return Task.FromResult(new List { party }); 24 | } 25 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Persistence/Migrations/20241008095547_MigIgnoreReservation.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace Altinn.Correspondence.Persistence.Migrations 6 | { 7 | /// 8 | public partial class MigIgnoreReservation : Migration 9 | { 10 | /// 11 | protected override void Up(MigrationBuilder migrationBuilder) 12 | { 13 | migrationBuilder.RenameColumn( 14 | name: "IsReservable", 15 | schema: "correspondence", 16 | table: "Correspondences", 17 | newName: "IgnoreReservation"); 18 | } 19 | 20 | /// 21 | protected override void Down(MigrationBuilder migrationBuilder) 22 | { 23 | migrationBuilder.RenameColumn( 24 | name: "IgnoreReservation", 25 | schema: "correspondence", 26 | table: "Correspondences", 27 | newName: "IsReservable"); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Persistence/Migrations/20250214073107_Add attachmentSize.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace Altinn.Correspondence.Persistence.Migrations 6 | { 7 | /// 8 | public partial class AddattachmentSize : Migration 9 | { 10 | /// 11 | protected override void Up(MigrationBuilder migrationBuilder) 12 | { 13 | migrationBuilder.AddColumn( 14 | name: "AttachmentSize", 15 | schema: "correspondence", 16 | table: "Attachments", 17 | type: "bigint", 18 | nullable: false, 19 | defaultValue: 0L); 20 | } 21 | 22 | /// 23 | protected override void Down(MigrationBuilder migrationBuilder) 24 | { 25 | migrationBuilder.DropColumn( 26 | name: "AttachmentSize", 27 | schema: "correspondence", 28 | table: "Attachments"); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Persistence/Migrations/20251118141328_AddResourceIdIndexForAttachments.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace Altinn.Correspondence.Persistence.Migrations 6 | { 7 | /// 8 | public partial class AddResourceIdIndexForAttachments : Migration 9 | { 10 | /// 11 | protected override void Up(MigrationBuilder migrationBuilder) 12 | { 13 | migrationBuilder.Sql( 14 | "CREATE INDEX CONCURRENTLY IF NOT EXISTS \"IX_Attachments_ResourceId\" " + 15 | "ON correspondence.\"Attachments\" (\"ResourceId\");", 16 | suppressTransaction: true); 17 | } 18 | 19 | /// 20 | protected override void Down(MigrationBuilder migrationBuilder) 21 | { 22 | migrationBuilder.Sql( 23 | "DROP INDEX CONCURRENTLY IF EXISTS correspondence.\"IX_Attachments_ResourceId\";", 24 | suppressTransaction: true); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Test/Altinn.Correspondence.Tests/Data/MalwareScanResult_Malicious.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "blobUri": "http://127.0.0.1:10000/devstoreaccount1/attachments/--FILEID--", 4 | "correlationId": "2ee9f258-c96a-4982-9e6e-16b8485d71da", 5 | "eTag": "--ETAGID--", 6 | "scanFinishedTimeUtc": "2023-12-08T08:12:31.9933275Z", 7 | "scanResultDetails": { 8 | "malwareNamesFound": [ 9 | "Virus:DOS/EICAR_Test_File" 10 | ], 11 | "sha256": "275A021BBFB6489E54D471899F7DB9D1663FC695EC2FE2A2C4538AABF651FD0F" 12 | }, 13 | "scanResultType": "Malicious" 14 | }, 15 | "dataVersion": "1.0", 16 | "eventTime": "2023-12-08T08:12:31.9939079Z", 17 | "eventType": "Microsoft.Security.MalwareScanningResult", 18 | "id": "2ee9f258-c96a-4982-9e6e-16b8485d71da", 19 | "metadataVersion": "1", 20 | "subject": "storageAccounts/devstoreaccount1/containers/attachments/blobs/--FILEID--", 21 | "topic": "/subscriptions/81cc3a6b-dfdf-49c7-96f0-3efddb159356/resourceGroups/serviceowner-test-0192-991825827-rg/providers/Microsoft.EventGrid/topics/test-broker-defenderresults" 22 | } 23 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Application/MalwareScanResult/Models/ScanResultData.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Altinn.Correspondence.Application.MalwareScanResult.Models; 4 | 5 | public class ScanResultDetails 6 | { 7 | [JsonPropertyName("malwareNamesFound")] 8 | public List MalwareNamesFound { get; set; } 9 | 10 | [JsonPropertyName("sha256")] 11 | public string Sha256 { get; set; } 12 | } 13 | 14 | public class ScanResultData 15 | { 16 | [JsonPropertyName("blobUri")] 17 | public string BlobUri { get; set; } 18 | 19 | [JsonPropertyName("correlationId")] 20 | public string CorrelationId { get; set; } 21 | 22 | [JsonPropertyName("eTag")] 23 | public string ETag { get; set; } 24 | 25 | [JsonPropertyName("scanFinishedTimeUtc")] 26 | public DateTime? ScanFinishedTimeUtc { get; set; } 27 | 28 | [JsonPropertyName("scanResultDetails")] 29 | public ScanResultDetails ScanResultDetails { get; set; } 30 | 31 | [JsonPropertyName("scanResultType")] 32 | public string ScanResultType { get; set; } 33 | } 34 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Persistence/Migrations/20250221124036_Notification_Order_Request.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace Altinn.Correspondence.Persistence.Migrations 6 | { 7 | /// 8 | public partial class Notification_Order_Request : Migration 9 | { 10 | /// 11 | protected override void Up(MigrationBuilder migrationBuilder) 12 | { 13 | migrationBuilder.AddColumn( 14 | name: "OrderRequest", 15 | schema: "correspondence", 16 | table: "CorrespondenceNotifications", 17 | type: "text", 18 | nullable: true); 19 | } 20 | 21 | /// 22 | protected override void Down(MigrationBuilder migrationBuilder) 23 | { 24 | migrationBuilder.DropColumn( 25 | name: "OrderRequest", 26 | schema: "correspondence", 27 | table: "CorrespondenceNotifications"); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.API/Models/Enums/NotificationChannelExt.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.API.Models.Enums 2 | { 3 | /// 4 | /// Enum describing available notification channels. 5 | /// 6 | public enum NotificationChannelExt 7 | { 8 | /// 9 | /// The selected channel for the notification is only email. 10 | /// 11 | Email = 0, 12 | 13 | /// 14 | /// The selected channel for the notification is only sms. 15 | /// 16 | Sms = 1, 17 | 18 | /// 19 | /// The selected channel for the notification is email preferred. 20 | /// 21 | EmailPreferred = 2, 22 | 23 | /// 24 | /// The selected channel for the notification is SMS preferred. 25 | /// 26 | SmsPreferred = 3, 27 | 28 | /// 29 | /// The selected channel for the notification is both email and sms. 30 | /// 31 | EmailAndSms = 4, 32 | } 33 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Persistence/Migrations/20241010081929_MigrationRequestPublishTime.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace Altinn.Correspondence.Persistence.Migrations 6 | { 7 | /// 8 | public partial class MigrationRequestPublishTime : Migration 9 | { 10 | /// 11 | protected override void Up(MigrationBuilder migrationBuilder) 12 | { 13 | migrationBuilder.RenameColumn( 14 | name: "VisibleFrom", 15 | schema: "correspondence", 16 | table: "Correspondences", 17 | newName: "RequestedPublishTime"); 18 | } 19 | 20 | /// 21 | protected override void Down(MigrationBuilder migrationBuilder) 22 | { 23 | migrationBuilder.RenameColumn( 24 | name: "RequestedPublishTime", 25 | schema: "correspondence", 26 | table: "Correspondences", 27 | newName: "VisibleFrom"); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Persistence/Migrations/20241029141456_Remove_RestrictionNameFromAttachmentEntity.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace Altinn.Correspondence.Persistence.Migrations 6 | { 7 | /// 8 | public partial class Remove_RestrictionNameFromAttachmentEntity : Migration 9 | { 10 | /// 11 | protected override void Up(MigrationBuilder migrationBuilder) 12 | { 13 | migrationBuilder.DropColumn( 14 | name: "RestrictionName", 15 | schema: "correspondence", 16 | table: "Attachments"); 17 | } 18 | 19 | /// 20 | protected override void Down(MigrationBuilder migrationBuilder) 21 | { 22 | migrationBuilder.AddColumn( 23 | name: "RestrictionName", 24 | schema: "correspondence", 25 | table: "Attachments", 26 | type: "text", 27 | nullable: true); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Persistence/Migrations/20241111104028_Remove_MarkedUnreadFromCorrespondenceEntity.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace Altinn.Correspondence.Persistence.Migrations 6 | { 7 | /// 8 | public partial class Remove_MarkedUnreadFromCorrespondenceEntity : Migration 9 | { 10 | /// 11 | protected override void Up(MigrationBuilder migrationBuilder) 12 | { 13 | migrationBuilder.DropColumn( 14 | name: "MarkedUnread", 15 | schema: "correspondence", 16 | table: "Correspondences"); 17 | } 18 | 19 | /// 20 | protected override void Down(MigrationBuilder migrationBuilder) 21 | { 22 | migrationBuilder.AddColumn( 23 | name: "MarkedUnread", 24 | schema: "correspondence", 25 | table: "Correspondences", 26 | type: "boolean", 27 | nullable: true); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Persistence/Migrations/20250527111747_AddIsConfidentialFlag.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace Altinn.Correspondence.Persistence.Migrations 6 | { 7 | /// 8 | public partial class AddIsConfidentialFlag : Migration 9 | { 10 | /// 11 | protected override void Up(MigrationBuilder migrationBuilder) 12 | { 13 | migrationBuilder.AddColumn( 14 | name: "IsConfidential", 15 | schema: "correspondence", 16 | table: "Correspondences", 17 | type: "boolean", 18 | nullable: false, 19 | defaultValue: false); 20 | } 21 | 22 | /// 23 | protected override void Down(MigrationBuilder migrationBuilder) 24 | { 25 | migrationBuilder.DropColumn( 26 | name: "IsConfidential", 27 | schema: "correspondence", 28 | table: "Correspondences"); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.API/Models/NotificationDetailsExt.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Altinn.Correspondence.API.Models; 4 | 5 | /// 6 | /// An abstract class representing a status overview of a notification channels 7 | /// 8 | public class NotificationDetailsExt 9 | { 10 | /// 11 | /// The notification id 12 | /// 13 | [JsonPropertyName("id")] 14 | public Guid? Id { get; set; } 15 | 16 | /// 17 | /// Boolean indicating if the sending of the notification was successful 18 | /// 19 | [JsonPropertyName("succeeded")] 20 | public bool Succeeded { get; set; } 21 | 22 | /// 23 | /// The recipient of the notification 24 | /// 25 | [JsonPropertyName("recipient")] 26 | public NotificationRecipientExt Recipient { get; set; } = new(); 27 | 28 | /// 29 | /// The result status of the notification 30 | /// 31 | [JsonPropertyName("sendStatus")] 32 | public NotificationStatusExt SendStatus { get; set; } = new(); 33 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Persistence/Migrations/20250507210753_CorrespondenceNotification_ShipmentId.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace Altinn.Correspondence.Persistence.Migrations 6 | { 7 | /// 8 | public partial class CorrespondenceNotification_ShipmentId : Migration 9 | { 10 | /// 11 | protected override void Up(MigrationBuilder migrationBuilder) 12 | { 13 | migrationBuilder.AddColumn( 14 | name: "ShipmentId", 15 | schema: "correspondence", 16 | table: "CorrespondenceNotifications", 17 | type: "uuid", 18 | nullable: true); 19 | } 20 | 21 | /// 22 | protected override void Down(MigrationBuilder migrationBuilder) 23 | { 24 | migrationBuilder.DropColumn( 25 | name: "ShipmentId", 26 | schema: "correspondence", 27 | table: "CorrespondenceNotifications"); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.API/Helpers/TokenHelper.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Correspondence.Core.Options; 2 | using Microsoft.AspNetCore.Authorization; 3 | 4 | namespace Altinn.Correspondence.API.Helpers 5 | { 6 | public static class TokenHelper 7 | { 8 | public static AuthorizationPolicyBuilder RequireScopeIfAltinn(this AuthorizationPolicyBuilder authorizationPolicyBuilder, IConfiguration config, params string[] scopes) => 9 | authorizationPolicyBuilder.RequireAssertion(context => 10 | { 11 | var altinnOptions = new AltinnOptions(); 12 | config.GetSection(nameof(AltinnOptions)).Bind(altinnOptions); 13 | bool isAltinnToken = context.User.HasClaim(c => c.Issuer == $"{altinnOptions.PlatformGatewayUrl.TrimEnd('/')}/authentication/api/v1/openid/"); 14 | if (isAltinnToken) 15 | { 16 | return context.User.HasClaim(c => c.Type == "scope" && scopes.Intersect(c.Value.Split(' ')).Any()); 17 | } 18 | return true; 19 | }); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Persistence/Migrations/20250227132312_Add_Attachment_DisplayName.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace Altinn.Correspondence.Persistence.Migrations 6 | { 7 | /// 8 | public partial class Add_Attachment_DisplayName : Migration 9 | { 10 | /// 11 | protected override void Up(MigrationBuilder migrationBuilder) 12 | { 13 | migrationBuilder.AddColumn( 14 | name: "DisplayName", 15 | schema: "correspondence", 16 | table: "Attachments", 17 | type: "character varying(255)", 18 | maxLength: 255, 19 | nullable: true); 20 | } 21 | 22 | /// 23 | protected override void Down(MigrationBuilder migrationBuilder) 24 | { 25 | migrationBuilder.DropColumn( 26 | name: "DisplayName", 27 | schema: "correspondence", 28 | table: "Attachments"); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.API/Models/Enums/AttachmentStatus.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.API.Models.Enums 2 | { 3 | /// 4 | /// Represents the important statuses for an attachment 5 | /// 6 | public enum AttachmentStatusExt 7 | { 8 | /// 9 | /// Attachment has been Initialized. 10 | /// 11 | Initialized = 0, 12 | 13 | /// 14 | /// Attachment is awaiting processing of upload 15 | /// 16 | UploadProcessing = 1, 17 | 18 | /// 19 | /// Attachment is published and available for download 20 | /// 21 | Published = 2, 22 | 23 | /// 24 | /// Attachment has been purged 25 | /// 26 | Purged = 3, 27 | 28 | /// 29 | /// Attachment has failed during processing 30 | /// 31 | Failed = 4, 32 | 33 | /// 34 | /// Attachment has expired 35 | /// 36 | Expired = 5 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Persistence/Migrations/20251027140102_AddExpirationTimeToAttachments.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore.Migrations; 3 | 4 | #nullable disable 5 | 6 | namespace Altinn.Correspondence.Persistence.Migrations 7 | { 8 | /// 9 | public partial class AddExpirationTimeToAttachments : Migration 10 | { 11 | /// 12 | protected override void Up(MigrationBuilder migrationBuilder) 13 | { 14 | migrationBuilder.AddColumn( 15 | name: "ExpirationTime", 16 | schema: "correspondence", 17 | table: "Attachments", 18 | type: "timestamp with time zone", 19 | nullable: true); 20 | } 21 | 22 | /// 23 | protected override void Down(MigrationBuilder migrationBuilder) 24 | { 25 | migrationBuilder.DropColumn( 26 | name: "ExpirationTime", 27 | schema: "correspondence", 28 | table: "Attachments"); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Persistence/Migrations/20240926100009_Notification_isReminder.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace Altinn.Correspondence.Persistence.Migrations 6 | { 7 | /// 8 | public partial class Notification_isReminder : Migration 9 | { 10 | /// 11 | protected override void Up(MigrationBuilder migrationBuilder) 12 | { 13 | migrationBuilder.AddColumn( 14 | name: "IsReminder", 15 | schema: "correspondence", 16 | table: "CorrespondenceNotifications", 17 | type: "boolean", 18 | nullable: false, 19 | defaultValue: false); 20 | } 21 | 22 | /// 23 | protected override void Down(MigrationBuilder migrationBuilder) 24 | { 25 | migrationBuilder.DropColumn( 26 | name: "IsReminder", 27 | schema: "correspondence", 28 | table: "CorrespondenceNotifications"); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Persistence/Migrations/20241024103535_AddedIsConfirmationNeeded.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace Altinn.Correspondence.Persistence.Migrations 6 | { 7 | /// 8 | public partial class AddedIsConfirmationNeeded : Migration 9 | { 10 | /// 11 | protected override void Up(MigrationBuilder migrationBuilder) 12 | { 13 | migrationBuilder.AddColumn( 14 | name: "IsConfirmationNeeded", 15 | schema: "correspondence", 16 | table: "Correspondences", 17 | type: "boolean", 18 | nullable: false, 19 | defaultValue: false); 20 | } 21 | 22 | /// 23 | protected override void Down(MigrationBuilder migrationBuilder) 24 | { 25 | migrationBuilder.DropColumn( 26 | name: "IsConfirmationNeeded", 27 | schema: "correspondence", 28 | table: "Correspondences"); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Persistence/Migrations/20250220130047_AddIsMigratingFlagToCorrespondences.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace Altinn.Correspondence.Persistence.Migrations 6 | { 7 | /// 8 | public partial class AddIsMigratingFlagToCorrespondences : Migration 9 | { 10 | /// 11 | protected override void Up(MigrationBuilder migrationBuilder) 12 | { 13 | migrationBuilder.AddColumn( 14 | name: "IsMigrating", 15 | schema: "correspondence", 16 | table: "Correspondences", 17 | type: "boolean", 18 | nullable: false, 19 | defaultValue: false); 20 | } 21 | 22 | /// 23 | protected override void Down(MigrationBuilder migrationBuilder) 24 | { 25 | migrationBuilder.DropColumn( 26 | name: "IsMigrating", 27 | schema: "correspondence", 28 | table: "Correspondences"); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.azure/modules/containerApp/fetchEventGridIps.bicep: -------------------------------------------------------------------------------- 1 | param location string 2 | 3 | @secure() 4 | param principal_id string 5 | 6 | resource deploymentScript 'Microsoft.Resources/deploymentScripts@2023-08-01' = { 7 | name: 'fetchAzureEventGridIpsScript' 8 | location: location 9 | kind: 'AzurePowerShell' 10 | identity: { 11 | type: 'UserAssigned' 12 | userAssignedIdentities: { 13 | '${principal_id}': {} 14 | } 15 | } 16 | properties: { 17 | azPowerShellVersion: '13.0' 18 | scriptContent: ''' 19 | param([string] $location) 20 | $serviceTags = Get-AzNetworkServiceTag -Location $location 21 | $EventgridIps = $serviceTags.Values | Where-Object { $_.Name -eq "AzureEventGrid" } 22 | $output = $EventgridIps.Properties.AddressPrefixes | Where-Object { $_ -notmatch ":" } 23 | $DeploymentScriptOutputs = @{} 24 | $DeploymentScriptOutputs['eventGridIps'] = $output 25 | ''' 26 | arguments: '-location ${location}' 27 | forceUpdateTag: '1' 28 | retentionInterval: 'PT2H' 29 | } 30 | } 31 | 32 | output eventGridIps array = deploymentScript.properties.outputs.eventGridIps 33 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.API/ValidationAttributes/MD5ChecksumAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace Altinn.Correspondence.API.Models 4 | { 5 | internal class MD5ChecksumAttribute : ValidationAttribute 6 | { 7 | public MD5ChecksumAttribute() 8 | { 9 | } 10 | 11 | protected override ValidationResult? IsValid(object? value, ValidationContext validationContext) 12 | { 13 | var stringValue = value as string; 14 | if (string.IsNullOrWhiteSpace(stringValue)) 15 | { 16 | return ValidationResult.Success; 17 | } 18 | if (stringValue.Length != 32) 19 | { 20 | return new ValidationResult("The checksum, if used, must be a MD5 hash with a length of 32 characters"); 21 | } 22 | if (stringValue.ToLowerInvariant() != stringValue) 23 | { 24 | return new ValidationResult("The checksum, if used, must be a MD5 hash in lower case"); 25 | } 26 | return ValidationResult.Success; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Persistence/Migrations/20250220090415_Remove_Attachment_Logical_Name.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace Altinn.Correspondence.Persistence.Migrations 6 | { 7 | /// 8 | public partial class Remove_Attachment_Logical_Name : Migration 9 | { 10 | /// 11 | protected override void Up(MigrationBuilder migrationBuilder) 12 | { 13 | migrationBuilder.DropColumn( 14 | name: "Name", 15 | schema: "correspondence", 16 | table: "Attachments"); 17 | } 18 | 19 | /// 20 | protected override void Down(MigrationBuilder migrationBuilder) 21 | { 22 | migrationBuilder.AddColumn( 23 | name: "Name", 24 | schema: "correspondence", 25 | table: "Attachments", 26 | type: "character varying(255)", 27 | maxLength: 255, 28 | nullable: false, 29 | defaultValue: ""); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.API/Mappers/AttachmentStatusMapper.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Correspondence.API.Models; 2 | using Altinn.Correspondence.API.Models.Enums; 3 | using Altinn.Correspondence.Core.Models.Entities; 4 | 5 | 6 | namespace Altinn.Correspondence.Mappers; 7 | 8 | internal static class AttachmentStatusMapper 9 | { 10 | internal static AttachmentStatusEvent MapToExternal(AttachmentStatusEntity AttachmentStatus) 11 | { 12 | var attachment = new AttachmentStatusEvent 13 | { 14 | Status = (AttachmentStatusExt)AttachmentStatus.Status, 15 | StatusText = AttachmentStatus.StatusText, 16 | StatusChanged = AttachmentStatus.StatusChanged 17 | }; 18 | return attachment; 19 | } 20 | 21 | internal static List MapToExternal(List AttachmentStatuses) 22 | { 23 | var attachmentStatuses = new List(); 24 | foreach (var status in AttachmentStatuses) 25 | { 26 | attachmentStatuses.Add(MapToExternal(status)); 27 | } 28 | return attachmentStatuses; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.API/ValidationAttributes/OrganizationNumberOptionalAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.Text.RegularExpressions; 3 | using Altinn.Correspondence.Common.Constants; 4 | 5 | namespace Altinn.Correspondence.API.Models; 6 | public class OrganizationNumberOptionalAttribute : ValidationAttribute 7 | { 8 | private static readonly string Pattern = $@"^(?:0192:|{UrnConstants.OrganizationNumberAttribute}:)\d{{9}}$"; 9 | private static readonly Regex Regex = new(Pattern); 10 | protected override ValidationResult? IsValid(object? value, ValidationContext validationContext) 11 | { 12 | if(value == null) 13 | { 14 | return ValidationResult.Success; 15 | } 16 | if (value is string stringValue && IsValidOrganizationNumber(stringValue)) 17 | { 18 | return ValidationResult.Success; 19 | } 20 | return new ValidationResult(ErrorMessage ?? "Invalid organization number format"); 21 | } 22 | public static bool IsValidOrganizationNumber(string value) 23 | { 24 | return Regex.IsMatch(value); 25 | } 26 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Application/SyncCorrespondenceEvent/SyncEventDateTimeOffsetExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Correspondence.Application.SyncCorrespondenceEvent; 2 | 3 | /// 4 | /// Extensions for DateTimeOffset to compare equality within a second, useful for evaluating duplicate sync events. 5 | /// 6 | public static class SyncEventDateTimeOffsetExtensions 7 | { 8 | /// 9 | /// Compares two DateTimeOffset instances for equality within a second, which is useful for determining if two sync events are duplicates. 10 | /// 11 | /// 12 | /// 13 | /// 14 | public static bool EqualsToSecond(this DateTimeOffset dto1, DateTimeOffset dto2) 15 | { 16 | return dto1.TruncateToSecondUtc() == dto2.TruncateToSecondUtc(); 17 | } 18 | 19 | public static DateTimeOffset TruncateToSecondUtc(this DateTimeOffset value) 20 | { 21 | var utc = value.ToUniversalTime(); 22 | return new DateTimeOffset(utc.Year, utc.Month, utc.Day, utc.Hour, utc.Minute, utc.Second, TimeSpan.Zero); 23 | } 24 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Core/Models/Entities/CorrespondenceStatusEntity.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Correspondence.Core.Models.Enums; 2 | using Microsoft.EntityFrameworkCore; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.ComponentModel.DataAnnotations.Schema; 5 | 6 | namespace Altinn.Correspondence.Core.Models.Entities 7 | { 8 | [Index(nameof(Status))] 9 | [Index(nameof(CorrespondenceId), nameof(Status), IsDescending = [false, true])] 10 | public class CorrespondenceStatusEntity 11 | { 12 | [Key] 13 | public Guid Id { get; set; } 14 | 15 | [Required] 16 | public CorrespondenceStatus Status { get; set; } 17 | 18 | public string StatusText { get; set; } = string.Empty; 19 | 20 | [Required] 21 | public DateTimeOffset StatusChanged { get; set; } 22 | 23 | public Guid CorrespondenceId { get; set; } 24 | 25 | [ForeignKey("CorrespondenceId")] 26 | public CorrespondenceEntity? Correspondence { get; set; } 27 | 28 | public Guid PartyUuid { get; set; } 29 | 30 | public DateTimeOffset? SyncedFromAltinn2 { get; set; } 31 | } 32 | } -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Persistence/Migrations/20250526122446_AddRequestInitializeCorrespondence.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace Altinn.Correspondence.Persistence.Migrations 6 | { 7 | /// 8 | public partial class AddRequestInitializeCorrespondence : Migration 9 | { 10 | /// 11 | protected override void Up(MigrationBuilder migrationBuilder) 12 | { 13 | // Add OriginalRequest column to CorrespondenceEntity 14 | migrationBuilder.AddColumn( 15 | name: "OriginalRequest", 16 | schema: "correspondence", 17 | table: "Correspondences", 18 | type: "jsonb", 19 | nullable: true); 20 | } 21 | 22 | /// 23 | protected override void Down(MigrationBuilder migrationBuilder) 24 | { 25 | migrationBuilder.DropColumn( 26 | name: "OriginalRequest", 27 | schema: "correspondence", 28 | table: "Correspondences"); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.API/Mappers/LegacyGetCorrespondencesMapper.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Correspondence.API.Models; 2 | using Altinn.Correspondence.Application.GetCorrespondences; 3 | using Altinn.Correspondence.Core.Models.Enums; 4 | 5 | namespace Altinn.Correspondence.Mappers; 6 | 7 | internal static class LegacyGetCorrespondencesMapper 8 | { 9 | internal static LegacyGetCorrespondencesRequest MapToRequest(LegacyGetCorrespondencesRequestExt requestExt) 10 | { 11 | return new LegacyGetCorrespondencesRequest() 12 | { 13 | From = requestExt.From, 14 | To = requestExt.To, 15 | IncludeActive = requestExt.IncludeActive, 16 | IncludeArchived = requestExt.IncludeArchived, 17 | IncludeDeleted = requestExt.IncludeDeleted, 18 | FilterMigrated = requestExt.FilterMigrated, 19 | InstanceOwnerPartyIdList = requestExt.InstanceOwnerPartyIdList, 20 | Language = requestExt.Language, 21 | SearchString = requestExt.SearchString, 22 | Status = requestExt.Status is null ? null : (CorrespondenceStatus)requestExt.Status 23 | }; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.API/Models/LegacyNotificationExt.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Altinn.Correspondence.API.Models 4 | { 5 | public class LegacyNotificationExt 6 | { 7 | /// 8 | /// The email address used for sending the notification 9 | /// 10 | [JsonPropertyName("emailAddress")] 11 | public string? EmailAddress { get; set; } 12 | 13 | /// 14 | /// The MobileNumber used for sending the notification SMS 15 | /// 16 | [JsonPropertyName("mobileNumber")] 17 | public string? MobileNumber { get; set; } 18 | 19 | /// 20 | /// The organizationNumber for the recipient of the notification 21 | /// 22 | [JsonPropertyName("organizationNumber")] 23 | public string? OrganizationNumber { get; set; } 24 | 25 | /// 26 | /// The SSN for the recipient of the notification 27 | /// 28 | [JsonPropertyName("nationalIdentityNumber")] 29 | public string? NationalIdentityNumber { get; set; } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.azure/modules/keyvault/addReaderRoles.bicep: -------------------------------------------------------------------------------- 1 | param keyvaultName string 2 | param principals array 3 | 4 | var secretsUserRoleId = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4633458b-17de-408a-b874-0445c86b69e6') 5 | var keyVaultReaderRoleId = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '21090545-7ca7-4776-b22c-e363652d74d2') 6 | 7 | resource kv 'Microsoft.KeyVault/vaults@2024-11-01' existing = { 8 | name: keyvaultName 9 | } 10 | 11 | resource secretsUsers 'Microsoft.Authorization/roleAssignments@2022-04-01' = [for p in principals: { 12 | name: guid(subscription().id, kv.id, p.objectId, 'kv-secrets-user') 13 | scope: kv 14 | properties: { 15 | roleDefinitionId: secretsUserRoleId 16 | principalId: p.objectId 17 | principalType: p.principalType 18 | } 19 | }] 20 | 21 | resource kvReaders 'Microsoft.Authorization/roleAssignments@2022-04-01' = [for p in principals: { 22 | name: guid(subscription().id,kv.id, p.objectId, 'kv-reader') 23 | scope: kv 24 | properties: { 25 | roleDefinitionId: keyVaultReaderRoleId 26 | principalId: p.objectId 27 | principalType: p.principalType 28 | } 29 | }] 30 | -------------------------------------------------------------------------------- /.github/actions/get-current-version/action.yml: -------------------------------------------------------------------------------- 1 | name: "Get current version" 2 | 3 | description: "Get the current version from a file and the git short sha" 4 | 5 | outputs: 6 | version: 7 | description: "Version" 8 | value: ${{ steps.set-current-version.outputs.version }} 9 | gitShortSha: 10 | description: "Git short sha" 11 | value: ${{ steps.set-git-short-sha.outputs.gitShortSha }} 12 | imageTag: 13 | description: "Image tag" 14 | value: ${{ steps.set-image-tag.outputs.imageTag }} 15 | 16 | runs: 17 | using: "composite" 18 | steps: 19 | - name: Checkout GitHub Action" 20 | uses: actions/checkout@v4 21 | 22 | - name: Set current version 23 | id: set-current-version 24 | shell: bash 25 | run: echo "version=$(cat version.txt)" >> $GITHUB_OUTPUT 26 | 27 | - name: Set git short sha 28 | id: set-git-short-sha 29 | shell: bash 30 | run: echo "gitShortSha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT 31 | 32 | - name: Set image tag 33 | id: set-image-tag 34 | shell: bash 35 | run: echo "imageTag=$(cat version.txt)-$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT 36 | -------------------------------------------------------------------------------- /src/Altinn.Correspondence.Persistence/Migrations/20241127134045_Add_PartyUuidToAttachmentStatusEntity.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace Altinn.Correspondence.Persistence.Migrations 6 | { 7 | /// 8 | public partial class Add_PartyUuidToAttachmentStatusEntity : Migration 9 | { 10 | /// 11 | protected override void Up(MigrationBuilder migrationBuilder) 12 | { 13 | migrationBuilder.AddColumn( 14 | name: "PartyUuid", 15 | schema: "correspondence", 16 | table: "AttachmentStatuses", 17 | type: "uuid", 18 | nullable: false, 19 | defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); 20 | } 21 | 22 | /// 23 | protected override void Down(MigrationBuilder migrationBuilder) 24 | { 25 | migrationBuilder.DropColumn( 26 | name: "PartyUuid", 27 | schema: "correspondence", 28 | table: "AttachmentStatuses"); 29 | } 30 | } 31 | } 32 | --------------------------------------------------------------------------------