├── test ├── k6 │ ├── .gitignore │ ├── src │ │ ├── data │ │ │ ├── storage-end-to-end.xml │ │ │ ├── apps-test.pdf │ │ │ ├── process-task2.json │ │ │ └── instance.json │ │ ├── cleanup.js │ │ ├── api │ │ │ ├── applications.js │ │ │ ├── process.js │ │ │ └── texts.js │ │ └── errorhandler.js │ └── docker-compose.yml ├── UnitTest │ ├── data │ │ ├── xmlfile.xml │ │ ├── cat.jpg │ │ ├── image.png │ │ ├── example.xml │ │ ├── binary_file.pdf │ │ ├── roles │ │ │ ├── user_3 │ │ │ │ ├── party_1600 │ │ │ │ │ └── roles.json │ │ │ │ ├── party_1606 │ │ │ │ │ └── roles.json │ │ │ │ ├── party_500004 │ │ │ │ │ └── roles.json │ │ │ │ ├── party_1000 │ │ │ │ │ └── roles.json │ │ │ │ └── party_1337 │ │ │ │ │ └── roles.json │ │ │ ├── user_1 │ │ │ │ ├── party_500004 │ │ │ │ │ └── roles.json │ │ │ │ ├── party_1000 │ │ │ │ │ └── roles.json │ │ │ │ ├── party_1600 │ │ │ │ │ └── roles.json │ │ │ │ ├── party_500 │ │ │ │ │ └── roles.json │ │ │ │ └── party_50000000 │ │ │ │ │ └── roles.json │ │ │ ├── user_10016 │ │ │ │ ├── party_500004 │ │ │ │ │ └── roles.json │ │ │ │ └── party_1600 │ │ │ │ │ └── roles.json │ │ │ ├── user_1337 │ │ │ │ ├── party_500004 │ │ │ │ │ └── roles.json │ │ │ │ ├── party_500700 │ │ │ │ │ └── roles.json │ │ │ │ └── party_1337 │ │ │ │ │ └── roles.json │ │ │ ├── user_5 │ │ │ │ └── party_500004 │ │ │ │ │ └── roles.json │ │ │ └── system_49913a53 │ │ │ │ └── party_1337 │ │ │ │ └── roles.json │ │ ├── blob │ │ │ ├── ttd │ │ │ │ ├── sensitive-data │ │ │ │ │ └── 99194777-a691-433a-ace1-225e9a691653 │ │ │ │ │ │ └── data │ │ │ │ │ │ ├── 70d122f8-0cae-44f4-8cd5-2887c251a959 │ │ │ │ │ │ ├── 15c0fa5d-a243-4fa2-882b-002bb60b6227 │ │ │ │ │ │ ├── bb64df50-fdb1-456b-943e-9c32f524943e │ │ │ │ │ │ └── 6448a556-2db0-4279-b535-13e7f9c05809 │ │ │ │ └── autodelete-data-app │ │ │ │ │ └── 4914257c-9920-47a5-a37a-eae80f950767 │ │ │ │ │ └── data │ │ │ │ │ └── 887c5e56-6f73-494a-9730-6ebd11bffe88 │ │ │ └── tdd │ │ │ │ └── endring-av-navn │ │ │ │ ├── 1e14b6cd-e310-4aff-aa83-720b2ec195e0 │ │ │ │ └── data │ │ │ │ │ └── d5b0f5d0-8ff9-4e7f-9c7a-25c43cc237c4 │ │ │ │ ├── 649388f0-a2c0-4774-bd11-c870223ed819 │ │ │ │ └── data │ │ │ │ │ ├── 11f7c994-6681-47a1-9626-fcf6c27308a5 │ │ │ │ │ └── 50c60b30-cb9a-435b-a31e-bbce47c2b936 │ │ │ │ ├── 6aa47207-f089-4c11-9cb2-f00af6f66a47 │ │ │ │ └── data │ │ │ │ │ └── 24bfec2e-c4ce-4e82-8fa9-aa39da329fd5 │ │ │ │ ├── ca9da17c-904a-44d2-9771-a5420acfbcf3 │ │ │ │ └── data │ │ │ │ │ └── 28023597-516b-4a71-a77c-d3736912abd5 │ │ │ │ ├── d91fd644-1028-4efd-924f-4ca187354514 │ │ │ │ └── data │ │ │ │ │ └── f4feb26c-8eed-4d1d-9d75-9239c40724e9 │ │ │ │ └── dd84cfe9-f875-42ea-8a96-eb725a6a8a95 │ │ │ │ └── data │ │ │ │ └── 7b475791-ce2c-45a5-be2f-195f37c2646d │ │ ├── postgresdata │ │ │ ├── instanceEvents │ │ │ │ ├── 71a83368-30b2-480f-86d2-ccf842f91a67.json │ │ │ │ ├── 97cce5ab-264c-4724-89ca-e405c9d1c58c.json │ │ │ │ ├── e73db067-6cf2-4880-9fe9-70d10e8ee466.json │ │ │ │ ├── 24db4c62-4472-4f8b-a692-e7ddafdca7d1.json │ │ │ │ └── 9f07c256-a344-490b-b42b-1c855a83f6fc.json │ │ │ ├── dataelements │ │ │ │ ├── 28023597-516b-4a71-a77c-d3736912abd5.json │ │ │ │ ├── 5ebeb498-677d-476f-8cab-b788a0fd0640.json │ │ │ │ ├── f4feb26c-8eed-4d1d-9d75-9239c40724e9.json │ │ │ │ ├── 50c60b30-cb9a-435b-a31e-bbce47c2b936.json │ │ │ │ ├── 24bfec2e-c4ce-4e82-8fa9-aa39da329fd5.json │ │ │ │ ├── 11f7c994-6681-47a1-9626-fcf6c27308a5.json │ │ │ │ ├── cdb627fd-c586-41f5-99db-bae38daa2b59.json │ │ │ │ ├── d03b4a04-f0df-4ead-be92-aa7a68959dab.json │ │ │ │ ├── 15c0fa5d-a243-4fa2-882b-002bb60b6227.json │ │ │ │ ├── 6448a556-2db0-4279-b535-13e7f9c05809.json │ │ │ │ ├── bb64df50-fdb1-456b-943e-9c32f524943e.json │ │ │ │ ├── 7b475791-ce2c-45a5-be2f-195f37c2646d.json │ │ │ │ ├── d5b0f5d0-8ff9-4e7f-9c7a-25c43cc237c4.json │ │ │ │ ├── 70d122f8-0cae-44f4-8cd5-2887c251a959.json │ │ │ │ ├── 887c5e56-6f73-494a-9730-6ebd11bffe30.json │ │ │ │ ├── 1336b773-4ae2-4bdf-9529-d71dfc1c8b43.json │ │ │ │ ├── 887c5e56-6f73-494a-9730-6ebd11bffe88.json │ │ │ │ ├── 998c5e56-6f73-494a-9730-6ebd11bffe88.json │ │ │ │ └── 998c5e56-6f73-494a-9730-6ebd11bfff99.json │ │ │ ├── instances │ │ │ │ ├── 67f568ce-f114-48e7-ba12-dd422f73667a.json │ │ │ │ ├── 23d6aa98-df3b-4982-8d8a-8fe67a53b828.json │ │ │ │ ├── 2f7fa5ce-e878-4e1f-a241-8c0eb1a83eab.json │ │ │ │ ├── 3c42ee2a-9464-42a8-a976-16eb926bd20a.json │ │ │ │ ├── 46133fb5-a9f2-45d4-90b1-f6d93ad40713.json │ │ │ │ ├── 7e6cc8e2-6cd4-4ad4-9ce8-c37a767677b5.json │ │ │ │ ├── 8727385b-e7cb-4bf2-b042-89558c612826.json │ │ │ │ ├── a6020470-2200-4448-bed9-ef46b679bdb8.json │ │ │ │ ├── d3b326de-2dd8-49a1-834a-b1d23b11e540.json │ │ │ │ ├── 20475edd-dc38-4ae0-bd64-1b20643f506c.json │ │ │ │ ├── 377efa97-80ee-4cc6-8d48-09de12cc273d.json │ │ │ │ ├── 1916cd18-3b8e-46f8-aeaf-4bc3397ddd55.json │ │ │ │ ├── ef1b16fc-4566-4577-b2d8-db74fbee4f7c.json │ │ │ │ ├── 99194777-a691-433a-ace1-225e9a691653.json │ │ │ │ ├── 1916cd18-3b8e-46f8-aeaf-4bc3397ddd56.json │ │ │ │ ├── 1916cd18-3b8e-46f8-aeaf-4bc3397ddd69.json │ │ │ │ ├── 1916cd18-3b8e-46f8-aeaf-4bc3397ddd70.json │ │ │ │ ├── 1916cd18-3b8e-46f8-aeaf-4bc3397ddd90.json │ │ │ │ ├── 1916cd18-3b8e-46f8-aeaf-4bc3397ddd89.json │ │ │ │ ├── 07274f48-8313-4e2d-9788-bbdacef5a54e.json │ │ │ │ ├── 89d4a1ec-6e33-4aaa-8249-64e3e5e6b39f.json │ │ │ │ ├── 3af7ad95-0dd4-45d5-bb97-c1ee66c600d7.json │ │ │ │ ├── 5a389710-9a8c-4441-80b8-0a3a2e276cb1.json │ │ │ │ ├── 1e14b6cd-e310-4aff-aa83-720b2ec195e0.json │ │ │ │ ├── 649388f0-a2c0-4774-bd11-c870223ed819.json │ │ │ │ ├── 69c259d1-9c1f-4ab6-9d8b-5c210042dc4f.json │ │ │ │ ├── 6aa47207-f089-4c11-9cb2-f00af6f66a47.json │ │ │ │ ├── ca9da17c-904a-44d2-9771-a5420acfbcf3.json │ │ │ │ ├── d91fd644-1028-4efd-924f-4ca187354514.json │ │ │ │ ├── dd84cfe9-f875-42ea-8a96-eb725a6a8a95.json │ │ │ │ ├── bc19107c-508f-48d9-bcd7-54ffec905306.json │ │ │ │ ├── 045ea5db-6dd4-4476-b774-bdb2a09da7ea.json │ │ │ │ ├── 045ea5db-6dd4-4476-b774-bdb2a09da7dd.json │ │ │ │ ├── aa6732ea-bb55-4f38-8ece-c53170dc92b2.json │ │ │ │ ├── b5433000-57be-4df5-b215-0558762e1c76.json │ │ │ │ ├── 4914257c-9920-47a5-a37a-eae80f950767.json │ │ │ │ └── 3f7fcd91-114e-4da1-95b6-72115f34945c.json │ │ │ └── texts │ │ │ │ └── tdd-endring-av-navn-en.json │ │ ├── apps │ │ │ ├── ttd │ │ │ │ ├── signing-app │ │ │ │ │ └── config │ │ │ │ │ │ └── applicationmetadata.json │ │ │ │ ├── complete-test │ │ │ │ │ └── config │ │ │ │ │ │ └── applicationmetadata.json │ │ │ │ ├── steffens-2020-v2 │ │ │ │ │ └── config │ │ │ │ │ │ └── applicationmetadata.json │ │ │ │ └── autodelete-data-app │ │ │ │ │ └── config │ │ │ │ │ └── applicationmetadata.json │ │ │ ├── tests │ │ │ │ └── sailor │ │ │ │ │ └── config │ │ │ │ │ └── applicationmetadata.json │ │ │ ├── tdd │ │ │ │ ├── test-applikasjon-1 │ │ │ │ │ └── config │ │ │ │ │ │ └── applicationmetadata.json │ │ │ │ ├── read-write-unlock │ │ │ │ │ └── config │ │ │ │ │ │ └── applicationmetadata.json │ │ │ │ └── endring-av-navn │ │ │ │ │ └── config │ │ │ │ │ └── applicationmetadata.json │ │ │ └── tests-sailor.json │ │ ├── response_deny.json │ │ └── response_permit.json │ ├── platform-org.pfx │ ├── xunit.runner.json │ ├── selfSignedTestCertificate.pfx │ ├── ModuleInitializer.cs │ ├── Extensions │ │ ├── InstanceExtentions.cs │ │ └── ContentDispositionHeaderValueExtensionsTests.cs │ ├── Models │ │ ├── Role.cs │ │ └── XacmlResourceAttributes.cs │ ├── HelperTests │ │ └── LanguageHelperTest.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Mocks │ │ ├── Repository │ │ │ ├── ServiceCollectionExtensions.cs │ │ │ └── InstanceAndEventsRepositoryMock.cs │ │ ├── PublicSigningKeyProviderMock.cs │ │ └── Clients │ │ │ ├── PartiesWithInstancesClientMock.cs │ │ │ └── CorrespondenceClientMock.cs │ ├── Stubs │ │ └── DelegatingHandlerStub.cs │ ├── Fixture │ │ └── TestApplicationFactory.cs │ ├── platform-org.pem │ └── Utils │ │ └── RequestTracker.cs ├── Altinn.Platform.Storage.Interface.Tests │ ├── SystemUser │ │ ├── platformUser_beforeChange.json │ │ ├── platformUser_afterChange.json │ │ └── PlatformUserTests.cs │ ├── ShadowFields │ │ ├── applicationMetadata_beforeChange.json │ │ └── applicationMetadata_afterChange.json │ ├── AllowUserActions │ │ ├── applicationMetadata_beforeChange.json │ │ └── applicationMetadata_afterChange.json │ └── AllowAnonymousOnStateless │ │ ├── applicationMetadata_beforeChange.json │ │ ├── ApplicationTests.cs │ │ └── applicationMetadata_afterChange.json └── .editorconfig ├── .github ├── CODEOWNERS └── workflows │ ├── regression-test-AT22.yml │ ├── regression-test-AT24.yml │ ├── regression-test-AT23.yml │ ├── assign-issues-to-projects.yml │ ├── dotnet-format.yml │ ├── publish-release.yml │ └── container-scan.yml ├── src ├── Storage │ ├── Migration │ │ ├── v0.26 │ │ │ ├── 01-drop-schema.sql │ │ │ └── 02-functions-and-procedures.sql │ │ ├── v0.23 │ │ │ ├── 01-create-schema-wolverine.sql │ │ │ └── 02-setup-grants.sql │ │ ├── _pre │ │ │ └── README.md │ │ ├── v0.00 │ │ │ ├── README.md │ │ │ └── 02-setup-grants.sql │ │ ├── v0.17 │ │ │ └── 01-functions-and-procedures.sql │ │ ├── v0.18 │ │ │ └── 01-functions-and-procedures.sql │ │ ├── v0.19 │ │ │ └── 01-functions-and-procedures.sql │ │ ├── v0.21 │ │ │ ├── 01-create-index-and computed-column.sql │ │ │ └── 02-functions-and-procedures.sql │ │ ├── _erase │ │ │ └── README.md │ │ ├── _post │ │ │ └── README.md │ │ ├── v0.13 │ │ │ ├── 02-functions-and-procedures.sql │ │ │ └── 01-setup-tables.sql │ │ ├── v0.16 │ │ │ └── 01-create-index.sql │ │ ├── v0.22 │ │ │ ├── 01-functions-and-procedures.sql │ │ │ └── 02-create-index.sql │ │ ├── v0.24 │ │ │ ├── 02-functions-and-procedures.sql │ │ │ └── 01-create-index.sql │ │ ├── v0.25 │ │ │ ├── 02-functions-and-procedures.sql │ │ │ └── 01-create-tables.sql │ │ ├── _init │ │ │ └── README.md │ │ ├── v0.14 │ │ │ ├── 03-new-index.sql │ │ │ └── 01-setup-tables.sql │ │ ├── _draft │ │ │ └── README.md │ │ ├── v0.12 │ │ │ └── 01-setup-tables.sql │ │ ├── v0.11 │ │ │ └── 01-setup-tables.sql │ │ ├── FunctionsAndProcedures │ │ │ ├── readdataelement.sql │ │ │ ├── deletemigrationstate.sql │ │ │ ├── inserta1migrationstate.sql │ │ │ ├── inserta2migrationstate.sql │ │ │ ├── readinstanceevent.sql │ │ │ ├── updatea1migrationstatestarted.sql │ │ │ ├── updatea2migrationstatestarted.sql │ │ │ ├── readinstancenoelements.sql │ │ │ ├── reada1migrationstate.sql │ │ │ ├── reada2migrationstate.sql │ │ │ ├── insertinstanceevents.sql │ │ │ ├── deleteinstance.sql │ │ │ ├── deleteinstanceevent.sql │ │ │ ├── reada2codelist.sql │ │ │ ├── updatemigrationstatecompleted.sql │ │ │ ├── insertinstanceevent.sql │ │ │ ├── inserta2codelist.sql │ │ │ ├── reada2image.sql │ │ │ ├── readinstance.sql │ │ │ ├── insertinstance.sql │ │ │ ├── filterinstanceevent.sql │ │ │ ├── inserta2image.sql │ │ │ ├── reada2xsls.sql │ │ │ ├── readdeletedinstances.sql │ │ │ ├── deletedataelements.sql │ │ │ ├── readdeletedelements.sql │ │ │ └── deletedataelement.sql │ │ ├── ReadMe.txt │ │ └── v0.08 │ │ │ └── 02-drop-functions.sql │ ├── appsettings.Development.json │ ├── Telemetry │ │ └── Metrics.cs │ ├── Models │ │ ├── PartyType.cs │ │ ├── SblBridgeParty.cs │ │ ├── ServiceError.cs │ │ └── CorrespondenceEventSync.cs │ ├── Properties │ │ └── launchSettings.json │ ├── appsettings.Staging.json │ ├── appsettings.Production.json │ ├── Clients │ │ ├── IOndemandClient.cs │ │ ├── IPdfGeneratorClient.cs │ │ ├── QueueStorageSettings.cs │ │ ├── IFileScanQueueClient.cs │ │ ├── ICorrespondenceClient.cs │ │ └── IPartiesWithInstancesClient.cs │ ├── Configuration │ │ ├── RegisterServiceSettings.cs │ │ ├── PostgreSqlSettings.cs │ │ └── AzureStorageConfiguration.cs │ ├── Wrappers │ │ ├── IKeyVaultClientWrapper.cs │ │ └── KeyVaultClientWrapper.cs │ ├── Views │ │ └── ContentOnDemand │ │ │ ├── Signature.cshtml │ │ │ └── Payment.cshtml │ ├── Authorization │ │ ├── IClaimsPrincipalProvider.cs │ │ └── ClaimsPrincipalProvider.cs │ ├── Services │ │ ├── IA2OndemandFormattingService.cs │ │ ├── IRegisterService.cs │ │ └── ISigningService.cs │ ├── Messages │ │ └── SyncInstanceToDialogportenCommand.cs │ ├── Helpers │ │ ├── StringHelper.cs │ │ ├── QueryResponse.cs │ │ └── DisableFormValueModelBindingAttribute.cs │ ├── Health │ │ └── HealthCheck.cs │ ├── Controllers │ │ └── ErrorController.cs │ ├── Repository │ │ └── IInstanceAndEventsRepository.cs │ └── Extensions │ │ └── ContentDispositionHeaderValueExtensions.cs ├── DbTools │ └── DbTools.csproj └── Storage.Interface │ ├── Models │ ├── LanguageString.cs │ ├── DataValues.cs │ ├── InstanceLockResponse.cs │ ├── PresentationTexts.cs │ ├── InstanceLockRequest.cs │ ├── OnEntryConfig.cs │ ├── DeleteStatus.cs │ ├── ProcessStateUpdate.cs │ ├── HideSettings.cs │ ├── AuthInfo.cs │ ├── ResourceLinks.cs │ ├── MessageBoxConfig.cs │ ├── CompleteConfirmation.cs │ ├── ValidationStatus.cs │ ├── ShadowFields.cs │ ├── DataField.cs │ ├── FileScanStatus.cs │ ├── CopyInstanceSettings.cs │ ├── Reference.cs │ ├── ChangableElement.cs │ ├── Signee.cs │ ├── InstanceOwner.cs │ └── ApiScopes.cs │ ├── Enums │ ├── RelationType.cs │ ├── ReferenceType.cs │ └── FileScanResult.cs │ └── Directory.Build.targets ├── .csharpierignore ├── .dockerignore ├── .config └── dotnet-tools.json ├── infra ├── servers.json └── postgres_init.sql ├── dbsetup.sh ├── .gitignore ├── Altinn.Platform.Storage.slnx ├── .git-blame-ignore-revs ├── docker-compose.dcproj ├── renovate.json ├── Directory.Build.props └── .gitattributes /test/k6/.gitignore: -------------------------------------------------------------------------------- 1 | #Junit reports 2 | **/reports/*.xml 3 | 4 | -------------------------------------------------------------------------------- /test/k6/src/data/storage-end-to-end.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | /.github/CODEOWNERS @altinn/team-altinn-studio 2 | -------------------------------------------------------------------------------- /src/Storage/Migration/v0.26/01-drop-schema.sql: -------------------------------------------------------------------------------- 1 | DROP SCHEMA IF EXISTS wolverine CASCADE; -------------------------------------------------------------------------------- /.csharpierignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | obj/ 3 | .git/ 4 | *.xml 5 | *.csproj 6 | *.props 7 | *.targets 8 | -------------------------------------------------------------------------------- /src/Storage/Migration/v0.23/01-create-schema-wolverine.sql: -------------------------------------------------------------------------------- 1 | CREATE SCHEMA IF NOT EXISTS wolverine; -------------------------------------------------------------------------------- /test/UnitTest/data/xmlfile.xml: -------------------------------------------------------------------------------- 1 | 2 | Content of subpart 3 | -------------------------------------------------------------------------------- /test/UnitTest/data/cat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altinn/altinn-storage/main/test/UnitTest/data/cat.jpg -------------------------------------------------------------------------------- /test/UnitTest/data/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altinn/altinn-storage/main/test/UnitTest/data/image.png -------------------------------------------------------------------------------- /test/UnitTest/data/example.xml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altinn/altinn-storage/main/test/UnitTest/data/example.xml -------------------------------------------------------------------------------- /test/UnitTest/platform-org.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altinn/altinn-storage/main/test/UnitTest/platform-org.pfx -------------------------------------------------------------------------------- /test/UnitTest/xunit.runner.json: -------------------------------------------------------------------------------- 1 | { 2 | "parallelizeAssembly": false, 3 | "parallelizeTestCollections": false 4 | } -------------------------------------------------------------------------------- /test/k6/src/data/apps-test.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altinn/altinn-storage/main/test/k6/src/data/apps-test.pdf -------------------------------------------------------------------------------- /test/UnitTest/data/binary_file.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altinn/altinn-storage/main/test/UnitTest/data/binary_file.pdf -------------------------------------------------------------------------------- /src/Storage/Migration/_pre/README.md: -------------------------------------------------------------------------------- 1 | # The `_pre` directory 2 | Pre migration scripts. Executed every time before any version. 3 | -------------------------------------------------------------------------------- /test/UnitTest/data/roles/user_3/party_1600/roles.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Type": "altinn", 4 | "value": "test" 5 | } 6 | ] 7 | -------------------------------------------------------------------------------- /test/UnitTest/data/roles/user_3/party_1606/roles.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Type": "altinn", 4 | "value": "REGNA" 5 | } 6 | ] 7 | -------------------------------------------------------------------------------- /src/Storage/Migration/v0.00/README.md: -------------------------------------------------------------------------------- 1 | # The `v0.00` directory 2 | Baseline scripts. Executed once. This is called when you do `yuniql run`. -------------------------------------------------------------------------------- /src/Storage/Migration/v0.17/01-functions-and-procedures.sql: -------------------------------------------------------------------------------- 1 | -- This script is autogenerated from the tool DbTools. Do not edit manually. -------------------------------------------------------------------------------- /src/Storage/Migration/v0.18/01-functions-and-procedures.sql: -------------------------------------------------------------------------------- 1 | -- This script is autogenerated from the tool DbTools. Do not edit manually. -------------------------------------------------------------------------------- /src/Storage/Migration/v0.19/01-functions-and-procedures.sql: -------------------------------------------------------------------------------- 1 | -- This script is autogenerated from the tool DbTools. Do not edit manually. -------------------------------------------------------------------------------- /src/Storage/Migration/v0.21/01-create-index-and computed-column.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE storage.instances ADD COLUMN IF NOT EXISTS confirmed bool; -------------------------------------------------------------------------------- /test/UnitTest/data/blob/ttd/sensitive-data/99194777-a691-433a-ace1-225e9a691653/data/70d122f8-0cae-44f4-8cd5-2887c251a959: -------------------------------------------------------------------------------- 1 | model-content -------------------------------------------------------------------------------- /test/UnitTest/data/roles/user_1/party_500004/roles.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Type": "altinn", 4 | "value": "REGNA" 5 | } 6 | ] 7 | -------------------------------------------------------------------------------- /test/UnitTest/data/roles/user_10016/party_500004/roles.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Type": "altinn", 4 | "value": "SIGNE" 5 | } 6 | ] 7 | -------------------------------------------------------------------------------- /test/UnitTest/data/roles/user_1337/party_500004/roles.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Type": "altinn", 4 | "value": "DAGL" 5 | } 6 | ] 7 | -------------------------------------------------------------------------------- /test/UnitTest/data/roles/user_3/party_500004/roles.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Type": "altinn", 4 | "value": "A0212" 5 | } 6 | ] 7 | -------------------------------------------------------------------------------- /test/UnitTest/data/roles/user_5/party_500004/roles.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Type": "altinn", 4 | "value": "A0236" 5 | } 6 | ] 7 | -------------------------------------------------------------------------------- /src/Storage/Migration/_erase/README.md: -------------------------------------------------------------------------------- 1 | # The `_erase` directory 2 | Database cleanup scripts. Executed once only when you do `yuniql erase`. -------------------------------------------------------------------------------- /src/Storage/Migration/_post/README.md: -------------------------------------------------------------------------------- 1 | # The `_post` directory 2 | Post migration scripts. Executed every time and always the last batch to run. -------------------------------------------------------------------------------- /src/Storage/Migration/v0.13/02-functions-and-procedures.sql: -------------------------------------------------------------------------------- 1 | -- This script is autogenerated from the tool DbTools. Do not edit manually. 2 | -------------------------------------------------------------------------------- /src/Storage/Migration/v0.16/01-create-index.sql: -------------------------------------------------------------------------------- 1 | CREATE INDEX IF NOT EXISTS instances_org_lastchanged ON storage.instances(org, lastChanged); -------------------------------------------------------------------------------- /src/Storage/Migration/v0.21/02-functions-and-procedures.sql: -------------------------------------------------------------------------------- 1 | -- This script is autogenerated from the tool DbTools. Do not edit manually. 2 | -------------------------------------------------------------------------------- /src/Storage/Migration/v0.22/01-functions-and-procedures.sql: -------------------------------------------------------------------------------- 1 | -- This script is autogenerated from the tool DbTools. Do not edit manually. 2 | -------------------------------------------------------------------------------- /src/Storage/Migration/v0.24/02-functions-and-procedures.sql: -------------------------------------------------------------------------------- 1 | -- This script is autogenerated from the tool DbTools. Do not edit manually. 2 | -------------------------------------------------------------------------------- /src/Storage/Migration/v0.25/02-functions-and-procedures.sql: -------------------------------------------------------------------------------- 1 | -- This script is autogenerated from the tool DbTools. Do not edit manually. 2 | -------------------------------------------------------------------------------- /src/Storage/Migration/v0.26/02-functions-and-procedures.sql: -------------------------------------------------------------------------------- 1 | -- This script is autogenerated from the tool DbTools. Do not edit manually. 2 | -------------------------------------------------------------------------------- /test/UnitTest/data/blob/tdd/endring-av-navn/1e14b6cd-e310-4aff-aa83-720b2ec195e0/data/d5b0f5d0-8ff9-4e7f-9c7a-25c43cc237c4: -------------------------------------------------------------------------------- 1 | This is a blob file -------------------------------------------------------------------------------- /test/UnitTest/data/blob/tdd/endring-av-navn/649388f0-a2c0-4774-bd11-c870223ed819/data/11f7c994-6681-47a1-9626-fcf6c27308a5: -------------------------------------------------------------------------------- 1 | This is a blob file -------------------------------------------------------------------------------- /test/UnitTest/data/blob/tdd/endring-av-navn/649388f0-a2c0-4774-bd11-c870223ed819/data/50c60b30-cb9a-435b-a31e-bbce47c2b936: -------------------------------------------------------------------------------- 1 | This is a blob file -------------------------------------------------------------------------------- /test/UnitTest/data/blob/tdd/endring-av-navn/6aa47207-f089-4c11-9cb2-f00af6f66a47/data/24bfec2e-c4ce-4e82-8fa9-aa39da329fd5: -------------------------------------------------------------------------------- 1 | This is a blob file -------------------------------------------------------------------------------- /test/UnitTest/data/blob/tdd/endring-av-navn/ca9da17c-904a-44d2-9771-a5420acfbcf3/data/28023597-516b-4a71-a77c-d3736912abd5: -------------------------------------------------------------------------------- 1 | This is a blob file -------------------------------------------------------------------------------- /test/UnitTest/data/blob/tdd/endring-av-navn/d91fd644-1028-4efd-924f-4ca187354514/data/f4feb26c-8eed-4d1d-9d75-9239c40724e9: -------------------------------------------------------------------------------- 1 | This is a blob file -------------------------------------------------------------------------------- /test/UnitTest/data/blob/tdd/endring-av-navn/dd84cfe9-f875-42ea-8a96-eb725a6a8a95/data/7b475791-ce2c-45a5-be2f-195f37c2646d: -------------------------------------------------------------------------------- 1 | This is a blob file -------------------------------------------------------------------------------- /test/UnitTest/data/roles/system_49913a53/party_1337/roles.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Type": "altinn", 4 | "value": "REGNA" 5 | } 6 | ] 7 | -------------------------------------------------------------------------------- /test/UnitTest/selfSignedTestCertificate.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altinn/altinn-storage/main/test/UnitTest/selfSignedTestCertificate.pfx -------------------------------------------------------------------------------- /test/Altinn.Platform.Storage.Interface.Tests/SystemUser/platformUser_beforeChange.json: -------------------------------------------------------------------------------- 1 | { 2 | "orgId": "tdd", 3 | "authenticationLevel": 3 4 | } -------------------------------------------------------------------------------- /test/UnitTest/data/blob/ttd/sensitive-data/99194777-a691-433a-ace1-225e9a691653/data/15c0fa5d-a243-4fa2-882b-002bb60b6227: -------------------------------------------------------------------------------- 1 | sensitive-data-read-content -------------------------------------------------------------------------------- /test/UnitTest/data/blob/ttd/sensitive-data/99194777-a691-433a-ace1-225e9a691653/data/bb64df50-fdb1-456b-943e-9c32f524943e: -------------------------------------------------------------------------------- 1 | sensitive-data-both-content -------------------------------------------------------------------------------- /test/UnitTest/data/blob/ttd/sensitive-data/99194777-a691-433a-ace1-225e9a691653/data/6448a556-2db0-4279-b535-13e7f9c05809: -------------------------------------------------------------------------------- 1 | sensitive-data-write-content -------------------------------------------------------------------------------- /src/Storage/Migration/_init/README.md: -------------------------------------------------------------------------------- 1 | # The `_init` directory 2 | Initialization scripts. Executed once. This is called the first time you do `yuniql run`. -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .dockerignore 2 | .env 3 | .git 4 | .gitignore 5 | .vs 6 | .vscode 7 | docker-compose.yml 8 | docker-compose.*.yml 9 | */bin 10 | */obj 11 | -------------------------------------------------------------------------------- /src/Storage/Migration/v0.22/02-create-index.sql: -------------------------------------------------------------------------------- 1 | CREATE INDEX IF NOT EXISTS instances_lastchanged_org_not_confirmed_by_org ON storage.instances(org, lastchanged desc, id) WHERE confirmed = False; -------------------------------------------------------------------------------- /src/Storage/Migration/v0.14/03-new-index.sql: -------------------------------------------------------------------------------- 1 | CREATE INDEX IF NOT EXISTS instances_lastchanged_filtered ON storage.instances(lastChanged) 2 | WHERE (instance -> 'Status' -> 'IsArchived')::BOOLEAN = false; -------------------------------------------------------------------------------- /src/Storage/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /test/UnitTest/data/roles/user_1/party_1000/roles.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Type": "altinn", 4 | "value": "regna" 5 | }, 6 | { 7 | "Type": "altinn", 8 | "value": "dagl" 9 | } 10 | ] 11 | -------------------------------------------------------------------------------- /test/UnitTest/data/roles/user_1/party_1600/roles.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Type": "altinn", 4 | "value": "regna" 5 | }, 6 | { 7 | "Type": "altinn", 8 | "value": "dagl" 9 | } 10 | ] 11 | -------------------------------------------------------------------------------- /test/UnitTest/data/roles/user_1/party_500/roles.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Type": "altinn", 4 | "value": "regna" 5 | }, 6 | { 7 | "Type": "altinn", 8 | "value": "dagl" 9 | } 10 | ] 11 | -------------------------------------------------------------------------------- /test/UnitTest/data/roles/user_3/party_1000/roles.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Type": "altinn", 4 | "value": "regna" 5 | }, 6 | { 7 | "Type": "altinn", 8 | "value": "dagl" 9 | } 10 | ] 11 | -------------------------------------------------------------------------------- /test/UnitTest/data/roles/user_1/party_50000000/roles.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Type": "altinn", 4 | "value": "regna" 5 | }, 6 | { 7 | "Type": "altinn", 8 | "value": "dagl" 9 | } 10 | ] 11 | -------------------------------------------------------------------------------- /src/Storage/Migration/_draft/README.md: -------------------------------------------------------------------------------- 1 | # The `_draft` directory 2 | Scripts in progress. Scripts that you are currently working and have not moved to specific version directory yet. Executed every time after the latest version. -------------------------------------------------------------------------------- /src/Storage/Migration/v0.12/01-setup-tables.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE storage.a2xsls ADD COLUMN IF NOT EXISTS isportrait BOOL NOT NULL DEFAULT true; 2 | DROP FUNCTION IF EXISTS storage.reada2xsls(_org TEXT, _app TEXT, _lformid INT, _language TEXT, _xsltype INT); -------------------------------------------------------------------------------- /test/k6/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | networks: 4 | k6: 5 | 6 | services: 7 | k6: 8 | image: grafana/k6:1.3.0 9 | networks: 10 | - k6 11 | ports: 12 | - "6565:6565" 13 | volumes: 14 | - ./src:/src 15 | -------------------------------------------------------------------------------- /.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "csharpier": { 6 | "version": "1.2.1", 7 | "commands": [ 8 | "csharpier" 9 | ], 10 | "rollForward": false 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /test/Altinn.Platform.Storage.Interface.Tests/SystemUser/platformUser_afterChange.json: -------------------------------------------------------------------------------- 1 | { 2 | "systemUserId": "2280457B-0A79-49C5-AC14-09217705C9A1", 3 | "systemUserOwnerOrgNo": "565433454", 4 | "systemUserName": "Vismalise", 5 | "authenticationLevel": 3 6 | } -------------------------------------------------------------------------------- /src/Storage/Migration/v0.13/01-setup-tables.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE storage.instances ADD COLUMN IF NOT EXISTS altinnmainversion SMALLINT NOT NULL DEFAULT 3; 2 | CREATE INDEX IF NOT EXISTS instances_partyid_altinnmainversion_lastchanged ON storage.instances(partyId, altinnmainversion, lastChanged); -------------------------------------------------------------------------------- /test/.editorconfig: -------------------------------------------------------------------------------- 1 | 2 | # For test projects, we don't follow the same rules as production code 3 | [*.cs] 4 | dotnet_diagnostic.SA1600.severity = none # Disable "Elements must be documented" rule 5 | dotnet_diagnostic.CA1859.severity = none # Use concrete types when possible for improved performance -------------------------------------------------------------------------------- /test/UnitTest/data/blob/ttd/autodelete-data-app/4914257c-9920-47a5-a37a-eae80f950767/data/887c5e56-6f73-494a-9730-6ebd11bffe88: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altinn/altinn-storage/main/test/UnitTest/data/blob/ttd/autodelete-data-app/4914257c-9920-47a5-a37a-eae80f950767/data/887c5e56-6f73-494a-9730-6ebd11bffe88 -------------------------------------------------------------------------------- /src/DbTools/DbTools.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net9.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Storage/Migration/v0.11/01-setup-tables.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE storage.a2xsls ADD COLUMN IF NOT EXISTS xsltype INT NOT NULL DEFAULT -1; 2 | ALTER TABLE storage.a2xsls DROP CONSTRAINT a2xslsalternateid; 3 | ALTER TABLE storage.a2xsls ADD CONSTRAINT a2xslsalternateid UNIQUE (app, org, lformid, pagenumber, language, xsltype); -------------------------------------------------------------------------------- /test/UnitTest/ModuleInitializer.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | using VerifyTests; 3 | 4 | namespace Altinn.Platform.Storage.UnitTest; 5 | 6 | public static class ModuleInitializer 7 | { 8 | [ModuleInitializer] 9 | public static void Initialize() => VerifierSettings.AutoVerify(includeBuildServer: false); 10 | } 11 | -------------------------------------------------------------------------------- /test/UnitTest/data/roles/user_1337/party_500700/roles.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Type": "altinn", 4 | "value": "MEDL" 5 | }, 6 | { 7 | "Type": "altinn", 8 | "value": "REGNA" 9 | }, 10 | { 11 | "Type": "altinn", 12 | "value": "UTINN" 13 | }, 14 | { 15 | "Type": "altinn", 16 | "value": "UTOMR" 17 | } 18 | ] 19 | -------------------------------------------------------------------------------- /.github/workflows/regression-test-AT22.yml: -------------------------------------------------------------------------------- 1 | name: Regression Test - AT22 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: '0 12 * * 1-5' 7 | 8 | jobs: 9 | at22: 10 | permissions: 11 | contents: read 12 | uses: ./.github/workflows/regression-test-ATX.yml 13 | with: 14 | environment: AT22 15 | secrets: inherit -------------------------------------------------------------------------------- /.github/workflows/regression-test-AT24.yml: -------------------------------------------------------------------------------- 1 | name: Regression Test - AT24 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: '0 12 * * 1-5' 7 | 8 | jobs: 9 | at24: 10 | permissions: 11 | contents: read 12 | uses: ./.github/workflows/regression-test-ATX.yml 13 | with: 14 | environment: AT24 15 | secrets: inherit -------------------------------------------------------------------------------- /src/Storage.Interface/Models/LanguageString.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Altinn.Platform.Storage.Interface.Models; 4 | 5 | /// 6 | /// Represents a dictionary collection of translated texts where the key is a language id and the value is the text. 7 | /// 8 | public class LanguageString : Dictionary { } 9 | -------------------------------------------------------------------------------- /src/Storage/Migration/FunctionsAndProcedures/readdataelement.sql: -------------------------------------------------------------------------------- 1 | CREATE OR REPLACE FUNCTION storage.readdataelement(_alternateid UUID) 2 | RETURNS TABLE (element JSONB) 3 | LANGUAGE 'plpgsql' 4 | 5 | AS $BODY$ 6 | BEGIN 7 | RETURN QUERY 8 | SELECT d.element FROM storage.dataelements d WHERE alternateid = _alternateid; 9 | 10 | END; 11 | $BODY$; -------------------------------------------------------------------------------- /.github/workflows/regression-test-AT23.yml: -------------------------------------------------------------------------------- 1 | name: Regression Test - AT23 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: '0 12 * * 1-5' 7 | jobs: 8 | at23: 9 | permissions: 10 | contents: read 11 | uses: ./.github/workflows/regression-test-ATX.yml 12 | with: 13 | environment: AT23 14 | secrets: inherit -------------------------------------------------------------------------------- /src/Storage/Migration/FunctionsAndProcedures/deletemigrationstate.sql: -------------------------------------------------------------------------------- 1 | CREATE OR REPLACE PROCEDURE storage.deletemigrationstate (_instanceguid UUID) 2 | LANGUAGE 'plpgsql' 3 | AS $BODY$ 4 | BEGIN 5 | DELETE FROM storage.a1migrationstate WHERE instanceguid = _instanceguid; 6 | DELETE FROM storage.a2migrationstate WHERE instanceguid = _instanceguid; 7 | END; 8 | $BODY$; -------------------------------------------------------------------------------- /src/Storage/Migration/FunctionsAndProcedures/inserta1migrationstate.sql: -------------------------------------------------------------------------------- 1 | CREATE OR REPLACE PROCEDURE storage.inserta1migrationstate (_a1archiveReference BIGINT) 2 | LANGUAGE 'plpgsql' 3 | AS $BODY$ 4 | BEGIN 5 | INSERT INTO storage.a1migrationstate (a1archivereference) VALUES 6 | (_a1archiveReference) 7 | ON CONFLICT (a1archivereference) DO NOTHING; 8 | END; 9 | $BODY$; -------------------------------------------------------------------------------- /src/Storage/Migration/FunctionsAndProcedures/inserta2migrationstate.sql: -------------------------------------------------------------------------------- 1 | CREATE OR REPLACE PROCEDURE storage.inserta2migrationstate (_a2archiveReference BIGINT) 2 | LANGUAGE 'plpgsql' 3 | AS $BODY$ 4 | BEGIN 5 | INSERT INTO storage.a2migrationstate (a2archivereference) VALUES 6 | (_a2archiveReference) 7 | ON CONFLICT (a2archivereference) DO NOTHING; 8 | END; 9 | $BODY$; -------------------------------------------------------------------------------- /src/Storage/Migration/FunctionsAndProcedures/readinstanceevent.sql: -------------------------------------------------------------------------------- 1 | CREATE OR REPLACE FUNCTION storage.readinstanceevent(_alternateid UUID) 2 | RETURNS TABLE (event JSONB) 3 | LANGUAGE 'plpgsql' 4 | 5 | AS $BODY$ 6 | BEGIN 7 | RETURN QUERY 8 | SELECT ie.event FROM storage.instanceevents ie WHERE alternateid = _alternateid; 9 | 10 | END; 11 | $BODY$; -------------------------------------------------------------------------------- /test/k6/src/cleanup.js: -------------------------------------------------------------------------------- 1 | import { check } from "k6"; 2 | import * as instancesApi from "./api/instances.js"; 3 | 4 | export function hardDeleteInstance(token, instanceId) { 5 | var res = instancesApi.deleteInstanceById(token, instanceId, true); 6 | check(res, { 7 | "// Cleanup // Delete instance. Status is 200": (r) => r.status === 200, 8 | }); 9 | } 10 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/instanceEvents/71a83368-30b2-480f-86d2-ccf842f91a67.json: -------------------------------------------------------------------------------- 1 | {"user":{"orgId":"tdd","authenticationLevel":3},"id":"71a83368-30b2-480f-86d2-ccf842f91a67","instanceId":"d3b326de-2dd8-49a1-834a-b1d23b11e540","created":"2020-05-02T06:21:53.5374088Z","eventType":"ConfirmedComplete","instanceOwnerPartyId":"1337","processInfo":{"currentTask":{"elementId":"Task_1"}}} -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/instanceEvents/97cce5ab-264c-4724-89ca-e405c9d1c58c.json: -------------------------------------------------------------------------------- 1 | {"user":{"orgId":"tdd","authenticationLevel":3},"id":"97cce5ab-264c-4724-89ca-e405c9d1c58c","instanceId":"d3b326de-2dd8-49a1-834a-b1d23b11e540","created":"2020-05-02T06:26:27.9901611Z","eventType":"ConfirmedComplete","instanceOwnerPartyId":"1337","processInfo":{"currentTask":{"elementId":"Task_1"}}} -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/instanceEvents/e73db067-6cf2-4880-9fe9-70d10e8ee466.json: -------------------------------------------------------------------------------- 1 | {"user":{"orgId":"tdd","authenticationLevel":3},"id":"e73db067-6cf2-4880-9fe9-70d10e8ee466","instanceId":"1337/ef1b16fc-4566-4577-b2d8-db74fbee4f7c","created":"2020-04-29T13:06:55.2328045Z","eventType":"ConfirmedComplete","instanceOwnerPartyId":"1337","processInfo":{"currentTask":{"elementId":"Task_1"}}} -------------------------------------------------------------------------------- /test/UnitTest/data/apps/ttd/signing-app/config/applicationmetadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "ttd/signing-app", 3 | "org": "ttd", 4 | "created": "2019-09-24T10:02:41.0839253Z", 5 | "createdBy": "Steph", 6 | "lastChanged": "2019-09-24T10:02:41.0839254Z", 7 | "lastChangedBy": "Steph", 8 | "title": { 9 | "nb": "signing-app", 10 | "en": "signing-app english" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Storage/Migration/FunctionsAndProcedures/updatea1migrationstatestarted.sql: -------------------------------------------------------------------------------- 1 | CREATE OR REPLACE PROCEDURE storage.updatea1migrationstatestarted (_a1archivereference BIGINT, _instanceguid UUID) 2 | LANGUAGE 'plpgsql' 3 | AS $BODY$ 4 | BEGIN 5 | UPDATE storage.a1migrationstate SET instanceguid = _instanceguid, started = now() 6 | WHERE a1archivereference = _a1archivereference; 7 | END; 8 | $BODY$; -------------------------------------------------------------------------------- /src/Storage/Migration/FunctionsAndProcedures/updatea2migrationstatestarted.sql: -------------------------------------------------------------------------------- 1 | CREATE OR REPLACE PROCEDURE storage.updatea2migrationstatestarted (_a2archivereference BIGINT, _instanceguid UUID) 2 | LANGUAGE 'plpgsql' 3 | AS $BODY$ 4 | BEGIN 5 | UPDATE storage.a2migrationstate SET instanceguid = _instanceguid, started = now() 6 | WHERE a2archivereference = _a2archivereference; 7 | END; 8 | $BODY$; -------------------------------------------------------------------------------- /src/Storage/Telemetry/Metrics.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.Metrics; 2 | 3 | namespace Altinn.Platform.Storage.Telemetry; 4 | 5 | /// 6 | /// Metrics 7 | /// 8 | internal static class Metrics 9 | { 10 | /// 11 | /// Metrics for this application 12 | /// 13 | public static readonly Meter Meter = new("Altinn.Platform.Storage"); 14 | } 15 | -------------------------------------------------------------------------------- /test/UnitTest/data/apps/ttd/complete-test/config/applicationmetadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "ttd/complete-test", 3 | "org": "ttd", 4 | "created": "2019-09-24T10:02:41.0839253Z", 5 | "createdBy": "Steph", 6 | "lastChanged": "2019-09-24T10:02:41.0839254Z", 7 | "lastChangedBy": "Steph", 8 | "title": { 9 | "nb": "Complete app", 10 | "en": "Complete app english" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Storage/Migration/FunctionsAndProcedures/readinstancenoelements.sql: -------------------------------------------------------------------------------- 1 | CREATE OR REPLACE FUNCTION storage.readinstancenoelements(_alternateid UUID) 2 | RETURNS TABLE (id BIGINT, instance JSONB) 3 | LANGUAGE 'plpgsql' 4 | 5 | AS $BODY$ 6 | BEGIN 7 | RETURN QUERY 8 | SELECT i.id, i.instance FROM storage.instances i 9 | WHERE i.alternateid = _alternateid; 10 | END; 11 | $BODY$; -------------------------------------------------------------------------------- /src/Storage/Migration/FunctionsAndProcedures/reada1migrationstate.sql: -------------------------------------------------------------------------------- 1 | CREATE OR REPLACE FUNCTION storage.reada1migrationstate(_a1archivereference BIGINT) 2 | RETURNS TABLE (instanceguid UUID) 3 | LANGUAGE 'plpgsql' 4 | 5 | AS $BODY$ 6 | BEGIN 7 | RETURN QUERY 8 | SELECT ms.instanceguid FROM storage.a1migrationstate ms WHERE ms.a1archivereference = _a1archivereference; 9 | END; 10 | $BODY$; -------------------------------------------------------------------------------- /src/Storage/Migration/FunctionsAndProcedures/reada2migrationstate.sql: -------------------------------------------------------------------------------- 1 | CREATE OR REPLACE FUNCTION storage.reada2migrationstate(_a2archivereference BIGINT) 2 | RETURNS TABLE (instanceguid UUID) 3 | LANGUAGE 'plpgsql' 4 | 5 | AS $BODY$ 6 | BEGIN 7 | RETURN QUERY 8 | SELECT ms.instanceguid FROM storage.a2migrationstate ms WHERE ms.a2archivereference = _a2archivereference; 9 | END; 10 | $BODY$; -------------------------------------------------------------------------------- /test/UnitTest/data/apps/ttd/steffens-2020-v2/config/applicationmetadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "ttd/steffens-2020-v2", 3 | "org": "ttd", 4 | "created": "2019-09-24T10:02:41.0839253Z", 5 | "createdBy": "Steph", 6 | "lastChanged": "2019-09-24T10:02:41.0839254Z", 7 | "lastChangedBy": "Steph", 8 | "title": { 9 | "nb": "Steffen sin testapp", 10 | "en": "Steffen's test app" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Storage/Models/PartyType.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Platform.Storage.Models; 2 | 3 | /// 4 | /// Represents the type of a party. 5 | /// 6 | public enum PartyType 7 | { 8 | /// 9 | /// Represents a person. 10 | /// 11 | Person, 12 | 13 | /// 14 | /// Represents an organisation. 15 | /// 16 | Organisation, 17 | } 18 | -------------------------------------------------------------------------------- /test/k6/src/data/process-task2.json: -------------------------------------------------------------------------------- 1 | { 2 | "started": "2023-06-09T10:59:42.653Z", 3 | "startEvent": "string", 4 | "currentTask": { 5 | "flow": 3, 6 | "started": "2023-06-10T10:59:42.653Z", 7 | "elementId": "Task_2", 8 | "name": "Automatisert-Test-Signering", 9 | "altinnTaskType": "signing", 10 | "flowType": "CompleteCurrentMoveToNext" 11 | } 12 | } -------------------------------------------------------------------------------- /src/Storage/Migration/FunctionsAndProcedures/insertinstanceevents.sql: -------------------------------------------------------------------------------- 1 | 2 | CREATE OR REPLACE PROCEDURE storage.insertinstanceevents(_instance UUID, _events JSONB) 3 | LANGUAGE 'plpgsql' 4 | AS $BODY$ 5 | BEGIN 6 | INSERT INTO storage.instanceevents (instance, alternateid, event) 7 | SELECT _instance, (evs->>'Id')::UUID, jsonb_strip_nulls(evs) 8 | FROM jsonb_array_elements(_events) evs; 9 | END; 10 | $BODY$; -------------------------------------------------------------------------------- /src/Storage/Migration/FunctionsAndProcedures/deleteinstance.sql: -------------------------------------------------------------------------------- 1 | CREATE OR REPLACE FUNCTION storage.deleteinstance(_alternateid UUID) 2 | RETURNS INT 3 | LANGUAGE 'plpgsql' 4 | AS $BODY$ 5 | DECLARE 6 | _deleteCount INTEGER; 7 | BEGIN 8 | DELETE FROM storage.instances WHERE alternateid = _alternateid; 9 | GET DIAGNOSTICS _deleteCount = ROW_COUNT; 10 | RETURN _deleteCount; 11 | END; 12 | $BODY$; -------------------------------------------------------------------------------- /src/Storage/Models/SblBridgeParty.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace Altinn.Platform.Storage.Models; 4 | 5 | /// 6 | /// Represents a party in SBL Bridge 7 | /// 8 | public class SblBridgeParty 9 | { 10 | /// 11 | /// Gets or sets the party id. 12 | /// 13 | [JsonProperty(PropertyName = "partyId")] 14 | public int PartyId { get; set; } 15 | } 16 | -------------------------------------------------------------------------------- /src/Storage/Migration/FunctionsAndProcedures/deleteinstanceevent.sql: -------------------------------------------------------------------------------- 1 | CREATE OR REPLACE FUNCTION storage.deleteinstanceevent(_instance UUID) 2 | RETURNS INT 3 | LANGUAGE 'plpgsql' 4 | AS $BODY$ 5 | DECLARE 6 | _deleteCount INTEGER; 7 | BEGIN 8 | DELETE FROM storage.instanceevents WHERE instance = _instance; 9 | GET DIAGNOSTICS _deleteCount = ROW_COUNT; 10 | RETURN _deleteCount; 11 | END; 12 | $BODY$; -------------------------------------------------------------------------------- /test/UnitTest/Extensions/InstanceExtentions.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using Altinn.Platform.Storage.Interface.Models; 3 | 4 | namespace Altinn.Platform.Storage.UnitTest.Extensions; 5 | 6 | public static class InstanceExtentions 7 | { 8 | public static Instance Clone(this Instance instance) 9 | { 10 | return JsonSerializer.Deserialize(JsonSerializer.Serialize(instance)); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Storage/Migration/FunctionsAndProcedures/reada2codelist.sql: -------------------------------------------------------------------------------- 1 | CREATE OR REPLACE FUNCTION storage.reada2codelist(_name TEXT, _language TEXT) 2 | RETURNS TABLE (codelist TEXT) 3 | LANGUAGE 'plpgsql' 4 | 5 | AS $BODY$ 6 | BEGIN 7 | RETURN QUERY 8 | SELECT c.codelist FROM storage.a2codelists c 9 | WHERE name ilike _name AND language = _language 10 | ORDER BY version DESC LIMIT 1; 11 | END; 12 | $BODY$; -------------------------------------------------------------------------------- /src/Storage/Migration/FunctionsAndProcedures/updatemigrationstatecompleted.sql: -------------------------------------------------------------------------------- 1 | CREATE OR REPLACE PROCEDURE storage.updatemigrationstatecompleted (_instanceguid UUID) 2 | LANGUAGE 'plpgsql' 3 | AS $BODY$ 4 | BEGIN 5 | UPDATE storage.a1migrationstate SET completed = now() 6 | WHERE instanceguid = _instanceguid; 7 | UPDATE storage.a2migrationstate SET completed = now() 8 | WHERE instanceguid = _instanceguid; 9 | END; 10 | $BODY$; -------------------------------------------------------------------------------- /src/Storage/Migration/FunctionsAndProcedures/insertinstanceevent.sql: -------------------------------------------------------------------------------- 1 | CREATE OR REPLACE PROCEDURE storage.insertinstanceevent(_instance UUID, _alternateid UUID, _event JSONB) 2 | LANGUAGE 'plpgsql' 3 | AS $BODY$ 4 | BEGIN 5 | -- Dummy comment to verify that new migration solution works end to end 6 | INSERT INTO storage.instanceevents(instance, alternateid, event) VALUES (_instance, _alternateid, jsonb_strip_nulls(_event)); 7 | END; 8 | $BODY$; -------------------------------------------------------------------------------- /infra/servers.json: -------------------------------------------------------------------------------- 1 | { 2 | "Servers": { 3 | "1": { 4 | "Group": "Servers", 5 | "Name": "Storage", 6 | "Host": "storage_postgres", 7 | "Port": 5432, 8 | "MaintenanceDB": "postgres", 9 | "Username": "platform_storage_admin", 10 | "Password": "Password", 11 | "SSLMode": "prefer", 12 | "Favorite": true 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Storage/Migration/FunctionsAndProcedures/inserta2codelist.sql: -------------------------------------------------------------------------------- 1 | CREATE OR REPLACE PROCEDURE storage.inserta2codelist (_name TEXT, _language TEXT, _version INT, _codelist TEXT) 2 | LANGUAGE 'plpgsql' 3 | AS $BODY$ 4 | BEGIN 5 | INSERT INTO storage.a2codelists (name, language, version, codelist) VALUES 6 | (_name, _language, _version, _codelist) 7 | ON CONFLICT (name, language, version) DO UPDATE SET codelist = _codelist; 8 | END; 9 | $BODY$; -------------------------------------------------------------------------------- /src/Storage/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "profiles": { 4 | "Altinn.Platform.Storage": { 5 | "commandName": "Project", 6 | "launchBrowser": true, 7 | "launchUrl": "swagger/index.html", 8 | "applicationUrl": "http://localhost:5010", 9 | "environmentVariables": { 10 | "ASPNETCORE_ENVIRONMENT": "Development" 11 | } 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /infra/postgres_init.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE storagedb; 2 | \c storagedb; 3 | CREATE SCHEMA storage; 4 | 5 | ALTER SYSTEM SET max_connections TO '200'; 6 | 7 | CREATE ROLE platform_storage WITH LOGIN PASSWORD 'Password'; 8 | 9 | GRANT USAGE ON SCHEMA storage TO platform_storage; 10 | GRANT SELECT,INSERT,UPDATE,REFERENCES,DELETE,TRUNCATE,REFERENCES,TRIGGER ON ALL TABLES IN SCHEMA storage TO platform_storage; 11 | GRANT ALL ON ALL SEQUENCES IN SCHEMA storage TO platform_storage; 12 | -------------------------------------------------------------------------------- /src/Storage/Migration/v0.23/02-setup-grants.sql: -------------------------------------------------------------------------------- 1 | GRANT USAGE, CREATE ON SCHEMA wolverine TO platform_storage; 2 | GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA wolverine TO platform_storage; 3 | GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA wolverine TO platform_storage; 4 | 5 | ALTER DEFAULT PRIVILEGES IN SCHEMA wolverine 6 | GRANT ALL PRIVILEGES ON TABLES TO platform_storage; 7 | 8 | ALTER DEFAULT PRIVILEGES IN SCHEMA wolverine 9 | GRANT ALL PRIVILEGES ON SEQUENCES TO platform_storage; -------------------------------------------------------------------------------- /src/Storage/appsettings.Staging.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning", 5 | "System": "Warning", 6 | "Microsoft": "Warning", 7 | "Npgsql": "Information", 8 | "Altinn.Platform.Storage.Controllers.CleanupController": "Information", 9 | "Altinn.Platform.Storage.Controllers.MessageBoxInstancesController": "Information", 10 | "Altinn.Platform.Storage.Services.OutboxService": "Information" 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/instanceEvents/24db4c62-4472-4f8b-a692-e7ddafdca7d1.json: -------------------------------------------------------------------------------- 1 | {"user":{"userId":3,"authenticationLevel":3},"id":"24db4c62-4472-4f8b-a692-e7ddafdca7d1","instanceId":"1337/bc19107c-508f-48d9-bcd7-54ffec905306","dataId":"11f7c994-6681-47a1-9626-fcf6c27308a5","created":"2020-06-05T12:37:15.0286299Z","eventType":"Saved","instanceOwnerPartyId":"1337","processInfo":{"started":"2020-04-29T13:53:01.7020218Z","startEvent":"StartEvent_1","currentTask":{"elementId":"Task_1"}}} -------------------------------------------------------------------------------- /src/Storage/appsettings.Production.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning", 5 | "System": "Warning", 6 | "Microsoft": "Warning", 7 | "Npgsql": "Information", 8 | "Altinn.Platform.Storage.Controllers.CleanupController": "Information", 9 | "Altinn.Platform.Storage.Controllers.MessageBoxInstancesController": "Information", 10 | "Altinn.Platform.Storage.Services.OutboxService": "Information" 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/instanceEvents/9f07c256-a344-490b-b42b-1c855a83f6fc.json: -------------------------------------------------------------------------------- 1 | { 2 | "user": { 3 | "orgId": "tdd", 4 | "authenticationLevel": 3 5 | }, 6 | "id": "9f07c256-a344-490b-b42b-1c855a83f6fc", 7 | "instanceId": "1337/a6020470-2200-4448-bed9-ef46b679bdb8", 8 | "created": "2020-04-29T13:06:55.2328045Z", 9 | "eventType": "ConfirmedComplete", 10 | "instanceOwnerPartyId": "1337", 11 | "processInfo": { "currentTask": { "elementId": "Task_1" } } 12 | } 13 | -------------------------------------------------------------------------------- /.github/workflows/assign-issues-to-projects.yml: -------------------------------------------------------------------------------- 1 | name: Auto Assign to Project 2 | 3 | on: 4 | issues: 5 | types: 6 | - opened 7 | jobs: 8 | add-to-project: 9 | name: Add issue to Team Altinn Studio project 10 | runs-on: ubuntu-latest 11 | permissions: {} 12 | steps: 13 | - uses: actions/add-to-project@main 14 | with: 15 | project-url: https://github.com/orgs/Altinn/projects/164 16 | github-token: ${{ secrets.ASSIGN_PROJECT_TOKEN }} 17 | -------------------------------------------------------------------------------- /src/Storage/Migration/FunctionsAndProcedures/reada2image.sql: -------------------------------------------------------------------------------- 1 | CREATE OR REPLACE FUNCTION storage.reada2image(_name TEXT) 2 | RETURNS TABLE (image BYTEA) 3 | LANGUAGE 'plpgsql' 4 | 5 | AS $BODY$ 6 | BEGIN 7 | RETURN QUERY 8 | SELECT 9 | CASE 10 | WHEN c.image IS NOT NULL THEN c.image 11 | ELSE (SELECT p.image FROM storage.a2images p WHERE p.id = c.parentid) 12 | END 13 | FROM storage.a2images c WHERE c.name = _name; 14 | END; 15 | $BODY$; -------------------------------------------------------------------------------- /src/Storage/Migration/FunctionsAndProcedures/readinstance.sql: -------------------------------------------------------------------------------- 1 | CREATE OR REPLACE FUNCTION storage.readinstance(_alternateid UUID) 2 | RETURNS TABLE (id BIGINT, instance JSONB, element JSONB) 3 | LANGUAGE 'plpgsql' 4 | 5 | AS $BODY$ 6 | BEGIN 7 | RETURN QUERY 8 | SELECT i.id, i.instance, d.element FROM storage.instances i 9 | LEFT JOIN storage.dataelements d ON i.id = d.instanceinternalid 10 | WHERE i.alternateid = _alternateid 11 | ORDER BY d.id; 12 | 13 | END; 14 | $BODY$; 15 | -------------------------------------------------------------------------------- /src/Storage/Migration/v0.00/02-setup-grants.sql: -------------------------------------------------------------------------------- 1 | GRANT USAGE ON SCHEMA storage TO platform_storage; 2 | GRANT SELECT,INSERT,UPDATE,REFERENCES,DELETE,TRUNCATE,REFERENCES,TRIGGER ON ALL TABLES IN SCHEMA storage TO platform_storage; 3 | GRANT SELECT,INSERT,UPDATE,REFERENCES,DELETE,TRUNCATE,REFERENCES,TRIGGER ON ALL TABLES IN SCHEMA storage TO platform_storage_admin; 4 | GRANT USAGE, SELECT, UPDATE ON ALL SEQUENCES IN SCHEMA storage TO platform_storage; 5 | GRANT ALL ON ALL SEQUENCES IN SCHEMA storage TO platform_storage; 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/Storage/Clients/IOndemandClient.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Threading.Tasks; 3 | 4 | namespace Altinn.Platform.Storage.Clients; 5 | 6 | /// 7 | /// Interface for ondemand access 8 | /// 9 | public interface IOnDemandClient 10 | { 11 | /// 12 | /// Get ondemand data 13 | /// 14 | /// The path to access ondemand data 15 | /// The on demand data content 16 | Task GetStreamAsync(string path); 17 | } 18 | -------------------------------------------------------------------------------- /test/k6/src/api/applications.js: -------------------------------------------------------------------------------- 1 | import http from "k6/http"; 2 | import * as apiHelper from "../apiHelpers.js"; 3 | import * as config from "../config.js"; 4 | import { stopIterationOnFail } from "../errorhandler.js"; 5 | 6 | export function getAppsForOrg(org) { 7 | var endpoint = config.buildAppUrl(org, "", "application"); 8 | 9 | return http.get(endpoint); 10 | } 11 | 12 | export function getApp(org, app) { 13 | var endpoint = config.buildAppUrl(org, app, "application"); 14 | 15 | return http.get(endpoint); 16 | } 17 | -------------------------------------------------------------------------------- /dbsetup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export PGPASSWORD=Password 3 | 4 | # alter max connections 5 | psql -h localhost -p 5432 -U platform_storage_admin -d storagedb \ 6 | -c "ALTER SYSTEM SET max_connections TO '200';" 7 | 8 | # set up platform_storage role 9 | psql -h localhost -p 5432 -U platform_storage_admin -d storagedb \ 10 | -c "DO \$\$ 11 | BEGIN CREATE ROLE platform_storage WITH LOGIN PASSWORD 'Password'; 12 | EXCEPTION WHEN duplicate_object THEN RAISE NOTICE '%, skipping', SQLERRM USING ERRCODE = SQLSTATE; 13 | END \$\$;" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.*~ 3 | project.lock.json 4 | .DS_Store 5 | *.pyc 6 | nupkg/ 7 | 8 | # Visual Studio Code 9 | .vscode 10 | 11 | # Visual Studio 2015 12 | .vs/ 13 | 14 | # Rider 15 | .idea 16 | 17 | # User-specific files 18 | *.suo 19 | *.user 20 | *.userosscache 21 | *.sln.docstates 22 | 23 | # Build results 24 | [Dd]ebug/ 25 | [Dd]ebugPublic/ 26 | [Rr]elease/ 27 | [Rr]eleases/ 28 | x64/ 29 | x86/ 30 | build/ 31 | bld/ 32 | [Bb]in/ 33 | [Oo]bj/ 34 | [Oo]ut/ 35 | msbuild.log 36 | msbuild.err 37 | msbuild.wrn 38 | 39 | #Test 40 | TestResults -------------------------------------------------------------------------------- /Altinn.Platform.Storage.slnx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/Storage.Interface/Models/DataValues.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Newtonsoft.Json; 3 | 4 | namespace Altinn.Platform.Storage.Interface.Models; 5 | 6 | /// 7 | /// Represents the body of a PUT request against the data values endpoint for Instance. 8 | /// 9 | public class DataValues 10 | { 11 | /// 12 | /// The actual collection of values to be added to. 13 | /// 14 | [JsonProperty(PropertyName = "values")] 15 | public Dictionary Values { get; set; } 16 | } 17 | -------------------------------------------------------------------------------- /src/Storage.Interface/Models/InstanceLockResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | using Newtonsoft.Json; 3 | 4 | namespace Altinn.Platform.Storage.Interface.Models; 5 | 6 | /// 7 | /// Represents a response when acquiring an instance lock. 8 | /// 9 | public class InstanceLockResponse 10 | { 11 | /// 12 | /// Gets or sets the lock token. 13 | /// 14 | [JsonProperty(PropertyName = "lockToken")] 15 | [JsonPropertyName("lockToken")] 16 | public string LockToken { get; set; } 17 | } 18 | -------------------------------------------------------------------------------- /test/UnitTest/Models/Role.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | 4 | namespace Altinn.Platform.Storage.UnitTest.Models; 5 | 6 | /// 7 | /// Entity representing a Role 8 | /// 9 | [Serializable] 10 | public class Role 11 | { 12 | /// 13 | /// Gets or sets the role type 14 | /// 15 | [JsonProperty] 16 | public string Type { get; set; } 17 | 18 | /// 19 | /// Gets or sets the role 20 | /// 21 | [JsonProperty] 22 | public string Value { get; set; } 23 | } 24 | -------------------------------------------------------------------------------- /src/Storage.Interface/Models/PresentationTexts.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Newtonsoft.Json; 3 | 4 | namespace Altinn.Platform.Storage.Interface.Models; 5 | 6 | /// 7 | /// Represents the body of a PUT request against the presentationtexts endpoint for Instance. 8 | /// 9 | public class PresentationTexts 10 | { 11 | /// 12 | /// The actual collection of texts to be added to 13 | /// 14 | [JsonProperty(PropertyName = "texts")] 15 | public Dictionary Texts { get; set; } 16 | } 17 | -------------------------------------------------------------------------------- /src/Storage.Interface/Models/InstanceLockRequest.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | using Newtonsoft.Json; 3 | 4 | namespace Altinn.Platform.Storage.Interface.Models; 5 | 6 | /// 7 | /// Represents a request to acquire or update an instance lock. 8 | /// 9 | public class InstanceLockRequest 10 | { 11 | /// 12 | /// Gets or sets the time to live in seconds. 13 | /// 14 | [JsonProperty(PropertyName = "ttlSeconds")] 15 | [JsonPropertyName("ttlSeconds")] 16 | public int TtlSeconds { get; set; } 17 | } 18 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/dataelements/28023597-516b-4a71-a77c-d3736912abd5.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "28023597-516b-4a71-a77c-d3736912abd5", 3 | "instanceGuid": "ca9da17c-904a-44d2-9771-a5420acfbcf3", 4 | "dataType": "default", 5 | "contentType": "text/plain; charset=utf-8", 6 | "blobStoragePath": "tdd/endring-av-navn/ca9da17c-904a-44d2-9771-a5420acfbcf3/data/28023597-516b-4a71-a77c-d3736912abd5", 7 | "size": 19, 8 | "locked": false, 9 | "refs": [ 10 | ], 11 | "created": "2020-05-11T17:09:28.4621953Z", 12 | "lastChanged": "2020-05-11T17:09:28.4621953Z" 13 | } 14 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/dataelements/5ebeb498-677d-476f-8cab-b788a0fd0640.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "5ebeb498-677d-476f-8cab-b788a0fd0640", 3 | "instanceGuid": "d851287a-8c7a-4cf1-91ca-7d216c1336c4", 4 | "dataType": "default", 5 | "contentType": "text/plain; charset=utf-8", 6 | "blobStoragePath": "tdd/endring-av-navn/d851287a-8c7a-4cf1-91ca-7d216c1336c4/data/5ebeb498-677d-476f-8cab-b788a0fd0640", 7 | "size": 19, 8 | "locked": false, 9 | "refs": [ 10 | ], 11 | "created": "2020-05-11T17:09:28.4621953Z", 12 | "lastChanged": "2020-05-11T17:09:28.4621953Z" 13 | } 14 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/dataelements/f4feb26c-8eed-4d1d-9d75-9239c40724e9.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "f4feb26c-8eed-4d1d-9d75-9239c40724e9", 3 | "instanceGuid": "d91fd644-1028-4efd-924f-4ca187354514", 4 | "dataType": "default", 5 | "contentType": "text/plain; charset=utf-8", 6 | "blobStoragePath": "tdd/endring-av-navn/d91fd644-1028-4efd-924f-4ca187354514/data/f4feb26c-8eed-4d1d-9d75-9239c40724e9", 7 | "size": 19, 8 | "locked": false, 9 | "refs": [ 10 | ], 11 | "created": "2020-05-11T17:09:28.4621953Z", 12 | "lastChanged": "2020-05-11T17:09:28.4621953Z" 13 | } 14 | -------------------------------------------------------------------------------- /src/Storage/Configuration/RegisterServiceSettings.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Platform.Storage.Configuration; 2 | 3 | /// 4 | /// Represents a set of configuration options when communicating with the platform API. 5 | /// Instances of this class is initialised with values from app settings. Some values can be overridden by environment variables. 6 | /// 7 | public class RegisterServiceSettings 8 | { 9 | /// 10 | /// Gets or sets the url for the Register API endpoint. 11 | /// 12 | public string ApiRegisterEndpoint { get; set; } 13 | } 14 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/dataelements/50c60b30-cb9a-435b-a31e-bbce47c2b936.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "50c60b30-cb9a-435b-a31e-bbce47c2b936", 3 | "instanceGuid": "649388f0-a2c0-4774-bd11-c870223ed819", 4 | "dataType": "default_with_fileScan", 5 | "contentType": "text/plain; charset=utf-8", 6 | "blobStoragePath": "tdd/endring-av-navn/649388f0-a2c0-4774-bd11-c870223ed819/data/50c60b30-cb9a-435b-a31e-bbce47c2b936", 7 | "size": 19, 8 | "locked": false, 9 | "refs": [ 10 | ], 11 | "created": "2020-05-11T17:09:28.4621953Z", 12 | "lastChanged": "2020-05-11T17:09:28.4621953Z" 13 | } 14 | -------------------------------------------------------------------------------- /.github/workflows/dotnet-format.yml: -------------------------------------------------------------------------------- 1 | name: Verify dotnet format 2 | 3 | on: 4 | pull_request: 5 | permissions: 6 | contents: read 7 | 8 | jobs: 9 | verify-no-changes: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 14 | - name: Setup .NET 15 | uses: actions/setup-dotnet@2016bd2012dba4e32de620c46fe006a3ac9f0602 # v5 16 | with: 17 | dotnet-version: 8.0.x 18 | - name: Install csharpier 19 | run: dotnet tool restore 20 | - name: Run csharpier 21 | run: dotnet csharpier check . 22 | -------------------------------------------------------------------------------- /src/Storage/Clients/IPdfGeneratorClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace Altinn.Platform.Storage.Clients; 7 | 8 | /// 9 | /// Defines the required operations on a client of the PDF generator service. 10 | /// 11 | public interface IPdfGeneratorClient 12 | { 13 | /// 14 | /// Generates a PDF. 15 | /// 16 | /// A stream with the binary content of the generated PDF 17 | Task GeneratePdf(string html, bool isPortrait, float scale); 18 | } 19 | -------------------------------------------------------------------------------- /src/Storage/Migration/v0.14/01-setup-tables.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS storage.a1migrationstate 2 | ( 3 | id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, 4 | a1archivereference BIGINT UNIQUE NOT NULL, 5 | instanceguid UUID UNIQUE NULL, 6 | started TIMESTAMPTZ NULL, 7 | completed TIMESTAMPTZ NULL 8 | ) 9 | TABLESPACE pg_default; 10 | 11 | GRANT SELECT,INSERT,UPDATE,REFERENCES,DELETE,TRUNCATE,REFERENCES,TRIGGER ON ALL TABLES IN SCHEMA storage TO platform_storage; 12 | GRANT SELECT,INSERT,UPDATE,REFERENCES,DELETE,TRUNCATE,REFERENCES,TRIGGER ON ALL TABLES IN SCHEMA storage TO platform_storage_admin; -------------------------------------------------------------------------------- /src/Storage.Interface/Models/OnEntryConfig.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace Altinn.Platform.Storage.Interface.Models 4 | { 5 | /// 6 | /// The on entry configuration 7 | /// 8 | public class OnEntryConfig 9 | { 10 | /// 11 | /// Defines what should be shown on entry. 12 | /// 13 | /// 14 | /// Valid selections include: a string matching the layoutSetId 15 | /// 16 | [JsonProperty(PropertyName = "show")] 17 | public string Show { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/k6/src/data/instance.json: -------------------------------------------------------------------------------- 1 | { 2 | "instanceOwner": { 3 | }, 4 | "visibleAfter": "2019-05-20T00:00:00Z", 5 | "process": { 6 | "started": "2020-02-18T09:32:12.316Z", 7 | "startEvent": "StartEvent_1", 8 | "currentTask": { 9 | "flow": 2, 10 | "started": "2020-02-18T09:32:12.316Z", 11 | "elementId": "Task_1", 12 | "name": "Utfylling", 13 | "altinnTaskType": "data", 14 | "ended": null, 15 | "validated": null 16 | }, 17 | "ended": null, 18 | "endEvent": null 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/dataelements/24bfec2e-c4ce-4e82-8fa9-aa39da329fd5.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "24bfec2e-c4ce-4e82-8fa9-aa39da329fd5", 3 | "instanceGuid": "6aa47207-f089-4c11-9cb2-f00af6f66a47", 4 | "dataType": "default", 5 | "contentType": "text/plain; charset=utf-8", 6 | "blobStoragePath": "tdd/endring-av-navn/6aa47207-f089-4c11-9cb2-f00af6f66a47/data/24bfec2e-c4ce-4e82-8fa9-aa39da329fd5", 7 | "size": 19, 8 | "locked": true, 9 | "refs": [ 10 | ], 11 | "created": "2020-05-11T17:09:28.4621953Z", 12 | "lastChanged": "2020-05-11T17:09:28.4621953Z", 13 | "lastChangedBy": "123", 14 | "isRead": false 15 | } 16 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/dataelements/11f7c994-6681-47a1-9626-fcf6c27308a5.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "11f7c994-6681-47a1-9626-fcf6c27308a5", 3 | "instanceGuid": "649388f0-a2c0-4774-bd11-c870223ed819", 4 | "dataType": "default", 5 | "contentType": "text/plain; charset=utf-8", 6 | "blobStoragePath": "tdd/endring-av-navn/649388f0-a2c0-4774-bd11-c870223ed819/data/11f7c994-6681-47a1-9626-fcf6c27308a5", 7 | "size": 19, 8 | "locked": false, 9 | "refs": [ 10 | ], 11 | "created": "2020-05-11T17:09:28.4621953Z", 12 | "lastChanged": "2020-05-11T17:09:28.4621953Z", 13 | "lastChangedBy": "123", 14 | "isRead": false 15 | } 16 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/dataelements/cdb627fd-c586-41f5-99db-bae38daa2b59.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "cdb627fd-c586-41f5-99db-bae38daa2b59", 3 | "instanceGuid": "31d0941f-6d56-40a6-b4a4-b7fe18ccff30", 4 | "dataType": "default", 5 | "contentType": "text/plain; charset=utf-8", 6 | "blobStoragePath": "tdd/endring-av-navn/31d0941f-6d56-40a6-b4a4-b7fe18ccff30/data/cdb627fd-c586-41f5-99db-bae38daa2b59", 7 | "size": 19, 8 | "locked": false, 9 | "refs": [ 10 | ], 11 | "created": "2020-05-11T17:09:28.4621953Z", 12 | "lastChanged": "2020-05-11T17:09:28.4621953Z", 13 | "lastChangedBy": "123", 14 | "isRead": false 15 | } 16 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/dataelements/d03b4a04-f0df-4ead-be92-aa7a68959dab.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "d03b4a04-f0df-4ead-be92-aa7a68959dab", 3 | "instanceGuid": "31d0941f-6d56-40a6-b4a4-b7fe18ccff30", 4 | "dataType": "default", 5 | "contentType": "text/plain; charset=utf-8", 6 | "blobStoragePath": "tdd/endring-av-navn/31d0941f-6d56-40a6-b4a4-b7fe18ccff30/data/d03b4a04-f0df-4ead-be92-aa7a68959dab", 7 | "size": 19, 8 | "locked": false, 9 | "refs": [ 10 | ], 11 | "created": "2020-05-11T17:09:28.4621953Z", 12 | "lastChanged": "2020-05-11T17:09:28.4621953Z", 13 | "lastChangedBy": "123", 14 | "isRead": false 15 | } 16 | -------------------------------------------------------------------------------- /src/Storage/Migration/FunctionsAndProcedures/insertinstance.sql: -------------------------------------------------------------------------------- 1 | CREATE OR REPLACE PROCEDURE storage.insertinstance_v3(_partyid BIGINT, _alternateid UUID, _instance JSONB, _created TIMESTAMPTZ, _lastchanged TIMESTAMPTZ, _org TEXT, _appid TEXT, _taskid TEXT, _altinnmainversion INT, _confirmed BOOLEAN) 2 | LANGUAGE 'plpgsql' 3 | AS $BODY$ 4 | BEGIN 5 | INSERT INTO storage.instances(partyid, alternateid, instance, created, lastchanged, org, appid, taskid, altinnmainversion, confirmed) 6 | VALUES (_partyid, _alternateid, jsonb_strip_nulls(_instance), _created, _lastchanged, _org, _appid, _taskid, _altinnmainversion, _confirmed); 7 | END; 8 | $BODY$; -------------------------------------------------------------------------------- /src/Storage/Migration/FunctionsAndProcedures/filterinstanceevent.sql: -------------------------------------------------------------------------------- 1 | CREATE OR REPLACE FUNCTION storage.filterinstanceevent(_instance UUID, _from TIMESTAMPTZ, _to TIMESTAMPTZ, _eventtype TEXT[]) 2 | RETURNS TABLE (event JSONB) 3 | LANGUAGE 'plpgsql' 4 | 5 | AS $BODY$ 6 | BEGIN 7 | RETURN QUERY 8 | SELECT ie.event 9 | FROM storage.instanceevents ie 10 | WHERE instance = _instance 11 | AND (ie.event->>'Created')::TIMESTAMP >= _from 12 | AND (ie.event->>'Created')::TIMESTAMP <= _to 13 | AND (_eventtype IS NULL OR ie.event->>'EventType' = ANY (_eventtype)) 14 | ORDER BY ie.event->'Created'; 15 | END; 16 | $BODY$; -------------------------------------------------------------------------------- /src/Storage.Interface/Enums/RelationType.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Converters; 3 | using TextJson = System.Text.Json.Serialization; 4 | 5 | namespace Altinn.Platform.Storage.Interface.Enums; 6 | 7 | /// 8 | /// The type of relation to the connected object 9 | /// 10 | [JsonConverter(typeof(StringEnumConverter))] 11 | [TextJson.JsonConverter(typeof(TextJson.JsonStringEnumConverter))] 12 | public enum RelationType 13 | { 14 | /// 15 | /// The connected object is generated from the connected object and should be deleted if the reference is changed 16 | /// 17 | GeneratedFrom, 18 | } 19 | -------------------------------------------------------------------------------- /src/Storage/Clients/QueueStorageSettings.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | 3 | namespace Altinn.Platform.Storage.Clients; 4 | 5 | /// 6 | /// Configuration object used to hold settings for the file scan queue. 7 | /// 8 | [ExcludeFromCodeCoverage] 9 | public class QueueStorageSettings 10 | { 11 | /// 12 | /// The connection string for the storage account with the queue. 13 | /// 14 | public string ConnectionString { get; set; } 15 | 16 | /// 17 | /// Name of the queue for malware scanning. 18 | /// 19 | public string FileScanQueueName { get; set; } 20 | } 21 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/instances/67f568ce-f114-48e7-ba12-dd422f73667a.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "67f568ce-f114-48e7-ba12-dd422f73667a", 3 | "instanceOwner": { "partyId": "1337" }, 4 | "appId": "tdd/endring-av-navn", 5 | "org": "tdd", 6 | "data": [], 7 | "visibleAfter": "2020-04-28T13:53:06.117891Z", 8 | "created": "2020-04-28T13:53:06.117891Z", 9 | "status": { 10 | "softDeleted": "2020-04-29T13:53:06.117891Z", 11 | "isArchived": false, 12 | "isHardDeleted": false, 13 | "isSoftDeleted": true, 14 | "substatus": { 15 | "label": "substatus.Initial.Label", 16 | "description": "substatus.Initial.Description" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/UnitTest/data/apps/tests/sailor/config/applicationmetadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "test/sailor", 3 | "org": "test", 4 | "created": "2019-09-24T10:02:41.0839253Z", 5 | "createdBy": "Steph", 6 | "lastChanged": "2019-09-24T10:02:41.0839254Z", 7 | "lastChangedBy": "Steph", 8 | "title": { 9 | "nb": "Test app: seiling", 10 | "en": "The sailing application" 11 | }, 12 | "dataTypes": [ 13 | { 14 | "id": "default", 15 | "allowedContentTypes": [ "application/xml" ], 16 | "maxCount": 1, 17 | "appLogic": { 18 | "autoCreate": true, 19 | "ClassRef": "Form" 20 | }, 21 | "taskId": "Task_1" 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/dataelements/15c0fa5d-a243-4fa2-882b-002bb60b6227.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "15c0fa5d-a243-4fa2-882b-002bb60b6227", 3 | "instanceGuid": "99194777-a691-433a-ace1-225e9a691653", 4 | "dataType": "sensitive-data-read", 5 | "contentType": "application/xml", 6 | "blobStoragePath": "ttd/sensitive-data/99194777-a691-433a-ace1-225e9a691653/data/15c0fa5d-a243-4fa2-882b-002bb60b6227", 7 | "locked": false, 8 | "isRead": false, 9 | "tags": [], 10 | "fileScanResult": "NotApplicable", 11 | "created": "2025-07-17T09:00:00.000000Z", 12 | "createdBy": "991825827", 13 | "lastChanged": "2025-07-17T09:00:00.000000Z", 14 | "lastChangedBy": "991825827" 15 | } -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/dataelements/6448a556-2db0-4279-b535-13e7f9c05809.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "6448a556-2db0-4279-b535-13e7f9c05809", 3 | "instanceGuid": "99194777-a691-433a-ace1-225e9a691653", 4 | "dataType": "sensitive-data-write", 5 | "contentType": "application/xml", 6 | "blobStoragePath": "ttd/sensitive-data/99194777-a691-433a-ace1-225e9a691653/data/6448a556-2db0-4279-b535-13e7f9c05809", 7 | "locked": false, 8 | "isRead": false, 9 | "tags": [], 10 | "fileScanResult": "NotApplicable", 11 | "created": "2025-07-17T09:00:00.000000Z", 12 | "createdBy": "991825827", 13 | "lastChanged": "2025-07-17T09:00:00.000000Z", 14 | "lastChangedBy": "991825827" 15 | } -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/dataelements/bb64df50-fdb1-456b-943e-9c32f524943e.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "bb64df50-fdb1-456b-943e-9c32f524943e", 3 | "instanceGuid": "99194777-a691-433a-ace1-225e9a691653", 4 | "dataType": "sensitive-data-both", 5 | "contentType": "application/xml", 6 | "blobStoragePath": "ttd/sensitive-data/99194777-a691-433a-ace1-225e9a691653/data/bb64df50-fdb1-456b-943e-9c32f524943e", 7 | "locked": false, 8 | "isRead": false, 9 | "tags": [], 10 | "fileScanResult": "NotApplicable", 11 | "created": "2025-07-17T09:00:00.000000Z", 12 | "createdBy": "991825827", 13 | "lastChanged": "2025-07-17T09:00:00.000000Z", 14 | "lastChangedBy": "991825827" 15 | } -------------------------------------------------------------------------------- /src/Storage/Wrappers/IKeyVaultClientWrapper.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace Altinn.Platform.Storage.Wrappers; 4 | 5 | /// 6 | /// Describes any implementation of a wrapper for a KeyVaultClient. 7 | /// 8 | public interface IKeyVaultClientWrapper 9 | { 10 | /// 11 | /// Gets the value of a secret from the given key vault. 12 | /// 13 | /// The URI of the key vault to ask for secret. 14 | /// The id of the secret. 15 | /// The secret value. 16 | Task GetSecretAsync(string vaultUri, string secretId); 17 | } 18 | -------------------------------------------------------------------------------- /test/UnitTest/data/apps/tdd/test-applikasjon-1/config/applicationmetadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "tdd/test-applikasjon-1", 3 | "org": "tdd", 4 | "created": "2019-09-24T10:02:41.0839253Z", 5 | "createdBy": "Steph", 6 | "lastChanged": "2019-09-24T10:02:41.0839254Z", 7 | "lastChangedBy": "Steph", 8 | "title": { 9 | "nb": "Test applikasjon", 10 | "en": "Test app" 11 | }, 12 | "dataTypes": [ 13 | { 14 | "id": "default", 15 | "allowedContentTypes": [ "application/xml" ], 16 | "maxCount": 1, 17 | "appLogic": { 18 | "autoCreate": true, 19 | "ClassRef": "Form" 20 | }, 21 | "taskId": "Task_1" 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/dataelements/7b475791-ce2c-45a5-be2f-195f37c2646d.json: -------------------------------------------------------------------------------- 1 | { 2 | "appOwner": { 3 | "downloaded": [ 4 | "2020-06-06T12:28:14.4852108Z" 5 | ] 6 | }, 7 | "id": "7b475791-ce2c-45a5-be2f-195f37c2646d", 8 | "instanceGuid": "dd84cfe9-f875-42ea-8a96-eb725a6a8a95", 9 | "dataType": "default", 10 | "contentType": "text/plain; charset=utf-8", 11 | "blobStoragePath": "tdd/endring-av-navn/dd84cfe9-f875-42ea-8a96-eb725a6a8a95/data/7b475791-ce2c-45a5-be2f-195f37c2646d", 12 | "size": 19, 13 | "locked": false, 14 | "refs": [ 15 | ], 16 | "created": "2020-05-11T17:09:28.4621953Z", 17 | "lastChanged": "2020-05-11T17:09:28.4621953Z" 18 | } 19 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/dataelements/d5b0f5d0-8ff9-4e7f-9c7a-25c43cc237c4.json: -------------------------------------------------------------------------------- 1 | { 2 | "appOwner": { 3 | "downloaded": [ 4 | "2020-06-06T12:28:14.4852108Z" 5 | ] 6 | }, 7 | "id": "d5b0f5d0-8ff9-4e7f-9c7a-25c43cc237c4", 8 | "instanceGuid": "1e14b6cd-e310-4aff-aa83-720b2ec195e0", 9 | "dataType": "default", 10 | "contentType": "text/plain; charset=utf-8", 11 | "blobStoragePath": "tdd/endring-av-navn/1e14b6cd-e310-4aff-aa83-720b2ec195e0/data/d5b0f5d0-8ff9-4e7f-9c7a-25c43cc237c4", 12 | "size": 19, 13 | "locked": false, 14 | "refs": [ 15 | ], 16 | "created": "2020-05-11T17:09:28.4621953Z", 17 | "lastChanged": "2020-05-11T17:09:28.4621953Z" 18 | } 19 | -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # Run this command to always ignore formatting commits in `git blame` 2 | # git config blame.ignoreRevsFile .git-blame-ignore-revs 3 | 4 | # More info: 5 | # https://www.stefanjudis.com/today-i-learned/how-to-exclude-commits-from-git-blame/ 6 | # Also, a horrible bash script to list out the 20 commits with the most files changed: 7 | # git log --pretty='@%h' --shortstat | grep -v \| | tr "\n" " " | tr "@" "\n" | sed 's/,.*//' | sort -k2 -n | tail -n 20 | awk '{print "echo $(git log -1 --format=\"%H # %s\" " $1 ") - " $2 " files changed"}' | bash 8 | 9 | c6603ca48625abf5735bc4abfd5b5bc13bcf8718 # Run CSharpier on the solution (#868) - 235 files changed 10 | 11 | 12 | -------------------------------------------------------------------------------- /docker-compose.dcproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 2.1 5 | Linux 6 | dfd11a0e-fc6d-4979-87a4-7a7b5dc66695 7 | LaunchBrowser 8 | http://localhost:{ServicePort} 9 | altinn_platform_storage 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Storage.Interface/Enums/ReferenceType.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Converters; 3 | using TextJson = System.Text.Json.Serialization; 4 | 5 | namespace Altinn.Platform.Storage.Interface.Enums; 6 | 7 | /// 8 | /// The type of the connected object 9 | /// 10 | [JsonConverter(typeof(StringEnumConverter))] 11 | [TextJson.JsonConverter(typeof(TextJson.JsonStringEnumConverter))] 12 | public enum ReferenceType 13 | { 14 | /// 15 | /// The connected object is a data element 16 | /// 17 | DataElement, 18 | 19 | /// 20 | /// The connected object is a task 21 | /// 22 | Task, 23 | } 24 | -------------------------------------------------------------------------------- /src/Storage/Views/ContentOnDemand/Signature.cshtml: -------------------------------------------------------------------------------- 1 | @model List 2 | 7 |

Signeringsinformasjon

8 | 9 | 10 | 11 | 12 | 13 | 14 | @{ 15 | foreach (var signature in Model) 16 | { 17 | 18 | 19 | 20 | 21 | 22 | } 23 | } 24 |
BrukernavnSignaturtekstDato
@signature.SignedByUserName@signature.SignatureText@signature.CreatedDateTime
25 | -------------------------------------------------------------------------------- /test/UnitTest/HelperTests/LanguageHelperTest.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Platform.Storage.Helpers; 2 | using Xunit; 3 | 4 | namespace Altinn.Platform.Storage.UnitTest; 5 | 6 | public class LanguageHelperTest 7 | { 8 | [Theory] 9 | [InlineData("nb", true)] 10 | [InlineData("en", true)] 11 | [InlineData("de", true)] 12 | [InlineData("12", false)] 13 | [InlineData("", false)] 14 | [InlineData(null, false)] 15 | [InlineData("norsk", false)] 16 | [InlineData("t1", false)] 17 | public void IsTwoLetterTest(string language, bool expected) 18 | { 19 | bool result = LanguageHelper.IsTwoLetters(language); 20 | Assert.Equal(expected, result); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Storage/Authorization/IClaimsPrincipalProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Claims; 2 | 3 | namespace Altinn.Platform.Storage.Authorization; 4 | 5 | /// 6 | /// Defines the methods required for an implementation of a user JSON Web Token provider. 7 | /// The provider is used by client implementations that needs the user token in requests 8 | /// against other systems. 9 | /// 10 | public interface IClaimsPrincipalProvider 11 | { 12 | /// 13 | /// Defines a method that can return a claims principal for the current user. 14 | /// 15 | /// The Json Web Token for the current user. 16 | public ClaimsPrincipal GetUser(); 17 | } 18 | -------------------------------------------------------------------------------- /src/Storage/Migration/FunctionsAndProcedures/inserta2image.sql: -------------------------------------------------------------------------------- 1 | CREATE OR REPLACE PROCEDURE storage.inserta2image (_name TEXT, _image BYTEA) 2 | LANGUAGE 'plpgsql' 3 | AS $BODY$ 4 | DECLARE 5 | _parentId INTEGER; 6 | BEGIN 7 | SELECT id into _parentId FROM storage.a2images WHERE md5(_image) = md5(image); 8 | IF _parentId IS NOT NULL THEN 9 | INSERT INTO storage.a2images (name, parentid, image) VALUES 10 | (_name, _parentId, null) 11 | ON CONFLICT (name) DO NOTHING; 12 | ELSE 13 | INSERT INTO storage.a2images (name, parentid, image) VALUES 14 | (_name, null, _image) 15 | ON CONFLICT (name) DO UPDATE SET image = _image; 16 | END IF; 17 | END; 18 | $BODY$; -------------------------------------------------------------------------------- /src/Storage/Services/IA2OndemandFormattingService.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | using System.Threading.Tasks; 4 | using Altinn.Platform.Storage.Models; 5 | 6 | namespace Altinn.Platform.Storage.Services; 7 | 8 | /// 9 | /// This interface describes the required methods for getting ondemand content 10 | /// 11 | public interface IA2OndemandFormattingService 12 | { 13 | /// 14 | /// Get html 15 | /// 16 | /// printXslList 17 | /// xmlData 18 | /// Html as string 19 | string GetFormdataHtml(PrintViewXslBEList printXslList, Stream xmlData); 20 | } 21 | -------------------------------------------------------------------------------- /src/Storage/Migration/FunctionsAndProcedures/reada2xsls.sql: -------------------------------------------------------------------------------- 1 | CREATE OR REPLACE FUNCTION storage.reada2xsls(_org TEXT, _app TEXT, _lformid INT, _language TEXT, _xsltype INT) 2 | RETURNS TABLE (xsl TEXT, isportrait BOOL) 3 | LANGUAGE 'plpgsql' 4 | 5 | AS $BODY$ 6 | BEGIN 7 | RETURN QUERY 8 | SELECT 9 | CASE 10 | WHEN x.xsl IS NOT NULL THEN x.xsl 11 | ELSE (SELECT p.xsl FROM storage.a2xsls p WHERE p.id = x.parentid) 12 | END, 13 | x.isportrait 14 | FROM storage.a2xsls x 15 | WHERE x.org = _org and x.app = _app and x.lformid = _lformid and x.language = _language and x.xsltype = _xsltype 16 | ORDER BY pagenumber; 17 | 18 | END; 19 | $BODY$; -------------------------------------------------------------------------------- /src/Storage.Interface/Models/DeleteStatus.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | 4 | namespace Altinn.Platform.Storage.Interface.Models; 5 | 6 | /// 7 | /// Represents metadata about the delete status of an element 8 | /// 9 | public class DeleteStatus 10 | { 11 | /// 12 | /// Gets or sets if the element is hard deleted. 13 | /// 14 | [JsonProperty(PropertyName = "isHardDeleted")] 15 | public bool IsHardDeleted { get; set; } 16 | 17 | /// 18 | /// Gets or sets the date the element was marked for hard delete. 19 | /// 20 | [JsonProperty(PropertyName = "hardDeleted")] 21 | public DateTime? HardDeleted { get; set; } 22 | } 23 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/dataelements/70d122f8-0cae-44f4-8cd5-2887c251a959.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "70d122f8-0cae-44f4-8cd5-2887c251a959", 3 | "instanceGuid": "99194777-a691-433a-ace1-225e9a691653", 4 | "dataType": "model", 5 | "contentType": "application/xml", 6 | "blobStoragePath": "ttd/sensitive-data/99194777-a691-433a-ace1-225e9a691653/data/70d122f8-0cae-44f4-8cd5-2887c251a959", 7 | "selfLinks": { 8 | "apps": null, 9 | "platform": null 10 | }, 11 | "locked": false, 12 | "isRead": true, 13 | "tags": [], 14 | "fileScanResult": "NotApplicable", 15 | "created": "2025-07-17T09:00:00.000000Z", 16 | "createdBy": "1337", 17 | "lastChanged": "2025-07-17T09:00:00.000000Z", 18 | "lastChangedBy": "1337" 19 | } -------------------------------------------------------------------------------- /src/Storage/Configuration/PostgreSqlSettings.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Platform.Storage.Configuration; 2 | 3 | /// 4 | /// Settings for Postgres database 5 | /// 6 | public class PostgreSqlSettings 7 | { 8 | /// 9 | /// Connection string for the postgres db 10 | /// 11 | public string ConnectionString { get; set; } 12 | 13 | /// 14 | /// Password for app user for the postgres db 15 | /// 16 | public string StorageDbPwd { get; set; } 17 | 18 | /// 19 | /// Gets or sets a value indicating whether to include parameter values in logging/tracing. 20 | /// 21 | public bool LogParameters { get; set; } = true; 22 | } 23 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/instances/23d6aa98-df3b-4982-8d8a-8fe67a53b828.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "23d6aa98-df3b-4982-8d8a-8fe67a53b828", 3 | "instanceOwner": { 4 | "partyId": "1337" 5 | }, 6 | "appId": "tdd/endring-av-navn", 7 | "org": "tdd", 8 | "created": "2019-07-31T09:57:23.4729995Z", 9 | "lastChanged": "2019-07-31T09:57:23.4729995Z", 10 | "lastChangedBy": "1337", 11 | "dueBefore": "2019-07-05T00:00:00Z", 12 | "visibleAfter": "2019-06-05T00:00:00Z", 13 | "process": { 14 | "currentTask": { 15 | "elementId": "Task_1", 16 | "altinnTaskType": "data" 17 | } 18 | }, 19 | "status": { 20 | "isArchived": false, 21 | "isHardDeleted": false, 22 | "isSoftDeleted": false 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/instances/2f7fa5ce-e878-4e1f-a241-8c0eb1a83eab.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "2f7fa5ce-e878-4e1f-a241-8c0eb1a83eab", 3 | "instanceOwner": { 4 | "partyId": "1337" 5 | }, 6 | "appId": "tdd/endring-av-navn", 7 | "org": "tdd", 8 | "created": "2019-07-31T09:57:23.4729995Z", 9 | "lastChanged": "2019-07-31T09:57:23.4729995Z", 10 | "lastChangedBy": "1337", 11 | "dueBefore": "2019-07-05T00:00:00Z", 12 | "visibleAfter": "2019-06-05T00:00:00Z", 13 | "process": { 14 | "currentTask": { 15 | "elementId": "Task_1", 16 | "altinnTaskType": "data" 17 | } 18 | }, 19 | "status": { 20 | "isArchived": false, 21 | "isHardDeleted": false, 22 | "isSoftDeleted": false 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/instances/3c42ee2a-9464-42a8-a976-16eb926bd20a.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "20475edd-dc38-4ae0-bd64-1b20643f506c", 3 | "instanceOwner": { 4 | "partyId": "1337" 5 | }, 6 | "appId": "tdd/endring-av-navn", 7 | "org": "tdd", 8 | "created": "2019-07-31T09:57:23.4729995Z", 9 | "lastChanged": "2019-07-31T09:57:23.4729995Z", 10 | "lastChangedBy": "1337", 11 | "dueBefore": "2019-07-05T00:00:00Z", 12 | "visibleAfter": "2019-06-05T00:00:00Z", 13 | "process": { 14 | "currentTask": { 15 | "elementId": "Task_1", 16 | "altinnTaskType": "data" 17 | } 18 | }, 19 | "status": { 20 | "isArchived": false, 21 | "isHardDeleted": false, 22 | "isSoftDeleted": false 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/instances/46133fb5-a9f2-45d4-90b1-f6d93ad40713.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "46133fb5-a9f2-45d4-90b1-f6d93ad40713", 3 | "instanceOwner": { 4 | "partyId": "1337" 5 | }, 6 | "appId": "tdd/endring-av-navn", 7 | "org": "tdd", 8 | "created": "2019-07-31T09:57:23.4729995Z", 9 | "lastChanged": "2019-07-31T09:57:23.4729995Z", 10 | "lastChangedBy": "1337", 11 | "dueBefore": "2019-07-05T00:00:00Z", 12 | "visibleAfter": "2019-06-05T00:00:00Z", 13 | "process": { 14 | "currentTask": { 15 | "elementId": "Task_1", 16 | "altinnTaskType": "data" 17 | } 18 | }, 19 | "status": { 20 | "isArchived": false, 21 | "isHardDeleted": false, 22 | "isSoftDeleted": false 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/instances/7e6cc8e2-6cd4-4ad4-9ce8-c37a767677b5.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "7e6cc8e2-6cd4-4ad4-9ce8-c37a767677b5", 3 | "instanceOwner": { 4 | "partyId": "1337" 5 | }, 6 | "appId": "tdd/endring-av-navn", 7 | "org": "tdd", 8 | "created": "2019-07-31T09:57:23.4729995Z", 9 | "lastChanged": "2019-07-31T09:57:23.4729995Z", 10 | "lastChangedBy": "1337", 11 | "dueBefore": "2019-07-05T00:00:00Z", 12 | "visibleAfter": "2019-06-05T00:00:00Z", 13 | "process": { 14 | "currentTask": { 15 | "elementId": "Task_1", 16 | "altinnTaskType": "data" 17 | } 18 | }, 19 | "status": { 20 | "isArchived": false, 21 | "isHardDeleted": false, 22 | "isSoftDeleted": false 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/instances/8727385b-e7cb-4bf2-b042-89558c612826.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "8727385b-e7cb-4bf2-b042-89558c612826", 3 | "instanceOwner": { 4 | "partyId": "1337" 5 | }, 6 | "appId": "tdd/endring-av-navn", 7 | "org": "tdd", 8 | "created": "2019-07-31T09:57:23.4729995Z", 9 | "lastChanged": "2019-07-31T09:57:23.4729995Z", 10 | "lastChangedBy": "1337", 11 | "dueBefore": "2019-07-05T00:00:00Z", 12 | "visibleAfter": "2019-06-05T00:00:00Z", 13 | "process": { 14 | "currentTask": { 15 | "elementId": "Task_1", 16 | "altinnTaskType": "data" 17 | } 18 | }, 19 | "status": { 20 | "isArchived": false, 21 | "isHardDeleted": false, 22 | "isSoftDeleted": false 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/instances/a6020470-2200-4448-bed9-ef46b679bdb8.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "20475edd-dc38-4ae0-bd64-1b20643f506c", 3 | "instanceOwner": { 4 | "partyId": "1337" 5 | }, 6 | "appId": "tdd/endring-av-navn", 7 | "org": "tdd", 8 | "created": "2019-07-31T09:57:23.4729995Z", 9 | "lastChanged": "2019-07-31T09:57:23.4729995Z", 10 | "lastChangedBy": "1337", 11 | "dueBefore": "2019-07-05T00:00:00Z", 12 | "visibleAfter": "2019-06-05T00:00:00Z", 13 | "process": { 14 | "currentTask": { 15 | "elementId": "Task_1", 16 | "altinnTaskType": "data" 17 | } 18 | }, 19 | "status": { 20 | "isArchived": false, 21 | "isHardDeleted": false, 22 | "isSoftDeleted": false 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/instances/d3b326de-2dd8-49a1-834a-b1d23b11e540.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "d3b326de-2dd8-49a1-834a-b1d23b11e540", 3 | "instanceOwner": { 4 | "partyId": "1337" 5 | }, 6 | "appId": "tdd/endring-av-navn", 7 | "org": "tdd", 8 | "created": "2019-07-31T09:57:23.4729995Z", 9 | "lastChanged": "2019-07-31T09:57:23.4729995Z", 10 | "lastChangedBy": "1337", 11 | "dueBefore": "2019-07-05T00:00:00Z", 12 | "visibleAfter": "2019-06-05T00:00:00Z", 13 | "process": { 14 | "currentTask": { 15 | "elementId": "Task_1", 16 | "altinnTaskType": "data" 17 | } 18 | }, 19 | "status": { 20 | "isArchived": false, 21 | "isHardDeleted": false, 22 | "isSoftDeleted": false 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/instances/20475edd-dc38-4ae0-bd64-1b20643f506c.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "20475edd-dc38-4ae0-bd64-1b20643f506c", 3 | "instanceOwner": { 4 | "partyId": "1337" 5 | }, 6 | "appId": "tdd/endring-av-navn", 7 | "org": "tdd", 8 | "created": "2019-07-31T09:57:23.4729995Z", 9 | "lastChanged": "2019-07-31T09:57:23.4729995Z", 10 | "lastChangedBy": "1337", 11 | "dueBefore": "2019-07-05T00:00:00Z", 12 | "visibleAfter": "2019-06-05T00:00:00Z", 13 | "process": { 14 | "currentTask": { 15 | "elementId": "Task_1", 16 | "altinnTaskType": "data" 17 | } 18 | }, 19 | "status": { 20 | "isArchived": false, 21 | "isHardDeleted": false, 22 | "isSoftDeleted": false 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "local>Altinn/renovate-config" 5 | ], 6 | "customManagers": [ 7 | { 8 | "customType": "regex", 9 | "description": "Manage Alpine OS versions in container image tags", 10 | "managerFilePatterns": [ 11 | "/Dockerfile/" 12 | ], 13 | "matchStrings": [ 14 | "(?:FROM\\s+)(?[\\S]+):(?[\\S]+)@(?sha256:[a-f0-9]+)" 15 | ], 16 | "versioningTemplate": "regex:^(?[\\S]*\\d+\\.\\d+(?:\\.\\d+)?(?:[\\S]*)?-alpine-?)(?\\d+)\\.(?\\d+)(?:\\.(?\\d+))?$", 17 | "datasourceTemplate": "docker" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /src/Storage.Interface/Models/ProcessStateUpdate.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Newtonsoft.Json; 3 | 4 | namespace Altinn.Platform.Storage.Interface.Models; 5 | 6 | /// 7 | /// Process state update including events 8 | /// 9 | [JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)] 10 | public class ProcessStateUpdate 11 | { 12 | /// 13 | /// The state of the process 14 | /// 15 | [JsonProperty(PropertyName = "state")] 16 | public ProcessState State { get; set; } 17 | 18 | /// 19 | /// The instance events produced during process/next 20 | /// 21 | [JsonProperty(PropertyName = "events")] 22 | public List Events { get; set; } 23 | } 24 | -------------------------------------------------------------------------------- /test/k6/src/api/process.js: -------------------------------------------------------------------------------- 1 | import http from "k6/http"; 2 | import * as apiHelper from "../apiHelpers.js"; 3 | import * as config from "../config.js"; 4 | 5 | export function putProcess(token, instanceId, serializedProcessState) { 6 | var endpoint = config.buildInstanceUrl(instanceId, null, "process"); 7 | var params = apiHelper.buildHeaderWithBearerAndContentType( 8 | token, 9 | "application/json" 10 | ); 11 | 12 | return http.put(endpoint, serializedProcessState, params); 13 | } 14 | 15 | export function getProcessHistory(token, instanceId) { 16 | var endpoint = 17 | config.buildInstanceUrl(instanceId, null, "process") + "/history"; 18 | var params = apiHelper.buildHeaderWithBearer(token); 19 | 20 | return http.get(endpoint, params); 21 | } 22 | -------------------------------------------------------------------------------- /src/Storage.Interface/Models/HideSettings.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Newtonsoft.Json; 3 | 4 | namespace Altinn.Platform.Storage.Interface.Models; 5 | 6 | /// 7 | /// A class to hold hide settings 8 | /// 9 | [JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)] 10 | public class HideSettings 11 | { 12 | /// 13 | /// Gets or sets the always hide property 14 | /// 15 | [JsonProperty(PropertyName = "hideAlways")] 16 | public bool HideAlways { get; set; } 17 | 18 | /// 19 | /// Gets or sets a list of tasks where hide should be applied. 20 | /// 21 | [JsonProperty(PropertyName = "hideOnTask")] 22 | public List HideOnTask { get; set; } 23 | } 24 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/dataelements/887c5e56-6f73-494a-9730-6ebd11bffe30.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "887c5e56-6f73-494a-9730-6ebd11bffe30", 3 | "instanceGuid": "4914257c-9920-47a5-a37a-eae80f950767", 4 | "dataType": "default", 5 | "filename": null, 6 | "contentType": "application/xml", 7 | "blobStoragePath": "ttd/autodelete-data-app/4914257c-9920-47a5-a37a-eae80f950767/data/887c5e56-6f73-494a-9730-6ebd11bffe30", 8 | "selfLinks": { 9 | "apps": null, 10 | "platform": null 11 | }, 12 | "size": 563, 13 | "locked": true, 14 | "refs": [], 15 | "isRead": true, 16 | "tags": [], 17 | "created": "2022-03-21T14:37:08.6095258Z", 18 | "createdBy": "20000000", 19 | "lastChanged": "2022-03-21T14:42:14.2692641Z", 20 | "lastChangedBy": "20000000" 21 | } 22 | -------------------------------------------------------------------------------- /src/Storage/Migration/FunctionsAndProcedures/readdeletedinstances.sql: -------------------------------------------------------------------------------- 1 | CREATE OR REPLACE FUNCTION storage.readdeletedinstances() 2 | RETURNS TABLE (instance JSONB) 3 | LANGUAGE 'plpgsql' 4 | 5 | AS $BODY$ 6 | BEGIN 7 | RETURN QUERY 8 | -- Make sure that part of the where clause is exactly as in filtered index instances_isharddeleted_and_more 9 | SELECT i.instance FROM storage.instances i 10 | WHERE (i.instance -> 'Status' -> 'IsHardDeleted')::BOOLEAN AND 11 | ( 12 | NOT (i.instance -> 'Status' -> 'IsArchived')::BOOLEAN 13 | OR (i.instance -> 'CompleteConfirmations') IS NOT NULL AND (i.instance -> 'Status' ->> 'HardDeleted')::TIMESTAMPTZ <= (NOW() - (7 ||' days')::INTERVAL) 14 | ) 15 | AND i.AltinnMainVersion >= 3; 16 | END; 17 | $BODY$; 18 | -------------------------------------------------------------------------------- /test/k6/src/errorhandler.js: -------------------------------------------------------------------------------- 1 | import { Counter } from "k6/metrics"; 2 | import { fail } from "k6"; 3 | 4 | let ErrorCount = new Counter("errors"); 5 | 6 | //Adds a count to the error counter when value of success is false 7 | export function addErrorCount(success) { 8 | if (!success) { 9 | ErrorCount.add(1); 10 | } 11 | } 12 | 13 | /** 14 | * Stops k6 iteration when success is false and prints test name with response code 15 | * @param {String} testName 16 | * @param {boolean} success 17 | * @param {JSON} res 18 | */ 19 | export function stopIterationOnFail(testName, success, res) { 20 | if (!success && res != null) { 21 | fail(testName + ": Response code: " + res.status + ". Response message: " + res.body); 22 | } else if (!success) { 23 | fail(testName); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Storage/Clients/IFileScanQueueClient.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | namespace Altinn.Platform.Storage.Clients; 5 | 6 | /// 7 | /// This interface describes the public interface of a file scan queue client implementation. 8 | /// 9 | public interface IFileScanQueueClient 10 | { 11 | /// 12 | /// Put the content of the given string on the File Scan queue. 13 | /// 14 | /// The content of the message to be put on the queue. 15 | /// A cancellation token should the request be cancelled. 16 | /// A task representing the asynconous call to the queue service. 17 | Task EnqueueFileScan(string content, CancellationToken ct); 18 | } 19 | -------------------------------------------------------------------------------- /src/Storage.Interface/Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | all 5 | runtime; build; native; contentfiles; analyzers 6 | 7 | 8 | 9 | 10 | $([System.IO.Path]::Combine('$(IntermediateOutputPath)','$(TargetFrameworkMoniker).AssemblyAttributes$(DefaultLanguageSourceExtension)')) 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Storage/Messages/SyncInstanceToDialogportenCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Altinn.Platform.Storage.Interface.Enums; 3 | using Wolverine.Attributes; 4 | 5 | namespace Altinn.Platform.Storage.Messages; 6 | 7 | /// 8 | /// Represents a message about update for an instance to send to service bus. 9 | /// 10 | [MessageIdentity("Altinn.DialogportenAdapter.SyncInstanceCommand")] 11 | public record SyncInstanceToDialogportenCommand( 12 | string AppId, // eks: krt/krt-1012a-1 13 | string PartyId, // eks: 51701090 14 | string InstanceId, // eks: 0dbc1da6-f744-4fff-83bc-131e7988a1bb 15 | DateTime InstanceCreatedAt, // eks: 2025-06-18T08:54:53.6233769Z 16 | bool IsMigration, // Always false when Storage is sender 17 | InstanceEventType EventType = InstanceEventType.None 18 | ); 19 | -------------------------------------------------------------------------------- /test/UnitTest/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:50746/", 7 | "sslPort": 44388 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "Altinn.Platform.Storage.UnitTest": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | }, 24 | "applicationUrl": "https://localhost:5001;http://localhost:5000" 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /test/k6/src/api/texts.js: -------------------------------------------------------------------------------- 1 | import http from "k6/http"; 2 | import * as apiHelper from "../apiHelpers.js"; 3 | import * as config from "../config.js"; 4 | import { stopIterationOnFail } from "../errorhandler.js"; 5 | 6 | export function postText(token, org, app, serializedTextResource) { 7 | var endpoint = config.buildAppUrl(org, app, "texts"); 8 | 9 | var params = apiHelper.buildHeaderWithBearerAndContentType( 10 | token, 11 | "application/json" 12 | ); 13 | 14 | var requestBody = serializedTextResource; 15 | return http.post(endpoint, requestBody, params); 16 | } 17 | 18 | export function getTexts(token, org, app, language) { 19 | var endpoint = config.buildAppUrl(org, app, "texts") + "/" + language; 20 | var params = apiHelper.buildHeaderWithBearer(token); 21 | return http.get(endpoint, params); 22 | } 23 | -------------------------------------------------------------------------------- /src/Storage/Views/ContentOnDemand/Payment.cshtml: -------------------------------------------------------------------------------- 1 | 2 | @model Altinn.Platform.Storage.Controllers.PaymentView 3 | 8 |

Betalingsinformasjon

9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
OrdrenummerBeskrivelseBeløpDato for betaling
@Model.OrderId@Model.Description@((Model.PaymentSum / 100.0).ToString("N2", new System.Globalization.CultureInfo("no-NB")))@Model.LastUpdatedDate.ToString("dd-MM-yyyy HH:mm:ss", System.Globalization.CultureInfo.InvariantCulture)
23 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/instances/377efa97-80ee-4cc6-8d48-09de12cc273d.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "377efa97-80ee-4cc6-8d48-09de12cc273d", 3 | "instanceOwner": { 4 | "partyId": "1337" 5 | }, 6 | "appId": "tdd/endring-av-navn", 7 | "org": "tdd", 8 | "visibleAfter": "2019-07-31T09:57:23.4729995Z", 9 | "created": "2019-07-31T09:57:23.4729995Z", 10 | "lastChanged": "2019-07-31T09:57:23.4729995Z", 11 | "lastChangedBy": "1337", 12 | "dueBefore": "2019-07-05T00:00:00Z", 13 | "process": { 14 | "started": "2020-04-29T13:53:01.7020218Z", 15 | "startEvent": "StartEvent_1", 16 | "currentTask": { 17 | "elementId": "Task_1", 18 | "altinnTaskType": "data" 19 | } 20 | }, 21 | "status": { 22 | "isArchived": false, 23 | "isHardDeleted": false, 24 | "isSoftDeleted": false 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Storage/Migration/v0.25/01-create-tables.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS storage.outbox 2 | ( 3 | instanceid UUID PRIMARY KEY, 4 | appid TEXT NOT NULL, 5 | partyid BIGINT NOT NULL, 6 | instancecreated TIMESTAMPTZ NOT NULL, 7 | validfrom TIMESTAMPTZ NOT NULL, 8 | ismigration BOOLEAN NOT NULL DEFAULT FALSE, 9 | instanceeventtype SMALLINT NOT NULL DEFAULT 0 10 | ) 11 | TABLESPACE pg_default; 12 | 13 | CREATE TABLE IF NOT EXISTS storage.leases ( 14 | resource TEXT PRIMARY KEY, 15 | holder UUID NOT NULL, 16 | expires_at TIMESTAMPTZ NOT NULL 17 | ); 18 | 19 | GRANT SELECT,INSERT,UPDATE,REFERENCES,DELETE,TRUNCATE,REFERENCES,TRIGGER ON ALL TABLES IN SCHEMA storage TO platform_storage; 20 | GRANT SELECT,INSERT,UPDATE,REFERENCES,DELETE,TRUNCATE,REFERENCES,TRIGGER ON ALL TABLES IN SCHEMA storage TO platform_storage_admin; -------------------------------------------------------------------------------- /src/Storage.Interface/Models/AuthInfo.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace Altinn.Platform.Storage.Interface.Models 4 | { 5 | /// 6 | /// Artifacts from storage needed by authorization for doing authorization 7 | /// 8 | [JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)] 9 | public class AuthInfo 10 | { 11 | /// 12 | /// Gets or sets the current process of the instance to authorize 13 | /// 14 | [JsonProperty(PropertyName = "process")] 15 | public ProcessState Process { get; set; } 16 | 17 | /// 18 | /// Gets or sets app id for the instance to authorize 19 | /// 20 | [JsonProperty(PropertyName = "appId")] 21 | public string AppId { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/UnitTest/data/apps/tdd/read-write-unlock/config/applicationmetadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "tdd/read-write-unlock", 3 | "org": "tdd", 4 | "created": "2019-09-24T10:02:41.0839253Z", 5 | "createdBy": "Kritsi", 6 | "lastChanged": "2019-09-24T10:02:41.0839254Z", 7 | "lastChangedBy": "Kritsi", 8 | "title": { 9 | "nb": "Read write unlock", 10 | "nb-NO": "Read write unlock", 11 | "en": "Read write unlock" 12 | }, 13 | "copyInstanceSettings": { 14 | "enabled": true 15 | }, 16 | "dataTypes": [ 17 | { 18 | "id": "default", 19 | "allowedContentTypes": [ "application/xml" ], 20 | "maxCount": 1, 21 | "appLogic": { 22 | "autoCreate": true, 23 | "ClassRef": "App.IntegrationTests.Mocks.Apps.tdd.endring_av_navn.Skjema" 24 | }, 25 | "taskId": "Task_1" 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/instances/1916cd18-3b8e-46f8-aeaf-4bc3397ddd55.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "1916cd18-3b8e-46f8-aeaf-4bc3397ddd55", 3 | "instanceOwner": { 4 | "partyId": "1600" 5 | }, 6 | "appId": "ttd/signing-app", 7 | "org": "ttd", 8 | "created": "2019-07-31T09:57:23.4729995Z", 9 | "lastChanged": "2019-07-31T09:57:23.4729995Z", 10 | "dueBefore": "2019-07-05T00:00:00Z", 11 | "process": { 12 | "started": "2020-03-02T16:41:30.2723648Z", 13 | "startEvent": "StartEvent_1", 14 | "currentTask": { 15 | "flow": 2, 16 | "started": "2020-03-02T16:41:30.2727355Z", 17 | "elementId": "Task_1", 18 | "name": "Signering", 19 | "altinnTaskType": "signing" 20 | } 21 | }, 22 | "status": { 23 | "isArchived": false, 24 | "isHardDeleted": false, 25 | "isSoftDeleted": false 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Storage.Interface/Models/ResourceLinks.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace Altinn.Platform.Storage.Interface.Models 4 | { 5 | /// 6 | /// Represent actual links to resources in various endpoints. 7 | /// 8 | [JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)] 9 | public class ResourceLinks 10 | { 11 | /// 12 | /// Gets or sets the application resource link. It is null if data is fetched from platform storage. 13 | /// 14 | [JsonProperty(PropertyName = "apps")] 15 | public string Apps { get; set; } 16 | 17 | /// 18 | /// Gets or sets platform resource link. 19 | /// 20 | [JsonProperty(PropertyName = "platform")] 21 | public string Platform { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/dataelements/1336b773-4ae2-4bdf-9529-d71dfc1c8b43.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "1336b773-4ae2-4bdf-9529-d71dfc1c8b43", 3 | "instanceGuid": "4914257c-9920-47a5-a37a-eae80f950767", 4 | "dataType": "ref-data-as-pdf", 5 | "filename": "autodelete-data-app.pdf", 6 | "contentType": "application/pdf", 7 | "blobStoragePath": "ttd/autodelete-data-app/4914257c-9920-47a5-a37a-eae80f950767/data/1336b773-4ae2-4bdf-9529-d71dfc1c8b43", 8 | "selfLinks": { 9 | "apps": null, 10 | "platform": null 11 | }, 12 | "size": 4918, 13 | "locked": false, 14 | "refs": [], 15 | "isRead": true, 16 | "tags": [], 17 | "created": "2022-03-21T14:42:15.3634075Z", 18 | "createdBy": "20000000", 19 | "lastChanged": "2022-03-21T14:42:15.3634075Z", 20 | "lastChangedBy": "20000000", 21 | "lastChangedBy": "123", 22 | "isRead": false 23 | } 24 | -------------------------------------------------------------------------------- /src/Storage.Interface/Models/MessageBoxConfig.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace Altinn.Platform.Storage.Interface.Models 4 | { 5 | /// 6 | /// Class that holds the message box configuration for an application. 7 | /// 8 | [JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)] 9 | public class MessageBoxConfig 10 | { 11 | /// 12 | /// Gets or setts the hide settings. 13 | /// 14 | [JsonProperty(PropertyName = "hideSettings")] 15 | public HideSettings HideSettings { get; set; } 16 | 17 | /// 18 | /// Gets or sets the sync adapter settings. 19 | /// 20 | [JsonProperty(PropertyName = "syncAdapterSettings")] 21 | public SyncAdapterSettings SyncAdapterSettings { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/UnitTest/Mocks/Repository/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Platform.Storage.Repository; 2 | using Microsoft.Extensions.DependencyInjection; 3 | 4 | namespace Altinn.Platform.Storage.UnitTest.Mocks.Repository; 5 | 6 | public static class ServiceCollectionExtensions 7 | { 8 | public static void AddMockRepositories(this IServiceCollection services) 9 | { 10 | services.AddSingleton(); 11 | services.AddSingleton(); 12 | services.AddSingleton(); 13 | services.AddSingleton(); 14 | services.AddSingleton(); 15 | services.AddSingleton(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/instances/ef1b16fc-4566-4577-b2d8-db74fbee4f7c.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "ef1b16fc-4566-4577-b2d8-db74fbee4f7c", 3 | "instanceOwner": { "partyId": "1337" }, 4 | "appId": "tdd/endring-av-navn", 5 | "org": "tdd", 6 | "dueBefore": "2019-07-05T00:00:00Z", 7 | "visibleAfter": "2019-06-05T00:00:00Z", 8 | "process": { 9 | "currentTask": { 10 | "elementId": "Task_1", 11 | "altinnTaskType": "data" 12 | } 13 | }, 14 | "status": { 15 | "isArchived": false, 16 | "isHardDeleted": false, 17 | "isSoftDeleted": false 18 | }, 19 | "completeConfirmations": [ 20 | { 21 | "stakeholderId": "tdd", 22 | "confirmedOn": "2020-04-29T13:06:51.6128771Z" 23 | } 24 | ], 25 | "created": "2019-07-31T09:57:23.4729995Z", 26 | "lastChanged": "2020-04-29T13:06:52.2636339Z", 27 | "lastChangedBy": "1337" 28 | } 29 | -------------------------------------------------------------------------------- /test/UnitTest/Mocks/PublicSigningKeyProviderMock.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Security.Cryptography.X509Certificates; 6 | using System.Threading.Tasks; 7 | using Altinn.Common.AccessToken.Services; 8 | using Microsoft.IdentityModel.Tokens; 9 | 10 | namespace Altinn.Platform.Storage.UnitTest.Mocks; 11 | 12 | public class PublicSigningKeyProviderMock : IPublicSigningKeyProvider 13 | { 14 | public Task> GetSigningKeys(string issuer) 15 | { 16 | List signingKeys = []; 17 | 18 | X509Certificate2 cert = X509CertificateLoader.LoadCertificateFromFile($"{issuer}-org.pem"); 19 | SecurityKey key = new X509SecurityKey(cert); 20 | 21 | signingKeys.Add(key); 22 | 23 | return Task.FromResult(signingKeys.AsEnumerable()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Storage/Models/ServiceError.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn.Platform.Storage.Models; 2 | 3 | /// 4 | /// A class representing a service error object used to transfer error information from service to controller. 5 | /// 6 | public class ServiceError 7 | { 8 | /// 9 | /// The error code 10 | /// 11 | /// An error code translates directly into an HTTP status code 12 | public int ErrorCode { get; private set; } 13 | 14 | /// 15 | /// The error message 16 | /// 17 | public string ErrorMessage { get; private set; } 18 | 19 | /// 20 | /// Create a new instance of a service error 21 | /// 22 | public ServiceError(int errorCode, string errorMessage) 23 | { 24 | ErrorCode = errorCode; 25 | ErrorMessage = errorMessage; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/dataelements/887c5e56-6f73-494a-9730-6ebd11bffe88.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "887c5e56-6f73-494a-9730-6ebd11bffe88", 3 | "instanceGuid": "4914257c-9920-47a5-a37a-eae80f950767", 4 | "dataType": "default", 5 | "filename": null, 6 | "contentType": "application/xml", 7 | "blobStoragePath": "ttd/autodelete-data-app/4914257c-9920-47a5-a37a-eae80f950767/data/887c5e56-6f73-494a-9730-6ebd11bffe88", 8 | "selfLinks": { 9 | "apps": null, 10 | "platform": null 11 | }, 12 | "deleteStatus": { 13 | "isHardDeleted": true, 14 | "hardDeleted": "2022-03-22T14:37:08.6095258Z" 15 | }, 16 | "size": 563, 17 | "locked": true, 18 | "refs": [], 19 | "isRead": true, 20 | "tags": [], 21 | "created": "2022-03-21T14:37:08.6095258Z", 22 | "createdBy": "20000000", 23 | "lastChanged": "2022-03-21T14:42:14.2692641Z", 24 | "lastChangedBy": "20000000" 25 | } 26 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/instances/99194777-a691-433a-ace1-225e9a691653.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "99194777-a691-433a-ace1-225e9a691653", 3 | "instanceOwner": { 4 | "partyId": "1337", 5 | "personNumber": "01039012345" 6 | }, 7 | "appId": "ttd/sensitive-data", 8 | "org": "ttd", 9 | "visibleAfter": "2025-07-17T09:00:00.000000Z", 10 | "process": { 11 | "started": "2025-07-17T09:00:00.000000Z", 12 | "startEvent": "StartEvent_1", 13 | "currentTask": { 14 | "elementId": "Task_1", 15 | "altinnTaskType": "data" 16 | } 17 | }, 18 | "status": { 19 | "isArchived": false, 20 | "isSoftDeleted": false, 21 | "isHardDeleted": false, 22 | "readStatus": "Read" 23 | }, 24 | "data": [], 25 | "created": "2025-07-17T09:00:00.000000Z", 26 | "createdBy": "1337", 27 | "lastChanged": "2025-07-17T09:00:00.000000Z", 28 | "lastChangedBy": "1337" 29 | } -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/instances/1916cd18-3b8e-46f8-aeaf-4bc3397ddd56.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "1916cd18-3b8e-46f8-aeaf-4bc3397ddd56", 3 | "instanceOwner": { 4 | "partyId": "1606" 5 | }, 6 | "appId": "ttd/complete-test", 7 | "org": "ttd", 8 | "created": "2019-07-31T09:57:23.4729995Z", 9 | "lastChanged": "2019-07-31T09:57:23.4729995Z", 10 | "dueBefore": "2019-07-05T00:00:00Z", 11 | "visibleAfter": "2093-06-05T00:00:00Z", 12 | "process": { 13 | "started": "2020-03-02T16:41:30.2723648Z", 14 | "startEvent": "StartEvent_1", 15 | "currentTask": { 16 | "flow": 2, 17 | "started": "2020-03-02T16:41:30.2727355Z", 18 | "elementId": "Task_1", 19 | "name": "Utfylling", 20 | "altinnTaskType": "data" 21 | } 22 | }, 23 | "status": { 24 | "isArchived": false, 25 | "isHardDeleted": false, 26 | "isSoftDeleted": false 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/instances/1916cd18-3b8e-46f8-aeaf-4bc3397ddd69.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "1916cd18-3b8e-46f8-aeaf-4bc3397ddd69", 3 | "instanceOwner": { 4 | "partyId": "1000" 5 | }, 6 | "appId": "ttd/complete-test", 7 | "org": "ttd", 8 | "created": "2019-07-31T09:57:23.4729995Z", 9 | "lastChanged": "2019-07-31T09:57:23.4729995Z", 10 | "dueBefore": "2019-07-05T00:00:00Z", 11 | "visibleAfter": "2019-06-05T00:00:00Z", 12 | "process": { 13 | "started": "2020-03-02T16:41:30.2723648Z", 14 | "startEvent": "StartEvent_1", 15 | "currentTask": { 16 | "flow": 2, 17 | "started": "2020-03-02T16:41:30.2727355Z", 18 | "elementId": "Task_1", 19 | "name": "Utfylling", 20 | "altinnTaskType": "data" 21 | } 22 | }, 23 | "status": { 24 | "isArchived": false, 25 | "isHardDeleted": false, 26 | "isSoftDeleted": false 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | NU1900;NU1901;NU1902;NU1903;NU1904 5 | 6 | 7 | 15 | 16 | 17 | 18 | all 19 | runtime; build; native; contentfiles; analyzers 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/instances/1916cd18-3b8e-46f8-aeaf-4bc3397ddd70.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "1916cd18-3b8e-46f8-aeaf-4bc3397ddd70", 3 | "instanceOwner": { 4 | "partyId": "1000" 5 | }, 6 | "appId": "ttd/complete-test", 7 | "org": "ttd", 8 | "dueBefore": "2019-07-05T00:00:00Z", 9 | "visibleAfter": "2019-06-05T00:00:00Z", 10 | "process": { 11 | "started": "2020-03-02T16:02:59.861614Z", 12 | "startEvent": "StartEvent_1", 13 | "ended": "2020-03-02T16:03:12.7607789Z", 14 | "endEvent": "EndEvent_1" 15 | }, 16 | "status": { 17 | "isArchived": true, 18 | "archived": "2020-03-02T16:03:12.7607789Z", 19 | "isHardDeleted": false, 20 | "isSoftDeleted": false 21 | }, 22 | "appOwner": {}, 23 | "created": "2020-03-02T16:02:59.9465205Z", 24 | "createdBy": "20003656", 25 | "lastChanged": "2020-03-02T16:03:14.7828144Z", 26 | "lastChangedBy": "20003656" 27 | } 28 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/instances/1916cd18-3b8e-46f8-aeaf-4bc3397ddd90.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "1916cd18-3b8e-46f8-aeaf-4bc3397ddd90", 3 | "instanceOwner": { 4 | "partyId": "1600" 5 | }, 6 | "appId": "ttd/complete-test", 7 | "org": "ttd", 8 | "dueBefore": "2019-07-05T00:00:00Z", 9 | "visibleAfter": "2019-06-05T00:00:00Z", 10 | "process": { 11 | "started": "2020-03-02T16:02:59.861614Z", 12 | "startEvent": "StartEvent_1", 13 | "ended": "2020-03-02T16:03:12.7607789Z", 14 | "endEvent": "EndEvent_1" 15 | }, 16 | "status": { 17 | "archived": "2020-03-02T16:03:12.7607789Z", 18 | "isArchived": true, 19 | "isHardDeleted": false, 20 | "isSoftDeleted": false 21 | }, 22 | "appOwner": {}, 23 | "created": "2020-03-02T16:02:59.9465205Z", 24 | "createdBy": "20003656", 25 | "lastChanged": "2020-03-02T16:03:14.7828144Z", 26 | "lastChangedBy": "20003656" 27 | } 28 | -------------------------------------------------------------------------------- /src/Storage/Migration/FunctionsAndProcedures/deletedataelements.sql: -------------------------------------------------------------------------------- 1 | CREATE OR REPLACE FUNCTION storage.deletedataelements(_instanceguid UUID) 2 | RETURNS INT 3 | LANGUAGE 'plpgsql' 4 | AS $BODY$ 5 | DECLARE 6 | _deleteCount INTEGER; 7 | BEGIN 8 | UPDATE storage.instances 9 | SET lastchanged = NOW(), 10 | instance = instance 11 | || jsonb_set('{"LastChanged":""}', '{LastChanged}', to_jsonb(REPLACE((NOW() AT TIME ZONE 'UTC')::TEXT, ' ', 'T') || 'Z')) 12 | || jsonb_set('{"LastChangedBy":""}', '{LastChangedBy}', to_jsonb('altinn'::TEXT)) 13 | WHERE alternateid = _instanceguid; 14 | 15 | DELETE FROM storage.dataelements d 16 | USING storage.instances i 17 | WHERE i.alternateid = d.instanceguid AND i.alternateid = _instanceguid; 18 | GET DIAGNOSTICS _deleteCount = ROW_COUNT; 19 | RETURN _deleteCount; 20 | END; 21 | $BODY$; -------------------------------------------------------------------------------- /test/UnitTest/data/apps/ttd/autodelete-data-app/config/applicationmetadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "ttd/autodelete-data-app", 3 | "org": "ttd", 4 | "title": { 5 | "nb": "autodelete-data-ap" 6 | }, 7 | "created": "2022-06-01T10:02:41.0839253Z", 8 | "createdBy": "JohnnyDepp", 9 | "lastChanged": "2022-06-01T10:02:41.0839254Z", 10 | "lastChangedBy": "JohnnyDepp", 11 | "dataTypes": [ 12 | { 13 | "id": "default", 14 | "allowedContentTypes": [ "application/xml" ], 15 | "maxCount": 1, 16 | "appLogic": { 17 | "autoCreate": true, 18 | "autoDeleteOnProcessEnd": true, 19 | "ClassRef": "App.IntegrationTests.Mocks.Apps.ttd.endring_av_navn.Skjema" 20 | }, 21 | "taskId": "Task_1" 22 | }, 23 | { 24 | "id": "ref-data-as-pdf", 25 | "allowedContentTypes": [ "application/pdf" ], 26 | "enablePdfCreation": true 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/dataelements/998c5e56-6f73-494a-9730-6ebd11bffe88.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "998c5e56-6f73-494a-9730-6ebd11bffe88", 3 | "instanceGuid": "4c67392f-36c6-42dc-998f-c367e771dccc", 4 | "dataType": "default", 5 | "filename": null, 6 | "contentType": "application/xml", 7 | "blobStoragePath": "tdd/read-write-unlock/4c67392f-36c6-42dc-998f-c367e771dccc/data/998c5e56-6f73-494a-9730-6ebd11bffe88", 8 | "selfLinks": { 9 | "apps": null, 10 | "platform": "https://platform.yt01.altinn.cloud/storage/api/v1/instances/58621971/4c67392f-36c6-42dc-998f-c367e771dccc/data/998c5e56-6f73-494a-9730-6ebd11bffe88" 11 | }, 12 | "size": 563, 13 | "locked": false, 14 | "refs": [], 15 | "isRead": true, 16 | "tags": [], 17 | "created": "2022-03-21T14:37:08.6095258Z", 18 | "createdBy": "20000000", 19 | "lastChanged": "2022-03-21T14:42:14.2692641Z", 20 | "lastChangedBy": "20000000" 21 | } 22 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/dataelements/998c5e56-6f73-494a-9730-6ebd11bfff99.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "998c5e56-6f73-494a-9730-6ebd11bfff99", 3 | "instanceGuid": "4c67392f-36c6-42dc-998f-c367e771dcdd", 4 | "dataType": "default", 5 | "filename": null, 6 | "contentType": "application/xml", 7 | "blobStoragePath": "tdd/read-write-unlock/4c67392f-36c6-42dc-998f-c367e771dcdd/data/998c5e56-6f73-494a-9730-6ebd11bfff99", 8 | "selfLinks": { 9 | "apps": null, 10 | "platform": "https://platform.yt01.altinn.cloud/storage/api/v1/instances/58621971/4c67392f-36c6-42dc-998f-c367e771dcdd/data/998c5e56-6f73-494a-9730-6ebd11bffe99" 11 | }, 12 | "size": 563, 13 | "locked": true, 14 | "refs": [], 15 | "isRead": true, 16 | "tags": [], 17 | "created": "2022-03-21T14:37:08.6095258Z", 18 | "createdBy": "20000000", 19 | "lastChanged": "2022-03-21T14:42:14.2692641Z", 20 | "lastChangedBy": "20000000" 21 | } 22 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/texts/tdd-endring-av-navn-en.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "tdd-endring-av-navn-en", 3 | "org": "tdd", 4 | "language": "en", 5 | "resources": [ 6 | { 7 | "id": "11339.InnsenderPostnummerdatadef11339.Label", 8 | "value": "Post code", 9 | "variables": null 10 | }, 11 | { 12 | "id": "34891.SignererTredjeReferanseAltinndatadef34891.Label", 13 | "value": "Reference number Altinn", 14 | "variables": null 15 | }, 16 | { 17 | "id": "ServiceName", 18 | "value": "Name change", 19 | "variables": null 20 | }, 21 | { 22 | "id": "substatus.accepted.description", 23 | "value": "Application approved by all instances.", 24 | "variables": null 25 | }, 26 | { 27 | "id": "substatus.accepted.label", 28 | "value": "Application approved", 29 | "variables": null 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /test/UnitTest/data/apps/tests-sailor.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "tests-sailor", 3 | "org": "tests", 4 | "title": { 5 | "nb": "Testapplikasjon", 6 | "en": "Test application" 7 | }, 8 | "process": { 9 | "currentTask": { 10 | "elementId": "Task_1", 11 | "name": "FormFilling" 12 | } 13 | }, 14 | "validFrom": "2019-12-05T17:27:50.6951077Z", 15 | "dataTypes": [ 16 | { 17 | "id": "default", 18 | "allowedContentTypes": [ 19 | "application/xml" 20 | ], 21 | "maxCount": 0, 22 | "minCount": 0 23 | } 24 | ], 25 | "created": "2019-12-05T17:27:50.6951077Z", 26 | "lastChanged": "2019-12-05T17:27:50.6951077Z", 27 | "_rid": "5k4UALAtbQvaAAAAAAAAAA==", 28 | "_self": "dbs/5k4UAA==/colls/5k4UALAtbQs=/docs/5k4UALAtbQvaAAAAAAAAAA==/", 29 | "_etag": "\"00000000-0000-0000-ab91-51c9f01501d5\"", 30 | "_attachments": "attachments/", 31 | "_ts": 1575566870 32 | } 33 | -------------------------------------------------------------------------------- /test/UnitTest/Mocks/Repository/InstanceAndEventsRepositoryMock.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Altinn.Platform.Storage.Interface.Models; 5 | using Altinn.Platform.Storage.Repository; 6 | 7 | namespace Altinn.Platform.Storage.UnitTest.Mocks.Repository; 8 | 9 | public class InstanceAndEventsRepositoryMock : IInstanceAndEventsRepository 10 | { 11 | public Task Update( 12 | Instance instance, 13 | List updateProperties, 14 | List events, 15 | CancellationToken cancellationToken 16 | ) 17 | { 18 | if (instance.Id.Equals("1337/d3b326de-2dd8-49a1-834a-b1d23b11e540")) 19 | { 20 | return Task.FromResult(null); 21 | } 22 | 23 | instance.Data = new List(); 24 | 25 | return Task.FromResult(instance); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Storage/Helpers/StringHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Altinn.Platform.Storage.Helpers; 4 | 5 | /// 6 | /// Provides string helper extension methods. 7 | /// 8 | public static class StringHelper 9 | { 10 | /// 11 | /// Removes all newline characters from the specified string. 12 | /// 13 | /// The string from which to remove newline characters. 14 | /// A string with all newline characters removed, or the original string if it is null or empty. 15 | public static string RemoveNewlines(this string value) 16 | { 17 | if (string.IsNullOrEmpty(value)) 18 | { 19 | return value; 20 | } 21 | 22 | return value 23 | .Replace("\n", string.Empty) 24 | ?.Replace("\r", string.Empty) 25 | ?.Replace(Environment.NewLine, string.Empty); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Storage/Services/IRegisterService.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Altinn.Platform.Register.Models; 3 | 4 | namespace Altinn.Platform.Storage.Services; 5 | 6 | /// 7 | /// Interface to handle services exposed in Platform Register 8 | /// 9 | public interface IRegisterService 10 | { 11 | /// 12 | /// Returns party information 13 | /// 14 | /// The partyId 15 | /// The party for the given partyId 16 | Task GetParty(int partyId); 17 | 18 | /// 19 | /// Party lookup 20 | /// 21 | /// The f or d number to look up 22 | /// The organisation number to look up 23 | /// >The partyId corresponding to the provided person or organisation 24 | Task PartyLookup(string person, string orgNo); 25 | } 26 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/instances/1916cd18-3b8e-46f8-aeaf-4bc3397ddd89.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "1916cd18-3b8e-46f8-aeaf-4bc3397ddd89", 3 | "instanceOwner": { 4 | "partyId": "1600" 5 | }, 6 | "appId": "ttd/complete-test", 7 | "org": "ttd", 8 | "created": "2019-07-31T09:57:23.4729995Z", 9 | "lastChanged": "2019-07-31T09:57:23.4729995Z", 10 | "dueBefore": "2019-07-05T00:00:00Z", 11 | "visibleAfter": "2019-06-05T00:00:00Z", 12 | "process": { 13 | "started": "2020-03-02T16:41:30.2723648Z", 14 | "startEvent": "StartEvent_1", 15 | "currentTask": { 16 | "flow": 2, 17 | "started": "2020-03-02T16:41:30.2727355Z", 18 | "elementId": "Task_1", 19 | "name": "Utfylling", 20 | "altinnTaskType": "data" 21 | } 22 | }, 23 | "status": { 24 | "isArchived": false, 25 | "isHardDeleted": true, 26 | "hardDeleted": "2020-03-02T16:41:30.2727355Z", 27 | "isSoftDeleted": false 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/instances/07274f48-8313-4e2d-9788-bbdacef5a54e.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "07274f48-8313-4e2d-9788-bbdacef5a54e", 3 | "instanceOwner": { 4 | "partyId": "1337", 5 | "organisationNumber": "725736800" 6 | }, 7 | "appId": "tdd/endring-av-navn", 8 | "org": "tdd", 9 | "visibleAfter": "2021-04-06T05:16:23.6793863Z", 10 | "process": { 11 | "started": "2021-04-07T05:16:23.6793863Z", 12 | "startEvent": "StartEvent_1", 13 | "ended": "2021-04-07T05:16:47.7911165Z", 14 | "endEvent": "EndEvent_1" 15 | }, 16 | "status": { 17 | "isArchived": true, 18 | "archived": "2021-04-07T05:16:47.7911165Z", 19 | "isSoftDeleted": false, 20 | "isHardDeleted": false, 21 | "readStatus": "Read" 22 | }, 23 | "appOwner": {}, 24 | "data": [ 25 | ], 26 | "created": "2021-04-07T05:16:23.6793863Z", 27 | "createdBy": "1337", 28 | "lastChanged": "2021-04-07T05:16:47.7911165Z", 29 | "lastChangedBy": "1337" 30 | } 31 | -------------------------------------------------------------------------------- /src/Storage/Helpers/QueryResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Newtonsoft.Json; 3 | 4 | namespace Altinn.Platform.Storage.Helpers; 5 | 6 | /// 7 | /// Query response object 8 | /// 9 | public class QueryResponse 10 | { 11 | /// 12 | /// The number of items in this response. 13 | /// 14 | [JsonProperty(PropertyName = "count")] 15 | public long Count { get; set; } 16 | 17 | /// 18 | /// The current query. 19 | /// 20 | [JsonProperty(PropertyName = "self")] 21 | public string Self { get; set; } 22 | 23 | /// 24 | /// A link to the next page. 25 | /// 26 | [JsonProperty(PropertyName = "next")] 27 | public string Next { get; set; } 28 | 29 | /// 30 | /// The metadata. 31 | /// 32 | [JsonProperty(PropertyName = "instances")] 33 | public List Instances { get; set; } 34 | } 35 | -------------------------------------------------------------------------------- /src/Storage.Interface/Models/CompleteConfirmation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | 4 | namespace Altinn.Platform.Storage.Interface.Models; 5 | 6 | /// 7 | /// Represents information about when a given stakeholder informed Altinn that they consider their own process as complete 8 | /// in regards to the instance. A typical stakeholder is the application owner. 9 | /// 10 | [JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)] 11 | public class CompleteConfirmation 12 | { 13 | /// 14 | /// Gets or sets a unique identifier for a stakeholder. 15 | /// 16 | [JsonProperty(PropertyName = "stakeholderId")] 17 | public string StakeholderId { get; set; } 18 | 19 | /// 20 | /// Gets or sets the date and time for when the complete confirmation was created. 21 | /// 22 | [JsonProperty(PropertyName = "confirmedOn")] 23 | public DateTime ConfirmedOn { get; set; } 24 | } 25 | -------------------------------------------------------------------------------- /src/Storage.Interface/Models/ValidationStatus.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | 4 | namespace Altinn.Platform.Storage.Interface.Models; 5 | 6 | /// 7 | /// Represents the validation status of a data element. 8 | /// 9 | [JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)] 10 | [Obsolete( 11 | "ValidationStatus is no longer used by apps. Validation is performed on process changes instead" 12 | )] 13 | public class ValidationStatus 14 | { 15 | /// 16 | /// Gets or sets the date and time of the last validation of task. 17 | /// 18 | [JsonProperty(PropertyName = "timestamp")] 19 | public DateTime? Timestamp { get; set; } 20 | 21 | /// 22 | /// Gets or sets a value indicating whether the validation was successful and that the task can be completed. 23 | /// 24 | [JsonProperty(PropertyName = "canCompleteTask")] 25 | public bool CanCompleteTask { get; set; } 26 | } 27 | -------------------------------------------------------------------------------- /test/UnitTest/data/response_deny.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": [ 3 | { 4 | "decision": "Deny", 5 | "status": { 6 | "statusMessage": null, 7 | "statusDetails": null, 8 | "statusCode": { 9 | "value": "urn:oasis:names:tc:xacml:1.0:status:ok", 10 | "statusCode": null 11 | } 12 | }, 13 | "obligations": [ 14 | { 15 | "id": "urn:altinn:obligation:authenticationLevel1", 16 | "attributeAssignment": [ 17 | { 18 | "attributeId": "urn:altinn:obligation1-assignment1", 19 | "value": "2", 20 | "category": "urn:altinn:minimum-authenticationlevel", 21 | "dataType": "http://www.w3.org/2001/XMLSchema#integer", 22 | "issuer": null 23 | } 24 | ] 25 | } 26 | ], 27 | "associateAdvice": null, 28 | "category": null, 29 | "policyIdentifierList": null 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /test/UnitTest/data/response_permit.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": [ 3 | { 4 | "decision": "Permit", 5 | "status": { 6 | "statusMessage": null, 7 | "statusDetails": null, 8 | "statusCode": { 9 | "value": "urn:oasis:names:tc:xacml:1.0:status:ok", 10 | "statusCode": null 11 | } 12 | }, 13 | "obligations": [ 14 | { 15 | "id": "urn:altinn:obligation:authenticationLevel1", 16 | "attributeAssignment": [ 17 | { 18 | "attributeId": "urn:altinn:obligation1-assignment1", 19 | "value": "2", 20 | "category": "urn:altinn:minimum-authenticationlevel", 21 | "dataType": "http://www.w3.org/2001/XMLSchema#integer", 22 | "issuer": null 23 | } 24 | ] 25 | } 26 | ], 27 | "associateAdvice": null, 28 | "category": null, 29 | "policyIdentifierList": null 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /src/Storage.Interface/Models/ShadowFields.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace Altinn.Platform.Storage.Interface.Models 4 | { 5 | /// 6 | /// Represents an object with information about how shadow fields are configured for the data type. 7 | /// 8 | [JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)] 9 | public class ShadowFields 10 | { 11 | /// 12 | /// Gets or sets the prefix to use to filter out shadow fields. 13 | /// 14 | [JsonProperty(PropertyName = "prefix")] 15 | public string Prefix { get; set; } 16 | 17 | /// 18 | /// Gets or sets the data type to save filtered data (without shadow fields) to. 19 | /// Optional. If not set, the containing data type will be updated. 20 | /// 21 | [JsonProperty(PropertyName = "saveToDataType")] 22 | public string SaveToDataType { get; set; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Storage/Health/HealthCheck.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using Microsoft.Extensions.Diagnostics.HealthChecks; 4 | 5 | namespace Altinn.Platform.Storage.Health; 6 | 7 | /// 8 | /// Health check service configured in startup https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/health-checks 9 | /// Listen to 10 | /// 11 | public class HealthCheck : IHealthCheck 12 | { 13 | /// 14 | /// Verifies the health status 15 | /// 16 | /// The healtcheck context 17 | /// The cancellationtoken 18 | /// The health check result 19 | public Task CheckHealthAsync( 20 | HealthCheckContext context, 21 | CancellationToken cancellationToken = default 22 | ) 23 | { 24 | return Task.FromResult(HealthCheckResult.Healthy("A healthy result.")); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Storage.Interface/Models/DataField.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Altinn.Platform.Storage.Interface.Models 4 | { 5 | /// 6 | /// Holds the definition of a data field for an application, ie. a named reference to a form data field. 7 | /// 8 | public class DataField 9 | { 10 | /// 11 | /// Gets or sets the id of the presentation field 12 | /// 13 | [JsonPropertyName("id")] 14 | public string Id { get; set; } 15 | 16 | /// 17 | /// Gets or sets the path of the presentation field 18 | /// 19 | [JsonPropertyName("path")] 20 | public string Path { get; set; } 21 | 22 | /// 23 | /// Gets or sets the data type where the presentation field is defined. 24 | /// 25 | [JsonPropertyName("dataTypeId")] 26 | public string DataTypeId { get; set; } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Storage/Controllers/ErrorController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authorization; 2 | using Microsoft.AspNetCore.Mvc; 3 | 4 | namespace Altinn.Platform.Storage.Controllers; 5 | 6 | /// 7 | /// Handles the presentation of unhandled exceptions during the execution of a request. 8 | /// 9 | [ApiController] 10 | [ApiExplorerSettings(IgnoreApi = true)] 11 | [AllowAnonymous] 12 | [Route("storage/api/v1")] 13 | public class ErrorController : ControllerBase 14 | { 15 | /// 16 | /// Create a response with a new instance with limited information. 17 | /// 18 | /// 19 | /// This method cannot be called directly. It is used by the API framework as a way to output ProblemDetails 20 | /// if there has been an unhandled exception. 21 | /// 22 | /// A new instance. 23 | [Route("error")] 24 | public IActionResult Error() => Problem(); 25 | } 26 | -------------------------------------------------------------------------------- /src/Storage/Clients/ICorrespondenceClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace Altinn.Platform.Storage.Clients; 5 | 6 | /// 7 | /// Interface for actions related to the parties with instances resource in SBL. 8 | /// 9 | public interface ICorrespondenceClient 10 | { 11 | /// 12 | /// Call SBL to sync a correspondence event from Altinn 3 with an Altinn 2 correspondence. 13 | /// 14 | /// Altinn 2 ServiceEngine correspondence Id. 15 | /// The party id of the user. 16 | /// Timestamp that the event took place in Altinn 3 (UTC Time) 17 | /// Event type 18 | /// 19 | Task SyncCorrespondenceEvent( 20 | int correspondenceId, 21 | int partyId, 22 | DateTimeOffset eventTimestamp, 23 | string eventType 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /src/Storage/Clients/IPartiesWithInstancesClient.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace Altinn.Platform.Storage.Clients; 4 | 5 | /// 6 | /// Interface for actions related to the parties with instances resource in SBL. 7 | /// 8 | public interface IPartiesWithInstancesClient 9 | { 10 | /// 11 | /// Call SBL to inform about a party getting an instance of an app. 12 | /// 13 | /// The party id of the instance owner. 14 | /// Nothing is returned. 15 | Task SetHasAltinn3Instances(int instanceOwnerPartyId); 16 | 17 | /// 18 | /// Call SBL to inform that a party has correspondences in Altinn 3 Correspondence 19 | /// 20 | /// The party id of the recipient 21 | /// A representing the result of the asynchronous operation. 22 | Task SetHasAltinn3Correspondence(int partyId); 23 | } 24 | -------------------------------------------------------------------------------- /test/UnitTest/Mocks/Clients/PartiesWithInstancesClientMock.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Altinn.Platform.Storage.Clients; 4 | 5 | namespace Altinn.Platform.Storage.UnitTest.Mocks.Clients; 6 | 7 | public class PartiesWithInstancesClientMock : IPartiesWithInstancesClient 8 | { 9 | public async Task SetHasAltinn3Instances(int instanceOwnerPartyId) 10 | { 11 | switch (instanceOwnerPartyId) 12 | { 13 | case 1337: 14 | await Task.CompletedTask; 15 | break; 16 | default: 17 | throw new ArgumentException("Unknown instanceOwnerPartyId"); 18 | } 19 | } 20 | 21 | public async Task SetHasAltinn3Correspondence(int partyId) 22 | { 23 | switch (partyId) 24 | { 25 | case 1337: 26 | await Task.CompletedTask; 27 | break; 28 | default: 29 | throw new ArgumentException("Unknown party id"); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Storage.Interface/Enums/FileScanResult.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Converters; 3 | using TextJson = System.Text.Json.Serialization; 4 | 5 | namespace Altinn.Platform.Storage.Interface.Enums; 6 | 7 | /// 8 | /// Represents different scanning results for when files are being scanned for malware. 9 | /// 10 | [JsonConverter(typeof(StringEnumConverter))] 11 | [TextJson.JsonConverter(typeof(TextJson.JsonStringEnumConverter))] 12 | public enum FileScanResult 13 | { 14 | /// 15 | /// The file will not be scanned. File scanning is turned off. 16 | /// 17 | NotApplicable, 18 | 19 | /// 20 | /// The scan status of the file is pending. This is the default value. 21 | /// 22 | Pending, 23 | 24 | /// 25 | /// The file scan did not find any malware in the file. 26 | /// 27 | Clean, 28 | 29 | /// 30 | /// The file scan found malware in the file. 31 | /// 32 | Infected, 33 | } 34 | -------------------------------------------------------------------------------- /test/UnitTest/data/apps/tdd/endring-av-navn/config/applicationmetadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "tdd/endring-av-navn", 3 | "org": "tdd", 4 | "created": "2019-09-24T10:02:41.0839253Z", 5 | "createdBy": "Kritsi", 6 | "lastChanged": "2019-09-24T10:02:41.0839254Z", 7 | "lastChangedBy": "Kritsi", 8 | "title": { 9 | "nb": "Endring av navn (RF-1453)", 10 | "nb-NO": "Endring av navn (RF-1453)", 11 | "en": "Name change" 12 | }, 13 | "copyInstanceSettings": { 14 | "enabled": true 15 | }, 16 | "dataTypes": [ 17 | { 18 | "id": "default", 19 | "allowedContentTypes": [ "application/xml" ], 20 | "maxCount": 1, 21 | "appLogic": { 22 | "autoCreate": true, 23 | "ClassRef": "App.IntegrationTests.Mocks.Apps.tdd.endring_av_navn.Skjema" 24 | }, 25 | "taskId": "Task_1" 26 | }, 27 | { 28 | "id": "default_with_fileScan", 29 | "allowedContentTypes": [ "application/xml" ], 30 | "maxCount": 1, 31 | "taskId": "Task_1", 32 | "enableFileScan": true 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /src/Storage/Authorization/ClaimsPrincipalProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using System.Security.Claims; 3 | using Microsoft.AspNetCore.Http; 4 | 5 | namespace Altinn.Platform.Storage.Authorization; 6 | 7 | /// 8 | /// Represents an implementation of using the HttpContext to obtain 9 | /// the current claims principal needed for the application to make calls to other services. 10 | /// 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | /// The http context accessor 15 | [ExcludeFromCodeCoverage] 16 | public class ClaimsPrincipalProvider(IHttpContextAccessor httpContextAccessor) 17 | : IClaimsPrincipalProvider 18 | { 19 | private readonly IHttpContextAccessor _httpContextAccessor = httpContextAccessor; 20 | 21 | /// 22 | public ClaimsPrincipal GetUser() 23 | { 24 | return _httpContextAccessor.HttpContext.User; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Storage/Wrappers/KeyVaultClientWrapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.CodeAnalysis; 3 | using System.Threading.Tasks; 4 | using Azure.Identity; 5 | using Azure.Security.KeyVault.Secrets; 6 | 7 | namespace Altinn.Platform.Storage.Wrappers; 8 | 9 | /// 10 | /// Wrapper implementation for a KeyVaultClient. The wrapped client is created with a principal obtained through configuration. 11 | /// 12 | /// This class is excluded from code coverage because it has no logic to be tested. 13 | [ExcludeFromCodeCoverage] 14 | public class KeyVaultClientWrapper : IKeyVaultClientWrapper 15 | { 16 | /// 17 | public async Task GetSecretAsync(string vaultUri, string secretId) 18 | { 19 | // Credentials are set based on environment variables set in Program.cs 20 | SecretClient secretClient = new(new Uri(vaultUri), new DefaultAzureCredential()); 21 | 22 | KeyVaultSecret secret = await secretClient.GetSecretAsync(secretId); 23 | 24 | return secret.Value; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Storage/Configuration/AzureStorageConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace Altinn.Platform.Storage.Configuration; 7 | 8 | /// 9 | /// Settings for Azure storage 10 | /// 11 | public class AzureStorageConfiguration 12 | { 13 | /// 14 | /// storage account name 15 | /// 16 | public string AccountName { get; set; } 17 | 18 | /// 19 | /// storage account key 20 | /// 21 | public string AccountKey { get; set; } 22 | 23 | /// 24 | /// url for the blob end point 25 | /// 26 | public string BlobEndPoint { get; set; } 27 | 28 | /// 29 | /// name of app owner storage account 30 | /// 31 | public string OrgStorageAccount { get; set; } 32 | 33 | /// 34 | /// name of storage container in app owner storage account 35 | /// 36 | public string OrgStorageContainer { get; set; } 37 | } 38 | -------------------------------------------------------------------------------- /src/Storage/Repository/IInstanceAndEventsRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Altinn.Platform.Storage.Interface.Models; 5 | 6 | namespace Altinn.Platform.Storage.Repository; 7 | 8 | /// 9 | /// Represents an implementation of . 10 | /// 11 | public interface IInstanceAndEventsRepository 12 | { 13 | /// 14 | /// update existing instance including instance events 15 | /// 16 | /// the instance to update 17 | /// a list of which properties should be updated 18 | /// the events to add 19 | /// CancellationToken 20 | /// The updated instance 21 | Task Update( 22 | Instance instance, 23 | List updateProperties, 24 | List events, 25 | CancellationToken cancellationToken 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /test/UnitTest/Stubs/DelegatingHandlerStub.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Net.Http; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace Altinn.Platform.Storage.Tests.Stubs; 8 | 9 | public class DelegatingHandlerStub : DelegatingHandler 10 | { 11 | private readonly Func< 12 | HttpRequestMessage, 13 | CancellationToken, 14 | Task 15 | > _handlerFunc; 16 | 17 | public DelegatingHandlerStub() 18 | { 19 | _handlerFunc = (request, cancellationToken) => 20 | Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)); 21 | } 22 | 23 | public DelegatingHandlerStub( 24 | Func> handlerFunc 25 | ) 26 | { 27 | _handlerFunc = handlerFunc; 28 | } 29 | 30 | protected override Task SendAsync( 31 | HttpRequestMessage request, 32 | CancellationToken cancellationToken 33 | ) 34 | { 35 | return _handlerFunc(request, cancellationToken); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Storage/Migration/ReadMe.txt: -------------------------------------------------------------------------------- 1 | Usage/rules: 2 | 3 | - The tool DbTools is used to autogenerate a Yuniql deploy file for functions and procedures. Other artifacts 4 | like tables and index must currently be maintained manually. 5 | 6 | - Each function/proc has a separate file in the Migration/FunctionsAndProcedures folder. 7 | 8 | - After build the tool DbTools.exe is automatically run to generate vx.xx/ZZ-functions-and-procedures.sql 9 | based on the content in all files in the FunctionsAndProcedures folder. ZZ is assumed to be 01 if no other 10 | file is found with another number. (Makes it possible to deploy other stuff before procs/funcs.) 11 | 12 | - The file name of a proc/func should be the base proc/function name without any version postfix. 13 | The same filename is kept if the proc/func gets a new version. 14 | 15 | - Any drop commands must be coded at the top of the func/proc file or in a separate file in the related v.x.xx folder. 16 | 17 | - A new vx.xx folder must be created when a func/proc is created/updated after last deploy. If not the current 18 | vx.xx will be used, and the migration will not be executed by yuniql. -------------------------------------------------------------------------------- /src/Storage/Migration/FunctionsAndProcedures/readdeletedelements.sql: -------------------------------------------------------------------------------- 1 | CREATE OR REPLACE FUNCTION storage.readdeletedelements() 2 | RETURNS TABLE (id BIGINT, instance JSONB, element JSONB) 3 | LANGUAGE 'plpgsql' 4 | AS $BODY$ 5 | BEGIN 6 | -- Force nested loop join to avoid hash join on large instances table 7 | -- With only a small count of hard-deleted records, nested loop with index lookups is optimal 8 | SET LOCAL enable_hashjoin = off; 9 | SET LOCAL enable_mergejoin = off; 10 | 11 | RETURN QUERY 12 | SELECT i.id, i.instance, d.element 13 | FROM ( 14 | -- Target index dataelements_isharddeleted 15 | SELECT instanceinternalid, de.element 16 | FROM storage.dataelements de 17 | WHERE (de.element -> 'DeleteStatus' -> 'IsHardDeleted')::BOOLEAN 18 | AND (de.element -> 'DeleteStatus' ->> 'HardDeleted')::TIMESTAMPTZ <= NOW() - INTERVAL '7 days' 19 | OFFSET 0 -- Optimization fence: prevents subquery flattening which causes wrong join strategy 20 | ) d 21 | JOIN storage.instances i ON i.id = d.instanceinternalid 22 | WHERE i.AltinnMainVersion >= 3; 23 | END; 24 | $BODY$; -------------------------------------------------------------------------------- /test/UnitTest/Models/XacmlResourceAttributes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Altinn.Platform.Storage.Models; 6 | 7 | public class XacmlResourceAttributes 8 | { 9 | /// 10 | /// Gets or sets the value for org attribute 11 | /// 12 | public string OrgValue { get; set; } 13 | 14 | /// 15 | /// Gets or sets the value for app attribute 16 | /// 17 | public string AppValue { get; set; } 18 | 19 | /// 20 | /// Gets or sets the value for instance attribute 21 | /// 22 | public string InstanceValue { get; set; } 23 | 24 | /// 25 | /// Gets or sets the value for resourceparty attribute 26 | /// 27 | public string ResourcePartyValue { get; set; } 28 | 29 | /// 30 | /// Gets or sets the value for task attribute 31 | /// 32 | public string TaskValue { get; set; } 33 | 34 | /// 35 | /// Gets or sets the value for app resource. 36 | /// 37 | public string AppResourceValue { get; set; } 38 | } 39 | -------------------------------------------------------------------------------- /src/Storage/Services/ISigningService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Altinn.Platform.Storage.Interface.Models; 5 | using Altinn.Platform.Storage.Models; 6 | 7 | namespace Altinn.Platform.Storage.Services; 8 | 9 | /// 10 | /// This interface describes the required methods and features of a signing service implementation. 11 | /// 12 | public interface ISigningService 13 | { 14 | /// 15 | /// Create signature document for given data elements, this includes creating md5 hash for all blobs listed. 16 | /// 17 | /// The instance guid 18 | /// Sign request containing data element ids and sign status 19 | /// User id or org no for the authenticated user 20 | /// CancellationToken 21 | Task<(bool Created, ServiceError ServiceError)> CreateSignDocument( 22 | Guid instanceGuid, 23 | SignRequest signRequest, 24 | string performedBy, 25 | CancellationToken cancellationToken 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /src/Storage.Interface/Models/FileScanStatus.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Platform.Storage.Interface.Enums; 2 | using Newtonsoft.Json; 3 | using Newtonsoft.Json.Converters; 4 | using TextJson = System.Text.Json.Serialization; 5 | 6 | namespace Altinn.Platform.Storage.Interface.Models; 7 | 8 | /// 9 | /// Represents file scan status for a data element 10 | /// 11 | [JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)] 12 | public class FileScanStatus 13 | { 14 | /// 15 | /// Gets or sets the MD5 content hash computed by Azure Blob Storage 16 | /// 17 | [JsonProperty(PropertyName = "contentHash")] 18 | public string ContentHash { get; set; } 19 | 20 | /// 21 | /// Gets or sets the scan result 22 | /// 23 | [JsonProperty(PropertyName = "fileScanResult")] 24 | [JsonConverter(typeof(StringEnumConverter))] 25 | [TextJson.JsonConverter(typeof(TextJson.JsonStringEnumConverter))] 26 | public FileScanResult FileScanResult { get; set; } 27 | 28 | /// 29 | public override string ToString() 30 | { 31 | return JsonConvert.SerializeObject(this); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test/UnitTest/Fixture/TestApplicationFactory.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.AspNetCore.Mvc.Testing; 3 | using Microsoft.AspNetCore.TestHost; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using OpenTelemetry.Metrics; 6 | using OpenTelemetry.Trace; 7 | 8 | namespace Altinn.Platform.Storage.UnitTest.Fixture; 9 | 10 | public sealed class TestApplicationFactory : WebApplicationFactory 11 | where TEntryPoint : class 12 | { 13 | protected override void ConfigureWebHost(IWebHostBuilder builder) 14 | { 15 | builder.ConfigureTestServices( 16 | (services) => 17 | { 18 | ConcurrentList metricsList = []; 19 | services.AddSingleton(sp => 20 | ActivatorUtilities.CreateInstance(sp, metricsList) 21 | ); 22 | services 23 | .AddOpenTelemetry() 24 | .WithMetrics(metrics => 25 | { 26 | metrics.AddInMemoryExporter(metricsList); 27 | }); 28 | } 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/UnitTest/data/roles/user_3/party_1337/roles.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Type": "altinn", 4 | "value": "PRIV" 5 | }, 6 | { 7 | "Type": "altinn", 8 | "value": "UTINN" 9 | }, 10 | { 11 | "Type": "altinn", 12 | "value": "LOPER" 13 | } 14 | , 15 | { 16 | "Type": "altinn", 17 | "value": "ADMAI" 18 | }, 19 | { 20 | "Type": "altinn", 21 | "value": "PRIUT" 22 | }, 23 | { 24 | "Type": "altinn", 25 | "value": "REGNA" 26 | }, 27 | { 28 | "Type": "altinn", 29 | "value": "SISKD" 30 | }, 31 | { 32 | "Type": "altinn", 33 | "value": "UILUF" 34 | }, 35 | { 36 | "Type": "altinn", 37 | "value": "UTOMR" 38 | }, 39 | { 40 | "Type": "altinn", 41 | "value": "PAVAD" 42 | }, 43 | { 44 | "Type": "altinn", 45 | "value": "KOMAB" 46 | }, 47 | { 48 | "Type": "altinn", 49 | "value": "BOADM" 50 | }, 51 | { 52 | "Type": "altinn", 53 | "value": "A0212" 54 | }, 55 | { 56 | "Type": "altinn", 57 | "value": "A0236" 58 | }, 59 | { 60 | "Type": "altinn", 61 | "value": "A0278" 62 | }, 63 | { 64 | "Type": "altinn", 65 | "value": "A0282" 66 | } 67 | ] 68 | -------------------------------------------------------------------------------- /test/UnitTest/data/roles/user_10016/party_1600/roles.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Type": "altinn", 4 | "value": "PRIV" 5 | }, 6 | { 7 | "Type": "altinn", 8 | "value": "UTINN" 9 | }, 10 | { 11 | "Type": "altinn", 12 | "value": "LOPER" 13 | } 14 | , 15 | { 16 | "Type": "altinn", 17 | "value": "ADMAI" 18 | }, 19 | { 20 | "Type": "altinn", 21 | "value": "PRIUT" 22 | }, 23 | { 24 | "Type": "altinn", 25 | "value": "REGNA" 26 | }, 27 | { 28 | "Type": "altinn", 29 | "value": "SISKD" 30 | }, 31 | { 32 | "Type": "altinn", 33 | "value": "UILUF" 34 | }, 35 | { 36 | "Type": "altinn", 37 | "value": "UTOMR" 38 | }, 39 | { 40 | "Type": "altinn", 41 | "value": "PAVAD" 42 | }, 43 | { 44 | "Type": "altinn", 45 | "value": "KOMAB" 46 | }, 47 | { 48 | "Type": "altinn", 49 | "value": "BOADM" 50 | }, 51 | { 52 | "Type": "altinn", 53 | "value": "A0212" 54 | }, 55 | { 56 | "Type": "altinn", 57 | "value": "A0236" 58 | }, 59 | { 60 | "Type": "altinn", 61 | "value": "A0278" 62 | }, 63 | { 64 | "Type": "altinn", 65 | "value": "A0282" 66 | } 67 | ] 68 | -------------------------------------------------------------------------------- /test/UnitTest/data/roles/user_1337/party_1337/roles.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Type": "altinn", 4 | "value": "PRIV" 5 | }, 6 | { 7 | "Type": "altinn", 8 | "value": "UTINN" 9 | }, 10 | { 11 | "Type": "altinn", 12 | "value": "LOPER" 13 | } 14 | , 15 | { 16 | "Type": "altinn", 17 | "value": "ADMAI" 18 | }, 19 | { 20 | "Type": "altinn", 21 | "value": "PRIUT" 22 | }, 23 | { 24 | "Type": "altinn", 25 | "value": "REGNA" 26 | }, 27 | { 28 | "Type": "altinn", 29 | "value": "SISKD" 30 | }, 31 | { 32 | "Type": "altinn", 33 | "value": "UILUF" 34 | }, 35 | { 36 | "Type": "altinn", 37 | "value": "UTOMR" 38 | }, 39 | { 40 | "Type": "altinn", 41 | "value": "PAVAD" 42 | }, 43 | { 44 | "Type": "altinn", 45 | "value": "KOMAB" 46 | }, 47 | { 48 | "Type": "altinn", 49 | "value": "BOADM" 50 | }, 51 | { 52 | "Type": "altinn", 53 | "value": "A0212" 54 | }, 55 | { 56 | "Type": "altinn", 57 | "value": "A0236" 58 | }, 59 | { 60 | "Type": "altinn", 61 | "value": "A0278" 62 | }, 63 | { 64 | "Type": "altinn", 65 | "value": "A0282" 66 | } 67 | ] 68 | -------------------------------------------------------------------------------- /test/UnitTest/platform-org.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDAzCCAeugAwIBAgIJANTdO8o3I8x5MA0GCSqGSIb3DQEBCwUAMA4xDDAKBgNV 3 | BAMTA3R0ZDAeFw0yMDA1MjUxMjIxMzdaFw0zMDA1MjQxMjIxMzdaMA4xDDAKBgNV 4 | BAMTA3R0ZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMcfTsXwwLyC 5 | UkIz06eadWJvG3yrzT+ZB2Oy/WPaZosDnPcnZvCDueN+oy0zTx5TyH5gCi1FvzX2 6 | 7G2eZEKwQaRPv0yuM+McHy1rXxMSOlH/ebP9KJj3FDMUgZl1DCAjJxSAANdTwdrq 7 | ydVg1Crp37AQx8IIEjnBhXsfQh1uPGt1XwgeNyjl00IejxvQOPzd1CofYWwODVtQ 8 | l3PKn1SEgOGcB6wuHNRlnZPCIelQmqxWkcEZiu/NU+kst3NspVUQG2Jf2AF8UWgC 9 | rnrhMQR0Ra1Vi7bWpu6QIKYkN9q0NRHeRSsELOvTh1FgDySYJtNd2xDRSf6IvOiu 10 | tSipl1NZlV0CAwEAAaNkMGIwIAYDVR0OAQH/BBYEFIwq/KbSMzLETdo9NNxj0rz4 11 | qMqVMAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgWgMCAGA1UdJQEB/wQWMBQG 12 | CCsGAQUFBwMBBggrBgEFBQcDAjANBgkqhkiG9w0BAQsFAAOCAQEAE56UmH5gEYbe 13 | 1kVw7nrfH0R9FyVZGeQQWBn4/6Ifn+eMS9mxqe0Lq74Ue1zEzvRhRRqWYi9JlKNf 14 | 7QQNrc+DzCceIa1U6cMXgXKuXquVHLmRfqvKHbWHJfIkaY8Mlfy++77UmbkvIzly 15 | T1HVhKKp6Xx0r5koa6frBh4Xo/vKBlEyQxWLWF0RPGpGErnYIosJ41M3Po3nw3lY 16 | f7lmH47cdXatcntj2Ho/b2wGi9+W29teVCDfHn2/0oqc7K0EOY9c2ODLjUvQyPZR 17 | OD2yykpyh9x/YeYHFDYdLDJ76/kIdxN43kLU4/hTrh9tMb1PZF+/4DshpAlRoQuL 18 | o8I8avQm/A== 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /src/Storage/Extensions/ContentDispositionHeaderValueExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Primitives; 2 | using Microsoft.Net.Http.Headers; 3 | 4 | namespace Altinn.Platform.Storage.Extensions; 5 | 6 | /// 7 | /// Extensions to simplify the use of . 8 | /// 9 | public static class ContentDispositionHeaderValueExtensions 10 | { 11 | /// 12 | /// Obtain the filename value from FileNameStar or FileName if the FileNameStar property is empty. 13 | /// Then remove any quotes and clean the filename with the AsFileName method. 14 | /// 15 | /// The ContentDispositionHeaderValue object to get a filename from. 16 | /// A filename cleaned of any impurities. 17 | public static string GetFilename(this ContentDispositionHeaderValue contentDisposition) 18 | { 19 | StringSegment filename = contentDisposition.FileNameStar.HasValue 20 | ? contentDisposition.FileNameStar 21 | : contentDisposition.FileName; 22 | 23 | return HeaderUtilities.RemoveQuotes(filename).Value.AsFileName(false); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Storage/Helpers/DisableFormValueModelBindingAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Mvc.Filters; 3 | using Microsoft.AspNetCore.Mvc.ModelBinding; 4 | 5 | namespace Altinn.Platform.Storage.Helpers; 6 | 7 | /// 8 | /// Turns of binding of attachement 9 | /// 10 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] 11 | public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter 12 | { 13 | /// 14 | /// Called before resource is processed and turns of formvalue provider an jquery provider 15 | /// 16 | /// the execution context 17 | public void OnResourceExecuting(ResourceExecutingContext context) 18 | { 19 | var factories = context.ValueProviderFactories; 20 | factories.RemoveType(); 21 | factories.RemoveType(); 22 | } 23 | 24 | /// 25 | /// Called after resource is processed. Does nothing. 26 | /// 27 | /// the execution context 28 | public void OnResourceExecuted(ResourceExecutedContext context) { } 29 | } 30 | -------------------------------------------------------------------------------- /test/Altinn.Platform.Storage.Interface.Tests/SystemUser/PlatformUserTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Altinn.Platform.Storage.Interface.Models; 3 | using Xunit; 4 | 5 | namespace Altinn.Platform.Storage.Interface.Tests.SystemUser; 6 | 7 | public class PlatformUserTests 8 | { 9 | [Fact] 10 | public void MetadataWithoutShadowFields_ShouldBeFalse() 11 | { 12 | PlatformUser target = TestdataHelper.LoadDataFromEmbeddedResourceAsType( 13 | "SystemUser.platformUser_beforeChange.json" 14 | ); 15 | 16 | Assert.Null(target.SystemUserId); 17 | Assert.Null(target.SystemUserName); 18 | Assert.Null(target.SystemUserOwnerOrgNo); 19 | } 20 | 21 | [Fact] 22 | public void MetadataWithShadowFields_ShouldBeTrue() 23 | { 24 | PlatformUser target = TestdataHelper.LoadDataFromEmbeddedResourceAsType( 25 | "SystemUser.platformUser_afterChange.json" 26 | ); 27 | 28 | Assert.Equal(Guid.Parse("2280457B-0A79-49C5-AC14-09217705C9A1"), target.SystemUserId); 29 | Assert.Equal("Vismalise", target.SystemUserName); 30 | Assert.Equal("565433454", target.SystemUserOwnerOrgNo); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/instances/89d4a1ec-6e33-4aaa-8249-64e3e5e6b39f.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "89d4a1ec-6e33-4aaa-8249-64e3e5e6b39f", 3 | "instanceOwner": { 4 | "partyId": "1600", 5 | "personNumber": "01044091042" 6 | }, 7 | "appId": "ttd/dato-eksempel", 8 | "org": "ttd", 9 | "title": { 10 | "nb": "dato-eksempel" 11 | }, 12 | "process": { 13 | "started": "2020-02-27T13:05:22.8148373Z", 14 | "startEvent": "StartEvent_1", 15 | "ended": "2020-02-27T13:09:13.343901Z", 16 | "endEvent": "EndEvent_1" 17 | }, 18 | "status": { 19 | "archived": "2020-02-27T13:09:13.343901Z", 20 | "isArchived": true, 21 | "isHardDeleted": false, 22 | "isSoftDeleted": false 23 | }, 24 | "appOwner": {}, 25 | "visibleAfter": "2020-02-27T13:05:22.9021981Z", 26 | "created": "2020-02-27T13:05:22.9021981Z", 27 | "createdBy": "20000013", 28 | "lastChanged": "2020-02-27T13:09:14.0498346Z", 29 | "lastChangedBy": "20000013", 30 | "_rid": "uHp7ALWZ1j9RAwAAAAAAAA==", 31 | "_self": "dbs/uHp7AA==/colls/uHp7ALWZ1j8=/docs/uHp7ALWZ1j9RAwAAAAAAAA==/", 32 | "_etag": "\"01004a55-0000-3c00-0000-5e57bf7a0000\"", 33 | "_attachments": "attachments/", 34 | "_ts": 1582808954 35 | } 36 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/instances/3af7ad95-0dd4-45d5-bb97-c1ee66c600d7.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "3af7ad95-0dd4-45d5-bb97-c1ee66c600d7", 3 | "instanceOwner": { 4 | "partyId": "1600", 5 | "personNumber": "01044091042" 6 | }, 7 | "appId": "ttd/steffens-2020-v2", 8 | "org": "ttd", 9 | "title": { 10 | "nb": "steffens-2020-v2" 11 | }, 12 | "process": { 13 | "started": "2020-01-23T15:38:31.7891852Z", 14 | "startEvent": "StartEvent_1", 15 | "ended": "2020-01-23T15:38:44.1504337Z", 16 | "endEvent": "EndEvent_1" 17 | }, 18 | "status": { 19 | "archived": "2020-01-23T15:38:44.1504337Z", 20 | "isArchived": true, 21 | "isHardDeleted": false, 22 | "isSoftDeleted": false 23 | }, 24 | "appOwner": {}, 25 | "visibleAfter": "2020-01-23T15:38:32.0832414Z", 26 | "created": "2020-01-23T15:38:32.0832414Z", 27 | "createdBy": "20000013", 28 | "lastChanged": "2020-01-23T15:38:44.9036392Z", 29 | "lastChangedBy": "20000013", 30 | "_rid": "uHp7ALWZ1j8EAAAAAAAAAA==", 31 | "_self": "dbs/uHp7AA==/colls/uHp7ALWZ1j8=/docs/uHp7ALWZ1j8EAAAAAAAAAA==/", 32 | "_etag": "\"5b004d91-0000-3c00-0000-5e29be040000\"", 33 | "_attachments": "attachments/", 34 | "_ts": 1579793924 35 | } 36 | -------------------------------------------------------------------------------- /src/Storage.Interface/Models/CopyInstanceSettings.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Newtonsoft.Json; 3 | 4 | namespace Altinn.Platform.Storage.Interface.Models; 5 | 6 | /// 7 | /// A class holding copy instance settings. 8 | /// 9 | public class CopyInstanceSettings 10 | { 11 | /// 12 | /// Gets or sets a boolean indicating if copy instance is enabled. 13 | /// 14 | [JsonProperty(PropertyName = "enabled")] 15 | public bool Enabled { get; set; } 16 | 17 | /// 18 | /// Gets or sets a list of excluded data types. 19 | /// 20 | [JsonProperty(PropertyName = "excludedDataTypes")] 21 | public List ExcludedDataTypes { get; set; } 22 | 23 | /// 24 | /// Gets or sets a list of excluded datafields. 25 | /// 26 | [JsonProperty(PropertyName = "excludedDataFields")] 27 | public List ExcludedDataFields { get; set; } 28 | 29 | /// 30 | /// Gets or sets a boolean indicating if copying of attachments is enabled. 31 | /// 32 | [JsonProperty(PropertyName = "includeAttachments")] 33 | public bool IncludeAttachments { get; set; } 34 | } 35 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/instances/5a389710-9a8c-4441-80b8-0a3a2e276cb1.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "5a389710-9a8c-4441-80b8-0a3a2e276cb1", 3 | "instanceOwner": { 4 | "partyId": "1600", 5 | "personNumber": "01044091042" 6 | }, 7 | "appId": "ttd/endring-av-navn-v2", 8 | "org": "ttd", 9 | "title": { 10 | "nb": "Endring av navn test" 11 | }, 12 | "process": { 13 | "started": "2020-01-29T13:16:29.1955508Z", 14 | "startEvent": "StartEvent_1", 15 | "ended": "2020-01-29T13:16:46.244377Z", 16 | "endEvent": "EndEvent_1" 17 | }, 18 | "status": { 19 | "archived": "2020-01-29T13:16:46.244377Z", 20 | "isArchived": true, 21 | "isHardDeleted": false, 22 | "isSoftDeleted": false 23 | }, 24 | "appOwner": {}, 25 | "visibleAfter": "2020-01-29T13:16:29.2725276Z", 26 | "created": "2020-01-29T13:16:29.2725276Z", 27 | "createdBy": "20000013", 28 | "lastChanged": "2020-01-29T13:16:47.0369678Z", 29 | "lastChangedBy": "20000013", 30 | "_rid": "uHp7ALWZ1j9vAAAAAAAAAA==", 31 | "_self": "dbs/uHp7AA==/colls/uHp7ALWZ1j8=/docs/uHp7ALWZ1j9vAAAAAAAAAA==/", 32 | "_etag": "\"0300a75b-0000-3c00-0000-5e3185bf0000\"", 33 | "_attachments": "attachments/", 34 | "_ts": 1580303807 35 | } 36 | -------------------------------------------------------------------------------- /src/Storage/Models/CorrespondenceEventSync.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | 4 | namespace Altinn.Platform.Storage.Models; 5 | 6 | /// 7 | /// Represents an event taking place in Altinn 3 to be synced via SBLBridge 8 | /// 9 | public class CorrespondenceEventSync 10 | { 11 | /// 12 | /// Gets or sets the Altinn 2 ServiceEngine correspondence Id. 13 | /// 14 | [JsonProperty(PropertyName = "correspondenceId")] 15 | public int CorrespondenceId { get; set; } 16 | 17 | /// 18 | /// Gets or sets the party id of the user causing the event. 19 | /// 20 | [JsonProperty(PropertyName = "partyId")] 21 | public int PartyId { get; set; } 22 | 23 | /// 24 | /// Gets or sets the timestamp of the event. Timestamp should always be UTC time. 25 | /// 26 | [JsonProperty(PropertyName = "eventTimestamp")] 27 | public DateTimeOffset EventTimeStamp { get; set; } 28 | 29 | /// 30 | /// Gets or sets the Correspondence Event Type. (Expects Read, Confirm or Delete). 31 | /// 32 | [JsonProperty(PropertyName = "eventType")] 33 | public string EventType { get; set; } 34 | } 35 | -------------------------------------------------------------------------------- /test/UnitTest/Extensions/ContentDispositionHeaderValueExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Platform.Storage.Extensions; 2 | using Microsoft.Net.Http.Headers; 3 | using Xunit; 4 | 5 | namespace Altinn.Platform.Storage.UnitTest.Extensions; 6 | 7 | public class ContentDispositionHeaderValueExtensionsTests 8 | { 9 | [Theory] 10 | [InlineData("attachment; filename = test.pdf", "test.pdf")] 11 | [InlineData("attachment; filename = \"test.pdf\"", "test.pdf")] 12 | [InlineData("attachment; filename=test.pdf;", "test.pdf")] 13 | [InlineData("attachment;filename=test.pdf;filename*=UTF-8''other.pdf", "other.pdf")] 14 | [InlineData("attachment;filename=\"test.pdf\";filename*=UTF-8''other.pdf", "other.pdf")] 15 | public void GetFilename_ReturnsCorrectFilename( 16 | string rawContentDisposition, 17 | string expectedFilename 18 | ) 19 | { 20 | // Arrange 21 | ContentDispositionHeaderValue contentDisposition = ContentDispositionHeaderValue.Parse( 22 | rawContentDisposition 23 | ); 24 | 25 | // Act 26 | string actualFilename = contentDisposition.GetFilename(); 27 | 28 | // Assert 29 | Assert.Equal(expectedFilename, actualFilename); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/UnitTest/Utils/RequestTracker.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace Altinn.Platform.Storage.UnitTest.Utils; 5 | 6 | public static class RequestTracker 7 | { 8 | private static Dictionary> _tracker = 9 | new Dictionary>(); 10 | 11 | private static readonly object DataLock = new object(); 12 | 13 | public static int GetRequestCount(string requestKey) 14 | { 15 | if (_tracker.TryGetValue(requestKey, out List value)) 16 | { 17 | return value.Count; 18 | } 19 | 20 | return 0; 21 | } 22 | 23 | public static void AddRequest(string requestKey, object request) 24 | { 25 | lock (DataLock) 26 | { 27 | if (!_tracker.TryGetValue(requestKey, out List value)) 28 | { 29 | value = new List(); 30 | _tracker.Add(requestKey, value); 31 | } 32 | 33 | value.Add(request); 34 | } 35 | } 36 | 37 | public static void Clear() 38 | { 39 | lock (DataLock) 40 | { 41 | _tracker = new Dictionary>(); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/instances/1e14b6cd-e310-4aff-aa83-720b2ec195e0.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "1e14b6cd-e310-4aff-aa83-720b2ec195e0", 3 | "instanceOwner": { 4 | "partyId": "1337", 5 | "organisationNumber": "725736800" 6 | }, 7 | "appId": "tdd/endring-av-navn", 8 | "org": "tdd", 9 | "title": { 10 | "nb": "Endring av navn" 11 | }, 12 | "process": { 13 | "started": "2020-04-29T13:53:01.7020218Z", 14 | "startEvent": "StartEvent_1", 15 | "currentTask": { 16 | "elementId": "Task_1", 17 | "altinnTaskType": "data" 18 | } 19 | }, 20 | "status": { 21 | "softDeleted": "2020-04-29T13:53:06.117891Z", 22 | "isArchived": false, 23 | "isHardDeleted": false, 24 | "isSoftDeleted": true 25 | }, 26 | "appOwner": {}, 27 | "data": [ 28 | ], 29 | "visibleAfter": "2020-04-29T13:53:02.2836971Z", 30 | "created": "2020-04-29T13:53:02.2836971Z", 31 | "createdBy": "1337", 32 | "lastChanged": "2020-04-29T13:53:09.1122622Z", 33 | "lastChangedBy": "1337", 34 | "_rid": "AnsTAK4aVt8czAIAAAAAAA==", 35 | "_self": "dbs/AnsTAA==/colls/AnsTAK4aVt8=/docs/AnsTAK4aVt8czAIAAAAAAA==/", 36 | "_etag": "\"1400ade0-0000-3c00-0000-5eaa8a220000\"", 37 | "_attachments": "attachments/", 38 | "_ts": 1588234786 39 | } 40 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/instances/649388f0-a2c0-4774-bd11-c870223ed819.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "649388f0-a2c0-4774-bd11-c870223ed819", 3 | "instanceOwner": { 4 | "partyId": "1337", 5 | "organisationNumber": "725736800" 6 | }, 7 | "appId": "tdd/endring-av-navn", 8 | "org": "tdd", 9 | "title": { 10 | "nb": "Endring av navn" 11 | }, 12 | "process": { 13 | "started": "2020-04-29T13:53:01.7020218Z", 14 | "startEvent": "StartEvent_1", 15 | "currentTask": { 16 | "elementId": "Task_1", 17 | "altinnTaskType": "data" 18 | } 19 | }, 20 | "status": { 21 | "softDeleted": "2020-04-29T13:53:06.117891Z", 22 | "isArchived": false, 23 | "isHardDeleted": false, 24 | "isSoftDeleted": true 25 | }, 26 | "appOwner": {}, 27 | "data": [ 28 | ], 29 | "visibleAfter": "2020-04-29T13:53:02.2836971Z", 30 | "created": "2020-04-29T13:53:02.2836971Z", 31 | "createdBy": "1337", 32 | "lastChanged": "2020-04-29T13:53:09.1122622Z", 33 | "lastChangedBy": "1337", 34 | "_rid": "AnsTAK4aVt8czAIAAAAAAA==", 35 | "_self": "dbs/AnsTAA==/colls/AnsTAK4aVt8=/docs/AnsTAK4aVt8czAIAAAAAAA==/", 36 | "_etag": "\"1400ade0-0000-3c00-0000-5eaa8a220000\"", 37 | "_attachments": "attachments/", 38 | "_ts": 1588234786 39 | } 40 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/instances/69c259d1-9c1f-4ab6-9d8b-5c210042dc4f.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "69c259d1-9c1f-4ab6-9d8b-5c210042dc4f", 3 | "instanceOwner": { 4 | "partyId": "1337", 5 | "organisationNumber": "725736800" 6 | }, 7 | "appId": "tdd/endring-av-navn", 8 | "org": "tdd", 9 | "title": { 10 | "nb": "Endring av navn" 11 | }, 12 | "process": { 13 | "started": "2020-04-29T13:53:01.7020218Z", 14 | "startEvent": "StartEvent_1", 15 | "currentTask": { 16 | "elementId": "Task_1", 17 | "altinnTaskType": "data" 18 | } 19 | }, 20 | "status": { 21 | "softDeleted": "2020-04-29T13:53:06.117891Z", 22 | "isArchived": false, 23 | "isHardDeleted": false, 24 | "isSoftDeleted": true 25 | }, 26 | "appOwner": {}, 27 | "data": [ 28 | ], 29 | "visibleAfter": "2020-04-29T13:53:02.2836971Z", 30 | "created": "2020-04-29T13:53:02.2836971Z", 31 | "createdBy": "1337", 32 | "lastChanged": "2020-04-29T13:53:09.1122622Z", 33 | "lastChangedBy": "1337", 34 | "_rid": "AnsTAK4aVt8czAIAAAAAAA==", 35 | "_self": "dbs/AnsTAA==/colls/AnsTAK4aVt8=/docs/AnsTAK4aVt8czAIAAAAAAA==/", 36 | "_etag": "\"1400ade0-0000-3c00-0000-5eaa8a220000\"", 37 | "_attachments": "attachments/", 38 | "_ts": 1588234786 39 | } 40 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/instances/6aa47207-f089-4c11-9cb2-f00af6f66a47.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "6aa47207-f089-4c11-9cb2-f00af6f66a47", 3 | "instanceOwner": { 4 | "partyId": "1337", 5 | "organisationNumber": "725736800" 6 | }, 7 | "appId": "tdd/endring-av-navn", 8 | "org": "tdd", 9 | "title": { 10 | "nb": "Endring av navn" 11 | }, 12 | "process": { 13 | "started": "2020-04-29T13:53:01.7020218Z", 14 | "startEvent": "StartEvent_1", 15 | "currentTask": { 16 | "elementId": "Task_1", 17 | "altinnTaskType": "data" 18 | } 19 | }, 20 | "status": { 21 | "softDeleted": "2020-04-29T13:53:06.117891Z", 22 | "isArchived": false, 23 | "isHardDeleted": false, 24 | "isSoftDeleted": true 25 | }, 26 | "appOwner": {}, 27 | "data": [ 28 | ], 29 | "visibleAfter": "2020-04-29T13:53:02.2836971Z", 30 | "created": "2020-04-29T13:53:02.2836971Z", 31 | "createdBy": "1337", 32 | "lastChanged": "2020-04-29T13:53:09.1122622Z", 33 | "lastChangedBy": "1337", 34 | "_rid": "AnsTAK4aVt8czAIAAAAAAA==", 35 | "_self": "dbs/AnsTAA==/colls/AnsTAK4aVt8=/docs/AnsTAK4aVt8czAIAAAAAAA==/", 36 | "_etag": "\"1400ade0-0000-3c00-0000-5eaa8a220000\"", 37 | "_attachments": "attachments/", 38 | "_ts": 1588234786 39 | } 40 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/instances/ca9da17c-904a-44d2-9771-a5420acfbcf3.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "ca9da17c-904a-44d2-9771-a5420acfbcf3", 3 | "instanceOwner": { 4 | "partyId": "1337", 5 | "organisationNumber": "725736800" 6 | }, 7 | "appId": "tdd/endring-av-navn", 8 | "org": "tdd", 9 | "title": { 10 | "nb": "Endring av navn" 11 | }, 12 | "process": { 13 | "started": "2020-04-29T13:53:01.7020218Z", 14 | "startEvent": "StartEvent_1", 15 | "currentTask": { 16 | "elementId": "Task_1", 17 | "altinnTaskType": "data" 18 | } 19 | }, 20 | "status": { 21 | "softDeleted": "2020-04-29T13:53:06.117891Z", 22 | "isArchived": false, 23 | "isHardDeleted": false, 24 | "isSoftDeleted": true 25 | }, 26 | "appOwner": {}, 27 | "data": [ 28 | ], 29 | "visibleAfter": "2020-04-29T13:53:02.2836971Z", 30 | "created": "2020-04-29T13:53:02.2836971Z", 31 | "createdBy": "1337", 32 | "lastChanged": "2020-04-29T13:53:09.1122622Z", 33 | "lastChangedBy": "1337", 34 | "_rid": "AnsTAK4aVt8czAIAAAAAAA==", 35 | "_self": "dbs/AnsTAA==/colls/AnsTAK4aVt8=/docs/AnsTAK4aVt8czAIAAAAAAA==/", 36 | "_etag": "\"1400ade0-0000-3c00-0000-5eaa8a220000\"", 37 | "_attachments": "attachments/", 38 | "_ts": 1588234786 39 | } 40 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/instances/d91fd644-1028-4efd-924f-4ca187354514.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "d91fd644-1028-4efd-924f-4ca187354514", 3 | "instanceOwner": { 4 | "partyId": "1337", 5 | "organisationNumber": "725736800" 6 | }, 7 | "appId": "tdd/endring-av-navn", 8 | "org": "tdd", 9 | "title": { 10 | "nb": "Endring av navn" 11 | }, 12 | "process": { 13 | "started": "2020-04-29T13:53:01.7020218Z", 14 | "startEvent": "StartEvent_1", 15 | "currentTask": { 16 | "elementId": "Task_1", 17 | "altinnTaskType": "data" 18 | } 19 | }, 20 | "status": { 21 | "softDeleted": "2020-04-29T13:53:06.117891Z", 22 | "isArchived": false, 23 | "isHardDeleted": false, 24 | "isSoftDeleted": true 25 | }, 26 | "appOwner": {}, 27 | "data": [ 28 | ], 29 | "visibleAfter": "2020-04-29T13:53:02.2836971Z", 30 | "created": "2020-04-29T13:53:02.2836971Z", 31 | "createdBy": "1337", 32 | "lastChanged": "2020-04-29T13:53:09.1122622Z", 33 | "lastChangedBy": "1337", 34 | "_rid": "AnsTAK4aVt8czAIAAAAAAA==", 35 | "_self": "dbs/AnsTAA==/colls/AnsTAK4aVt8=/docs/AnsTAK4aVt8czAIAAAAAAA==/", 36 | "_etag": "\"1400ade0-0000-3c00-0000-5eaa8a220000\"", 37 | "_attachments": "attachments/", 38 | "_ts": 1588234786 39 | } 40 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/instances/dd84cfe9-f875-42ea-8a96-eb725a6a8a95.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "dd84cfe9-f875-42ea-8a96-eb725a6a8a95", 3 | "instanceOwner": { 4 | "partyId": "1337", 5 | "organisationNumber": "725736800" 6 | }, 7 | "appId": "tdd/endring-av-navn", 8 | "org": "tdd", 9 | "title": { 10 | "nb": "Endring av navn" 11 | }, 12 | "process": { 13 | "started": "2020-04-29T13:53:01.7020218Z", 14 | "startEvent": "StartEvent_1", 15 | "currentTask": { 16 | "elementId": "Task_1", 17 | "altinnTaskType": "data" 18 | } 19 | }, 20 | "status": { 21 | "isArchived": false, 22 | "isHardDeleted": false, 23 | "softDeleted": "2020-04-29T13:53:06.117891Z", 24 | "isSoftDeleted": true 25 | }, 26 | "appOwner": {}, 27 | "data": [ 28 | ], 29 | "visibleAfter": "2020-04-29T13:53:02.2836971Z", 30 | "created": "2020-04-29T13:53:02.2836971Z", 31 | "createdBy": "1337", 32 | "lastChanged": "2020-04-29T13:53:09.1122622Z", 33 | "lastChangedBy": "1337", 34 | "_rid": "AnsTAK4aVt8czAIAAAAAAA==", 35 | "_self": "dbs/AnsTAA==/colls/AnsTAK4aVt8=/docs/AnsTAK4aVt8czAIAAAAAAA==/", 36 | "_etag": "\"1400ade0-0000-3c00-0000-5eaa8a220000\"", 37 | "_attachments": "attachments/", 38 | "_ts": 1588234786 39 | } 40 | -------------------------------------------------------------------------------- /src/Storage/Migration/v0.24/01-create-index.sql: -------------------------------------------------------------------------------- 1 | drop index if exists storage.instances_isharddeleted_confirmed; 2 | drop index if exists storage.instances_lastchanged_appid; 3 | drop index if exists storage.instances_lastchanged_filtered; 4 | 5 | CREATE INDEX IF NOT EXISTS instances_org_lastchanged_archived_id_filtered 6 | ON storage.instances USING btree 7 | (org COLLATE pg_catalog."default" ASC NULLS LAST, lastchanged DESC NULLS FIRST, (((instance -> 'Status'::text) -> 'IsArchived'::text)::boolean) ASC NULLS LAST, id ASC NULLS LAST) 8 | TABLESPACE pg_default 9 | WHERE confirmed = false AND altinnmainversion = 3; 10 | 11 | CREATE INDEX IF NOT EXISTS instances_org_lastchanged_id_filtered 12 | ON storage.instances USING btree 13 | (org COLLATE pg_catalog."default" ASC NULLS LAST, lastchanged DESC NULLS FIRST, id ASC NULLS LAST) 14 | TABLESPACE pg_default 15 | WHERE confirmed = false AND altinnmainversion = 3; 16 | 17 | CREATE INDEX IF NOT EXISTS instances_lastchanged_org_filtered 18 | ON storage.instances USING btree 19 | (lastchanged ASC NULLS LAST, org COLLATE pg_catalog."default" ASC NULLS LAST) 20 | TABLESPACE pg_default 21 | WHERE altinnmainversion = 3 AND ((instance -> 'Status'::text) -> 'IsArchived'::text)::boolean = true; -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/instances/bc19107c-508f-48d9-bcd7-54ffec905306.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "bc19107c-508f-48d9-bcd7-54ffec905306", 3 | "instanceOwner": { 4 | "partyId": "1337", 5 | "organisationNumber": "725736800" 6 | }, 7 | "appId": "tdd/endring-av-navn", 8 | "org": "tdd", 9 | "title": { 10 | "nb": "Endring av navn" 11 | }, 12 | "process": { 13 | "started": "2020-04-29T13:53:01.7020218Z", 14 | "startEvent": "StartEvent_1", 15 | "currentTask": { 16 | "elementId": "Task_1", 17 | "altinnTaskType": "data" 18 | } 19 | }, 20 | "status": { 21 | "softDeleted": "2020-04-29T13:53:06.117891Z", 22 | "isArchived": false, 23 | "isHardDeleted": false, 24 | "isSoftDeleted": true 25 | }, 26 | "appOwner": {}, 27 | "data": [ 28 | ], 29 | "visibleAfter": "2020-04-29T13:53:02.2836971Z", 30 | "created": "2020-04-29T13:53:02.2836971Z", 31 | "createdBy": "1337", 32 | "lastChanged": "2020-04-29T13:53:09.1122622Z", 33 | "lastChangedBy": "1337", 34 | "_rid": "AnsTAK4aVt8czAIAAAAAAA==", 35 | "_self": "dbs/AnsTAA==/colls/AnsTAK4aVt8=/docs/AnsTAK4aVt8czAIAAAAAAA==/", 36 | "_etag": "\"1400ade0-0000-3c00-0000-5eaa8a220000\"", 37 | "_attachments": "attachments/", 38 | "_ts": 1588234786 39 | } 40 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/instances/045ea5db-6dd4-4476-b774-bdb2a09da7ea.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "045ea5db-6dd4-4476-b774-bdb2a09da7ea", 3 | "instanceOwner": { 4 | "partyId": "1600", 5 | "personNumber": "01044091042" 6 | }, 7 | "appId": "ttd/steffens-2020-v2", 8 | "org": "ttd", 9 | "title": { 10 | "nb": "steffens-2020-v2" 11 | }, 12 | "process": { 13 | "started": "2020-01-23T13:00:30.0913327Z", 14 | "startEvent": "StartEvent_1", 15 | "ended": "2020-01-23T13:00:47.414903Z", 16 | "endEvent": "EndEvent_1" 17 | }, 18 | "status": { 19 | "isArchived": true, 20 | "archived": "2020-01-23T13:00:47.414903Z", 21 | "isHardDeleted": false, 22 | "isSoftDeleted": true, 23 | "softDeleted": "2020-01-23T13:00:47.414903Z" 24 | }, 25 | "appOwner": {}, 26 | "visibleAfter": "2020-01-23T13:00:38.7425926Z", 27 | "created": "2020-01-23T13:00:38.7425926Z", 28 | "createdBy": "20000013", 29 | "lastChanged": "2020-01-23T13:00:49.7102101Z", 30 | "lastChangedBy": "20000013", 31 | "_rid": "uHp7ALWZ1j8BAAAAAAAAAA==", 32 | "_self": "dbs/uHp7AA==/colls/uHp7ALWZ1j8=/docs/uHp7ALWZ1j8BAAAAAAAAAA==/", 33 | "_etag": "\"5b004591-0000-3c00-0000-5e2999010000\"", 34 | "_attachments": "attachments/", 35 | "_ts": 1579784449 36 | } 37 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # General setting that applies Git's binary detection for file-types not specified below 2 | # Meaning, for 'text-guessed' files: 3 | # use normalization (convert crlf -> lf on commit, i.e. use `text` setting) 4 | # & do unspecified diff behavior (if file content is recognized as text & filesize < core.bigFileThreshold, do text diff on file changes) 5 | * text=auto 6 | 7 | # .editorconfig makes csharpier ensure lf on all platforms 8 | *.cs text eol=lf 9 | 10 | # Override with explicit specific settings for known and/or likely text files in our repo that should be normalized 11 | # where diff{=optional_pattern} means "do text diff {with specific text pattern} and -diff means "don't do text diffs". 12 | # Unspecified diff behavior is decribed above 13 | *.cer text -diff 14 | *.cmd text 15 | *.cs text diff=csharp 16 | *.csproj text 17 | *.css text diff=css 18 | Dockerfile text 19 | *.json text 20 | *.md text diff=markdown 21 | *.msbuild text 22 | *.pem text -diff 23 | *.ps1 text 24 | *.sln text 25 | *.yaml text 26 | *.yml text 27 | 28 | # Files that should be treated as binary ('binary' is a macro for '-text -diff', i.e. "don't normalize or do text diff on content") 29 | *.jpeg binary 30 | *.pfx binary 31 | *.png binary 32 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/instances/045ea5db-6dd4-4476-b774-bdb2a09da7dd.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "045ea5db-6dd4-4476-b774-bdb2a09da7dd", 3 | "instanceOwner": { 4 | "partyId": "1600", 5 | "personNumber": "01044091042" 6 | }, 7 | "appId": "ttd/steffens-2020-v2", 8 | "org": "ttd", 9 | "title": { 10 | "nb": "steffens-2020-v2" 11 | }, 12 | "process": { 13 | "started": "2020-01-23T13:00:30.0913327Z", 14 | "startEvent": "StartEvent_1", 15 | "currentTask": { 16 | "flow": 2, 17 | "started": "2020-03-02T16:41:30.2727355Z", 18 | "elementId": "Task_1", 19 | "name": "Utfylling", 20 | "altinnTaskType": "data" 21 | } 22 | }, 23 | "status": { 24 | "isArchived": false, 25 | "isHardDeleted": false, 26 | "isSoftDeleted": false 27 | }, 28 | "appOwner": {}, 29 | "visibleAfter": "2020-01-23T13:00:38.7425926Z", 30 | "created": "2020-01-23T13:00:38.7425926Z", 31 | "createdBy": "20000013", 32 | "lastChanged": "2020-01-23T13:00:49.7102101Z", 33 | "lastChangedBy": "20000013", 34 | "_rid": "uHp7ALWZ1j8BAAAAAAAAAA==", 35 | "_self": "dbs/uHp7AA==/colls/uHp7ALWZ1j8=/docs/uHp7ALWZ1j8BAAAAAAAAAA==/", 36 | "_etag": "\"5b004591-0000-3c00-0000-5e2999010000\"", 37 | "_attachments": "attachments/", 38 | "_ts": 1579784449 39 | } 40 | -------------------------------------------------------------------------------- /src/Storage/Migration/FunctionsAndProcedures/deletedataelement.sql: -------------------------------------------------------------------------------- 1 | CREATE OR REPLACE FUNCTION storage.deletedataelement_v2(_alternateid UUID, _instanceGuid UUID, _lastChangedBy TEXT) 2 | RETURNS INT 3 | LANGUAGE 'plpgsql' 4 | AS $BODY$ 5 | DECLARE 6 | _deleteCount INTEGER; 7 | BEGIN 8 | IF (SELECT COUNT(*) FROM storage.dataelements WHERE element -> 'IsRead' = 'true' AND instanceguid = _instanceGuid) = 0 THEN 9 | UPDATE storage.instances 10 | SET instance = jsonb_set(instance, '{Status, ReadStatus}', '0') 11 | WHERE alternateid = _instanceGuid AND instance -> 'Status' ->> 'ReadStatus' = '1'; 12 | END IF; 13 | 14 | UPDATE storage.instances 15 | SET lastchanged = NOW(), 16 | instance = instance 17 | || jsonb_set('{"LastChanged":""}', '{LastChanged}', to_jsonb(REPLACE((NOW() AT TIME ZONE 'UTC')::TEXT, ' ', 'T') || 'Z')) 18 | || jsonb_set('{"LastChangedBy":""}', '{LastChangedBy}', to_jsonb(_lastChangedBy)) 19 | WHERE alternateid = (SELECT instanceguid FROM storage.dataelements WHERE alternateid = _alternateid); 20 | 21 | DELETE FROM storage.dataelements WHERE alternateid = _alternateid; 22 | GET DIAGNOSTICS _deleteCount = ROW_COUNT; 23 | 24 | RETURN _deleteCount; 25 | END; 26 | $BODY$; -------------------------------------------------------------------------------- /test/Altinn.Platform.Storage.Interface.Tests/ShadowFields/applicationMetadata_beforeChange.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "ttd/anonymous-stateless", 3 | "org": "ttd", 4 | "title": { 5 | "nb": "anonymous-stateless", 6 | "en": "Oppstart av bedrift" 7 | }, 8 | "dataTypes": [ 9 | { 10 | "id": "ref-data-as-pdf", 11 | "allowedContentTypes": [ 12 | "application/pdf" 13 | ], 14 | "maxCount": 0, 15 | "minCount": 0, 16 | "enablePdfCreation": true 17 | }, 18 | { 19 | "id": "Veileder", 20 | "allowedContentTypes": [ 21 | "application/xml" 22 | ], 23 | "appLogic": { 24 | "autoCreate": true, 25 | "classRef": "Altinn.App.Models.StarteBedrift" 26 | }, 27 | "taskId": "Task_1", 28 | "maxCount": 1, 29 | "minCount": 1, 30 | "enablePdfCreation": true 31 | } 32 | ], 33 | "onEntry": { 34 | "show": "stateless" 35 | }, 36 | "partyTypesAllowed": { 37 | "bankruptcyEstate": false, 38 | "organisation": false, 39 | "person": false, 40 | "subUnit": false 41 | }, 42 | "autoDeleteOnProcessEnd": false, 43 | "created": "2022-04-27T08:00:46.5336752Z", 44 | "createdBy": "Ronny", 45 | "lastChanged": "2022-04-27T08:00:46.533678Z", 46 | "lastChangedBy": "Ronny" 47 | } 48 | -------------------------------------------------------------------------------- /src/Storage.Interface/Models/Reference.cs: -------------------------------------------------------------------------------- 1 | using Altinn.Platform.Storage.Interface.Enums; 2 | using Newtonsoft.Json; 3 | using Newtonsoft.Json.Converters; 4 | using TextJson = System.Text.Json.Serialization; 5 | 6 | namespace Altinn.Platform.Storage.Interface.Models; 7 | 8 | /// 9 | /// Reference to other objects in storage 10 | /// 11 | public class Reference 12 | { 13 | /// 14 | /// Value of the connected reference 15 | /// 16 | [JsonProperty(PropertyName = "value")] 17 | public string Value { get; set; } 18 | 19 | /// 20 | /// The type of relation to the connected object see 21 | /// 22 | [JsonProperty(PropertyName = "relation")] 23 | [JsonConverter(typeof(StringEnumConverter))] 24 | [TextJson.JsonConverter(typeof(TextJson.JsonStringEnumConverter))] 25 | public RelationType? Relation { get; set; } 26 | 27 | /// 28 | /// The value type of the connected object see 29 | /// 30 | [JsonProperty(PropertyName = "valueType")] 31 | [JsonConverter(typeof(StringEnumConverter))] 32 | [TextJson.JsonConverter(typeof(TextJson.JsonStringEnumConverter))] 33 | public ReferenceType? ValueType { get; set; } 34 | } 35 | -------------------------------------------------------------------------------- /test/Altinn.Platform.Storage.Interface.Tests/AllowUserActions/applicationMetadata_beforeChange.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "ttd/anonymous-stateless", 3 | "org": "ttd", 4 | "title": { 5 | "nb": "anonymous-stateless", 6 | "en": "Oppstart av bedrift" 7 | }, 8 | "dataTypes": [ 9 | { 10 | "id": "ref-data-as-pdf", 11 | "allowedContentTypes": [ 12 | "application/pdf" 13 | ], 14 | "maxCount": 0, 15 | "minCount": 0, 16 | "enablePdfCreation": true 17 | }, 18 | { 19 | "id": "Veileder", 20 | "allowedContentTypes": [ 21 | "application/xml" 22 | ], 23 | "appLogic": { 24 | "autoCreate": true, 25 | "classRef": "Altinn.App.Models.StarteBedrift" 26 | }, 27 | "taskId": "Task_1", 28 | "maxCount": 1, 29 | "minCount": 1, 30 | "enablePdfCreation": true 31 | } 32 | ], 33 | "onEntry": { 34 | "show": "stateless" 35 | }, 36 | "partyTypesAllowed": { 37 | "bankruptcyEstate": false, 38 | "organisation": false, 39 | "person": false, 40 | "subUnit": false 41 | }, 42 | "autoDeleteOnProcessEnd": false, 43 | "created": "2022-04-27T08:00:46.5336752Z", 44 | "createdBy": "Ronny", 45 | "lastChanged": "2022-04-27T08:00:46.533678Z", 46 | "lastChangedBy": "Ronny" 47 | } 48 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/instances/aa6732ea-bb55-4f38-8ece-c53170dc92b2.json: -------------------------------------------------------------------------------- 1 | { 2 | "instanceOwner": { 3 | "partyId": "1600", 4 | "personNumber": "01044091042" 5 | }, 6 | "appId": "ttd/dato-eksempel", 7 | "org": "ttd", 8 | "title": { 9 | "nb": "dato-eksempel" 10 | }, 11 | "process": { 12 | "started": "2020-02-27T13:09:25.7490122Z", 13 | "startEvent": "StartEvent_1", 14 | "currentTask": { 15 | "flow": 2, 16 | "started": "2020-02-27T13:09:25.7492502Z", 17 | "elementId": "Task_1", 18 | "name": "Utfylling", 19 | "altinnTaskType": "data" 20 | } 21 | }, 22 | "status": { 23 | "isArchived": false, 24 | "isHardDeleted": false, 25 | "isSoftDeleted": false 26 | }, 27 | "appOwner": {}, 28 | "data": [], 29 | "visibleAfter": "2020-02-27T13:09:25.7869239Z", 30 | "created": "2020-02-27T13:09:25.7869239Z", 31 | "createdBy": "20000013", 32 | "lastChanged": "2020-02-27T13:09:25.7869239Z", 33 | "lastChangedBy": "20000013", 34 | "id": "aa6732ea-bb55-4f38-8ece-c53170dc92b2", 35 | "_rid": "uHp7ALWZ1j9SAwAAAAAAAA==", 36 | "_self": "dbs/uHp7AA==/colls/uHp7ALWZ1j8=/docs/uHp7ALWZ1j9SAwAAAAAAAA==/", 37 | "_etag": "\"01006355-0000-3c00-0000-5e57bf850000\"", 38 | "_attachments": "attachments/", 39 | "_ts": 1582808965 40 | } 41 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/instances/b5433000-57be-4df5-b215-0558762e1c76.json: -------------------------------------------------------------------------------- 1 | { 2 | "instanceOwner": { 3 | "partyId": "1600", 4 | "personNumber": "01044091042" 5 | }, 6 | "appId": "ttd/dato-eksempel", 7 | "org": "ttd", 8 | "title": { 9 | "nb": "dato-eksempel" 10 | }, 11 | "visibleAfter": "2199-02-27T12:52:37.9398651Z", 12 | "process": { 13 | "started": "2020-02-27T12:52:37.9211181Z", 14 | "startEvent": "StartEvent_1", 15 | "currentTask": { 16 | "flow": 2, 17 | "started": "2020-02-27T12:52:37.9398651Z", 18 | "elementId": "Task_1", 19 | "name": "Utfylling", 20 | "altinnTaskType": "data" 21 | } 22 | }, 23 | "status": { 24 | "isArchived": false, 25 | "isHardDeleted": false, 26 | "isSoftDeleted": false 27 | }, 28 | "appOwner": {}, 29 | "data": [], 30 | "created": "2020-02-27T12:52:38.0517187Z", 31 | "createdBy": "20000013", 32 | "lastChanged": "2020-02-27T12:52:38.0517187Z", 33 | "lastChangedBy": "20000013", 34 | "id": "b5433000-57be-4df5-b215-0558762e1c76", 35 | "_rid": "uHp7ALWZ1j9QAwAAAAAAAA==", 36 | "_self": "dbs/uHp7AA==/colls/uHp7ALWZ1j8=/docs/uHp7ALWZ1j9QAwAAAAAAAA==/", 37 | "_etag": "\"0100514e-0000-3c00-0000-5e57bb980000\"", 38 | "_attachments": "attachments/", 39 | "_ts": 1582807960 40 | } 41 | -------------------------------------------------------------------------------- /test/Altinn.Platform.Storage.Interface.Tests/AllowAnonymousOnStateless/applicationMetadata_beforeChange.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "ttd/anonymous-stateless", 3 | "org": "ttd", 4 | "title": { 5 | "nb": "anonymous-stateless", 6 | "en": "Oppstart av bedrift" 7 | }, 8 | "dataTypes": [ 9 | { 10 | "id": "ref-data-as-pdf", 11 | "allowedContentTypes": [ 12 | "application/pdf" 13 | ], 14 | "maxCount": 0, 15 | "minCount": 0, 16 | "enablePdfCreation": true 17 | }, 18 | { 19 | "id": "Veileder", 20 | "allowedContentTypes": [ 21 | "application/xml" 22 | ], 23 | "appLogic": { 24 | "autoCreate": true, 25 | "classRef": "Altinn.App.Models.StarteBedrift" 26 | }, 27 | "taskId": "Task_1", 28 | "maxCount": 1, 29 | "minCount": 1, 30 | "enablePdfCreation": true 31 | } 32 | ], 33 | "onEntry": { 34 | "show": "stateless" 35 | }, 36 | "partyTypesAllowed": { 37 | "bankruptcyEstate": false, 38 | "organisation": false, 39 | "person": false, 40 | "subUnit": false 41 | }, 42 | "autoDeleteOnProcessEnd": false, 43 | "created": "2022-04-27T08:00:46.5336752Z", 44 | "createdBy": "Ronny", 45 | "lastChanged": "2022-04-27T08:00:46.533678Z", 46 | "lastChangedBy": "Ronny" 47 | } 48 | -------------------------------------------------------------------------------- /src/Storage.Interface/Models/ChangableElement.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | 4 | namespace Altinn.Platform.Storage.Interface.Models; 5 | 6 | /// 7 | /// Represents a super type for other classes that need properties associated with creation and editing. 8 | /// 9 | [JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)] 10 | public abstract class ChangableElement 11 | { 12 | /// 13 | /// Gets or sets the date and time for when the element was created. 14 | /// 15 | [JsonProperty(PropertyName = "created")] 16 | public DateTime? Created { get; set; } 17 | 18 | /// 19 | /// Gets or sets the id of the user who created this element. 20 | /// 21 | [JsonProperty(PropertyName = "createdBy")] 22 | public string CreatedBy { get; set; } 23 | 24 | /// 25 | /// Gets or sets the date and time for when the element was last edited. 26 | /// 27 | [JsonProperty(PropertyName = "lastChanged")] 28 | public DateTime? LastChanged { get; set; } 29 | 30 | /// 31 | /// Gets or sets the id of the user who last changed this element. 32 | /// 33 | [JsonProperty(PropertyName = "lastChangedBy")] 34 | public string LastChangedBy { get; set; } 35 | } 36 | -------------------------------------------------------------------------------- /src/Storage.Interface/Models/Signee.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Text.Json.Serialization; 5 | using Newtonsoft.Json; 6 | 7 | namespace Altinn.Platform.Storage.Interface.Models; 8 | 9 | /// 10 | /// Information about the signee 11 | /// 12 | public class Signee 13 | { 14 | /// 15 | /// The userId representing the signee. 16 | /// 17 | [JsonProperty(PropertyName = "userId")] 18 | [JsonPropertyName("userId")] 19 | public string? UserId { get; set; } 20 | 21 | /// 22 | /// The ID of the systemuser performing the signing 23 | /// 24 | [JsonProperty(PropertyName = "systemUserId")] 25 | [JsonPropertyName("systemUserId")] 26 | public Guid? SystemUserId { get; set; } 27 | 28 | /// 29 | /// The personNumber representing the signee. 30 | /// 31 | [JsonProperty(PropertyName = "personNumber")] 32 | [JsonPropertyName("personNumber")] 33 | public string? PersonNumber { get; set; } 34 | 35 | /// 36 | /// The organisationNumber representing the signee. 37 | /// 38 | [JsonProperty(PropertyName = "organisationNumber")] 39 | [JsonPropertyName("organisationNumber")] 40 | public string? OrganisationNumber { get; set; } 41 | } 42 | -------------------------------------------------------------------------------- /test/Altinn.Platform.Storage.Interface.Tests/AllowAnonymousOnStateless/ApplicationTests.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Altinn.Platform.Storage.Interface.Models; 3 | using Xunit; 4 | 5 | namespace Altinn.Platform.Storage.Interface.Tests; 6 | 7 | public class ApplicationTests 8 | { 9 | [Fact] 10 | public void MetadataWithoutAllowAnonymous_ShouldBeFalse() 11 | { 12 | Application applicationBefore = 13 | TestdataHelper.LoadDataFromEmbeddedResourceAsType( 14 | "AllowAnonymousOnStateless.applicationMetadata_beforeChange.json" 15 | ); 16 | 17 | Assert.False( 18 | applicationBefore 19 | .DataTypes.First(d => d.Id == "Veileder") 20 | .AppLogic.AllowAnonymousOnStateless 21 | ); 22 | } 23 | 24 | [Fact] 25 | public void MetadataWithAllowAnonymous_ShouldBeTrue() 26 | { 27 | Application applicationBefore = 28 | TestdataHelper.LoadDataFromEmbeddedResourceAsType( 29 | "AllowAnonymousOnStateless.applicationMetadata_afterChange.json" 30 | ); 31 | 32 | Assert.True( 33 | applicationBefore 34 | .DataTypes.First(d => d.Id == "Veileder") 35 | .AppLogic.AllowAnonymousOnStateless 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /.github/workflows/publish-release.yml: -------------------------------------------------------------------------------- 1 | name: Pack and publish nugets 2 | 3 | on: 4 | release: 5 | types: 6 | - published 7 | jobs: 8 | build-pack: 9 | if: startsWith(github.ref, 'refs/tags/Storage.Interface-') 10 | runs-on: ubuntu-latest 11 | permissions: 12 | contents: read 13 | steps: 14 | - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 15 | with: 16 | fetch-depth: 0 17 | 18 | - name: Setup .NET 19 | uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0 20 | with: 21 | dotnet-version: | 22 | 9.0.x 23 | - name: Install deps 24 | run: | 25 | cd src/Storage.Interface 26 | dotnet restore 27 | - name: Build 28 | run: | 29 | cd src/Storage.Interface 30 | dotnet build --configuration Release --no-restore -p:Deterministic=true -p:BuildNumber=${{ github.run_number }} 31 | - name: Pack and publish Storage.Interface 32 | run: | 33 | cd src/Storage.Interface 34 | dotnet pack --configuration Release --no-restore --no-build -p:BuildNumber=${{ github.run_number }} -p:Deterministic=true 35 | dotnet nuget push bin/Release/*.nupkg --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_API_KEY }} 36 | -------------------------------------------------------------------------------- /test/Altinn.Platform.Storage.Interface.Tests/AllowAnonymousOnStateless/applicationMetadata_afterChange.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "ttd/anonymous-stateless", 3 | "org": "ttd", 4 | "title": { 5 | "nb": "anonymous-stateless", 6 | "en": "Oppstart av bedrift" 7 | }, 8 | "dataTypes": [ 9 | { 10 | "id": "ref-data-as-pdf", 11 | "allowedContentTypes": [ 12 | "application/pdf" 13 | ], 14 | "maxCount": 0, 15 | "minCount": 0, 16 | "enablePdfCreation": true 17 | }, 18 | { 19 | "id": "Veileder", 20 | "allowedContentTypes": [ 21 | "application/xml" 22 | ], 23 | "appLogic": { 24 | "autoCreate": true, 25 | "classRef": "Altinn.App.Models.StarteBedrift", 26 | "allowAnonymousOnStateless": true 27 | }, 28 | "taskId": "Task_1", 29 | "maxCount": 1, 30 | "minCount": 1, 31 | "enablePdfCreation": true 32 | } 33 | ], 34 | "onEntry": { 35 | "show": "stateless" 36 | }, 37 | "partyTypesAllowed": { 38 | "bankruptcyEstate": false, 39 | "organisation": false, 40 | "person": false, 41 | "subUnit": false 42 | }, 43 | "autoDeleteOnProcessEnd": false, 44 | "created": "2022-04-27T08:00:46.5336752Z", 45 | "createdBy": "Ronny", 46 | "lastChanged": "2022-04-27T08:00:46.533678Z", 47 | "lastChangedBy": "Ronny" 48 | } 49 | -------------------------------------------------------------------------------- /src/Storage.Interface/Models/InstanceOwner.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace Altinn.Platform.Storage.Interface.Models; 4 | 5 | /// 6 | /// Represents information to identify the owner of an instance. 7 | /// 8 | [JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)] 9 | public class InstanceOwner 10 | { 11 | /// 12 | /// Gets or sets the party id of the instance owner (also called instance owner party id). 13 | /// 14 | [JsonProperty(PropertyName = "partyId")] 15 | public string PartyId { get; set; } 16 | 17 | /// 18 | /// Gets or sets person number (national identification number) of the party. Null if the party is not a person. 19 | /// 20 | [JsonProperty(PropertyName = "personNumber")] 21 | public string PersonNumber { get; set; } 22 | 23 | /// 24 | /// Gets or sets the organisation number of the party. Null if the party is not an organisation. 25 | /// 26 | [JsonProperty(PropertyName = "organisationNumber")] 27 | public string OrganisationNumber { get; set; } 28 | 29 | /// 30 | /// Gets or sets the username of the party. Null if the party is not self identified. 31 | /// 32 | [JsonProperty(PropertyName = "username")] 33 | public string Username { get; set; } 34 | } 35 | -------------------------------------------------------------------------------- /test/Altinn.Platform.Storage.Interface.Tests/AllowUserActions/applicationMetadata_afterChange.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "ttd/anonymous-stateless", 3 | "org": "ttd", 4 | "title": { 5 | "nb": "anonymous-stateless", 6 | "en": "Oppstart av bedrift" 7 | }, 8 | "dataTypes": [ 9 | { 10 | "id": "ref-data-as-pdf", 11 | "allowedContentTypes": [ 12 | "application/pdf" 13 | ], 14 | "maxCount": 0, 15 | "minCount": 0, 16 | "enablePdfCreation": true 17 | }, 18 | { 19 | "id": "Veileder", 20 | "allowedContentTypes": [ 21 | "application/xml" 22 | ], 23 | "appLogic": { 24 | "autoCreate": true, 25 | "classRef": "Altinn.App.Models.StarteBedrift", 26 | "disallowUserCreate": true, 27 | "disallowUserDelete": false 28 | }, 29 | "taskId": "Task_1", 30 | "maxCount": 1, 31 | "minCount": 1, 32 | "enablePdfCreation": true 33 | } 34 | ], 35 | "onEntry": { 36 | "show": "stateless" 37 | }, 38 | "partyTypesAllowed": { 39 | "bankruptcyEstate": false, 40 | "organisation": false, 41 | "person": false, 42 | "subUnit": false 43 | }, 44 | "autoDeleteOnProcessEnd": false, 45 | "created": "2022-04-27T08:00:46.5336752Z", 46 | "createdBy": "Ronny", 47 | "lastChanged": "2022-04-27T08:00:46.533678Z", 48 | "lastChangedBy": "Ronny" 49 | } -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/instances/4914257c-9920-47a5-a37a-eae80f950767.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "4914257c-9920-47a5-a37a-eae80f950767", 3 | "instanceOwner": { 4 | "partyId": "1337", 5 | "organisationNumber": "725736800" 6 | }, 7 | "appId": "ttd/autodelete-data-app", 8 | "org": "ttd", 9 | "process": { 10 | "started": "2022-06-13T12:39:24.0715404Z", 11 | "startEvent": "StartEvent_1", 12 | "currentTask": { 13 | "flow": 2, 14 | "started": "2022-06-13T12:39:24.0715791Z", 15 | "elementId": "Task_1", 16 | "name": "Utfylling", 17 | "altinnTaskType": "data", 18 | "ended": null, 19 | "validated": null, 20 | "flowType": "CompleteCurrentMoveToNext" 21 | }, 22 | "ended": null, 23 | "endEvent": null 24 | }, 25 | "status": { 26 | "isArchived": false, 27 | "isHardDeleted": false, 28 | "isSoftDeleted": false 29 | }, 30 | "data": [], 31 | "visibleAfter": "2020-04-29T13:53:02.2836971Z", 32 | "created": "2020-04-29T13:53:02.2836971Z", 33 | "createdBy": "1337", 34 | "lastChanged": "2020-04-29T13:53:09.1122622Z", 35 | "lastChangedBy": "1337", 36 | "_rid": "AnsTAK4aVt8czAIAAAAAAA==", 37 | "_self": "dbs/AnsTAA==/colls/AnsTAK4aVt8=/docs/AnsTAK4aVt8czAIAAAAAAA==/", 38 | "_etag": "\"1400ade0-0000-3c00-0000-5eaa8a220000\"", 39 | "_attachments": "attachments/", 40 | "_ts": 1588234786 41 | } 42 | -------------------------------------------------------------------------------- /test/UnitTest/data/postgresdata/instances/3f7fcd91-114e-4da1-95b6-72115f34945c.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "3f7fcd91-114e-4da1-95b6-72115f34945c", 3 | "instanceOwner": { 4 | "partyId": "1337", 5 | "organisationNumber": "725736800" 6 | }, 7 | "appId": "tdd/endring-av-navn", 8 | "org": "tdd", 9 | "title": { 10 | "nb": "Endring av navn" 11 | }, 12 | "process": { 13 | "started": "2020-04-29T13:53:01.7020218Z", 14 | "startEvent": "StartEvent_1", 15 | "currentTask": { 16 | "elementId": "Task_1", 17 | "altinnTaskType": "data" 18 | } 19 | }, 20 | "status": { 21 | "softDeleted": "2020-04-29T13:53:06.117891Z", 22 | "isArchived": true, 23 | "isHardDeleted": false, 24 | "isSoftDeleted": false, 25 | "archived": "2024-04-29T13:53:06.117891Z" 26 | }, 27 | "appOwner": {}, 28 | "data": [], 29 | "visibleAfter": "2020-04-29T13:53:02.2836971Z", 30 | "created": "2020-04-29T13:53:02.2836971Z", 31 | "createdBy": "1337", 32 | "lastChanged": "2020-04-29T13:53:09.1122622Z", 33 | "lastChangedBy": "1337", 34 | "_rid": "AnsTAK4aVt8czAIAAAAAAA==", 35 | "_self": "dbs/AnsTAA==/colls/AnsTAK4aVt8=/docs/AnsTAK4aVt8czAIAAAAAAA==/", 36 | "_etag": "\"1400ade0-0000-3c00-0000-5eaa8a220000\"", 37 | "_attachments": "attachments/", 38 | "_ts": 1588234786 39 | } -------------------------------------------------------------------------------- /.github/workflows/container-scan.yml: -------------------------------------------------------------------------------- 1 | name: Storage Scan 2 | on: 3 | schedule: 4 | - cron: "0 8 * * 1,4" 5 | push: 6 | branches: [main] 7 | paths: 8 | - "src/Storage/**" 9 | - "Dockerfile" 10 | pull_request: 11 | branches: [main] 12 | types: [opened, synchronize, reopened] 13 | paths: 14 | - "src/Storage/**" 15 | - "Dockerfile" 16 | - ".github/workflows/container-scan.yml" 17 | jobs: 18 | scan: 19 | runs-on: ubuntu-latest 20 | permissions: 21 | contents: read 22 | steps: 23 | - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 24 | - name: Build the Docker image 25 | run: docker build . --tag altinn-storage:${{github.sha}} 26 | 27 | - name: Run Trivy vulnerability scanner 28 | uses: aquasecurity/trivy-action@b6643a29fecd7f34b3597bc6acb0a98b03d33ff8 # 0.33.1 29 | with: 30 | image-ref: "altinn-storage:${{ github.sha }}" 31 | format: "table" 32 | exit-code: "1" 33 | ignore-unfixed: true 34 | vuln-type: "os,library" 35 | severity: "CRITICAL,HIGH" 36 | env: 37 | TRIVY_DB_REPOSITORY: public.ecr.aws/aquasecurity/trivy-db,aquasec/trivy-db,ghcr.io/aquasecurity/trivy-db 38 | TRIVY_JAVA_DB_REPOSITORY: public.ecr.aws/aquasecurity/trivy-java-db,aquasec/trivy-java-db,ghcr.io/aquasecurity/trivy-java-db 39 | -------------------------------------------------------------------------------- /test/UnitTest/Mocks/Clients/CorrespondenceClientMock.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using Altinn.Platform.Storage.Clients; 5 | 6 | namespace Altinn.Platform.Storage.UnitTest.Mocks.Clients; 7 | 8 | public class CorrespondenceClientMock : ICorrespondenceClient 9 | { 10 | private readonly string[] _eventTypes = ["read", "confirm", "delete"]; 11 | 12 | public async Task SyncCorrespondenceEvent( 13 | int correspondenceId, 14 | int partyId, 15 | DateTimeOffset eventTimestamp, 16 | string eventType 17 | ) 18 | { 19 | switch (correspondenceId) 20 | { 21 | case 2674: 22 | await Task.CompletedTask; 23 | break; 24 | case 2675: 25 | if (partyId == 1337) 26 | { 27 | throw new ArgumentException("Unknown party id"); 28 | } 29 | else if (!_eventTypes.Contains(eventType)) 30 | { 31 | throw new ArgumentException("Invalid event type"); 32 | } 33 | else 34 | { 35 | throw new ArgumentException("Unknown correspondenceId"); 36 | } 37 | 38 | default: 39 | throw new ArgumentException("Unknown correspondenceId"); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Storage.Interface/Models/ApiScopes.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | #nullable enable 4 | 5 | namespace Altinn.Platform.Storage.Interface.Models; 6 | 7 | /// 8 | /// Custom scopes for an app 9 | /// 10 | public class ApiScopes 11 | { 12 | /// 13 | /// Gets or sets the read scope for the app. 14 | /// Can use '[app]' as placeholders for the app name that will be substituted when validating the token. 15 | /// Example: "<your-IDporten-org>:[app]/instances.read" 16 | /// 17 | [JsonProperty(PropertyName = "read")] 18 | public string? Read { get; set; } 19 | 20 | /// 21 | /// Gets or sets the write scope for the app. 22 | /// Can use '[app]' as placeholders for the app name that will be substituted when validating the token. 23 | /// Example: "<your-IDporten-org>:[app]/instances.write" 24 | /// 25 | [JsonProperty(PropertyName = "write")] 26 | public string? Write { get; set; } 27 | 28 | /// 29 | /// Text resource key for a custom error message to show when a user tries to access the app API without having the required scopes 30 | /// Will apply if set (takes precedence over the one in 'apiScopesConfiguration') 31 | /// 32 | [JsonProperty(PropertyName = "errorMessageTextResourceKey")] 33 | public string? ErrorMessageTextResourceKey { get; set; } 34 | } 35 | -------------------------------------------------------------------------------- /src/Storage/Migration/v0.08/02-drop-functions.sql: -------------------------------------------------------------------------------- 1 | DROP FUNCTION IF EXISTS storage.readinstancefromquery( 2 | _appId TEXT, 3 | _appIds TEXT[], 4 | _archiveReference TEXT, 5 | _continue_idx BIGINT, 6 | _created_eq TIMESTAMPTZ, 7 | _created_gt TIMESTAMPTZ, 8 | _created_gte TIMESTAMPTZ, 9 | _created_lt TIMESTAMPTZ, 10 | _created_lte TIMESTAMPTZ, 11 | _dueBefore_eq TEXT, 12 | _dueBefore_gt TEXT, 13 | _dueBefore_gte TEXT, 14 | _dueBefore_lt TEXT, 15 | _dueBefore_lte TEXT, 16 | _excludeConfirmedBy JSONB[], 17 | _instanceOwner_partyId INTEGER, 18 | _instanceOwner_partyIds INTEGER[], 19 | _lastChanged_eq TIMESTAMPTZ, 20 | _lastChanged_gt TIMESTAMPTZ, 21 | _lastChanged_gte TIMESTAMPTZ, 22 | _lastChanged_idx TIMESTAMPTZ, 23 | _lastChanged_lt TIMESTAMPTZ, 24 | _lastChanged_lte TIMESTAMPTZ, 25 | _org TEXT, 26 | _process_currentTask TEXT, 27 | _process_ended_eq TEXT, 28 | _process_ended_gt TEXT, 29 | _process_ended_gte TEXT, 30 | _process_ended_lt TEXT, 31 | _process_ended_lte TEXT, 32 | _process_isComplete BOOLEAN, 33 | _size INTEGER, 34 | _sort_ascending BOOLEAN, 35 | _status_isActiveOrSoftDeleted BOOLEAN , 36 | _status_isArchived BOOLEAN, 37 | _status_isArchivedOrSoftDeleted BOOLEAN, 38 | _status_isHardDeleted BOOLEAN, 39 | _status_isSoftDeleted BOOLEAN, 40 | _visibleAfter_eq TEXT, 41 | _visibleAfter_gt TEXT, 42 | _visibleAfter_gte TEXT, 43 | _visibleAfter_lt TEXT, 44 | _visibleAfter_lte TEXT 45 | ); -------------------------------------------------------------------------------- /test/Altinn.Platform.Storage.Interface.Tests/ShadowFields/applicationMetadata_afterChange.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "ttd/anonymous-stateless", 3 | "org": "ttd", 4 | "title": { 5 | "nb": "anonymous-stateless", 6 | "en": "Oppstart av bedrift" 7 | }, 8 | "dataTypes": [ 9 | { 10 | "id": "ref-data-as-pdf", 11 | "allowedContentTypes": [ 12 | "application/pdf" 13 | ], 14 | "maxCount": 0, 15 | "minCount": 0, 16 | "enablePdfCreation": true 17 | }, 18 | { 19 | "id": "Veileder", 20 | "allowedContentTypes": [ 21 | "application/xml" 22 | ], 23 | "appLogic": { 24 | "autoCreate": true, 25 | "classRef": "Altinn.App.Models.StarteBedrift", 26 | "shadowFields": { 27 | "prefix": "AltinnSF_", 28 | "saveToDataType": "model-clean" 29 | } 30 | }, 31 | "taskId": "Task_1", 32 | "maxCount": 1, 33 | "minCount": 1, 34 | "enablePdfCreation": true 35 | } 36 | ], 37 | "onEntry": { 38 | "show": "stateless" 39 | }, 40 | "partyTypesAllowed": { 41 | "bankruptcyEstate": false, 42 | "organisation": false, 43 | "person": false, 44 | "subUnit": false 45 | }, 46 | "autoDeleteOnProcessEnd": false, 47 | "created": "2022-04-27T08:00:46.5336752Z", 48 | "createdBy": "Ronny", 49 | "lastChanged": "2022-04-27T08:00:46.533678Z", 50 | "lastChangedBy": "Ronny" 51 | } --------------------------------------------------------------------------------