├── test
├── k6
│ ├── src
│ │ ├── tests
│ │ │ └── .gitkeep
│ │ ├── reports
│ │ │ └── readme.md
│ │ ├── errorhandler.js
│ │ ├── api
│ │ │ ├── favorites.js
│ │ │ ├── notificationsettings.js
│ │ │ └── org-notification-addresses.js
│ │ └── config.js
│ ├── .gitignore
│ └── docker-compose.yml
├── Bruno
│ ├── .gitignore
│ ├── Profile
│ │ ├── Users
│ │ │ ├── folder.bru
│ │ │ ├── current user.bru
│ │ │ ├── Patch profile settings.bru
│ │ │ └── Update profile settings.bru
│ │ ├── Favorites
│ │ │ ├── folder.bru
│ │ │ ├── Get Party Groups.bru
│ │ │ ├── Get favorites.bru
│ │ │ ├── Add favorites.bru
│ │ │ └── Delete favorites.bru
│ │ ├── Organizations Notification Addresses
│ │ │ ├── folder.bru
│ │ │ ├── Run org sync.bru
│ │ │ ├── Email address.bru
│ │ │ ├── Phone number.bru
│ │ │ ├── Lookup org notificationAddresses.bru
│ │ │ ├── Get org notificationAddresses.bru
│ │ │ ├── Delete org notificationAddresses.bru
│ │ │ └── Update org notificationAddresses.bru
│ │ ├── Personal notificationaddress for an org
│ │ │ ├── folder.bru
│ │ │ ├── Get all parties.bru
│ │ │ ├── Get party.bru
│ │ │ ├── Delete party.bru
│ │ │ └── PUT party.bru
│ │ ├── folder.bru
│ │ ├── Run Org sync (local only).bru
│ │ ├── Run KRR sync (local only).bru
│ │ ├── unit contact points.bru
│ │ └── Dashboard
│ │ │ └── folder.bru
│ ├── bruno.json
│ ├── TokenGenerator
│ │ ├── folder.bru
│ │ ├── platform access token.bru
│ │ ├── Application Owner Profile scope.bru
│ │ └── portal user.bru
│ ├── environments
│ │ ├── Prod.bru
│ │ ├── TT02.bru
│ │ ├── Local.bru
│ │ ├── AT22.bru
│ │ ├── AT23.bru
│ │ └── AT24.bru
│ ├── .env.sample
│ └── collection.bru
└── Altinn.Profile.Tests
│ ├── xunit.runner.json
│ ├── skd-org.pfx
│ ├── ttd-org.pfx
│ ├── jwtselfsignedcert.pfx
│ ├── Testdata
│ ├── NotificationAddressChangesLog
│ │ ├── changes_0_faulty.json
│ │ ├── changes_0.json
│ │ ├── changes_4.json
│ │ ├── changes_3.json
│ │ ├── changes_5.json
│ │ └── changes_1.json
│ ├── UserProfile
│ │ ├── 2516356.json
│ │ ├── OrstaECUser.json
│ │ ├── 2001606.json
│ │ ├── 2001607.json
│ │ ├── 4c3b4909-eb17-45d5-bde1-256e065e196a.json
│ │ └── cc86d2c7-1695-44b0-8e82-e633243fdf31.json
│ └── TestDataLoader.cs
│ ├── Profile.Integrations
│ ├── Notifications
│ │ └── OrderContentTests.cs
│ ├── Register
│ │ └── LookupMainUnitRequestTests.cs
│ └── Person
│ │ └── PersonExtensions.cs
│ ├── appsettings.test.json
│ ├── IntegrationTests
│ ├── Mocks
│ │ ├── Role.cs
│ │ ├── PublicSigningKeyProviderMock.cs
│ │ ├── DelegatingHandlerStub.cs
│ │ └── Authentication
│ │ │ └── JwtCookiePostConfigureOptionsStub.cs
│ ├── HealthCheckTests.cs
│ ├── API
│ │ ├── OpenApiSpecificationTests.cs
│ │ └── Controllers
│ │ │ └── ErrorHandlingTests.cs
│ └── Utils
│ │ └── XacmlResourceAttributes.cs
│ ├── skd-org.pem
│ ├── ttd-org.pem
│ ├── JWTValidationCert.cer
│ └── Profile.Core
│ └── Utils
│ └── OptionalJsonConverterTests.cs
├── .github
├── CODEOWNERS
└── workflows
│ ├── assign-issues-to-projects.yml
│ ├── send-slack-warning.yml
│ └── container-scan.yml
├── src
├── Altinn.Profile.Integrations
│ ├── Migration
│ │ ├── v0.12
│ │ │ ├── 01-create-schema-wolverine.sql
│ │ │ └── 02-setup-grants.sql
│ │ ├── _pre
│ │ │ └── README.md
│ │ ├── _erase
│ │ │ └── README.md
│ │ ├── _post
│ │ │ └── README.md
│ │ ├── v0.00
│ │ │ └── 01-setup-schema.sql
│ │ ├── v0.05
│ │ │ └── 01-alter-table.sql
│ │ ├── v0.04
│ │ │ ├── 01-setup-schema.sql
│ │ │ ├── 02-setup-grants.sql
│ │ │ └── 04-setup-table-grants.sql
│ │ ├── _init
│ │ │ └── README.md
│ │ ├── v0.19
│ │ │ └── 01-grant-portal-settings.sql
│ │ ├── v0.20
│ │ │ └── 01-add-index-for-notification-address.sql
│ │ ├── v0.09
│ │ │ └── 01-add-index.sql
│ │ ├── v0.06
│ │ │ └── 01-add-index.sql
│ │ ├── v0.17
│ │ │ └── 01-add-index-for-notification-settings.sql
│ │ ├── v0.21
│ │ │ └── 01-add-index-for-user-party-contact-info.sql
│ │ ├── _draft
│ │ │ └── README.md
│ │ ├── v0.01
│ │ │ └── 01-setup-grants.sql
│ │ ├── v0.22
│ │ │ └── 01-add-index-phonenumber-for-user-party-contact-info.sql
│ │ ├── v0.15
│ │ │ └── 01-create-schema-lease.sql
│ │ ├── v0.08
│ │ │ └── 01-alter-table.sql
│ │ ├── v0.11
│ │ │ └── 01-grant-access.sql
│ │ ├── v0.16
│ │ │ └── 01-alter-table-changelog-sync-metadata.sql
│ │ ├── v0.02
│ │ │ └── 01-setup-grants.sql
│ │ ├── v0.18
│ │ │ └── 01-add-table-portal-settings.sql
│ │ ├── v0.03
│ │ │ └── 01-ef-cleanup.sql
│ │ ├── v0.13
│ │ │ └── 01-seed-fake-data.sql
│ │ ├── v0.14
│ │ │ └── 01-create-schema-lease.sql
│ │ ├── v0.07
│ │ │ └── 01-setup-schema.sql
│ │ └── v0.10
│ │ │ └── 01-setup-schema.sql
│ ├── SblBridge
│ │ ├── Changelog
│ │ │ ├── ChangeLog.cs
│ │ │ ├── DataType.cs
│ │ │ ├── OperationType.cs
│ │ │ └── IChangeLogClient.cs
│ │ ├── User.Favorites
│ │ │ ├── IUserFavoriteClient.cs
│ │ │ └── FavoriteChangedRequest.cs
│ │ ├── User.ProfileSettings
│ │ │ └── IProfileSettingsClient.cs
│ │ ├── User.NotificationSettings
│ │ │ └── IUserNotificationSettingsClient.cs
│ │ ├── SblBridgeSettings.cs
│ │ ├── Unit.Profile
│ │ │ └── SblUserRegisteredContactPoint.cs
│ │ └── InternalServerErrorException.cs
│ ├── Register
│ │ ├── RegisterSettings.cs
│ │ ├── QueryPartiesResponse.cs
│ │ ├── LookupMainUnitResponse.cs
│ │ ├── OrganizationRecord.cs
│ │ ├── PartyIdentifiersResponse.cs
│ │ ├── LookupMainUnitRequest.cs
│ │ └── QueryPartiesRequest.cs
│ ├── Notifications
│ │ └── NotificationsSettings.cs
│ ├── ContactRegister
│ │ ├── IContactRegisterUpdateJob.cs
│ │ ├── ContactRegisterSettings.cs
│ │ ├── IContactRegisterHttpClient.cs
│ │ ├── ContactRegisterChangesLog.cs
│ │ └── ContactAndReservationChangesException.cs
│ ├── OrganizationNotificationAddressRegistry
│ │ ├── IOrganizationNotificationAddressSyncJob.cs
│ │ ├── Models
│ │ │ ├── CanUseModel.cs
│ │ │ ├── UnitContactInfoModel.cs
│ │ │ ├── EntryContent.cs
│ │ │ └── RegistryResponse.cs
│ │ ├── IOrganizationNotificationAddressSyncClient.cs
│ │ ├── OrganizationNotificationAddressSettings.cs
│ │ └── NotificationAddressChangesLog.cs
│ ├── Events
│ │ ├── FavoriteAddedEvent.cs
│ │ ├── NotificationSettingsDeletedEvent.cs
│ │ ├── FavoriteRemovedEvent.cs
│ │ ├── NotificationSettingsAddedEvent.cs
│ │ ├── NotificationSettingsUpdatedEvent.cs
│ │ └── ProfileSettingsUpdatedEvent.cs
│ ├── Entities
│ │ ├── UpdateSource.cs
│ │ ├── RegistrySyncMetadata.cs
│ │ ├── Metadata.cs
│ │ ├── OrganizationDE.cs
│ │ ├── ChangelogSyncMetadata.cs
│ │ └── MailboxSupplier.cs
│ ├── Handlers
│ │ ├── ChangeType.cs
│ │ ├── FavoriteAddedEventHandler.cs
│ │ └── FavoriteRemovedEventHandler.cs
│ ├── Repositories
│ │ ├── A2Sync
│ │ │ ├── IProfileSettingsSyncRepository.cs
│ │ │ ├── IFavoriteSyncRepository.cs
│ │ │ └── IChangelogSyncMetadataRepository.cs
│ │ ├── IMetadataRepository.cs
│ │ ├── IPersonUpdater.cs
│ │ ├── IRegistrySyncMetadataRepository.cs
│ │ ├── IOrganizationNotificationAddressUpdater.cs
│ │ └── EFCoreTransactionalOutbox.cs
│ ├── Authorization
│ │ └── IAuthorizationClient.cs
│ ├── Mappings
│ │ └── PersonContactPreferencesMapper.cs
│ ├── Leases
│ │ └── Lease.cs
│ └── Persistence
│ │ └── PostgreSQLSettings.cs
├── Altinn.Profile.Core
│ ├── CoreSettings.cs
│ ├── User.PartyGroups
│ │ ├── PartyGroupConstants.cs
│ │ ├── Group.cs
│ │ ├── PartyGroupAssociation.cs
│ │ └── IPartyGroupService.cs
│ ├── Unit.ContactPoints
│ │ ├── Party.cs
│ │ ├── UnitContactPointLookup.cs
│ │ └── IUnitContactPointsService.cs
│ ├── OrganizationNotificationAddresses
│ │ ├── AddressType.cs
│ │ └── Organization.cs
│ ├── AddressMaintenanceSettings.cs
│ ├── Integrations
│ │ ├── IUnitProfileRepository.cs
│ │ ├── IPersonService.cs
│ │ ├── IProfileSettingsRepository.cs
│ │ └── INotificationsClient.cs
│ ├── ProfessionalNotificationAddresses
│ │ ├── UserPartyContactInfoResource.cs
│ │ └── UserPartyContactInfoWithIdentity.cs
│ ├── Person.ContactPreferences
│ │ └── PersonContactPreferences.cs
│ ├── Altinn.Profile.Core.csproj
│ ├── User.ContactPoints
│ │ ├── IUserContactPointsService.cs
│ │ ├── UserContactPoints.cs
│ │ └── UserContactPointAvailability.cs
│ └── User.ProfileSettings
│ │ ├── LanguageType.cs
│ │ └── ProfileSettingsPatchModel.cs
├── Altinn.Profile
│ ├── Models
│ │ ├── NotificationSettingsRequest.cs
│ │ ├── NotificationAddressResponse.cs
│ │ ├── UserContactDetailsLookupCriteria.cs
│ │ ├── OrganizationResponse.cs
│ │ ├── NotificationAddressModel.cs
│ │ ├── GroupResponse.cs
│ │ ├── NotificationSettingsResponse.cs
│ │ ├── OrgNotificationAddressRequest.cs
│ │ ├── UserProfileLookup.cs
│ │ ├── DashboardNotificationAddressResponse.cs
│ │ ├── PersonContactDetails.cs
│ │ └── ProfileSettingsPatchRequest.cs
│ ├── Properties
│ │ └── launchSettings.json
│ ├── appsettings.Development.json
│ ├── Authorization
│ │ ├── PartyAccessRequirement.cs
│ │ ├── AuthConstants.cs
│ │ └── ClaimsHelper.cs
│ ├── Configuration
│ │ └── GeneralSettings.cs
│ ├── appsettings.Staging.json
│ ├── appsettings.Production.json
│ ├── Changelog
│ │ ├── LeaseNames.cs
│ │ └── ImportJobSettings.cs
│ ├── Health
│ │ └── HealthCheck.cs
│ └── Controllers
│ │ └── ErrorController.cs
├── Altinn.Profile.Models
│ ├── Altinn.Profile.Models.csproj
│ └── Enums
│ │ └── UserType.cs
├── ServiceDefaults.Leases
│ ├── LeaseInfo.cs
│ ├── Altinn.Authorization.ServiceDefaults.Leases.csproj
│ ├── Microsoft.Extensions.DependencyInjection
│ │ └── LeaseServiceCollectionExtensions.cs
│ ├── LeaseTicket.cs
│ └── LeaseReleaseResult.cs
└── ServiceDefaults.Jobs
│ ├── IJob.cs
│ ├── Job.cs
│ ├── Altinn.Authorization.ServiceDefaults.Jobs.csproj
│ ├── IJobCondition.cs
│ └── JobHostLifecycles.cs
├── .dockerignore
├── container-scan.md
├── stylecop.json
├── dbsetup.sh
├── docker-compose.yml
├── renovate.json
├── .gitattributes
└── Dockerfile
/test/k6/src/tests/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/Bruno/.gitignore:
--------------------------------------------------------------------------------
1 | # Bruno
2 | .env
3 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | /.github/CODEOWNERS @altinn/team-core
2 |
--------------------------------------------------------------------------------
/test/k6/.gitignore:
--------------------------------------------------------------------------------
1 | #Junit reports
2 | **/reports/*.xml
3 | .env
--------------------------------------------------------------------------------
/test/k6/src/reports/readme.md:
--------------------------------------------------------------------------------
1 | Empty file to ensure folder exists
--------------------------------------------------------------------------------
/test/Bruno/Profile/Users/folder.bru:
--------------------------------------------------------------------------------
1 | meta {
2 | name: Users
3 | seq: 4
4 | }
5 |
--------------------------------------------------------------------------------
/test/Bruno/Profile/Favorites/folder.bru:
--------------------------------------------------------------------------------
1 | meta {
2 | name: Favorites
3 | seq: 6
4 | }
5 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Migration/v0.12/01-create-schema-wolverine.sql:
--------------------------------------------------------------------------------
1 | CREATE SCHEMA IF NOT EXISTS wolverine;
--------------------------------------------------------------------------------
/test/Altinn.Profile.Tests/xunit.runner.json:
--------------------------------------------------------------------------------
1 | {
2 | "parallelizeAssembly": false,
3 | "parallelizeTestCollections": false
4 | }
--------------------------------------------------------------------------------
/test/Altinn.Profile.Tests/skd-org.pfx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/altinn/altinn-profile/main/test/Altinn.Profile.Tests/skd-org.pfx
--------------------------------------------------------------------------------
/test/Altinn.Profile.Tests/ttd-org.pfx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/altinn/altinn-profile/main/test/Altinn.Profile.Tests/ttd-org.pfx
--------------------------------------------------------------------------------
/test/Bruno/Profile/Organizations Notification Addresses/folder.bru:
--------------------------------------------------------------------------------
1 | meta {
2 | name: Organizations Notification Addresses
3 | seq: 5
4 | }
5 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Migration/_pre/README.md:
--------------------------------------------------------------------------------
1 | # The `_pre` directory
2 | Pre migration scripts. Executed every time before any version.
3 |
--------------------------------------------------------------------------------
/test/Altinn.Profile.Tests/jwtselfsignedcert.pfx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/altinn/altinn-profile/main/test/Altinn.Profile.Tests/jwtselfsignedcert.pfx
--------------------------------------------------------------------------------
/test/Bruno/Profile/Personal notificationaddress for an org/folder.bru:
--------------------------------------------------------------------------------
1 | meta {
2 | name: Personal notificationaddress for an org
3 | seq: 7
4 | }
5 |
--------------------------------------------------------------------------------
/.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/Altinn.Profile.Integrations/Migration/_erase/README.md:
--------------------------------------------------------------------------------
1 | # The `_erase` directory
2 | Database cleanup scripts. Executed once only when you do `yuniql erase`.
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Migration/_post/README.md:
--------------------------------------------------------------------------------
1 | # The `_post` directory
2 | Post migration scripts. Executed every time and always the last batch to run.
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Migration/v0.00/01-setup-schema.sql:
--------------------------------------------------------------------------------
1 | -- Create schema if it doesn't exist
2 | CREATE SCHEMA IF NOT EXISTS contact_and_reservation;
3 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Migration/v0.05/01-alter-table.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE organization_notification_address.notifications_address ALTER COLUMN domain DROP NOT NULL;
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Migration/v0.04/01-setup-schema.sql:
--------------------------------------------------------------------------------
1 | -- Create schema if it doesn't exist
2 | CREATE SCHEMA IF NOT EXISTS organization_notification_address;
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Migration/_init/README.md:
--------------------------------------------------------------------------------
1 | # The `_init` directory
2 | Initialization scripts. Executed once. This is called the first time you do `yuniql run`.
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Migration/v0.19/01-grant-portal-settings.sql:
--------------------------------------------------------------------------------
1 | GRANT DELETE, INSERT, SELECT, UPDATE ON TABLE user_preferences.profile_settings TO platform_profile;
--------------------------------------------------------------------------------
/test/Bruno/bruno.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1",
3 | "name": "Profile Bruno",
4 | "type": "collection",
5 | "ignore": [
6 | "node_modules",
7 | ".git"
8 | ]
9 | }
--------------------------------------------------------------------------------
/test/Bruno/Profile/folder.bru:
--------------------------------------------------------------------------------
1 | meta {
2 | name: Profile
3 | seq: 11
4 | }
5 |
6 | auth {
7 | mode: bearer
8 | }
9 |
10 | auth:bearer {
11 | token: {{BearerToken}}
12 | }
13 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Migration/v0.20/01-add-index-for-notification-address.sql:
--------------------------------------------------------------------------------
1 | CREATE INDEX ix_full_address ON organization_notification_address.notifications_address (full_address);
2 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Migration/v0.09/01-add-index.sql:
--------------------------------------------------------------------------------
1 | CREATE UNIQUE INDEX IF NOT EXISTS ix_groups_user_id_is_favorite ON user_preferences.groups (user_id, is_favorite) WHERE is_favorite = true;
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Migration/v0.06/01-add-index.sql:
--------------------------------------------------------------------------------
1 | CREATE UNIQUE INDEX IF NOT EXISTS ix_notifications_address_registry_id ON organization_notification_address.notifications_address (registry_id);
2 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Migration/v0.17/01-add-index-for-notification-settings.sql:
--------------------------------------------------------------------------------
1 | CREATE INDEX ix_user_party_contact_info_user_id ON professional_notification_settings.user_party_contact_info (user_id);
--------------------------------------------------------------------------------
/test/Altinn.Profile.Tests/Testdata/NotificationAddressChangesLog/changes_0_faulty.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Kofuvi digital alert address fragments as json",
3 | "updated": "2025-02-24T09:42:58.271142983Z"
4 | }
5 |
--------------------------------------------------------------------------------
/test/Altinn.Profile.Tests/Profile.Integrations/Notifications/OrderContentTests.cs:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/altinn/altinn-profile/main/test/Altinn.Profile.Tests/Profile.Integrations/Notifications/OrderContentTests.cs
--------------------------------------------------------------------------------
/test/Altinn.Profile.Tests/appsettings.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "PostgreSqlSettings": {
3 | "MigrationScriptPath": "../../../../../src/Altinn.Profile.Integrations/Migration",
4 | "EnableDBConnection": true
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Migration/v0.21/01-add-index-for-user-party-contact-info.sql:
--------------------------------------------------------------------------------
1 | CREATE INDEX IF NOT EXISTS ix_user_party_contact_info_email_address ON professional_notification_settings.user_party_contact_info (email_address);
--------------------------------------------------------------------------------
/test/Altinn.Profile.Tests/Testdata/NotificationAddressChangesLog/changes_0.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Kofuvi digital alert address fragments as json",
3 | "updated": "2025-02-24T09:42:58.271142983Z",
4 | "entries": [
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/container-scan.md:
--------------------------------------------------------------------------------
1 | # Status for container scans
2 |
3 | [](https://github.com/Altinn/altinn-profile/actions/workflows/container-scan.yml)
4 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/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/Altinn.Profile.Integrations/Migration/v0.01/01-setup-grants.sql:
--------------------------------------------------------------------------------
1 | -- Grant access to the schema
2 | GRANT ALL ON SCHEMA contact_and_reservation TO platform_profile_admin;
3 | GRANT USAGE ON SCHEMA contact_and_reservation TO platform_profile;
4 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Migration/v0.22/01-add-index-phonenumber-for-user-party-contact-info.sql:
--------------------------------------------------------------------------------
1 | CREATE INDEX IF NOT EXISTS ix_user_party_contact_info_phone_number ON professional_notification_settings.user_party_contact_info (phone_number);
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Migration/v0.04/02-setup-grants.sql:
--------------------------------------------------------------------------------
1 | -- Grant access to the schema
2 | GRANT ALL ON SCHEMA organization_notification_address TO platform_profile_admin;
3 | GRANT USAGE ON SCHEMA organization_notification_address TO platform_profile;
--------------------------------------------------------------------------------
/test/k6/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 |
3 | networks:
4 | k6:
5 |
6 | services:
7 | k6:
8 | image: grafana/k6:1.4.2
9 | networks:
10 | - k6
11 | ports:
12 | - "6565:6565"
13 | volumes:
14 | - ./src:/src
15 |
--------------------------------------------------------------------------------
/test/Bruno/Profile/Run Org sync (local only).bru:
--------------------------------------------------------------------------------
1 | meta {
2 | name: Run Org sync (local only)
3 | type: http
4 | seq: 3
5 | }
6 |
7 | get {
8 | url: http://localhost:5030/profile/api/v1/trigger/syncorgchanges
9 | body: none
10 | auth: none
11 | }
12 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Migration/v0.15/01-create-schema-lease.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE professional_notification_settings.user_party_contact_info ALTER COLUMN last_changed DROP DEFAULT;
2 |
3 | ALTER TABLE lease.changelog_sync_metadata ADD nanosecond integer NOT NULL DEFAULT 0;
--------------------------------------------------------------------------------
/test/Bruno/Profile/Organizations Notification Addresses/Run org sync.bru:
--------------------------------------------------------------------------------
1 | meta {
2 | name: Run org sync
3 | type: http
4 | seq: 6
5 | }
6 |
7 | get {
8 | url: {{ProfileBaseAddress}}/api/v1/trigger/syncorgchanges
9 | body: none
10 | auth: inherit
11 | }
12 |
--------------------------------------------------------------------------------
/test/Bruno/Profile/Run KRR sync (local only).bru:
--------------------------------------------------------------------------------
1 | meta {
2 | name: Run KRR sync (local only)
3 | type: http
4 | seq: 3
5 | }
6 |
7 | get {
8 | url: http://localhost:5030/profile/api/v1/trigger/syncpersonchanges
9 | body: none
10 | auth: none
11 | }
12 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Migration/v0.08/01-alter-table.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE user_preferences.party_group_association DROP COLUMN party_id;
2 |
3 | ALTER TABLE user_preferences.party_group_association ADD party_uuid uuid NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000';
4 |
--------------------------------------------------------------------------------
/test/Altinn.Profile.Tests/Testdata/NotificationAddressChangesLog/changes_4.json:
--------------------------------------------------------------------------------
1 | {
2 | "entries": [
3 | {
4 | "title": "other@test.no",
5 | "id": "27ae0c8bea1f4f02a974c10429c32758",
6 | "updated": "2018-01-15T10:01:14Z",
7 | "isdeleted": true
8 | }
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/test/Bruno/TokenGenerator/folder.bru:
--------------------------------------------------------------------------------
1 | meta {
2 | name: TokenGenerator
3 | }
4 |
5 | vars:pre-request {
6 | TokenGeneratorBaseUrl: {{process.env.TOKEN_URL}}
7 | TokenGeneratorUserName: {{process.env.TOKEN_BASIC_AUTH_USER}}
8 | TokenGeneratorPassword: {{process.env.TOKEN_BASIC_AUTH_PW}}
9 | }
10 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Migration/v0.11/01-grant-access.sql:
--------------------------------------------------------------------------------
1 | GRANT DELETE, INSERT, SELECT, UPDATE ON TABLE professional_notification_settings.user_party_contact_info TO platform_profile;
2 |
3 | GRANT DELETE, INSERT, SELECT, UPDATE ON TABLE professional_notification_settings.user_party_contact_info_resources TO platform_profile;
--------------------------------------------------------------------------------
/test/Bruno/Profile/Users/current user.bru:
--------------------------------------------------------------------------------
1 | meta {
2 | name: current user
3 | type: http
4 | seq: 1
5 | }
6 |
7 | get {
8 | url: {{ProfileBaseAddress}}/api/v1/users/current
9 | body: none
10 | auth: inherit
11 | }
12 |
13 | script:pre-request {
14 | await bru.runRequest("TokenGenerator/portal user")
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/test/Bruno/Profile/Favorites/Get Party Groups.bru:
--------------------------------------------------------------------------------
1 | meta {
2 | name: Get Party Groups
3 | type: http
4 | seq: 4
5 | }
6 |
7 | get {
8 | url: {{ProfileBaseAddress}}/api/v1/users/current/party-groups
9 | body: none
10 | auth: inherit
11 | }
12 |
13 | script:pre-request {
14 | await bru.runRequest("TokenGenerator/portal user")
15 | }
16 |
--------------------------------------------------------------------------------
/test/Bruno/environments/Prod.bru:
--------------------------------------------------------------------------------
1 | vars {
2 | Environment: none
3 | BaseUrl: https://platform.altinn.no
4 | ProfileBaseAddress: {{BaseUrl}}/profile
5 | }
6 | vars:secret [
7 | AuthN_UserId,
8 | AuthN_PartyId,
9 | AuthN_Pid,
10 | AuthN_PartyUuid,
11 | Party_OrgNo,
12 | Party_PartyId,
13 | Party_PartyUuid,
14 | BearerToken
15 | ]
16 |
--------------------------------------------------------------------------------
/test/Bruno/Profile/Favorites/Get favorites.bru:
--------------------------------------------------------------------------------
1 | meta {
2 | name: Get favorites
3 | type: http
4 | seq: 1
5 | }
6 |
7 | get {
8 | url: {{ProfileBaseAddress}}/api/v1/users/current/party-groups/favorites
9 | body: none
10 | auth: inherit
11 | }
12 |
13 | script:pre-request {
14 | await bru.runRequest("TokenGenerator/portal user")
15 | }
16 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Migration/v0.16/01-alter-table-changelog-sync-metadata.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE lease.changelog_sync_metadata DROP COLUMN IF EXISTS last_changed_date_time;
2 |
3 | ALTER TABLE lease.changelog_sync_metadata DROP COLUMN IF EXISTS nanosecond;
4 |
5 | ALTER TABLE lease.changelog_sync_metadata ADD IF NOT EXISTS last_change_ticks bigint NOT NULL DEFAULT 0;
--------------------------------------------------------------------------------
/test/Bruno/Profile/Favorites/Add favorites.bru:
--------------------------------------------------------------------------------
1 | meta {
2 | name: Add favorites
3 | type: http
4 | seq: 2
5 | }
6 |
7 | put {
8 | url: {{ProfileBaseAddress}}/api/v1/users/current/party-groups/favorites/{{Party_PartyUuid}}
9 | body: json
10 | auth: inherit
11 | }
12 |
13 | script:pre-request {
14 | await bru.runRequest("TokenGenerator/portal user")
15 | }
16 |
--------------------------------------------------------------------------------
/test/Bruno/Profile/Favorites/Delete favorites.bru:
--------------------------------------------------------------------------------
1 | meta {
2 | name: Delete favorites
3 | type: http
4 | seq: 3
5 | }
6 |
7 | delete {
8 | url: {{ProfileBaseAddress}}/api/v1/users/current/party-groups/favorites/{{Party_PartyUuid}}
9 | body: none
10 | auth: inherit
11 | }
12 |
13 | script:pre-request {
14 | await bru.runRequest("TokenGenerator/portal user")
15 | }
16 |
--------------------------------------------------------------------------------
/test/Bruno/Profile/Personal notificationaddress for an org/Get all parties.bru:
--------------------------------------------------------------------------------
1 | meta {
2 | name: Get all parties
3 | type: http
4 | seq: 5
5 | }
6 |
7 | get {
8 | url: {{ProfileBaseAddress}}/api/v1/users/current/notificationsettings/parties
9 | body: none
10 | auth: inherit
11 | }
12 |
13 | script:pre-request {
14 | await bru.runRequest("TokenGenerator/end user token")
15 | }
16 |
--------------------------------------------------------------------------------
/test/Bruno/environments/TT02.bru:
--------------------------------------------------------------------------------
1 | vars {
2 | Environment: tt02
3 | BaseUrl: https://platform.{{Environment}}.altinn.no
4 | ProfileBaseAddress: {{BaseUrl}}/profile
5 | AuthN_UserId:
6 | AuthN_PartyId:
7 | AuthN_Pid: 17902349936
8 | AuthN_PartyUuid:
9 | Party_OrgNo: 313605590
10 | Party_PartyId:
11 | Party_PartyUuid:
12 | }
13 | vars:secret [
14 | BearerToken
15 | ]
16 |
--------------------------------------------------------------------------------
/test/Bruno/Profile/Personal notificationaddress for an org/Get party.bru:
--------------------------------------------------------------------------------
1 | meta {
2 | name: Get party
3 | type: http
4 | seq: 1
5 | }
6 |
7 | get {
8 | url: {{ProfileBaseAddress}}/api/v1/users/current/notificationsettings/parties/{{Party_PartyUuid}}
9 | body: none
10 | auth: inherit
11 | }
12 |
13 | script:pre-request {
14 | await bru.runRequest("TokenGenerator/portal user")
15 | }
16 |
--------------------------------------------------------------------------------
/test/Bruno/environments/Local.bru:
--------------------------------------------------------------------------------
1 | vars {
2 | Environment: dev
3 | BaseUrl: http://localhost:5030
4 | ProfileBaseAddress: {{BaseUrl}}/profile
5 | AuthN_UserId: 20002579
6 | AuthN_PartyId:
7 | AuthN_Pid: 07837399275
8 | AuthN_PartyUuid:
9 | Party_OrgNo: 313441571
10 | Party_PartyId:
11 | Party_PartyUuid:
12 | }
13 | vars:secret [
14 | AccessToken,
15 | BearerToken
16 | ]
17 |
--------------------------------------------------------------------------------
/test/Bruno/Profile/Organizations Notification Addresses/Email address.bru:
--------------------------------------------------------------------------------
1 | meta {
2 | name: Email address
3 | type: http
4 | seq: 4
5 | }
6 |
7 | post {
8 | url: {{ProfileBaseAddress}}/api/v1/organizations/{{Party_OrgNo}}/notificationaddresses/mandatory
9 | body: json
10 | auth: inherit
11 | }
12 |
13 | body:json {
14 | {
15 | "email": "wvjckqug@sharklasers.com"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Core/CoreSettings.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Core;
2 |
3 | ///
4 | /// General configuration settings for the core project
5 | ///
6 | public class CoreSettings
7 | {
8 | ///
9 | /// The number of seconds the user profile will be kept in the cache
10 | ///
11 | public int ProfileCacheLifetimeSeconds { get; set; } = 600;
12 | }
13 |
--------------------------------------------------------------------------------
/src/Altinn.Profile/Models/NotificationSettingsRequest.cs:
--------------------------------------------------------------------------------
1 | #nullable enable
2 |
3 | namespace Altinn.Profile.Models
4 | {
5 | ///
6 | /// Request model for the professional notification address for an organization, also called personal notification address.
7 | ///
8 | public class NotificationSettingsRequest : ProfessionalNotificationAddress
9 | {
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/test/Bruno/Profile/Personal notificationaddress for an org/Delete party.bru:
--------------------------------------------------------------------------------
1 | meta {
2 | name: Delete party
3 | type: http
4 | seq: 3
5 | }
6 |
7 | delete {
8 | url: {{ProfileBaseAddress}}/api/v1/users/current/notificationsettings/parties/{{Party_PartyUuid}}
9 | body: none
10 | auth: inherit
11 | }
12 |
13 | script:pre-request {
14 | await bru.runRequest("TokenGenerator/portal user")
15 | }
16 |
--------------------------------------------------------------------------------
/test/Bruno/Profile/Organizations Notification Addresses/Phone number.bru:
--------------------------------------------------------------------------------
1 | meta {
2 | name: Phone number
3 | type: http
4 | seq: 3
5 | }
6 |
7 | post {
8 | url: {{ProfileBaseAddress}}/api/v1/organizations/{{Party_OrgNo}}/notificationaddresses/mandatory
9 | body: json
10 | auth: inherit
11 | }
12 |
13 | body:json {
14 | {
15 | "phone": "91111112",
16 | "countryCode": "+47"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Models/Altinn.Profile.Models.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net9.0
5 | enable
6 | disable
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/test/Bruno/.env.sample:
--------------------------------------------------------------------------------
1 | TOKEN_BASIC_AUTH_USER=
2 | TOKEN_BASIC_AUTH_PW=
3 | TOKEN_URL=
4 |
5 | # Variables for Dashboard tests
6 | # Use your own test email or a known email address that exists in the test environment
7 | # Use a phone number that is valid in the test environment
8 | # Use the country code in URL encoded format
9 | EMAIL_ADDRESS_TEST=test@test.no
10 | PHONE_NUMBER_TEST=99999999
11 | COUNTRY_CODE_TEST=%2B47
--------------------------------------------------------------------------------
/test/Bruno/Profile/Organizations Notification Addresses/Lookup org notificationAddresses.bru:
--------------------------------------------------------------------------------
1 | meta {
2 | name: Lookup org notificationAddresses
3 | type: http
4 | seq: 7
5 | }
6 |
7 | post {
8 | url: {{ProfileBaseAddress}}/api/v1/organizations/notificationaddresses/lookup
9 | body: json
10 | auth: inherit
11 | }
12 |
13 | body:json {
14 | {
15 | "organizationNumbers": [ "{{orgNumber}}" ]
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/test/Bruno/Profile/Organizations Notification Addresses/Get org notificationAddresses.bru:
--------------------------------------------------------------------------------
1 | meta {
2 | name: Get org notificationAddresses
3 | type: http
4 | seq: 1
5 | }
6 |
7 | get {
8 | url: {{ProfileBaseAddress}}/api/v1/organizations/{{Party_OrgNo}}/notificationaddresses/mandatory
9 | body: none
10 | auth: inherit
11 | }
12 |
13 | script:pre-request {
14 | await bru.runRequest("TokenGenerator/portal user")
15 | }
16 |
--------------------------------------------------------------------------------
/test/Bruno/Profile/unit contact points.bru:
--------------------------------------------------------------------------------
1 | meta {
2 | name: unit contact points
3 | type: http
4 | seq: 2
5 | }
6 |
7 | post {
8 | url: {{ProfileBaseAddress}}/profile/api/v1/contact/details/lookup
9 | body: json
10 | auth: inherit
11 | }
12 |
13 | body:json {
14 | {
15 | "organizationNumbers": [
16 | "310604771",
17 | "312508729"
18 | ],
19 | "resourceId": "app_ttd_apps-test"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/SblBridge/Changelog/ChangeLog.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Integrations.SblBridge.Changelog;
2 |
3 | ///
4 | /// Represents a container for a list of profile change log entries.
5 | ///
6 | public class ChangeLog
7 | {
8 | ///
9 | /// Gets or sets the list of found log entries
10 | ///
11 | public List ProfileChangeLogList { get; set; } = [];
12 | }
13 |
--------------------------------------------------------------------------------
/src/Altinn.Profile/Models/NotificationAddressResponse.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Models
2 | {
3 | ///
4 | /// Represents a notification address
5 | ///
6 | public class NotificationAddressResponse : NotificationAddressModel
7 | {
8 | ///
9 | ///
10 | ///
11 | public int NotificationAddressId { get; set; }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/test/k6/src/errorhandler.js:
--------------------------------------------------------------------------------
1 | import { fail } from "k6";
2 |
3 | /**
4 | * Terminates the k6 iteration when the success condition is false and outputs detailed information about the failure.
5 | * @param {String} failReason The reason for stopping the tests
6 | * @param {boolean} success The result of a check
7 | */
8 | export function stopIterationOnFail(failReason, success) {
9 | if (!success) {
10 | fail(failReason);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/test/Bruno/Profile/Organizations Notification Addresses/Delete org notificationAddresses.bru:
--------------------------------------------------------------------------------
1 | meta {
2 | name: Delete org notificationAddresses
3 | type: http
4 | seq: 2
5 | }
6 |
7 | delete {
8 | url: {{ProfileBaseAddress}}/api/v1/organizations/{{Party_OrgNo}}/notificationaddresses/mandatory/120695
9 | body: none
10 | auth: inherit
11 | }
12 |
13 | headers {
14 | Authorization: Bearer {{BearerToken}}
15 | ~PlatformAccessToken: {{AccessToken}}
16 | }
17 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Migration/v0.04/04-setup-table-grants.sql:
--------------------------------------------------------------------------------
1 | -- Grant access to the tables
2 | GRANT DELETE, INSERT, SELECT, UPDATE ON TABLE organization_notification_address.notifications_address TO platform_profile;
3 |
4 | GRANT DELETE, INSERT, SELECT, UPDATE ON TABLE organization_notification_address.registry_sync_metadata TO platform_profile;
5 |
6 | GRANT DELETE, INSERT, SELECT, UPDATE ON TABLE organization_notification_address.organizations TO platform_profile;
--------------------------------------------------------------------------------
/src/Altinn.Profile.Core/User.PartyGroups/PartyGroupConstants.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Core.PartyGroups
2 | {
3 | ///
4 | /// Class containing the default name of the group of favorites
5 | ///
6 | public static class PartyGroupConstants
7 | {
8 | ///
9 | /// Default name of the group of favorites
10 | ///
11 | public const string DefaultFavoritesName = "__favoritter__";
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/test/Bruno/environments/AT22.bru:
--------------------------------------------------------------------------------
1 | vars {
2 | Environment: at22
3 | BaseUrl: https://platform.{{Environment}}.altinn.cloud
4 | ProfileBaseAddress: {{BaseUrl}}/profile
5 | AuthN_UserId: 20885478
6 | AuthN_PartyId: 51118947
7 | AuthN_Pid: 17902349936
8 | AuthN_PartyUuid: b8a3981b-9948-4109-88a9-679d90c4ab37
9 | Party_OrgNo: 313605590
10 | Party_PartyId: 51643854
11 | Party_PartyUuid: 8027a287-e3f1-42ad-bb50-57b4c4584f13
12 | }
13 | vars:secret [
14 | BearerToken
15 | ]
16 |
--------------------------------------------------------------------------------
/test/Bruno/environments/AT23.bru:
--------------------------------------------------------------------------------
1 | vars {
2 | Environment: at23
3 | BaseUrl: https://platform.{{Environment}}.altinn.cloud
4 | ProfileBaseAddress: {{BaseUrl}}/profile
5 | AuthN_UserId: 20462603
6 | AuthN_PartyId: 50891883
7 | AuthN_Pid: 17902349936
8 | AuthN_PartyUuid: 629fa2c0-27cd-40a2-ac6a-99bdf374dba2
9 | Party_OrgNo: 313605590
10 | Party_PartyId: 51519644
11 | Party_PartyUuid: 4a1cfef9-82e1-4be9-96b9-b99f116f8350
12 | }
13 | vars:secret [
14 | BearerToken
15 | ]
16 |
--------------------------------------------------------------------------------
/test/Bruno/environments/AT24.bru:
--------------------------------------------------------------------------------
1 | vars {
2 | Environment: at24
3 | BaseUrl: https://platform.{{Environment}}.altinn.cloud
4 | ProfileBaseAddress: {{BaseUrl}}/profile
5 | AuthN_UserId: 20245418
6 | AuthN_PartyId: 51074789
7 | AuthN_Pid: 17902349936
8 | AuthN_PartyUuid: 3155a6c7-0967-4c31-9cb3-0afe525d5899
9 | Party_OrgNo: 313605590
10 | Party_PartyId: 51605705
11 | Party_PartyUuid: e0347436-a499-49aa-b651-8c67c3c8d17e
12 | }
13 | vars:secret [
14 | BearerToken
15 | ]
16 |
--------------------------------------------------------------------------------
/.github/workflows/assign-issues-to-projects.yml:
--------------------------------------------------------------------------------
1 | name: Auto Assign to Project
2 |
3 | on:
4 | issues:
5 | types:
6 | - opened
7 |
8 | jobs:
9 | add-to-project:
10 | name: Add issue to Team Core project
11 | runs-on: ubuntu-latest
12 | permissions: {}
13 | steps:
14 | - uses: actions/add-to-project@main
15 | with:
16 | project-url: https://github.com/orgs/Altinn/projects/20
17 | github-token: ${{ secrets.ASSIGN_PROJECT_TOKEN }}
18 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Migration/v0.12/02-setup-grants.sql:
--------------------------------------------------------------------------------
1 | GRANT USAGE, CREATE ON SCHEMA wolverine TO platform_profile;
2 | GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA wolverine TO platform_profile;
3 | GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA wolverine TO platform_profile;
4 |
5 | ALTER DEFAULT PRIVILEGES IN SCHEMA wolverine
6 | GRANT ALL PRIVILEGES ON TABLES TO platform_profile;
7 |
8 | ALTER DEFAULT PRIVILEGES IN SCHEMA wolverine
9 | GRANT ALL PRIVILEGES ON SEQUENCES TO platform_profile;
--------------------------------------------------------------------------------
/stylecop.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json",
3 | "settings": {
4 | "documentationRules": {
5 | "companyName": "PlaceholderCompany"
6 | },
7 | "orderingRules": {
8 | "usingDirectivesPlacement": "outsideNamespace",
9 | "systemUsingDirectivesFirst": true,
10 | "blankLinesBetweenUsingGroups": "allow"
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Migration/v0.02/01-setup-grants.sql:
--------------------------------------------------------------------------------
1 | -- Grant access to the mailbox_supplier table
2 | GRANT SELECT,INSERT,UPDATE,DELETE ON TABLE contact_and_reservation.mailbox_supplier TO platform_profile;
3 |
4 | -- Grant access to the metadata table
5 | GRANT SELECT,INSERT,UPDATE,DELETE ON TABLE contact_and_reservation.metadata TO platform_profile;
6 |
7 | -- Grant access to the person table
8 | GRANT SELECT,INSERT,UPDATE,DELETE ON TABLE contact_and_reservation.person TO platform_profile;
9 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Register/RegisterSettings.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Integrations.Register
2 | {
3 | ///
4 | /// Configuration object used to hold settings for all Altinn Register integrations.
5 | ///
6 | public class RegisterSettings
7 | {
8 | ///
9 | /// Gets or sets the url for the register API
10 | ///
11 | public string ApiRegisterEndpoint { get; set; } = string.Empty;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Altinn.Profile/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json.schemastore.org/launchsettings.json",
3 | "profiles": {
4 | "Altinn.Profile": {
5 | "commandName": "Project",
6 | "launchBrowser": true,
7 | "launchUrl": "swagger",
8 | "applicationUrl": "http://localhost:5030",
9 | "environmentVariables": {
10 | "ASPNETCORE_ENVIRONMENT": "Development",
11 | "ASPNETCORE_URLS": "http://localhost:5030"
12 | }
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/Altinn.Profile/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "PostgreSqlSettings": {
3 | "MigrationScriptPath": "../Altinn.Profile.Integrations/Migration"
4 | },
5 | "Logging": {
6 | "LogLevel": {
7 | "Default": "Information",
8 | "System": "Information",
9 | "Microsoft": "Information",
10 | "System.Net.Http.HttpClient": "Warning"
11 | }
12 | },
13 | "ImportJobSettings": {
14 | "FavoritesImportEnabled": false,
15 | "NotificationSettingsImportEnabled": false
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/test/Bruno/Profile/Users/Patch profile settings.bru:
--------------------------------------------------------------------------------
1 | meta {
2 | name: Patch profile settings
3 | type: http
4 | seq: 3
5 | }
6 |
7 | patch {
8 | url: {{ProfileBaseAddress}}/api/v1/users/current/profilesettings
9 | body: json
10 | auth: inherit
11 | }
12 |
13 | body:json {
14 | {
15 | "language": "nn",
16 | "doNotPromptForParty": true,
17 | "preselectedPartyUuid": null
18 | }
19 | }
20 |
21 | script:pre-request {
22 | await bru.runRequest("TokenGenerator/portal user")
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/test/Altinn.Profile.Tests/Testdata/NotificationAddressChangesLog/changes_3.json:
--------------------------------------------------------------------------------
1 | {
2 | "entries": [
3 | {
4 | "title": "other@test.no",
5 | "id": "27ae0c8bea1f4f02a974c10429c32758",
6 | "updated": "2018-01-15T10:01:14Z",
7 | "isdeleted": true
8 | },
9 | {
10 | "title": "4798765432",
11 | "id": "37ab4733648c4d5b825a813c6e1ace70",
12 | "updated": "2025-01-16T09:07:11Z",
13 | "isdeleted": false,
14 | "content": "{\"Kontaktinformasjon\":{}}"
15 | }
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/dbsetup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | export PGPASSWORD=Password
3 |
4 | # alter max connections
5 | psql -h localhost -p 5432 -U platform_profile_admin -d profiledb \
6 | -c "ALTER SYSTEM SET max_connections TO '200';"
7 |
8 | # set up platform_profile role
9 | psql -h localhost -p 5432 -U platform_profile_admin -d profiledb \
10 | -c "DO \$\$
11 | BEGIN CREATE ROLE platform_profile WITH LOGIN PASSWORD 'Password';
12 | EXCEPTION WHEN duplicate_object THEN RAISE NOTICE '%, skipping', SQLERRM USING ERRCODE = SQLSTATE;
13 | END \$\$;"
14 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Notifications/NotificationsSettings.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Integrations.Notifications
2 | {
3 | ///
4 | /// Configuration object used to hold settings for all Altinn Notifications integrations.
5 | ///
6 | public class NotificationsSettings
7 | {
8 | ///
9 | /// Gets or sets the url for the Notifications API
10 | ///
11 | public string ApiNotificationsEndpoint { get; set; } = string.Empty;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/test/Bruno/Profile/Organizations Notification Addresses/Update org notificationAddresses.bru:
--------------------------------------------------------------------------------
1 | meta {
2 | name: Update org notificationAddresses
3 | type: http
4 | seq: 5
5 | }
6 |
7 | put {
8 | url: {{ProfileBaseAddress}}/api/v1/organizations/{{Party_OrgNo}}/notificationaddresses/mandatory/120695
9 | body: json
10 | auth: inherit
11 | }
12 |
13 | body:json {
14 | {
15 | "email": "wvjckqug@sharklasers.com"
16 | }
17 | }
18 |
19 | script:pre-request {
20 | await bru.runRequest("TokenGenerator/portal user")
21 | }
22 |
--------------------------------------------------------------------------------
/src/Altinn.Profile/Authorization/PartyAccessRequirement.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Authorization;
2 |
3 | namespace Altinn.Profile.Authorization
4 | {
5 | ///
6 | /// Requirement for authorization policies used for accessing parties for a user.
7 | /// for details about authorization
8 | /// in asp.net core.
9 | ///
10 | public class PartyAccessRequirement : IAuthorizationRequirement
11 | {
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Migration/v0.18/01-add-table-portal-settings.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE user_preferences.profile_settings (
2 | user_id integer NOT NULL,
3 | language_type character varying(2) NOT NULL,
4 | do_not_prompt_for_party boolean NOT NULL,
5 | preselected_party_uuid uuid,
6 | show_client_units boolean NOT NULL,
7 | should_show_sub_entities boolean NOT NULL,
8 | should_show_deleted_entities boolean NOT NULL,
9 | ignore_unit_profile_date_time timestamp with time zone,
10 | CONSTRAINT user_id_pkey PRIMARY KEY (user_id)
11 | );
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Register/QueryPartiesResponse.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | using Altinn.Profile.Core.Unit.ContactPoints;
4 |
5 | namespace Altinn.Profile.Integrations.Register
6 | {
7 | ///
8 | /// Response model for the lookup resource for parties
9 | ///
10 | public class QueryPartiesResponse
11 | {
12 | ///
13 | /// Data containing the party list.
14 | ///
15 | [JsonPropertyName("data")]
16 | public List Data { get; init; } = [];
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/ContactRegister/IContactRegisterUpdateJob.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Integrations.ContactRegister;
2 |
3 | ///
4 | /// Defines a component that can perform synchronization of contact information for individuals.
5 | ///
6 | public interface IContactRegisterUpdateJob
7 | {
8 | ///
9 | /// Retrieves all changes from the source registry and updates the local contact information.
10 | ///
11 | /// A task that represents the asynchronous operation.
12 | Task SyncContactInformationAsync();
13 | }
14 |
--------------------------------------------------------------------------------
/src/Altinn.Profile/Models/UserContactDetailsLookupCriteria.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace Altinn.Profile.Models;
4 |
5 | ///
6 | /// Represents the lookup criteria to retrieve the contact details for one or more persons.
7 | ///
8 | public class UserContactDetailsLookupCriteria
9 | {
10 | ///
11 | /// A collection of national identity numbers used to retrieve contact points, obtain contact details, or check the availability of contact points.
12 | ///
13 | public List NationalIdentityNumbers { get; set; } = [];
14 | }
15 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Register/LookupMainUnitResponse.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace Altinn.Profile.Integrations.Register
4 | {
5 | ///
6 | /// Response model for the lookup resource for main units
7 | ///
8 | public class LookupMainUnitResponse()
9 | {
10 | ///
11 | /// Data containing the urn of the organization with either orgNumber, partyId or PartyUuid.
12 | ///
13 | [JsonPropertyName("data")]
14 | public List Data { get; init; } = [];
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Register/OrganizationRecord.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace Altinn.Profile.Integrations.Register
4 | {
5 | ///
6 | /// A record for an organization.
7 | ///
8 | public record OrganizationRecord
9 | {
10 | ///
11 | /// Gets the organization identifier of the party, or if the party is not an organization.
12 | ///
13 | [JsonPropertyName("organizationIdentifier")]
14 | public string? OrganizationIdentifier { get; set; }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/test/Altinn.Profile.Tests/Testdata/UserProfile/2516356.json:
--------------------------------------------------------------------------------
1 | {
2 | "UserId": 2516356,
3 | "UserUUID": "4f4e31b5-0e4f-400c-b89d-551fe2385d9f",
4 | "UserName": "sophie",
5 | "PhoneNumber": "90001337",
6 | "Email": "2516356@altinnstudiotestusers.com",
7 | "PartyId": 5780927,
8 | "Party": {
9 | "PartyId": 5780927,
10 | "PartyUUID": "4f4e31b5-0e4f-400c-b89d-551fe2385d9f",
11 | "Name": "Sophie Salt",
12 | "SSN": "01017512345",
13 | "Person": {
14 | "FirstName": "Sophie"
15 | }
16 | },
17 | "UserType": 1,
18 | "ProfileSettingPreference": {
19 | "Language": "nb"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/test/Bruno/Profile/Personal notificationaddress for an org/PUT party.bru:
--------------------------------------------------------------------------------
1 | meta {
2 | name: Put party
3 | type: http
4 | seq: 2
5 | }
6 |
7 | put {
8 | url: {{ProfileBaseAddress}}/api/v1/users/current/notificationsettings/parties/{{Party_PartyUuid}}
9 | body: json
10 | auth: inherit
11 | }
12 |
13 | body:json {
14 | {
15 | "emailAddress": "test@test.no",
16 | "phoneNumber": "+1234567890",
17 | "resourceIncludeList": [
18 | "urn:altinn:resource:app_ttd_storage-end-to-end"
19 | ]
20 | }
21 | }
22 |
23 | script:pre-request {
24 | await bru.runRequest("TokenGenerator/portal user")
25 | }
26 |
--------------------------------------------------------------------------------
/test/Altinn.Profile.Tests/IntegrationTests/Mocks/Role.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 |
3 | namespace Altinn.Profile.Tests.IntegrationTests.Mocks
4 | {
5 | ///
6 | /// Entity representing a Role
7 | ///
8 | [DebuggerDisplay("{Value}", Name = "[{Type}]")]
9 | public record Role
10 | {
11 | ///
12 | /// Gets or sets the role type
13 | ///
14 | public string Type { get; set; }
15 |
16 | ///
17 | /// Gets or sets the role
18 | ///
19 | public string Value { get; set; }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/test/Bruno/Profile/Users/Update profile settings.bru:
--------------------------------------------------------------------------------
1 | meta {
2 | name: Update profile settings
3 | type: http
4 | seq: 2
5 | }
6 |
7 | put {
8 | url: {{ProfileBaseAddress}}/api/v1/users/current/profilesettings
9 | body: json
10 | auth: inherit
11 | }
12 |
13 | body:json {
14 | {
15 | "language": "en",
16 | "doNotPromptForParty": false,
17 | "preselectedPartyUuid": null,
18 | "showClientUnits": false,
19 | "shouldShowSubEntities": false,
20 | "shouldShowDeletedEntities": false
21 | }
22 | }
23 |
24 | script:pre-request {
25 | await bru.runRequest("TokenGenerator/portal user")
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/test/Altinn.Profile.Tests/Testdata/NotificationAddressChangesLog/changes_5.json:
--------------------------------------------------------------------------------
1 | {
2 | "entries": [
3 | {
4 | "title": "4798765432",
5 | "id": "27ae0c8bea1f4f02a974c10429c32758",
6 | "updated": "2025-01-16T09:07:11Z",
7 | "isdeleted": false,
8 | "content": "{\"Kontaktinformasjon\":{\"digitalVarslingsinformasjon\":{\"mobiltelefon\":{\"navn\":\"4798765432\",\"internasjonaltPrefiks\":\"47\",\"nasjonaltNummer\":\"98765432\"}},\"identifikator\":\"37ab4733648c4d5b825a813c6e1ace70\",\"kontaktinformasjonForEnhet\":{\"enhetsidentifikator\":{\"verdi\":\"987654321\",\"type\":\"ORGANISASJONSNUMMER\"}}}}"
9 | }
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/OrganizationNotificationAddressRegistry/IOrganizationNotificationAddressSyncJob.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Integrations.OrganizationNotificationAddressRegistry;
2 |
3 | ///
4 | /// Defines a component that can perform synchronization of contact information for organizations.
5 | ///
6 | public interface IOrganizationNotificationAddressSyncJob
7 | {
8 | ///
9 | /// Retrieves all changes from the source registry and updates the local contact information.
10 | ///
11 | /// A task that represents the asynchronous operation.
12 | Task SyncNotificationAddressesAsync();
13 | }
14 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Core/Unit.ContactPoints/Party.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Core.Unit.ContactPoints
2 | {
3 | ///
4 | /// The party object
5 | ///
6 | public class Party
7 | {
8 | ///
9 | /// The party id.
10 | ///
11 | public int PartyId { get; init; }
12 |
13 | ///
14 | /// The party uuid.
15 | ///
16 | public Guid PartyUuid { get; init; }
17 |
18 | ///
19 | /// The organization identifier (org number).
20 | ///
21 | public required string OrganizationIdentifier { get; init; }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/SblBridge/User.Favorites/IUserFavoriteClient.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Integrations.SblBridge.User.Favorites
2 | {
3 | ///
4 | /// Interface for managing user favorites.
5 | ///
6 | public interface IUserFavoriteClient
7 | {
8 | ///
9 | /// Updates the user's favorites based on the provided request.
10 | ///
11 | /// The request containing details of the favorite change.
12 | /// A task representing the asynchronous operation.
13 | Task UpdateFavorites(FavoriteChangedRequest request);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Core/OrganizationNotificationAddresses/AddressType.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Core.OrganizationNotificationAddresses
2 | {
3 | ///
4 | /// The type of digital notification address
5 | ///
6 | public enum AddressType
7 | {
8 | ///
9 | /// Specify that address is an SMS address
10 | ///
11 | None = 0,
12 |
13 | ///
14 | /// Specify that address is an SMS address
15 | ///
16 | SMS = 1,
17 |
18 | ///
19 | /// Specify that address is an EMAIL address
20 | ///
21 | Email = 2
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Altinn.Profile/Models/OrganizationResponse.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace Altinn.Profile.Models
4 | {
5 | ///
6 | /// Represents a on organization with notification addresses
7 | ///
8 | public class OrganizationResponse
9 | {
10 | ///
11 | /// The organizations organization number
12 | ///
13 | public string OrganizationNumber { get; set; }
14 |
15 | ///
16 | /// Represents a list of mandatory notification address
17 | ///
18 | public List NotificationAddresses { get; set; }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Altinn.Profile/Configuration/GeneralSettings.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Configuration;
2 |
3 | ///
4 | /// General configuration settings
5 | ///
6 | public class GeneralSettings
7 | {
8 | ///
9 | /// Open Id Connect Well known endpoint
10 | ///
11 | public string OpenIdWellKnownEndpoint { get; set; }
12 |
13 | ///
14 | /// Name of the cookie for where JWT is stored
15 | ///
16 | public string JwtCookieName { get; set; }
17 |
18 | ///
19 | /// Feature flag to lookup unit contact points at SBL bridge
20 | ///
21 | public bool LookupUnitContactPointsAtSblBridge { get; set; }
22 | }
23 |
--------------------------------------------------------------------------------
/src/ServiceDefaults.Leases/LeaseInfo.cs:
--------------------------------------------------------------------------------
1 | #nullable enable
2 |
3 | namespace Altinn.Authorization.ServiceDefaults.Leases;
4 |
5 | ///
6 | /// Information about a lease.
7 | ///
8 | public record struct LeaseInfo
9 | {
10 | ///
11 | /// Gets the lease id.
12 | ///
13 | public required string LeaseId { get; init; }
14 |
15 | ///
16 | /// Gets when the lease was last acquired at.
17 | ///
18 | public required DateTimeOffset? LastAcquiredAt { get; init; }
19 |
20 | ///
21 | /// Gets when the lease was last released at.
22 | ///
23 | public required DateTimeOffset? LastReleasedAt { get; init; }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Register/PartyIdentifiersResponse.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Integrations.Register
2 | {
3 | ///
4 | /// A set of identifiers for a party.
5 | ///
6 | public record PartyIdentifiersResponse
7 | {
8 | ///
9 | /// The party id.
10 | ///
11 | public int PartyId { get; init; }
12 |
13 | ///
14 | /// The party uuid.
15 | ///
16 | public Guid PartyUuid { get; init; }
17 |
18 | ///
19 | /// OrgNumber for the party if it is an organization.
20 | ///
21 | public string? OrgNumber { get; init; }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/SblBridge/User.ProfileSettings/IProfileSettingsClient.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Integrations.SblBridge.User.ProfileSettings
2 | {
3 | ///
4 | /// Interface for managing the user's portal settings.
5 | ///
6 | public interface IProfileSettingsClient
7 | {
8 | ///
9 | /// Updates the user's portal settings in A2 based on the provided request.
10 | ///
11 | /// The request containing details of the change.
12 | /// A task representing the asynchronous operation.
13 | Task UpdatePortalSettings(ProfileSettingsChangedRequest request);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/ServiceDefaults.Leases/Altinn.Authorization.ServiceDefaults.Leases.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net9.0
5 | enable
6 | enable
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/SblBridge/Changelog/DataType.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Integrations.SblBridge.Changelog;
2 |
3 | ///
4 | /// Represents the different type of data that can be changed.
5 | ///
6 | public enum DataType
7 | {
8 | ///
9 | /// The change happened to "party as user favorite".
10 | ///
11 | Favorites,
12 |
13 | ///
14 | /// The change happened to professional notification settings. Must use the same name as in A2.
15 | ///
16 | ReporteeNotificationSettings,
17 |
18 | ///
19 | /// The change happened to portal settings. Must use the same name as in A2.
20 | ///
21 | PortalSettingPreferences,
22 | }
23 |
--------------------------------------------------------------------------------
/test/Altinn.Profile.Tests/Profile.Integrations/Register/LookupMainUnitRequestTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Altinn.Profile.Integrations.Register;
3 | using Xunit;
4 |
5 | namespace Altinn.Profile.Tests.Profile.Integrations.Register
6 | {
7 | public class LookupMainUnitRequestTests
8 | {
9 | [Fact]
10 | public void Create_ValidOrgNumber_SetsDataCorrectly()
11 | {
12 | // Arrange
13 | var orgNumber = "123456789";
14 |
15 | // Act
16 | var result = new LookupMainUnitRequest(orgNumber);
17 |
18 | // Assert
19 | Assert.NotNull(result);
20 | Assert.Equal($"urn:altinn:organization:identifier-no:{orgNumber}", result.Data);
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/SblBridge/User.NotificationSettings/IUserNotificationSettingsClient.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Integrations.SblBridge.User.NotificationSettings
2 | {
3 | ///
4 | /// Interface for managing user notificationSettings.
5 | ///
6 | public interface IUserNotificationSettingsClient
7 | {
8 | ///
9 | /// Updates the user's notificationSettings based on the provided request.
10 | ///
11 | /// The request containing details of the change.
12 | /// A task representing the asynchronous operation.
13 | Task UpdateNotificationSettings(NotificationSettingsChangedRequest request);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Events/FavoriteAddedEvent.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Integrations.Events;
2 |
3 | ///
4 | /// Represents an event that notifies of an addition of a party to a user's favorites.
5 | ///
6 | /// The unique identifier of the user whose favorites has changed. Must be a positive integer.
7 | /// The unique identifier of the party that was added to the user's favorites.
8 | /// The timestamp for when the favorite-addition was registered
9 | /// Can be removed when Altinn2 is decommissioned
10 | public record FavoriteAddedEvent(
11 | int UserId,
12 | Guid PartyUuid,
13 | DateTime RegistrationTimestamp);
14 |
--------------------------------------------------------------------------------
/test/Bruno/TokenGenerator/platform access token.bru:
--------------------------------------------------------------------------------
1 | meta {
2 | name: platform access token
3 | type: http
4 | seq: 10
5 | }
6 |
7 | get {
8 | url: {{TokenGeneratorBaseUrl}}/api/GetPlatformAccessToken?env={{Environment}}&app=notifications
9 | body: none
10 | auth: basic
11 | }
12 |
13 | params:query {
14 | env: {{Environment}}
15 | app: notifications
16 | }
17 |
18 | auth:basic {
19 | username: {{TokenGeneratorUserName}}
20 | password: {{TokenGeneratorPassword}}
21 | }
22 |
23 | script:post-response {
24 | let data = res.getBody();
25 |
26 | // Check if the response contains a valid token
27 | if (!data || data.trim() === '') {
28 | console.error('Failed to retrieve a valid platform access token');
29 | return;
30 | }
31 |
32 | bru.setEnvVar("AccessToken", data);
33 | }
34 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/ContactRegister/ContactRegisterSettings.cs:
--------------------------------------------------------------------------------
1 | #nullable enable
2 |
3 | using Altinn.ApiClients.Maskinporten.Config;
4 |
5 | namespace Altinn.Profile.Integrations.ContactRegister;
6 |
7 | ///
8 | /// Represents the settings for managing contact details and reservation information for individuals.
9 | ///
10 | public class ContactRegisterSettings
11 | {
12 | ///
13 | /// Gets the endpoint URL used to retrieve updates in the contact information for one or more individuals.
14 | ///
15 | public string? ChangesLogEndpoint { get; init; }
16 |
17 | ///
18 | /// Gets the settings required for Maskinporten authentication.
19 | ///
20 | public MaskinportenSettings? MaskinportenSettings { get; init; }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Entities/UpdateSource.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Integrations.Entities
2 | {
3 | ///
4 | /// UpdateOrigin is used to specify the the changes of the UnitNotificationEndPoint
5 | ///
6 | public enum UpdateSource
7 | {
8 | ///
9 | /// UpdateOrigin is None
10 | ///
11 | None = 0,
12 |
13 | ///
14 | /// UpdateOrigin is Altinn
15 | ///
16 | Altinn = 1,
17 |
18 | ///
19 | /// UpdateOrigin Type is KoFuVi
20 | ///
21 | KoFuVi = 2,
22 |
23 | ///
24 | /// Data is syntetically generated for testing purposes.
25 | ///
26 | Synthetic = 3,
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Handlers/ChangeType.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Integrations.Handlers
2 | {
3 | ///
4 | /// Provides constants for different types of changes.
5 | ///
6 | /// Can be removed when Altinn2 is decommissioned
7 | public static class ChangeType
8 | {
9 | ///
10 | /// Represents an insert change type.
11 | ///
12 | public const string Insert = "insert";
13 |
14 | ///
15 | /// Represents an update change type.
16 | ///
17 | public const string Update = "update";
18 |
19 | ///
20 | /// Represents a delete change type.
21 | ///
22 | public const string Delete = "delete";
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/test/Altinn.Profile.Tests/Testdata/TestDataLoader.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.Text.Json;
3 | using System.Threading.Tasks;
4 |
5 | namespace Altinn.Profile.Tests.Testdata;
6 |
7 | public static class TestDataLoader
8 | {
9 | private static readonly JsonSerializerOptions _options = new()
10 | {
11 | PropertyNameCaseInsensitive = true
12 | };
13 |
14 | public static async Task Load(string id)
15 | {
16 | string path = $"../../../Testdata/{typeof(T).Name}/{id}.json";
17 |
18 | if (!File.Exists(path))
19 | {
20 | return default(T);
21 | }
22 |
23 | string fileContent = await File.ReadAllTextAsync(path);
24 |
25 | T data = JsonSerializer.Deserialize(fileContent, _options);
26 | return data;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.4'
2 |
3 | networks:
4 | altinnplatform_network:
5 | external: false
6 |
7 | services:
8 | altinn_platform_profile:
9 | container_name: altinn-platform-profile
10 | image: altinnplatformprofile:latest
11 | restart: always
12 | networks:
13 | - altinnplatform_network
14 | environment:
15 | - ASPNETCORE_ENVIRONMENT=Docker
16 | - ASPNETCORE_URLS=http://+:5030
17 | - PostgreSqlSettings__AdminConnectionString=Host=host.docker.internal;Port=5432;Username=platform_profile_admin;Password={0};Database=profiledb
18 | - PostgreSqlSettings__ConnectionString=Host=host.docker.internal;Port=5432;Username=platform_profile;Password={0};Database=profiledb
19 | ports:
20 | - "5030:5030"
21 | build:
22 | context: .
23 | dockerfile: Dockerfile
24 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Core/AddressMaintenanceSettings.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Core
2 | {
3 | ///
4 | /// Configuration settings for Altinn Profile
5 | ///
6 | public class AddressMaintenanceSettings
7 | {
8 | ///
9 | /// The number of days a user will not be prompted to confirm their unit profile after choosing to ignore the confirmation.
10 | ///
11 | public int IgnoreUnitProfileConfirmationDays { get; set; }
12 |
13 | ///
14 | /// The number of days before personal entity consent expires that a reminder should be sent to the user.
15 | /// Called PersonalEntityConsentValidationReminderDays in Altinn2
16 | ///
17 | public int ValidationReminderDays { get; set; }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Repositories/A2Sync/IProfileSettingsSyncRepository.cs:
--------------------------------------------------------------------------------
1 | using Altinn.Profile.Core.User.ProfileSettings;
2 |
3 | namespace Altinn.Profile.Integrations.Repositories.A2Sync
4 | {
5 | ///
6 | /// Defines methods for synchronizing profile settings with Altinn2
7 | ///
8 | /// Can be removed when Altinn2 is decommissioned
9 | public interface IProfileSettingsSyncRepository
10 | {
11 | ///
12 | /// Updates the profile settings for a user.
13 | ///
14 | /// The profile settings to update.
15 | /// A task representing the asynchronous operation.
16 | Task UpdateProfileSettings(ProfileSettings profileSettings);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Core/Unit.ContactPoints/UnitContactPointLookup.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations;
2 |
3 | namespace Altinn.Profile.Core.Unit.ContactPoints
4 | {
5 | ///
6 | /// A class describing the query model for contact points for units
7 | ///
8 | public class UnitContactPointLookup
9 | {
10 | ///
11 | /// Gets or sets the list of organization numbers to lookup contact points for
12 | ///
13 | [Required]
14 | [MinLength(1)]
15 | public List OrganizationNumbers { get; set; } = [];
16 |
17 | ///
18 | /// Gets or sets the resource id to filter the contact points by
19 | ///
20 | [Required]
21 | public string ResourceId { get; set; } = string.Empty;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Events/NotificationSettingsDeletedEvent.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Integrations.Events
2 | {
3 | ///
4 | /// Event representing an deletion of a professional notification address.
5 | ///
6 | /// The user ID that deleted their address.
7 | /// The unique identifier of the party.
8 | /// The timestamp when the event was created.
9 | /// The timestamp when the event occurred.
10 | /// Can be removed when Altinn2 is decommissioned
11 | public record NotificationSettingsDeletedEvent(
12 | int UserId,
13 | Guid PartyUuid,
14 | DateTime CreationTimestamp,
15 | DateTime EventTimestamp);
16 | }
17 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Authorization/IAuthorizationClient.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Integrations.Authorization
2 | {
3 | ///
4 | /// Interface for authorization functionality.
5 | ///
6 | public interface IAuthorizationClient
7 | {
8 | ///
9 | /// Verifies that the selected party is contained in the user's party list.
10 | ///
11 | /// The user id.
12 | /// The party id.
13 | /// The cancellation token.
14 | /// Boolean indicating whether or not the user can represent the selected party.
15 | Task ValidateSelectedParty(int userId, int partyId, CancellationToken cancellationToken = default);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Events/FavoriteRemovedEvent.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Integrations.Events;
2 |
3 | ///
4 | /// Represents an event that notifies of a removal of a party from a user's favorites.
5 | ///
6 | /// The unique identifier of the user whose favorites has changed. Must be a positive integer.
7 | /// The unique identifier of the party that was removed from the user's favorites.
8 | /// Creation timestamp for the favorite
9 | /// Deletion timestamp for the favorite
10 | /// Can be removed when Altinn2 is decommissioned
11 | public record FavoriteRemovedEvent(
12 | int UserId,
13 | Guid PartyUuid,
14 | DateTime CreationTimestamp,
15 | DateTime EventTimestamp);
16 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/ContactRegister/IContactRegisterHttpClient.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Integrations.ContactRegister;
2 |
3 | ///
4 | /// An HTTP client to interact with the contact register.
5 | ///
6 | public interface IContactRegisterHttpClient
7 | {
8 | ///
9 | /// Retrieves the changes in persons' contact details from the specified endpoint.
10 | ///
11 | /// The URL of the endpoint to retrieve contact details changes from.
12 | /// The starting identifier for retrieving contact details changes.
13 | ///
14 | /// A task that represents the asynchronous operation.
15 | ///
16 | Task GetContactDetailsChangesAsync(string endpointUrl, long startingIdentifier);
17 | }
18 |
--------------------------------------------------------------------------------
/src/Altinn.Profile/appsettings.Staging.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "System": "Information",
6 | "Microsoft": "Information",
7 | "System.Net.Http.HttpClient": "Warning"
8 | },
9 | "ApplicationInsights": {
10 | "LogLevel": {
11 | "Default": "Warning",
12 | "System": "Warning",
13 | "Microsoft": "Warning",
14 | "Microsoft.EntityFrameworkCore": "Information", // keep DB info logs
15 | "System.Data": "Information"
16 | }
17 | },
18 | "OpenTelemetry": {
19 | "LogLevel": {
20 | "Default": "Warning",
21 | "System": "Warning",
22 | "Microsoft": "Warning",
23 | "Microsoft.EntityFrameworkCore": "Information", // keep DB info logs
24 | "System.Data": "Information"
25 | }
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/SblBridge/Changelog/OperationType.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace Altinn.Profile.Integrations.SblBridge.Changelog
8 | {
9 | ///
10 | /// Represents the different type of changes that can occur. Typically insert, update and delete operation.
11 | ///
12 | public enum OperationType
13 | {
14 | ///
15 | /// A profile data element was created.
16 | ///
17 | Insert,
18 |
19 | ///
20 | /// A profile data element was updated.
21 | ///
22 | Update,
23 |
24 | ///
25 | /// A profile data element was deleted.
26 | ///
27 | Delete
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Altinn.Profile/appsettings.Production.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "System": "Information",
6 | "Microsoft": "Information",
7 | "System.Net.Http.HttpClient": "Warning"
8 | },
9 | "ApplicationInsights": {
10 | "LogLevel": {
11 | "Default": "Warning",
12 | "System": "Warning",
13 | "Microsoft": "Warning",
14 | "Microsoft.EntityFrameworkCore": "Information", // keep DB info logs
15 | "System.Data": "Information"
16 | }
17 | },
18 | "OpenTelemetry": {
19 | "LogLevel": {
20 | "Default": "Warning",
21 | "System": "Warning",
22 | "Microsoft": "Warning",
23 | "Microsoft.EntityFrameworkCore": "Information", // keep DB info logs
24 | "System.Data": "Information"
25 | }
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Entities/RegistrySyncMetadata.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations;
2 | using System.ComponentModel.DataAnnotations.Schema;
3 |
4 | namespace Altinn.Profile.Integrations.Entities
5 | {
6 | ///
7 | /// Table of metadata for last brreg kof sync batch
8 | ///
9 | [Table("registry_sync_metadata", Schema = "organization_notification_address")]
10 | public class RegistrySyncMetadata
11 | {
12 | ///
13 | /// An identifier for this table
14 | ///
15 | [StringLength(32)]
16 | [Required]
17 | public string? LastChangedId { get; set; }
18 |
19 | ///
20 | /// The time and date if last sync with changes
21 | ///
22 | [Required]
23 | public DateTime LastChangedDateTime { get; set; }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Register/LookupMainUnitRequest.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace Altinn.Profile.Integrations.Register
4 | {
5 | ///
6 | /// Request model for the lookup resource for main units
7 | ///
8 | ///
9 | /// Initializes a new instance of the class.
10 | ///
11 | /// Organization Number of the organization to lookup parent units for
12 | public class LookupMainUnitRequest(string orgNumber)
13 | {
14 | ///
15 | /// Data containing the urn of the organization with either orgNumber, partyId or PartyUuid.
16 | ///
17 | [JsonPropertyName("data")]
18 | public string Data { get; init; } = $"urn:altinn:organization:identifier-no:{orgNumber}";
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/ServiceDefaults.Leases/Microsoft.Extensions.DependencyInjection/LeaseServiceCollectionExtensions.cs:
--------------------------------------------------------------------------------
1 | using Altinn.Authorization.ServiceDefaults.Leases;
2 | using Microsoft.Extensions.DependencyInjection.Extensions;
3 |
4 | namespace Microsoft.Extensions.DependencyInjection;
5 |
6 | ///
7 | /// Extension methods for .
8 | ///
9 | public static class LeaseServiceCollectionExtensions
10 | {
11 | ///
12 | /// Adds a to the service collection.
13 | ///
14 | /// The .
15 | /// .
16 | public static IServiceCollection AddLeaseManager(this IServiceCollection services)
17 | {
18 | services.TryAddTransient();
19 |
20 | return services;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Register/QueryPartiesRequest.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace Altinn.Profile.Integrations.Register
4 | {
5 | ///
6 | /// Request model for the query resource for parties
7 | ///
8 | ///
9 | /// Initializes a new instance of the class.
10 | ///
11 | /// Organization Numbers to query parties for
12 | public class QueryPartiesRequest(string[] orgNumbers)
13 | {
14 | ///
15 | /// Data containing the urn of the organization with the orgNumber.
16 | ///
17 | [JsonPropertyName("data")]
18 | public string[] Data { get; init; } = [.. orgNumbers.Where(o => !string.IsNullOrWhiteSpace(o)).Select(o => $"urn:altinn:organization:identifier-no:{o}")];
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Repositories/A2Sync/IFavoriteSyncRepository.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Integrations.Repositories.A2Sync
2 | {
3 | ///
4 | /// Interface to delete or insert favorites to the DB without notifying A2
5 | ///
6 | /// Can be removed when Altinn2 is decommissioned
7 | public interface IFavoriteSyncRepository
8 | {
9 | ///
10 | /// Adds a party to the favorites group for a given user
11 | ///
12 | Task AddPartyToFavorites(int userId, Guid partyUuid, DateTime created, CancellationToken cancellationToken);
13 |
14 | ///
15 | /// Removes a party from the favorites group for a given user
16 | ///
17 | Task DeleteFromFavorites(int userId, Guid partyUuid, DateTime deleted, CancellationToken cancellationToken);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Altinn.Profile/Models/NotificationAddressModel.cs:
--------------------------------------------------------------------------------
1 | using Altinn.Profile.Validators;
2 |
3 | namespace Altinn.Profile.Models
4 | {
5 | ///
6 | /// Represents a notification address
7 | ///
8 | public abstract class NotificationAddressModel
9 | {
10 | ///
11 | /// Country code for phone number
12 | ///
13 | [CustomRegexForNotificationAddresses("CountryCode")]
14 | public string CountryCode { get; set; }
15 |
16 | ///
17 | /// Email address
18 | ///
19 | [CustomRegexForNotificationAddresses("Email")]
20 | public string Email { get; set; }
21 |
22 | ///
23 | /// Phone number
24 | ///
25 | [CustomRegexForNotificationAddresses("Phone")]
26 | public string Phone { get; set; }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/test/Bruno/TokenGenerator/Application Owner Profile scope.bru:
--------------------------------------------------------------------------------
1 | meta {
2 | name: Application Owner Profile scope
3 | type: http
4 | seq: 3
5 | }
6 |
7 | get {
8 | url: {{TokenGeneratorBaseUrl}}/api/GetEnterpriseToken?env={{Environment}}&scopes=altinn:profile.support.admin&orgNo=991825827&org=digdir
9 | body: none
10 | auth: basic
11 | }
12 |
13 | params:query {
14 | env: {{Environment}}
15 | scopes: altinn:profile.support.admin
16 | orgNo: 991825827
17 | org: digdir
18 | }
19 |
20 | auth:basic {
21 | username: {{TokenGeneratorUserName}}
22 | password: {{TokenGeneratorPassword}}
23 | }
24 |
25 | script:post-response {
26 | let data = res.getBody();
27 |
28 | // Check if the response contains a valid token
29 | if (!data || data.trim() === '') {
30 | console.error('Failed to retrieve a valid bearer token');
31 | return;
32 | }
33 |
34 | bru.setEnvVar("BearerToken", data);
35 | }
36 |
--------------------------------------------------------------------------------
/src/Altinn.Profile/Models/GroupResponse.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Altinn.Profile.Models
4 | {
5 | ///
6 | /// GroupResponse is used to represent a group of parties
7 | ///
8 | public class GroupResponse
9 | {
10 | ///
11 | /// The unique identifier of the group
12 | ///
13 | public int GroupId { get; set; }
14 |
15 | ///
16 | /// The name of the group
17 | ///
18 | public string Name { get; set; }
19 |
20 | ///
21 | /// A flag indicating whether the group is a group of favorite parties
22 | ///
23 | public bool IsFavorite { get; set; }
24 |
25 | ///
26 | /// Array of party IDs that belong to this group
27 | ///
28 | public Guid[] Parties { get; set; }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/test/Altinn.Profile.Tests/IntegrationTests/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 |
8 | using Altinn.Common.AccessToken.Services;
9 |
10 | using Microsoft.IdentityModel.Tokens;
11 |
12 | namespace Altinn.Profile.Tests.IntegrationTests.Mocks;
13 |
14 | public class PublicSigningKeyProviderMock : IPublicSigningKeyProvider
15 | {
16 | public Task> GetSigningKeys(string issuer)
17 | {
18 | List signingKeys = [];
19 |
20 | X509Certificate2 cert = X509CertificateLoader.LoadCertificateFromFile($"{issuer}-org.pem");
21 | SecurityKey key = new X509SecurityKey(cert);
22 |
23 | signingKeys.Add(key);
24 |
25 | return Task.FromResult(signingKeys.AsEnumerable());
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/test/k6/src/api/favorites.js:
--------------------------------------------------------------------------------
1 | import http from "k6/http";
2 | import * as config from "../config.js";
3 | import * as apiHelpers from "../apiHelpers.js";
4 |
5 | export function getFavorites(token) {
6 | const endpoint = config.profileUrl.favorites;
7 |
8 | const params = apiHelpers.buildHeaderWithBearer(token);
9 |
10 | return http.get(endpoint, params);
11 | }
12 |
13 | export function addFavorites(token, partyUuid) {
14 | const endpoint = config.profileUrl.modifyFavorites(partyUuid);
15 |
16 | const params = apiHelpers.buildHeaderWithBearerAndContentType(token);
17 |
18 | return http.put(endpoint, null, params);
19 | }
20 |
21 | export function removeFavorites(token, partyUuid) {
22 | const endpoint = config.profileUrl.modifyFavorites(partyUuid);
23 |
24 | const params = apiHelpers.buildHeaderWithBearerAndContentType(token);
25 |
26 | return http.del(endpoint, null, params);
27 | }
--------------------------------------------------------------------------------
/test/Bruno/Profile/Dashboard/folder.bru:
--------------------------------------------------------------------------------
1 | meta {
2 | name: Dashboard
3 | }
4 |
5 | vars:pre-request {
6 | emailAddress: {{process.env.EMAIL_ADDRESS_TEST}}
7 | phoneNumber: {{process.env.PHONE_NUMBER_TEST}}
8 | countryCode: {{process.env.COUNTRY_CODE_TEST}}
9 | }
10 |
11 | docs {
12 | # Dashboard API Tests
13 |
14 | This folder contains tests for the Support Dashboard endpoints.
15 |
16 | ## Environment Variables Required
17 |
18 | Set the following environment variables in your `.env` file:
19 |
20 | ```
21 | EMAIL_ADDRESS_TEST=your-test-email@example.com
22 | PHONE_NUMBER_TEST=99999999
23 | COUNTRY_CODE_TEST=%2B47
24 | ```
25 |
26 | - **EMAIL_ADDRESS_TEST**: Email address for notification lookup tests
27 | - **PHONE_NUMBER_TEST**: Phone number used in phone number search tests
28 | - **COUNTRY_CODE_TEST**: Country code for phone number testing (URL-encoded, e.g., `%2B47` for +47)
29 | }
30 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/SblBridge/Changelog/IChangeLogClient.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Integrations.SblBridge.Changelog;
2 |
3 | ///
4 | /// Interface for fetching change log.
5 | ///
6 | public interface IChangeLogClient
7 | {
8 | ///
9 | /// Fetches the profile change log starting from a given change id for the specified data type.
10 | ///
11 | /// The change date to start from (exclusive per API contract).
12 | /// The type of data to filter the change log by.
13 | /// A token to monitor for cancellation requests.
14 | /// The deserialized change log on success, or null if no content/non-success is handled as null.
15 | Task GetChangeLog(DateTime changeDate, DataType dataType, CancellationToken cancellationToken);
16 | }
17 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Migration/v0.03/01-ef-cleanup.sql:
--------------------------------------------------------------------------------
1 | -- Unique index on fnumber_ak in person table instead of unique constraint
2 | ALTER TABLE contact_and_reservation.person DROP CONSTRAINT IF EXISTS person_fnumber_ak_key;
3 |
4 | CREATE UNIQUE INDEX IF NOT EXISTS person_fnumber_ak_key ON contact_and_reservation.person (fnumber_ak);
5 |
6 | -- Drop unnecessary/duplicate index
7 | DROP INDEX IF EXISTS contact_and_reservation.idx_fnumber_ak;
8 |
9 | -- Drop check constraint chk_language_code in person table
10 | ALTER TABLE contact_and_reservation.person DROP CONSTRAINT IF EXISTS chk_language_code;
11 |
12 | -- Unique index on org_number_ak in mailbox_supplier table instead of unique constraint
13 | ALTER TABLE contact_and_reservation.mailbox_supplier DROP CONSTRAINT IF EXISTS unique_org_number_ak;
14 |
15 | CREATE UNIQUE INDEX IF NOT EXISTS unique_org_number_ak ON contact_and_reservation.mailbox_supplier (org_number_ak);
16 |
17 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Repositories/IMetadataRepository.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Integrations.Repositories;
2 |
3 | ///
4 | /// Defines a repository for handling metadata operations.
5 | ///
6 | public interface IMetadataRepository
7 | {
8 | ///
9 | /// Asynchronously retrieves the latest change number from the metadata repository.
10 | ///
11 | ///
12 | /// A task that represents the asynchronous operation.
13 | ///
14 | Task GetLatestChangeNumberAsync();
15 |
16 | ///
17 | /// Asynchronously updates the latest change number from the metadata repository.
18 | ///
19 | /// The new changed number.
20 | ///
21 | /// A task that represents the asynchronous operation.
22 | ///
23 | Task UpdateLatestChangeNumberAsync(long newNumber);
24 | }
25 |
--------------------------------------------------------------------------------
/src/Altinn.Profile/Changelog/LeaseNames.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Changelog
2 | {
3 | ///
4 | /// Lease names for register.
5 | ///
6 | /// Can be removed when Altinn2 is decommissioned
7 | internal static class LeaseNames
8 | {
9 | ///
10 | /// Lease name for .
11 | ///
12 | internal const string A2FavoriteImport = "a2-favorites-import";
13 |
14 | ///
15 | /// Lease name for .
16 | ///
17 | internal const string A2NotificationSettingImport = "a2-notification-settings-import";
18 |
19 | ///
20 | /// Lease name for .
21 | ///
22 | internal const string A2ProfileSettingImport = "a2-profile-settings-import";
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Altinn.Profile/Health/HealthCheck.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 |
4 | using Microsoft.Extensions.Diagnostics.HealthChecks;
5 |
6 | namespace Altinn.Profile.Health
7 | {
8 | ///
9 | /// Health check service configured in startup
10 | /// Listen to
11 | ///
12 | public class HealthCheck : IHealthCheck
13 | {
14 | ///
15 | /// Verifies the health status
16 | ///
17 | /// The healthcheck context
18 | /// A cancellation token
19 | /// A health result
20 | public Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
21 | {
22 | return Task.FromResult(HealthCheckResult.Healthy("A healthy result."));
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/ServiceDefaults.Leases/LeaseTicket.cs:
--------------------------------------------------------------------------------
1 | #nullable enable
2 |
3 | namespace Altinn.Authorization.ServiceDefaults.Leases;
4 |
5 | ///
6 | /// A ticket representing a lease.
7 | ///
8 | public sealed record LeaseTicket
9 | {
10 | ///
11 | /// Gets the lease id.
12 | ///
13 | public string LeaseId { get; }
14 |
15 | ///
16 | /// Gets the lease token.
17 | ///
18 | public Guid Token { get; }
19 |
20 | ///
21 | /// Gets when the lease expires.
22 | ///
23 | public DateTimeOffset Expires { get; }
24 |
25 | ///
26 | /// Initializes a new instance of the class.
27 | ///
28 | public LeaseTicket(string leaseId, Guid token, DateTimeOffset expires)
29 | {
30 | LeaseId = leaseId;
31 | Token = token;
32 | Expires = expires;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Repositories/IPersonUpdater.cs:
--------------------------------------------------------------------------------
1 | using Altinn.Profile.Core;
2 | using Altinn.Profile.Integrations.ContactRegister;
3 |
4 | namespace Altinn.Profile.Integrations.Repositories;
5 |
6 | ///
7 | /// Defines a repository for updating person data.
8 | ///
9 | public interface IPersonUpdater
10 | {
11 | ///
12 | /// Asynchronously synchronizes the changes in person contact preferences.
13 | ///
14 | /// The snapshots of person contact preferences to be synchronized.
15 | ///
16 | /// A task that represents the asynchronous operation. The task result contains a object with a indicating success or failure.
17 | ///
18 | Task SyncPersonContactPreferencesAsync(ContactRegisterChangesLog personContactPreferencesSnapshots);
19 | }
20 |
--------------------------------------------------------------------------------
/test/Bruno/TokenGenerator/portal user.bru:
--------------------------------------------------------------------------------
1 | meta {
2 | name: portal user
3 | type: http
4 | seq: 3
5 | }
6 |
7 | get {
8 | url: {{TokenGeneratorBaseUrl}}/api/GetPersonalToken?env={{Environment}}&scopes=altinn:portal/enduser&userId={{AuthN_UserId}}&partyId={{AuthN_PartyId}}&pid={{AuthN_Pid}}
9 | body: none
10 | auth: basic
11 | }
12 |
13 | params:query {
14 | env: {{Environment}}
15 | scopes: altinn:portal/enduser
16 | userId: {{AuthN_UserId}}
17 | partyId: {{AuthN_PartyId}}
18 | pid: {{AuthN_Pid}}
19 | }
20 |
21 | auth:basic {
22 | username: {{TokenGeneratorUserName}}
23 | password: {{TokenGeneratorPassword}}
24 | }
25 |
26 | script:post-response {
27 | let data = res.getBody();
28 |
29 | // Check if the response contains a valid token
30 | if (!data || data.trim() === '') {
31 | console.error('Failed to retrieve a valid bearer token');
32 | return;
33 | }
34 |
35 | bru.setEnvVar("BearerToken", data);
36 | }
37 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Entities/Metadata.cs:
--------------------------------------------------------------------------------
1 | // This file has been auto generated by EF Core Power Tools.
2 | #nullable disable
3 |
4 | using System.ComponentModel.DataAnnotations;
5 | using System.ComponentModel.DataAnnotations.Schema;
6 |
7 | namespace Altinn.Profile.Integrations.Entities;
8 |
9 | ///
10 | /// Represents metadata in the contact and reservation schema.
11 | ///
12 | [Table("metadata", Schema = "contact_and_reservation")]
13 | public partial class Metadata
14 | {
15 | ///
16 | /// Gets or sets the latest change number.
17 | ///
18 | [Key]
19 | [Column("latest_change_number")]
20 | public long LatestChangeNumber { get; set; }
21 |
22 | ///
23 | /// Gets or sets the date and time when the metadata was exported.
24 | ///
25 | [Column("exported")]
26 | public DateTime? Exported { get; set; }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Core/User.PartyGroups/Group.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Core.PartyGroups;
2 |
3 | ///
4 | /// A group of parties organized by a user
5 | ///
6 | public record Group
7 | {
8 | ///
9 | /// The group id
10 | ///
11 | public int GroupId { get; set; }
12 |
13 | ///
14 | /// The group name
15 | ///
16 | public required string Name { get; set; }
17 |
18 | ///
19 | /// The id of the user owning the group
20 | ///
21 | public int UserId { get; set; }
22 |
23 | ///
24 | /// Indicating whether or not he group is a favorite-group
25 | ///
26 | public bool IsFavorite { get; set; }
27 |
28 | ///
29 | /// A collection of parties in this group
30 | ///
31 | public List Parties { get; set; } = new List();
32 | }
33 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/OrganizationNotificationAddressRegistry/Models/CanUseModel.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace Altinn.Profile.Integrations.OrganizationNotificationAddressRegistry.Models
4 | {
5 | ///
6 | /// Metadata object
7 | ///
8 | public class CanUseModel
9 | {
10 | ///
11 | /// Document type
12 | ///
13 | [JsonPropertyName("dokumenttype")]
14 | public string? DocumentType { get; set; }
15 |
16 | ///
17 | /// The service area (tjenesteområde) for this contact point. Currently unused
18 | ///
19 | [JsonPropertyName("tjenesteområde")]
20 | public string? ServiceArea { get; set; }
21 |
22 | ///
23 | /// Trace id
24 | ///
25 | [JsonPropertyName("traceId")]
26 | public string? TraceId { get; set; }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/test/Altinn.Profile.Tests/IntegrationTests/HealthCheckTests.cs:
--------------------------------------------------------------------------------
1 | #nullable enable
2 |
3 | using System.Net;
4 | using System.Net.Http;
5 | using System.Threading.Tasks;
6 |
7 | using Xunit;
8 |
9 | namespace Altinn.Profile.Tests.IntegrationTests;
10 |
11 | public class HealthCheckTests(ProfileWebApplicationFactory factory)
12 | : IClassFixture>
13 | {
14 | private readonly ProfileWebApplicationFactory _factory = factory;
15 |
16 | [Fact]
17 | public async Task GetHealth_ReturnsOk()
18 | {
19 | // Arrange
20 | HttpClient client = _factory.CreateClient();
21 |
22 | HttpRequestMessage httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, "/health");
23 |
24 | // Act
25 | HttpResponseMessage response = await client.SendAsync(httpRequestMessage);
26 |
27 | // Assert
28 | Assert.Equal(HttpStatusCode.OK, response.StatusCode);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/.github/workflows/send-slack-warning.yml:
--------------------------------------------------------------------------------
1 | name: Send Slack Warning
2 |
3 | on:
4 | workflow_call:
5 | inputs:
6 | warning-message:
7 | required: true
8 | type: string
9 | description: 'The warning message to include in the Slack notification'
10 | secrets:
11 | SLACK_WEBHOOK_URL:
12 | required: true
13 |
14 | jobs:
15 | notify-warning-on-slack:
16 | runs-on: ubuntu-latest
17 | permissions: {}
18 | steps:
19 | - name: Send notification to Slack
20 | uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a # v2.1.1
21 | with:
22 | webhook-type: incoming-webhook
23 | webhook: ${{ secrets.SLACK_WEBHOOK_URL }}
24 | payload: |
25 | {
26 | "text": ":warning: ${{ inputs.warning-message }} :warning: \n\n Workflow available here: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
27 | }
28 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Core/Integrations/IUnitProfileRepository.cs:
--------------------------------------------------------------------------------
1 | using Altinn.Profile.Core.Unit.ContactPoints;
2 |
3 | namespace Altinn.Profile.Core.Integrations;
4 |
5 | ///
6 | /// Interface for accessing user profile services related to unit contact points.
7 | ///
8 | public interface IUnitProfileRepository
9 | {
10 | ///
11 | /// Retrieves a list of user-registered contact points based on the specified lookup criteria.
12 | ///
13 | /// An object containing a list of organization numbers and a resource ID to filter the contact points.
14 | /// A task that represents the asynchronous operation. The task result contains a object with a on success, or a boolean indicating failure.
15 | Task> GetUserRegisteredContactPoints(UnitContactPointLookup lookup);
16 | }
17 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Core/User.PartyGroups/PartyGroupAssociation.cs:
--------------------------------------------------------------------------------
1 | #nullable disable
2 |
3 | namespace Altinn.Profile.Core.PartyGroups;
4 |
5 | ///
6 | /// An association between a party and a group
7 | ///
8 | public record PartyGroupAssociation
9 | {
10 | ///
11 | /// The id of the association
12 | ///
13 | public int AssociationId { get; set; }
14 |
15 | ///
16 | /// The group id
17 | ///
18 | public int GroupId { get; set; }
19 |
20 | ///
21 | /// The Party's universally unique id
22 | ///
23 | public Guid PartyUuid { get; set; }
24 |
25 | ///
26 | /// The datetime when the association was created
27 | ///
28 | public DateTime Created { get; set; }
29 |
30 | ///
31 | /// The group the party is associated with
32 | ///
33 | public virtual Group Group { get; set; }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Altinn.Profile/Models/NotificationSettingsResponse.cs:
--------------------------------------------------------------------------------
1 | #nullable enable
2 |
3 | using System;
4 |
5 | namespace Altinn.Profile.Models
6 | {
7 | ///
8 | /// Response model for the professional notification address for an organization, also called personal notification address.
9 | ///
10 | public class NotificationSettingsResponse : ProfessionalNotificationAddress
11 | {
12 | ///
13 | /// The user id of logged-in user for whom the specific contact information belongs to.
14 | ///
15 | public int UserId { get; set; }
16 |
17 | ///
18 | /// Id of the party
19 | ///
20 | public Guid PartyUuid { get; set; }
21 |
22 | ///
23 | /// An indication of whether the notification address needs confirmation from a user
24 | ///
25 | public bool NeedsConfirmation { get; set; }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Altinn.Profile/Controllers/ErrorController.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Authorization;
2 | using Microsoft.AspNetCore.Mvc;
3 |
4 | namespace Altinn.Profile.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("profile/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 |
--------------------------------------------------------------------------------
/test/Altinn.Profile.Tests/IntegrationTests/API/OpenApiSpecificationTests.cs:
--------------------------------------------------------------------------------
1 | #nullable enable
2 |
3 | using System.Net;
4 | using System.Net.Http;
5 | using System.Threading.Tasks;
6 |
7 | using Xunit;
8 |
9 | namespace Altinn.Profile.Tests.IntegrationTests.API;
10 |
11 | public class OpenApiSpecificationTests(ProfileWebApplicationFactory factory)
12 | : IClassFixture>
13 | {
14 | private readonly ProfileWebApplicationFactory _factory = factory;
15 |
16 | [Fact]
17 | public async Task GetOpenApiSpecification_ReturnsOk()
18 | {
19 | // Arrange
20 | HttpClient client = _factory.CreateClient();
21 |
22 | HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, "/swagger");
23 |
24 | // Act
25 | HttpResponseMessage response = await client.SendAsync(httpRequestMessage);
26 |
27 | // Assert
28 | Assert.Equal(HttpStatusCode.OK, response.StatusCode);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Repositories/IRegistrySyncMetadataRepository.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Integrations.Repositories;
2 |
3 | ///
4 | /// Defines a repository for operations related to registry sync metadata.
5 | ///
6 | public interface IRegistrySyncMetadataRepository
7 | {
8 | ///
9 | /// Asynchronously retrieves the latest sync timestamp from the metadata repository.
10 | ///
11 | ///
12 | /// A task that represents the asynchronous operation.
13 | ///
14 | Task GetLatestSyncTimestampAsync();
15 |
16 | ///
17 | /// Asynchronously updates the latest sync timestamp in the metadata repository.
18 | ///
19 | /// The new timestamp for last sync.
20 | ///
21 | /// A task that represents the asynchronous operation.
22 | ///
23 | Task UpdateLatestChangeTimestampAsync(DateTime updated);
24 | }
25 |
--------------------------------------------------------------------------------
/src/ServiceDefaults.Jobs/IJob.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Authorization.ServiceDefaults.Jobs;
2 |
3 | ///
4 | /// A job that can be run on a schedule or on host lifecycle events.
5 | ///
6 | public interface IJob
7 | {
8 | ///
9 | /// Gets the name of the job.
10 | ///
11 | public string Name { get; }
12 |
13 | ///
14 | /// Checks if the job should run at this time.
15 | ///
16 | /// A .
17 | /// , if the job should be allowed to run at this time, otherwise .
18 | public ValueTask ShouldRun(CancellationToken cancellationToken = default);
19 |
20 | ///
21 | /// Runs the job.
22 | ///
23 | /// A .
24 | public Task RunAsync(CancellationToken cancellationToken = default);
25 | }
26 |
--------------------------------------------------------------------------------
/src/Altinn.Profile/Changelog/ImportJobSettings.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Changelog
2 | {
3 | ///
4 | /// Represents the settings for the changelog import jobs.
5 | ///
6 | /// Can be removed when Altinn2 is decommissioned
7 | public class ImportJobSettings
8 | {
9 | ///
10 | /// Gets or sets a value indicating whether the favorites import is enabled.
11 | ///
12 | public bool FavoritesImportEnabled { get; set; } = false;
13 |
14 | ///
15 | /// Gets or sets a value indicating whether the notification settings import is enabled.
16 | ///
17 | public bool NotificationSettingsImportEnabled { get; set; } = false;
18 |
19 | ///
20 | /// Gets or sets a value indicating whether the portal settings import is enabled.
21 | ///
22 | public bool ProfileSettingsImportEnabled { get; set; } = false;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Repositories/IOrganizationNotificationAddressUpdater.cs:
--------------------------------------------------------------------------------
1 | using Altinn.Profile.Core;
2 | using Altinn.Profile.Integrations.OrganizationNotificationAddressRegistry;
3 |
4 | namespace Altinn.Profile.Integrations.Repositories;
5 |
6 | ///
7 | /// Defines operations for syncrhonizing changes to notification addresses for organizations
8 | ///
9 | public interface IOrganizationNotificationAddressUpdater
10 | {
11 | ///
12 | /// Asynchronously synchronizes the changes in organizations notification addresses.
13 | ///
14 | /// The snapshots of notification addresses to be synchronized.
15 | ///
16 | /// A task that represents the asynchronous operation. The task result contains an integer value giving the number of writes to the database.
17 | ///
18 | Task SyncNotificationAddressesAsync(NotificationAddressChangesLog organizationNotificationAddressChanges);
19 | }
20 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/SblBridge/SblBridgeSettings.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Integrations.SblBridge;
2 |
3 | ///
4 | /// Class containing all configuration settings for SBL Bridge
5 | ///
6 | public class SblBridgeSettings
7 | {
8 | ///
9 | /// Gets or sets the SBL Bridge Profile API endpoint
10 | ///
11 | public string ApiProfileEndpoint { get; set; } = string.Empty;
12 |
13 | ///
14 | /// A feature flag indicating whether to update A2 when updating favorites
15 | ///
16 | public bool UpdateA2Favorites { get; set; }
17 |
18 | ///
19 | /// A feature flag indicating whether to update A2 when updating notification settings
20 | ///
21 | public bool UpdateA2NotificationSettings { get; set; }
22 |
23 | ///
24 | /// A feature flag indicating whether to update A2 when updating portal setting preferences
25 | ///
26 | public bool UpdateA2ProfileSettings { get; set; }
27 | }
28 |
--------------------------------------------------------------------------------
/test/Altinn.Profile.Tests/Profile.Integrations/Person/PersonExtensions.cs:
--------------------------------------------------------------------------------
1 | using Altinn.Profile.Core.Person.ContactPreferences;
2 | using Altinn.Profile.Integrations.Entities;
3 |
4 | namespace Altinn.Profile.Tests.Profile.Integrations.Extensions
5 | {
6 | ///
7 | /// Extensions to help testing with the Person class
8 | ///
9 | internal static class PersonExtensions
10 | {
11 | ///
12 | /// Custom mapper from Person -> PersonContactPreferences
13 | ///
14 | internal static PersonContactPreferences AsPersonContactPreferences(this Person person)
15 | {
16 | return new PersonContactPreferences()
17 | {
18 | NationalIdentityNumber = person.FnumberAk,
19 | Email = person.EmailAddress,
20 | IsReserved = person.Reservation ?? false,
21 | LanguageCode = person.LanguageCode,
22 | MobileNumber = person.MobilePhoneNumber,
23 | };
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/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 | "packageRules": [
21 | {
22 | "matchManagers": [
23 | "nuget"
24 | ],
25 | "matchUpdateTypes": [
26 | "major"
27 | ],
28 | "groupName": "Grouped major upgrades: {{depName}}",
29 | "matchPackageNames": [
30 | "//^([^\\.]+)//"
31 | ]
32 | }
33 | ]
34 | }
35 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Migration/v0.13/01-seed-fake-data.sql:
--------------------------------------------------------------------------------
1 | DO $$
2 | DECLARE lastid int;
3 | BEGIN
4 | INSERT INTO organization_notification_address.organizations (registry_organization_number)
5 | values ('810889802')
6 | Returning registry_organization_id INTO lastid;
7 |
8 | INSERT INTO organization_notification_address.notifications_address(registry_id, address_type, domain, address, full_address, created_date_time, registry_updated_date_time, update_source, has_registry_accepted, is_soft_deleted, notification_name, fk_registry_organization_id)
9 | values (1, 2, 'default.digdir.no', 'nullstillt', 'nullstillt@default.digdir.no', now(), now(), 3, true, false, NULL, lastid);
10 |
11 | INSERT INTO organization_notification_address.notifications_address(registry_id, address_type, domain, address, full_address, created_date_time, registry_updated_date_time, update_source, has_registry_accepted, is_soft_deleted, notification_name, fk_registry_organization_id)
12 | values (2, 1, '+47', '99999999', '+4799999999', now(), now(), 3, true, false, NULL, lastid);
13 | END $$;
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/SblBridge/User.Favorites/FavoriteChangedRequest.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Integrations.SblBridge.User.Favorites
2 | {
3 | ///
4 | /// Describes an event where a user either added or removed a party from their favorites.
5 | ///
6 | public class FavoriteChangedRequest
7 | {
8 | ///
9 | /// Gets or sets the type of change. Supported values are "insert" and "delete".
10 | ///
11 | public required string ChangeType { get; set; }
12 |
13 | ///
14 | /// Gets or sets the ID of the user.
15 | ///
16 | public int UserId { get; set; }
17 |
18 | ///
19 | /// Gets or sets the UUID of the added or removed favorite party.
20 | ///
21 | public Guid PartyUuid { get; set; }
22 |
23 | ///
24 | /// Gets or sets the date and time for the change.
25 | ///
26 | public DateTime ChangeDateTime { get; set; }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/ServiceDefaults.Jobs/Job.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Authorization.ServiceDefaults.Jobs;
2 |
3 | ///
4 | /// Base class for jobs that can be run on a schedule or on host lifecycle events.
5 | ///
6 | /// Project can be removed when Altinn2 is decommissioned
7 | public abstract class Job
8 | : IJob
9 | {
10 | ///
11 | string IJob.Name => GetType().Name;
12 |
13 | ///
14 | ValueTask IJob.ShouldRun(CancellationToken cancellationToken)
15 | => ShouldRun(cancellationToken);
16 |
17 | ///
18 | Task IJob.RunAsync(CancellationToken cancellationToken)
19 | => RunAsync(cancellationToken);
20 |
21 | ///
22 | protected virtual ValueTask ShouldRun(CancellationToken cancellationToken = default)
23 | => ValueTask.FromResult(true);
24 |
25 | ///
26 | protected abstract Task RunAsync(CancellationToken cancellationToken = default);
27 | }
28 |
--------------------------------------------------------------------------------
/src/ServiceDefaults.Jobs/Altinn.Authorization.ServiceDefaults.Jobs.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net9.0
5 | enable
6 | enable
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Migration/v0.14/01-create-schema-lease.sql:
--------------------------------------------------------------------------------
1 | CREATE SCHEMA IF NOT EXISTS lease;
2 |
3 | GRANT ALL ON SCHEMA lease TO platform_profile_admin;
4 | GRANT USAGE ON SCHEMA lease TO platform_profile;
5 |
6 | CREATE TABLE IF NOT EXISTS lease.changelog_sync_metadata (
7 | last_changed_id character varying(32) NOT NULL,
8 | last_changed_date_time timestamp with time zone NOT NULL,
9 | data_type integer NOT NULL,
10 | CONSTRAINT changelog_sync_metadata_pkey PRIMARY KEY (last_changed_id)
11 | );
12 |
13 | CREATE TABLE IF NOT EXISTS lease.lease (
14 | id text NOT NULL,
15 | token uuid NOT NULL,
16 | expires timestamp with time zone NOT NULL,
17 | acquired timestamp with time zone,
18 | released timestamp with time zone,
19 | CONSTRAINT lease_id_pkey PRIMARY KEY (id)
20 | );
21 |
22 | CREATE UNIQUE INDEX IF NOT EXISTS ix_lease_id ON lease.lease (id);
23 |
24 | GRANT DELETE, INSERT, SELECT, UPDATE ON TABLE lease.changelog_sync_metadata TO platform_profile;
25 |
26 | GRANT DELETE, INSERT, SELECT, UPDATE ON TABLE lease.lease TO platform_profile;
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Events/NotificationSettingsAddedEvent.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Integrations.Events
2 | {
3 | ///
4 | /// Event representing the creation of a new professional notification address.
5 | ///
6 | /// The user ID that added the address.
7 | /// The unique identifier of the party.
8 | /// The timestamp when the event occurred.
9 | /// The emailAddress of the notificationSettings
10 | /// The phoneNumber of the notificationSettings
11 | /// Optional, the selected resourceIds of the notificationSettings
12 | /// Can be removed when Altinn2 is decommissioned
13 | public record NotificationSettingsAddedEvent(
14 | int UserId,
15 | Guid PartyUuid,
16 | DateTime EventTimestamp,
17 | string? EmailAddress,
18 | string? PhoneNumber,
19 | string[]? ResourceIds);
20 | }
21 |
--------------------------------------------------------------------------------
/src/ServiceDefaults.Jobs/IJobCondition.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Immutable;
2 |
3 | namespace Altinn.Authorization.ServiceDefaults.Jobs;
4 |
5 | ///
6 | /// A condition that can be applied to one or more jobs, determining if they are allowed to run or not.
7 | ///
8 | public interface IJobCondition
9 | {
10 | ///
11 | /// Gets the name of the job condition. Used for logging and diagnostics.
12 | ///
13 | public string Name { get; }
14 |
15 | ///
16 | /// Gets tags this condition is applied to. If the list is empty, the condition applies to all jobs.
17 | ///
18 | public ImmutableArray JobTags { get; }
19 |
20 | ///
21 | /// Checks if the job is allowed to run.
22 | ///
23 | /// A .
24 | /// , if the job should be allowed to run at this time, otherwise .
25 | public ValueTask ShouldRun(CancellationToken cancellationToken = default);
26 | }
27 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/SblBridge/Unit.Profile/SblUserRegisteredContactPoint.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Integrations.SblBridge.Unit.Profile
2 | {
3 | ///
4 | /// Model describing the contact information that a user has associated with a party they can represent.
5 | ///
6 | public class SblUserRegisteredContactPoint
7 | {
8 | ///
9 | /// Gets or sets the legacy user id for the owner of this party notification endpoint.
10 | ///
11 | ///
12 | /// This was named as legacy for consistency. Property for UUID will probably never be added.
13 | ///
14 | public int LegacyUserId { get; set; }
15 |
16 | ///
17 | /// Gets or sets the email address for this contact point.
18 | ///
19 | public string Email { get; set; } = string.Empty;
20 |
21 | ///
22 | /// Gets or sets the mobile number for this contact point.
23 | ///
24 | public string MobileNumber { get; set; } = string.Empty;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Core/ProfessionalNotificationAddresses/UserPartyContactInfoResource.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Core.ProfessionalNotificationAddresses
2 | {
3 | ///
4 | /// Data model for the personal notification address for an organization
5 | ///
6 | public class UserPartyContactInfoResource
7 | {
8 | ///
9 | /// Id of the association of resource to the user party contact info
10 | ///
11 | public long UserPartyContactInfoResourceId { get; set; }
12 |
13 | ///
14 | /// Foreign key to the user party contact info
15 | ///
16 | public long UserPartyContactInfoId { get; set; }
17 |
18 | ///
19 | /// Id of the resource that this contact info is associated with
20 | ///
21 | public required string ResourceId { get; set; }
22 |
23 | ///
24 | /// The contact info the resource is associated with
25 | ///
26 | public virtual UserPartyContactInfo? UserPartyContactInfo { get; set; }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Core/OrganizationNotificationAddresses/Organization.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Core.OrganizationNotificationAddresses
2 | {
3 | ///
4 | /// Represents an organization with associated notification addresses
5 | ///
6 | public class Organization
7 | {
8 | ///
9 | /// OrganizationNumber of the organization
10 | ///
11 | public required string OrganizationNumber { get; set; }
12 |
13 | ///
14 | /// A collection of notification addresses associated with this organization
15 | ///
16 | public List? NotificationAddresses { get; set; }
17 |
18 | private string _addressOrigin = string.Empty;
19 |
20 | ///
21 | /// OrganizationNumber of the organization where the address was found
22 | ///
23 | public string AddressOrigin
24 | {
25 | get => string.IsNullOrEmpty(_addressOrigin) ? OrganizationNumber : _addressOrigin;
26 | init => _addressOrigin = value;
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Core/Person.ContactPreferences/PersonContactPreferences.cs:
--------------------------------------------------------------------------------
1 | #nullable enable
2 |
3 | namespace Altinn.Profile.Core.Person.ContactPreferences;
4 |
5 | ///
6 | /// Represents a person's contact details.
7 | ///
8 | public record PersonContactPreferences
9 | {
10 | ///
11 | /// Gets the email address of the person.
12 | ///
13 | public string? Email { get; init; }
14 |
15 | ///
16 | /// Gets a value indicating whether the person opts out of being contacted.
17 | ///
18 | public bool IsReserved { get; init; }
19 |
20 | ///
21 | /// Gets the language code of the person, represented as an ISO 639-1 code.
22 | ///
23 | public string? LanguageCode { get; init; }
24 |
25 | ///
26 | /// Gets the mobile phone number of the person.
27 | ///
28 | public string? MobileNumber { get; init; }
29 |
30 | ///
31 | /// Gets the national identity number of the person.
32 | ///
33 | public required string NationalIdentityNumber { get; init; }
34 | }
35 |
--------------------------------------------------------------------------------
/test/k6/src/api/notificationsettings.js:
--------------------------------------------------------------------------------
1 | import http from "k6/http";
2 | import * as config from "../config.js";
3 | import * as apiHelpers from "../apiHelpers.js";
4 |
5 | export function getPersonalNotificationAddresses(token, partyUuid) {
6 | const endpoint = config.profileUrl.personalNotificationAddresses(partyUuid);
7 |
8 | const params = apiHelpers.buildHeaderWithBearer(token);
9 |
10 | return http.get(endpoint, params);
11 | }
12 |
13 | export function addPersonalNotificationAddresses(token, partyUuid, notificationSettings) {
14 | const endpoint = config.profileUrl.personalNotificationAddresses(partyUuid);
15 |
16 | const params = apiHelpers.buildHeaderWithBearerAndContentType(token);
17 | const requestBody = JSON.stringify(notificationSettings);
18 | return http.put(endpoint, requestBody, params);
19 | }
20 |
21 | export function removePersonalNotificationAddresses(token, partyUuid) {
22 | const endpoint = config.profileUrl.personalNotificationAddresses(partyUuid);
23 |
24 | const params = apiHelpers.buildHeaderWithBearerAndContentType(token);
25 |
26 | return http.del(endpoint, null, params);
27 | }
--------------------------------------------------------------------------------
/src/Altinn.Profile.Core/Integrations/IPersonService.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Immutable;
2 |
3 | using Altinn.Profile.Core.Person.ContactPreferences;
4 |
5 | namespace Altinn.Profile.Core.Integrations;
6 |
7 | ///
8 | /// Defines a service for read-operations related to person data.
9 | ///
10 | public interface IPersonService
11 | {
12 | ///
13 | /// Asynchronously retrieves the contact details for multiple persons by their national identity numbers.
14 | ///
15 | /// A collection of national identity numbers to look up.
16 | /// A token to monitor for cancellation requests.
17 | ///
18 | /// A task that represents the asynchronous operation. The task result contains a an of objects representing the contact details of the persons.
19 | ///
20 | Task> GetContactPreferencesAsync(IEnumerable nationalIdentityNumbers, CancellationToken cancellationToken);
21 | }
22 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/OrganizationNotificationAddressRegistry/IOrganizationNotificationAddressSyncClient.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Integrations.OrganizationNotificationAddressRegistry;
2 |
3 | ///
4 | /// Defines an HTTP client to sync with a source registry for organizational notification addresses.
5 | ///
6 | public interface IOrganizationNotificationAddressSyncClient
7 | {
8 | ///
9 | /// Retrieves changes to organizational notification addresses
10 | ///
11 | /// The URL of the endpoint to retrieve contact details changes from.
12 | ///
13 | /// A task that represents the asynchronous operation with the returned values.
14 | ///
15 | Task GetAddressChangesAsync(string endpointUrl);
16 |
17 | ///
18 | /// Formats the url to get the initial dataload - either from the last changed timestamp or from the beginning.
19 | ///
20 | /// The timestamp to get changes since.
21 | string GetInitialUrl(DateTime? lastUpdated);
22 | }
23 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Models/Enums/UserType.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Models.Enums
2 | {
3 | ///
4 | /// Enumeration for the available user types
5 | ///
6 | public enum UserType
7 | {
8 | ///
9 | /// User type has not been specified
10 | ///
11 | None = 0,
12 |
13 | ///
14 | /// User Type is SSN Identified User.
15 | ///
16 | SSNIdentified = 1,
17 |
18 | ///
19 | /// User Type is Self Identified User.
20 | ///
21 | SelfIdentified = 2,
22 |
23 | ///
24 | /// User Type is Enterprise Identified User.
25 | ///
26 | EnterpriseIdentified = 3,
27 |
28 | ///
29 | /// User Type is Agency User
30 | ///
31 | AgencyUser = 4,
32 |
33 | ///
34 | /// User Type is PSAN User
35 | ///
36 | PSAN = 5,
37 |
38 | ///
39 | /// User Type is PSA User
40 | ///
41 | PSA = 6
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Repositories/EFCoreTransactionalOutbox.cs:
--------------------------------------------------------------------------------
1 | using Altinn.Profile.Integrations.Persistence;
2 |
3 | using Wolverine.EntityFrameworkCore;
4 |
5 | namespace Altinn.Profile.Integrations.Repositories
6 | {
7 | ///
8 | /// Adds support for transactional outbox for repositories that persist using EF Core
9 | ///
10 | /// Can be removed when Altinn2 is decommissioned
11 | public abstract class EFCoreTransactionalOutbox(IDbContextOutbox contextOutbox)
12 | {
13 | private readonly IDbContextOutbox _outbox = contextOutbox;
14 |
15 | ///
16 | /// Transactionally publishes a message and saves the dbContext changes
17 | ///
18 | protected async Task NotifyAndSave(ProfileDbContext databaseContext, Func eventRaiser, CancellationToken cancellationToken)
19 | {
20 | _outbox.Enroll(databaseContext);
21 | var eventForSending = eventRaiser();
22 |
23 | await _outbox.PublishAsync(eventForSending);
24 | await _outbox.SaveChangesAndFlushMessagesAsync(cancellationToken);
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/test/Altinn.Profile.Tests/skd-org.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIDAjCCAeqgAwIBAgIIQtFLr5On1tIwDQYJKoZIhvcNAQELBQAwDjEMMAoGA1UE
3 | AxMDc2tkMB4XDTIwMDUyNTEyMjE0NFoXDTMwMDUyNDEyMjE0NFowDjEMMAoGA1UE
4 | AxMDc2tkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3PMrdoHBX0X/
5 | Jkr7abPlsdOBB2Gka+zXf63gADz3xvzyE3LSAV75VgT1LgJD+DuljJj/lNpXwUxe
6 | Dae4ypLYCH0E+NZBnUQbcNry9BB3wbe8yXZIy/7vwbV23YxXxnipWAbfQnVQEIWC
7 | QpR0Ew0rq/Tbn17+ImonzbwM7XiVADdUjzAlJ9Tfesr/lnOqEozDlhfEHTc2z4bF
8 | 3AvfvwED5s5/O1GMjzGohrVUF3jVLIRbZHsLSZljNbdawMqMh4Fh5s9+iNkYavQS
9 | W4rIirCmQuGT0VgP6mHXmmXHsjGeuXzXcP334XIRZcTBjGL+a/+3Gn1+imezcfVR
10 | +KOFqIj0WQIDAQABo2QwYjAgBgNVHQ4BAf8EFgQUKOVCRgdUqH1PyiLziJhkEVfI
11 | 9lgwDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCBaAwIAYDVR0lAQH/BBYwFAYI
12 | KwYBBQUHAwEGCCsGAQUFBwMCMA0GCSqGSIb3DQEBCwUAA4IBAQBwxA1sGsW1BXmn
13 | HpZmTQBCoHWywUk2gWfJTjgmSTPdh55KlO+F9+1bCOx2+HYrGMEwKmyq5JR3W9zT
14 | zPfqd5wPJClqL0LrZG0k2Rc1BTbqDysbMFn/ygC5iLgez8pByDa0V3oJfoUVWnES
15 | uPFjAwCrlLPvyq/KeT6KtcynrDwg9dPgqirx0TAY11LlgkRwZHSGhtK2q8KWI6nU
16 | jsrYw7rH55KNzzJETTd0P6es644+WI2/XRUlPTJWIgnAaXH2hS+wLNrtB1DoVPey
17 | XNxeSUEFH2eqdDAnTjCD2Imk1KDNnnKG9/89i6AvB8SkyjB6cx4ZRNWW92vGcfur
18 | y5BlJ7Cr
19 | -----END CERTIFICATE-----
20 |
--------------------------------------------------------------------------------
/.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 | # Override with explicit specific settings for known and/or likely text files in our repo that should be normalized
8 | # where diff{=optional_pattern} means "do text diff {with specific text pattern} and -diff means "don't do text diffs".
9 | # Unspecified diff behavior is decribed above
10 | *.cer text -diff
11 | *.cmd text
12 | *.cs text diff=csharp
13 | *.csproj text
14 | *.css text diff=css
15 | Dockerfile text
16 | *.json text
17 | *.md text diff=markdown
18 | *.msbuild text
19 | *.pem text -diff
20 | *.ps1 text
21 | *.sln text
22 | *.yaml text
23 | *.yml text
24 |
25 | # 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")
26 | *.jpeg binary
27 | *.pfx binary
28 | *.png binary
--------------------------------------------------------------------------------
/test/Altinn.Profile.Tests/ttd-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 |
--------------------------------------------------------------------------------
/test/Altinn.Profile.Tests/IntegrationTests/Mocks/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.Profile.Tests.IntegrationTests.Mocks;
8 |
9 | public class DelegatingHandlerStub : DelegatingHandler
10 | {
11 | private Func> _handlerFunc;
12 |
13 | public DelegatingHandlerStub()
14 | {
15 | _handlerFunc = (request, cancellationToken) => Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK));
16 | }
17 |
18 | public DelegatingHandlerStub(Func> handlerFunc)
19 | {
20 | _handlerFunc = handlerFunc;
21 | }
22 |
23 | public void ChangeHandlerFunction(Func> handlerFunc)
24 | {
25 | _handlerFunc = handlerFunc;
26 | }
27 |
28 | protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
29 | {
30 | return _handlerFunc(request, cancellationToken);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Mappings/PersonContactPreferencesMapper.cs:
--------------------------------------------------------------------------------
1 | using Altinn.Profile.Core.Person.ContactPreferences;
2 | using Altinn.Profile.Integrations.Entities;
3 |
4 | namespace Altinn.Profile.Integrations.Mappings;
5 |
6 | ///
7 | /// Provides mapping functionality from to .
8 | ///
9 | public static class PersonContactPreferencesMapper
10 | {
11 | ///
12 | /// Maps a entity to a record.
13 | ///
14 | /// The entity to map from.
15 | /// A record containing mapped contact preferences.
16 | public static PersonContactPreferences Map(Person person)
17 | {
18 | return new PersonContactPreferences
19 | {
20 | Email = person.EmailAddress,
21 | IsReserved = person.Reservation ?? false,
22 | LanguageCode = person.LanguageCode,
23 | MobileNumber = person.MobilePhoneNumber,
24 | NationalIdentityNumber = person.FnumberAk
25 | };
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/test/Altinn.Profile.Tests/IntegrationTests/Utils/XacmlResourceAttributes.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Tests.IntegrationTests.Utils
2 | {
3 | public class XacmlResourceAttributes
4 | {
5 | ///
6 | /// Gets or sets the value for org attribute
7 | ///
8 | public string OrgValue { get; set; }
9 |
10 | ///
11 | /// Gets or sets the value for app attribute
12 | ///
13 | public string AppValue { get; set; }
14 |
15 | ///
16 | /// Gets or sets the value for instance attribute
17 | ///
18 | public string InstanceValue { get; set; }
19 |
20 | ///
21 | /// Gets or sets the value for resourceparty attribute
22 | ///
23 | public string ResourcePartyValue { get; set; }
24 |
25 | ///
26 | /// Gets or sets the value for task attribute
27 | ///
28 | public string TaskValue { get; set; }
29 |
30 | ///
31 | /// Gets or sets the value for app resource.
32 | ///
33 | public string AppResourceValue { get; set; }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/OrganizationNotificationAddressRegistry/Models/UnitContactInfoModel.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace Altinn.Profile.Integrations.OrganizationNotificationAddressRegistry.Models
4 | {
5 | ///
6 | /// The identifier of the unit of the contact point.
7 | ///
8 | public record UnitContactInfoModel
9 | {
10 | ///
11 | /// The identifier of the unit of the contact point.
12 | ///
13 | [JsonPropertyName("enhetsidentifikator")]
14 | public UnitIdentifierModel? UnitIdentifier { get; init; }
15 | }
16 |
17 | ///
18 | /// The identifier of the unit.
19 | ///
20 | public record UnitIdentifierModel
21 | {
22 | ///
23 | /// The kind of identifier, e.g. "ORGANISASJONSNUMMER"
24 | ///
25 | [JsonPropertyName("type")]
26 | public string? Type { get; init; }
27 |
28 | ///
29 | /// The unique value of the identifier, e.g. "920254321"
30 | ///
31 | [JsonPropertyName("verdi")]
32 | public string? Value { get; init; }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Events/NotificationSettingsUpdatedEvent.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Integrations.Events
2 | {
3 | ///
4 | /// Event representing an update to a professional notification address.
5 | ///
6 | /// The user ID associated with the update.
7 | /// The unique identifier of the party.
8 | /// The timestamp when the event was created.
9 | /// The timestamp when the event occurred.
10 | /// The emailAddress of the notificationSettings
11 | /// The phoneNumber of the notificationSettings
12 | /// Optional, the selected resourceIds of the notificationSettings
13 | /// Can be removed when Altinn2 is decommissioned
14 | public record NotificationSettingsUpdatedEvent(
15 | int UserId,
16 | Guid PartyUuid,
17 | DateTime CreationTimestamp,
18 | DateTime EventTimestamp,
19 | string? EmailAddress,
20 | string? PhoneNumber,
21 | string[]? ResourceIds);
22 | }
23 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/OrganizationNotificationAddressRegistry/OrganizationNotificationAddressSettings.cs:
--------------------------------------------------------------------------------
1 | using Altinn.ApiClients.Maskinporten.Config;
2 |
3 | namespace Altinn.Profile.Integrations.OrganizationNotificationAddressRegistry;
4 |
5 | ///
6 | /// Represents the settings for managing contact details and reservation information for individuals.
7 | ///
8 | public class OrganizationNotificationAddressSettings
9 | {
10 | ///
11 | /// The endpoint URL used to retrieve updates in the contact information for one or more organizations.
12 | ///
13 | public string? ChangesLogEndpoint { get; init; }
14 |
15 | ///
16 | /// The maximum number of entries to retrieve from the changelog
17 | ///
18 | public int ChangesLogPageSize { get; init; }
19 |
20 | ///
21 | /// The endpoint URL used to send updates in the contact information for one or more organizations.
22 | ///
23 | public string? UpdateEndpoint { get; init; }
24 |
25 | ///
26 | /// The settings required for Maskinporten authentication.
27 | ///
28 | public MaskinportenSettings? MaskinportenSettings { get; init; }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Leases/Lease.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Integrations.Leases
2 | {
3 | ///
4 | /// A record representing a lease in the system.
5 | ///
6 | /// Can be removed when Altinn2 is decommissioned
7 | public record Lease
8 | {
9 | ///
10 | /// Gets or sets the unique identifier for the lease.
11 | ///
12 | public required string Id { get; set; }
13 |
14 | ///
15 | /// Gets or sets the cancellationToken associated with the lease.
16 | ///
17 | public Guid Token { get; set; }
18 |
19 | ///
20 | /// Gets or sets the expiration timestamp of the lease.
21 | ///
22 | public DateTimeOffset Expires { get; set; }
23 |
24 | ///
25 | /// Gets or sets the timestamp when the lease was acquired, if applicable.
26 | ///
27 | public DateTimeOffset? Acquired { get; set; }
28 |
29 | ///
30 | /// Gets or sets the timestamp when the lease was released, if applicable.
31 | ///
32 | public DateTimeOffset? Released { get; set; }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/ServiceDefaults.Leases/LeaseReleaseResult.cs:
--------------------------------------------------------------------------------
1 | #nullable enable
2 |
3 | namespace Altinn.Authorization.ServiceDefaults.Leases;
4 |
5 | ///
6 | /// The result of calling .
7 | ///
8 | public sealed class LeaseReleaseResult
9 | {
10 | ///
11 | /// Gets a value indicating whether or not the lease was released.
12 | ///
13 | public required bool IsReleased { get; init; }
14 |
15 | ///
16 | /// Gets when the lease will expire (regardless of whether it was acquired).
17 | ///
18 | public required DateTimeOffset Expires { get; init; }
19 |
20 | ///
21 | /// Gets when the lease was last acquired at.
22 | ///
23 | ///
24 | /// This does not signify that the lease is currently held.
25 | ///
26 | public required DateTimeOffset? LastAcquiredAt { get; init; }
27 |
28 | ///
29 | /// Gets when the lease was last released at.
30 | ///
31 | ///
32 | /// This does not signify that the lease is not currently held.
33 | ///
34 | public required DateTimeOffset? LastReleasedAt { get; init; }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Altinn.Profile/Models/OrgNotificationAddressRequest.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.ComponentModel.DataAnnotations;
3 | using System.Linq;
4 | using System.Text.Json.Serialization;
5 |
6 | namespace Altinn.Profile.Models
7 | {
8 | ///
9 | /// A class describing the query model for contact points for organizations
10 | ///
11 | public class OrgNotificationAddressRequest: IValidatableObject
12 | {
13 | ///
14 | /// Gets or sets the list of organization numbers to lookup contact points for
15 | ///
16 | [JsonPropertyName("organizationNumbers")]
17 | [Required]
18 | public List OrganizationNumbers { get; set; }
19 |
20 | ///
21 | public IEnumerable Validate(ValidationContext validationContext)
22 | {
23 | if (OrganizationNumbers == null ||
24 | OrganizationNumbers.Count == 0 ||
25 | OrganizationNumbers.Any(string.IsNullOrWhiteSpace))
26 | {
27 | yield return new ValidationResult("OrganizationNumbers must contain a list of valid organization number values", [nameof(OrganizationNumbers)]);
28 | }
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/test/Altinn.Profile.Tests/IntegrationTests/API/Controllers/ErrorHandlingTests.cs:
--------------------------------------------------------------------------------
1 | #nullable enable
2 |
3 | using System.Net;
4 | using System.Net.Http;
5 | using System.Net.Http.Json;
6 | using System.Threading.Tasks;
7 |
8 | using Microsoft.AspNetCore.Mvc;
9 |
10 | using Xunit;
11 |
12 | namespace Altinn.Profile.Tests.IntegrationTests.API.Controllers;
13 |
14 | public class ErrorHandlingTests(ProfileWebApplicationFactory factory)
15 | : IClassFixture>
16 | {
17 | private readonly ProfileWebApplicationFactory _factory = factory;
18 |
19 | [Fact]
20 | public async Task GetError_ReturnsInternalServerError()
21 | {
22 | // Arrange
23 | HttpClient client = _factory.CreateClient();
24 |
25 | HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, "/profile/api/v1/error");
26 |
27 | // Act
28 | HttpResponseMessage response = await client.SendAsync(httpRequestMessage);
29 |
30 | // Assert
31 | Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
32 |
33 | ProblemDetails? problemDetails = await response.Content.ReadFromJsonAsync();
34 |
35 | Assert.StartsWith("An error occurred", problemDetails?.Title);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Core/Altinn.Profile.Core.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net9.0
5 | enable
6 | enable
7 | true
8 | {7BA8E238-C930-45C0-BB5D-28AE4D89669B}
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | all
21 | runtime; build; native; contentfiles; analyzers; buildtransitive
22 |
23 |
24 | stylecop.json
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/OrganizationNotificationAddressRegistry/NotificationAddressChangesLog.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 | using Altinn.Profile.Integrations.OrganizationNotificationAddressRegistry.Models;
3 |
4 | namespace Altinn.Profile.Integrations.OrganizationNotificationAddressRegistry;
5 |
6 | ///
7 | /// Represents a change log for organizational notification addresses.
8 | ///
9 | public record NotificationAddressChangesLog
10 | {
11 | ///
12 | /// The collection of snapshots representing the changes to notification addresses of an organization.
13 | ///
14 | [JsonPropertyName("entries")]
15 | public List? OrganizationNotificationAddressList { get; init; }
16 |
17 | ///
18 | /// The title of this change log page.
19 | ///
20 | [JsonPropertyName("title")]
21 | public string? Title { get; init; }
22 |
23 | ///
24 | /// The datetime of when the changes were fetched.
25 | ///
26 | [JsonPropertyName("updated")]
27 | public DateTime? Updated { get; init; }
28 |
29 | ///
30 | /// The uri for the next batch of data.
31 | ///
32 | [JsonPropertyName("nextPage")]
33 | public Uri? NextPage { get; init; }
34 | }
35 |
--------------------------------------------------------------------------------
/.github/workflows/container-scan.yml:
--------------------------------------------------------------------------------
1 | name: Profile Scan
2 |
3 | on:
4 | schedule:
5 | - cron: '0 8 * * 1,4'
6 | push:
7 | branches: [ main ]
8 | paths:
9 | - 'src/**'
10 | - 'Dockerfile'
11 | pull_request:
12 | branches: [ main ]
13 | types: [opened, synchronize, reopened]
14 | paths:
15 | - 'src/**'
16 | - 'Dockerfile'
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-profile:${{github.sha}}
26 |
27 | - name: Run Trivy vulnerability scanner
28 | uses: aquasecurity/trivy-action@b6643a29fecd7f34b3597bc6acb0a98b03d33ff8 # 0.33.1
29 | with:
30 | image-ref: 'altinn-profile:${{ 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 |
--------------------------------------------------------------------------------
/src/Altinn.Profile/Authorization/AuthConstants.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Authorization
2 | {
3 | ///
4 | /// Constants for authorization policies
5 | ///
6 | public static class AuthConstants
7 | {
8 | ///
9 | /// Policy name for retrieving notification addresses for an organization
10 | ///
11 | public const string SupportDashboardAccess = "AltinnProfileSupportAdmin";
12 |
13 | ///
14 | /// Policy name for platform access
15 | ///
16 | public const string PlatformAccess = "PlatformAccess";
17 |
18 | ///
19 | /// Policy name for reading organization notification addresses
20 | ///
21 | public const string OrgNotificationAddress_Read = "OrgNotificationAddress_Read";
22 |
23 | ///
24 | /// Policy name for writing organization notification addresses
25 | ///
26 | public const string OrgNotificationAddress_Write = "OrgNotificationAddress_Write";
27 |
28 | ///
29 | /// Policy name for checking that a user has access to a given party
30 | ///
31 | public const string UserPartyAccess = "UserId_Party_Access";
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM mcr.microsoft.com/dotnet/sdk:9.0.307-alpine3.22@sha256:512f8347b0d2f9848f099a8c31be07286955ceea337cadb1114057ed0b15862f AS build
2 | WORKDIR /app
3 |
4 | COPY src/Altinn.Profile/*.csproj ./src/Altinn.Profile/
5 | COPY src/Altinn.Profile.Models/*.csproj ./src/Altinn.Profile.Models/
6 | COPY src/Altinn.Profile.Core/*.csproj ./src/Altinn.Profile.Core/
7 | COPY src/Altinn.Profile.Integrations/*.csproj ./src/Altinn.Profile.Integrations/
8 |
9 | RUN dotnet restore ./src/Altinn.Profile/Altinn.Profile.csproj
10 |
11 | COPY src ./src
12 | RUN dotnet publish -c Release -o /app_output ./src/Altinn.Profile/Altinn.Profile.csproj
13 |
14 | FROM mcr.microsoft.com/dotnet/aspnet:9.0.11-alpine3.22@sha256:be36809e32840cf9fcbf1a3366657c903e460d3c621d6593295a5e5d02268a0d AS final
15 | EXPOSE 5030
16 | WORKDIR /app
17 |
18 | COPY --from=build /app_output .
19 | COPY --from=build /app/src/Altinn.Profile.Integrations/Migration ./Migration
20 |
21 | # setup the user and group
22 | # the user will have no password, using shell /bin/false and using the group dotnet
23 | RUN addgroup -g 3000 dotnet && adduser -u 1000 -G dotnet -D -s /bin/false dotnet
24 |
25 | # update permissions of files if neccessary before becoming dotnet user
26 | USER dotnet
27 | RUN mkdir /tmp/logtelemetry
28 |
29 | ENTRYPOINT ["dotnet", "Altinn.Profile.dll"]
30 |
--------------------------------------------------------------------------------
/test/Altinn.Profile.Tests/Testdata/NotificationAddressChangesLog/changes_1.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Kofuvi digital alert address fragments as json",
3 | "updated": "2025-02-24T09:42:58.271142983Z",
4 | "nextPage": "http://someurl.no/next",
5 | "entries": [
6 | {
7 | "title": "test@test.no",
8 | "id": "27ae0c8bea1f4f02a974c10429c32758",
9 | "updated": "2018-01-15T10:01:14Z",
10 | "isdeleted": false,
11 | "content": "{\"Kontaktinformasjon\":{\"digitalVarslingsinformasjon\":{\"epostadresse\":{\"navn\":\"test@test.no\",\"domenenavn\":\"test.no\",\"brukernavn\":\"test\"}},\"identifikator\":\"27ae0c8bea1f4f02a974c10429c32758\",\"kontaktinformasjonForEnhet\":{\"enhetsidentifikator\":{\"verdi\":\"920212345\",\"type\":\"ORGANISASJONSNUMMER\"}}}}"
12 | },
13 | {
14 | "title": "4798765432",
15 | "id": "37ab4733648c4d5b825a813c6e1ace70",
16 | "updated": "2025-01-16T09:07:11Z",
17 | "isdeleted": false,
18 | "content": "{\"Kontaktinformasjon\":{\"digitalVarslingsinformasjon\":{\"mobiltelefon\":{\"navn\":\"4798765432\",\"internasjonaltPrefiks\":\"47\",\"nasjonaltNummer\":\"98765432\"}},\"identifikator\":\"37ab4733648c4d5b825a813c6e1ace70\",\"kontaktinformasjonForEnhet\":{\"enhetsidentifikator\":{\"verdi\":\"123456789\",\"type\":\"ORGANISASJONSNUMMER\"}}}}"
19 | }
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Entities/OrganizationDE.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations;
2 | using System.ComponentModel.DataAnnotations.Schema;
3 | using Microsoft.EntityFrameworkCore;
4 |
5 | namespace Altinn.Profile.Integrations.Entities
6 | {
7 | ///
8 | /// Class for organizations connection id and orgNumber
9 | ///
10 | [Table("organizations", Schema = "organization_notification_address")]
11 | [Index(nameof(RegistryOrganizationNumber), IsUnique = true)]
12 | public class OrganizationDE
13 | {
14 | ///
15 | /// OrganizationNumber of the organization
16 | ///
17 | [Required]
18 | [StringLength(9)]
19 | public required string RegistryOrganizationNumber { get; set; }
20 |
21 | ///
22 | /// The incremental id of the organization in the database
23 | ///
24 | [Required]
25 | public int RegistryOrganizationId { get; set; }
26 |
27 | ///
28 | /// A collection of notification addresses associated with this organization
29 | ///
30 | [InverseProperty("Organization")]
31 | public List NotificationAddresses { get; set; } = new List();
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Repositories/A2Sync/IChangelogSyncMetadataRepository.cs:
--------------------------------------------------------------------------------
1 | using Altinn.Profile.Integrations.SblBridge.Changelog;
2 |
3 | namespace Altinn.Profile.Integrations.Repositories.A2Sync;
4 |
5 | ///
6 | /// Defines a repository for operations related to Changelog sync metadata.
7 | ///
8 | /// Can be removed when Altinn2 is decommissioned
9 | public interface IChangelogSyncMetadataRepository
10 | {
11 | ///
12 | /// Asynchronously retrieves the latest sync timestamp from the metadata repository.
13 | ///
14 | ///
15 | /// A task that represents the asynchronous operation.
16 | ///
17 | Task GetLatestSyncTimestampAsync(DataType dataType, CancellationToken cancellationToken);
18 |
19 | ///
20 | /// Asynchronously updates the latest sync timestamp in the metadata repository.
21 | ///
22 | /// The new timestamp for last sync.
23 | /// The type of data for which the sync timestamp is being updated.
24 | ///
25 | /// A task that represents the asynchronous operation.
26 | ///
27 | Task UpdateLatestChangeTimestampAsync(DateTime updated, DataType dataType);
28 | }
29 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/SblBridge/InternalServerErrorException.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Integrations.SblBridge
2 | {
3 | ///
4 | /// Represents an exception that occurs when an internal server error is encountered.
5 | ///
6 | public class InternalServerErrorException : Exception
7 | {
8 | ///
9 | /// Initializes a new instance of the class with a specified error message.
10 | ///
11 | /// The message that describes the error.
12 | public InternalServerErrorException(string message)
13 | : base(message)
14 | {
15 | }
16 |
17 | ///
18 | /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception.
19 | ///
20 | /// The message that describes the error.
21 | /// The exception that is the cause of the current exception.
22 | public InternalServerErrorException(string message, Exception innerException)
23 | : base(message, innerException)
24 | {
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/test/Altinn.Profile.Tests/IntegrationTests/Mocks/Authentication/JwtCookiePostConfigureOptionsStub.cs:
--------------------------------------------------------------------------------
1 | using AltinnCore.Authentication.JwtCookie;
2 |
3 | using Microsoft.AspNetCore.Authentication.Cookies;
4 | using Microsoft.Extensions.Options;
5 |
6 | namespace Altinn.Profile.Tests.IntegrationTests.Mocks.Authentication;
7 |
8 | ///
9 | /// Represents a stub for the class to be used in integration tests.
10 | ///
11 | public class JwtCookiePostConfigureOptionsStub : IPostConfigureOptions
12 | {
13 | ///
14 | public void PostConfigure(string name, JwtCookieOptions options)
15 | {
16 | if (string.IsNullOrEmpty(options.JwtCookieName))
17 | {
18 | options.JwtCookieName = JwtCookieDefaults.CookiePrefix + name;
19 | }
20 |
21 | if (options.CookieManager == null)
22 | {
23 | options.CookieManager = new ChunkingCookieManager();
24 | }
25 |
26 | if (!string.IsNullOrEmpty(options.MetadataAddress) && !options.MetadataAddress.EndsWith('/'))
27 | {
28 | options.MetadataAddress += "/";
29 | }
30 |
31 | options.MetadataAddress += ".well-known/openid-configuration";
32 | options.ConfigurationManager = new ConfigurationManagerStub();
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Core/User.ContactPoints/IUserContactPointsService.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Core.User.ContactPoints;
2 |
3 | ///
4 | /// Class describing the methods required for user contact point service
5 | ///
6 | public interface IUserContactPointsService
7 | {
8 | ///
9 | /// Method for retriveing contact points for a user
10 | ///
11 | /// A list of national identity numbers to lookup contact points for
12 | /// A token to monitor for cancellation requests.
13 | /// The users' contact points and reservation status or a boolean if failure.
14 | Task GetContactPoints(List nationalIdentityNumbers, CancellationToken cancellationToken);
15 |
16 | ///
17 | /// Method for retriveing information about the availability of contact points for a user
18 | ///
19 | /// A list of national identity numbers to look up availability for
20 | /// Information on the existense of the users' contact points and reservation status or a boolean if failure.
21 | Task GetContactPointAvailability(List nationalIdentityNumbers);
22 | }
23 |
--------------------------------------------------------------------------------
/src/Altinn.Profile/Models/UserProfileLookup.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Altinn.Profile.Models;
4 |
5 | ///
6 | /// Input model for internal UserProfile lookup requests, where one of the lookup identifiers available must be set for performing the lookup request:
7 | /// UserId (from Altinn 2 Authn UserProfile)
8 | /// Username (from Altinn 2 Authn UserProfile)
9 | /// SSN/Dnr (from Freg)
10 | /// Uuid (from Altinn 2 Party/UserProfile implementation will be added later)
11 | ///
12 | public class UserProfileLookup
13 | {
14 | ///
15 | /// Gets or sets the users UserId if the lookup is to be performed based on this identifier
16 | ///
17 | public int? UserId { get; set; }
18 |
19 | ///
20 | /// Gets or sets the users UserUuid if the lookup is to be performed based on this identifier
21 | ///
22 | public Guid? UserUuid { get; set; }
23 |
24 | ///
25 | /// Gets or sets the users Username if the lookup is to be performed based on this identifier
26 | ///
27 | public string Username { get; set; }
28 |
29 | ///
30 | /// Gets or sets the users social security number or d-number from Folkeregisteret if the lookup is to be performed based on this identifier
31 | ///
32 | public string Ssn { get; set; }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Core/User.ContactPoints/UserContactPoints.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Core.User.ContactPoints;
2 |
3 | ///
4 | /// Class describing the contact points for a user
5 | ///
6 | public class UserContactPoints
7 | {
8 | ///
9 | /// Gets or sets the ID of the user
10 | ///
11 | public int UserId { get; set; } = 0;
12 |
13 | ///
14 | /// Gets or sets the national identity number of the user
15 | ///
16 | public string? NationalIdentityNumber { get; set; }
17 |
18 | ///
19 | /// Gets or sets a boolean indicating whether the user has reserved themselves from electronic communication
20 | ///
21 | public bool IsReserved { get; set; }
22 |
23 | ///
24 | /// Gets or sets the mobile number
25 | ///
26 | public string? MobileNumber { get; set; }
27 |
28 | ///
29 | /// Gets or sets the email address
30 | ///
31 | public string? Email { get; set; }
32 | }
33 |
34 | ///
35 | /// A list representation of
36 | ///
37 | public class UserContactPointsList
38 | {
39 | ///
40 | /// A list containing contact points for users
41 | ///
42 | public List ContactPointsList { get; set; } = [];
43 | }
44 |
--------------------------------------------------------------------------------
/src/Altinn.Profile/Models/DashboardNotificationAddressResponse.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Altinn.Profile.Models
4 | {
5 | ///
6 | /// Represents a notification address
7 | ///
8 | public class DashboardNotificationAddressResponse
9 | {
10 | ///
11 | ///
12 | ///
13 | public int NotificationAddressId { get; set; }
14 |
15 | ///
16 | /// Country code for phone number
17 | ///
18 | public string CountryCode { get; set; }
19 |
20 | ///
21 | /// Email address
22 | ///
23 | public string Email { get; set; }
24 |
25 | ///
26 | /// Phone number
27 | ///
28 | public string Phone { get; set; }
29 |
30 | ///
31 | /// Source organization number
32 | ///
33 | public string SourceOrgNumber { get; set; }
34 |
35 | ///
36 | /// Requested organization number
37 | ///
38 | public string RequestedOrgNumber { get; set; }
39 |
40 | ///
41 | /// Last changed timestamp
42 | ///
43 | public DateTime? LastChanged { get; set; }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/test/Altinn.Profile.Tests/Testdata/UserProfile/OrstaECUser.json:
--------------------------------------------------------------------------------
1 | {
2 | "UserId": 2001072,
3 | "UserUUID": "34b62493-84c6-4794-bf91-6cd23ead761c",
4 | "UserType": 3,
5 | "UserName": "OrstaECUser",
6 | "ExternalIdentity": "",
7 | "PhoneNumber": null,
8 | "Email": null,
9 | "PartyId": 50005545,
10 | "Party": {
11 | "PartyTypeName": 2,
12 | "SSN": "",
13 | "OrgNumber": "910459880",
14 | "Person": null,
15 | "Organization": {
16 | "OrgNumber": "910459880",
17 | "Name": "ORSTA OG HEGGEDAL",
18 | "UnitType": "AS",
19 | "TelephoneNumber": "12345678",
20 | "MobileNumber": "99999999",
21 | "FaxNumber": "12345679",
22 | "EMailAddress": "test@test.test",
23 | "InternetAddress": null,
24 | "MailingAddress": null,
25 | "MailingPostalCode": "",
26 | "MailingPostalCity": "",
27 | "BusinessAddress": null,
28 | "BusinessPostalCode": "",
29 | "BusinessPostalCity": "",
30 | "UnitStatus": "N"
31 | },
32 | "PartyId": 50005545,
33 | "PartyUUID": "ec061efa-4c2a-4dbd-87f5-bcb59cdeaf91",
34 | "UnitType": "AS",
35 | "Name": "ORSTA OG HEGGEDAL ",
36 | "IsDeleted": false,
37 | "OnlyHierarchyElementWithNoAccess": false,
38 | "ChildParties": null
39 | },
40 | "ProfileSettingPreference": {
41 | "Language": "nb",
42 | "PreSelectedPartyId": 0,
43 | "DoNotPromptForParty": false
44 | }
45 | }
--------------------------------------------------------------------------------
/src/Altinn.Profile.Core/Unit.ContactPoints/IUnitContactPointsService.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Core.Unit.ContactPoints;
2 |
3 | ///
4 | /// Class describing the methods required for user contact point service
5 | ///
6 | public interface IUnitContactPointsService
7 | {
8 | ///
9 | /// Method for retrieving user registered contact points for a unit
10 | ///
11 | /// A lookup object containing a list of organization numbers and the resource to lookup contact points for
12 | /// The users' contact points and reservation status or a boolean if failure.
13 | Task> GetUserRegisteredContactPoints(UnitContactPointLookup lookup);
14 |
15 | ///
16 | /// Method for retrieving user registered contact points for a unit
17 | ///
18 | /// Array of organization numbers to lookup contact points for
19 | /// The resource ID to filter the contact points by
20 | /// Cancellation token to cancel the operation
21 | /// A list of contact points for the specified organizations and resource
22 | Task GetUserRegisteredContactPoints(string[] orgNumbers, string resourceId, CancellationToken cancellationToken);
23 | }
24 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Core/User.PartyGroups/IPartyGroupService.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Core.PartyGroups
2 | {
3 | ///
4 | /// Interface for the party group service
5 | ///
6 | public interface IPartyGroupService
7 | {
8 | ///
9 | /// Retrieves all groups for a given user. If none are found, an empty list is returned.
10 | ///
11 | Task> GetGroupsForAUser(int userId, CancellationToken cancellationToken);
12 |
13 | ///
14 | /// Gets the favorite parties for a given user. If no favorites are added, an empty group will be returned.
15 | ///
16 | Task GetFavorites(int userId, CancellationToken cancellationToken);
17 |
18 | ///
19 | /// Mark a party as a favorite for the current user
20 | ///
21 | /// A representing the result with a boolean telling whether the party was added as a favorite or if it already existed.
22 | Task AddPartyToFavorites(int userId, Guid partyUuid, CancellationToken cancellationToken);
23 |
24 | ///
25 | /// Delete the given party from a users list of favorites.
26 | ///
27 | Task DeleteFromFavorites(int userId, Guid partyUuid, CancellationToken cancellationToken);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Entities/ChangelogSyncMetadata.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations;
2 | using System.ComponentModel.DataAnnotations.Schema;
3 |
4 | using DataType = Altinn.Profile.Integrations.SblBridge.Changelog.DataType;
5 |
6 | namespace Altinn.Profile.Integrations.Entities
7 | {
8 | ///
9 | /// Table of metadata for last changelog sync batch
10 | ///
11 | /// Can be removed when Altinn2 is decommissioned
12 | [Table("changelog_sync_metadata", Schema = "lease")]
13 | public class ChangelogSyncMetadata
14 | {
15 | ///
16 | /// An identifier for this table
17 | ///
18 | [StringLength(32)]
19 | [Required]
20 | public required string LastChangedId { get; set; }
21 |
22 | ///
23 | /// What dataType this metadata is for.
24 | ///
25 | [Required]
26 | public DataType DataType { get; set; }
27 |
28 | ///
29 | /// The number of ticks (100 nanoseconds each) representing the last change time.
30 | /// This is needed because DateTime in C# supports up to 100-nanosecond precision 10^-7
31 | /// PostgreSQL does not support nanosecond precision, so precision 10^-6
32 | ///
33 | [Required]
34 | public long LastChangeTicks { get; set; }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/test/k6/src/api/org-notification-addresses.js:
--------------------------------------------------------------------------------
1 | import http from "k6/http";
2 | import * as config from "../config.js";
3 | import * as apiHelpers from "../apiHelpers.js";
4 |
5 | export function getOrgNotificationAddresses(token, orgNo) {
6 | const endpoint = config.profileUrl.organization(orgNo);
7 |
8 | const params = apiHelpers.buildHeaderWithBearer(token);
9 |
10 | return http.get(endpoint, params);
11 | }
12 |
13 | export function addOrgNotificationAddresses(token, orgNo, address) {
14 | const endpoint = config.profileUrl.organization(orgNo);
15 |
16 | const params = apiHelpers.buildHeaderWithBearerAndContentType(token);
17 |
18 | const requestBody = JSON.stringify(address);
19 | return http.post(endpoint, requestBody, params);
20 | }
21 |
22 | export function updateOrgNotificationAddresses(token, orgNo, address, addressId) {
23 | const endpoint = config.profileUrl.organization(orgNo)+'/'+addressId;
24 |
25 | const params = apiHelpers.buildHeaderWithBearerAndContentType(token);
26 | const requestBody = JSON.stringify(address);
27 | return http.put(endpoint, requestBody, params);
28 | }
29 |
30 | export function removeOrgNotificationAddresses(token, orgNo, addressId) {
31 | const endpoint = config.profileUrl.organization(orgNo)+'/'+addressId;
32 |
33 | const params = apiHelpers.buildHeaderWithBearerAndContentType(token);
34 |
35 | return http.del(endpoint, null, params);
36 | }
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Migration/v0.07/01-setup-schema.sql:
--------------------------------------------------------------------------------
1 | CREATE SCHEMA IF NOT EXISTS user_preferences;
2 |
3 | -- Grant access to the schema
4 | GRANT ALL ON SCHEMA user_preferences TO platform_profile_admin;
5 | GRANT USAGE ON SCHEMA user_preferences TO platform_profile;
6 |
7 | CREATE TABLE user_preferences.groups (
8 | group_id integer GENERATED BY DEFAULT AS IDENTITY,
9 | name text NOT NULL,
10 | user_id integer NOT NULL,
11 | is_favorite boolean NOT NULL,
12 | CONSTRAINT group_id_pkey PRIMARY KEY (group_id)
13 | );
14 |
15 | CREATE TABLE user_preferences.party_group_association (
16 | association_id integer GENERATED BY DEFAULT AS IDENTITY,
17 | group_id integer NOT NULL,
18 | party_id integer NOT NULL,
19 | created timestamp with time zone NOT NULL DEFAULT (now()),
20 | CONSTRAINT association_id_pkey PRIMARY KEY (association_id),
21 | CONSTRAINT fk_group_id FOREIGN KEY (group_id) REFERENCES user_preferences.groups (group_id) ON DELETE CASCADE
22 | );
23 |
24 | CREATE INDEX ix_groups_user_id ON user_preferences.groups (user_id);
25 |
26 | CREATE INDEX ix_party_group_association_group_id ON user_preferences.party_group_association (group_id);
27 |
28 | -- Grant access to the tables
29 | GRANT DELETE, INSERT, SELECT, UPDATE ON TABLE user_preferences.party_group_association TO platform_profile;
30 |
31 | GRANT DELETE, INSERT, SELECT, UPDATE ON TABLE user_preferences.groups TO platform_profile;
32 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/OrganizationNotificationAddressRegistry/Models/EntryContent.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace Altinn.Profile.Integrations.OrganizationNotificationAddressRegistry.Models;
4 |
5 | ///
6 | /// The content of the notification address.
7 | ///
8 | public record EntryContent
9 | {
10 | ///
11 | /// The content of the contact point.
12 | ///
13 | [JsonPropertyName("Kontaktinformasjon")]
14 | public ContactPointModel? ContactPoint { get; init; }
15 |
16 | ///
17 | /// The content of the contact point.
18 | ///
19 | public record ContactPointModel
20 | {
21 | ///
22 | /// The identificator of the contact point.
23 | ///
24 | [JsonPropertyName("identifikator")]
25 | public string? Id { get; init; }
26 |
27 | ///
28 | /// Digital contact information such as email or phone number.
29 | ///
30 | [JsonPropertyName("digitalVarslingsinformasjon")]
31 | public DigitalContactPointModel? DigitalContactPoint { get; init; }
32 |
33 | ///
34 | /// Contact information for the organizational unit.
35 | ///
36 | [JsonPropertyName("kontaktinformasjonForEnhet")]
37 | public UnitContactInfoModel? UnitContactInfo { get; init; }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Entities/MailboxSupplier.cs:
--------------------------------------------------------------------------------
1 | // This file has been auto generated by EF Core Power Tools.
2 | #nullable disable
3 |
4 | using System.ComponentModel.DataAnnotations;
5 | using System.ComponentModel.DataAnnotations.Schema;
6 |
7 | using Microsoft.EntityFrameworkCore;
8 |
9 | namespace Altinn.Profile.Integrations.Entities;
10 |
11 | ///
12 | /// Represents a mailbox supplier in the contact and reservation schema.
13 | ///
14 | [Table("mailbox_supplier", Schema = "contact_and_reservation")]
15 | [Index("OrgNumberAk", Name = "unique_org_number_ak", IsUnique = true)]
16 | public partial class MailboxSupplier
17 | {
18 | ///
19 | /// Gets or sets the unique identifier for the mailbox supplier.
20 | ///
21 | [Key]
22 | [Column("mailbox_supplier_id")]
23 | public int MailboxSupplierId { get; set; }
24 |
25 | ///
26 | /// Gets or sets the organization number of the mailbox supplier.
27 | ///
28 | [Required]
29 | [Column("org_number_ak")]
30 | [StringLength(9)]
31 | public string OrgNumberAk { get; set; }
32 |
33 | ///
34 | /// Gets or sets the collection of people associated with the mailbox supplier.
35 | ///
36 | [InverseProperty("MailboxSupplierIdFkNavigation")]
37 | public virtual ICollection People { get; set; } = new List();
38 | }
39 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/OrganizationNotificationAddressRegistry/Models/RegistryResponse.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace Altinn.Profile.Integrations.OrganizationNotificationAddressRegistry.Models
4 | {
5 | ///
6 | /// Used for sync return values
7 | ///
8 | public record RegistryResponse
9 | {
10 | ///
11 | /// Sync status, eg "OK", "VALIDATION_ERROR"
12 | ///
13 | [JsonPropertyName("status")]
14 | public string? Status { get; init; }
15 |
16 | ///
17 | /// A value indicating whether the request was handled as a success or failure
18 | ///
19 | [JsonPropertyName("boolResult")]
20 | public bool? BoolResult { get; init; }
21 |
22 | ///
23 | /// Id of the address in the registry of the response
24 | ///
25 | [JsonPropertyName("addressId")]
26 | public string? AddressID { get; init; }
27 |
28 | ///
29 | /// TraceID of the response
30 | ///
31 | [JsonPropertyName("traceId")]
32 | public string? TraceId { get; init; }
33 |
34 | ///
35 | /// Details of the error if there is a validation error
36 | ///
37 | [JsonPropertyName("details")]
38 | public string? Details { get; init; }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/test/Altinn.Profile.Tests/Testdata/UserProfile/2001606.json:
--------------------------------------------------------------------------------
1 | {
2 | "UserId": 2001606,
3 | "UserUUID": "1a131a3b-c6c9-4572-86fd-dfe36c3de06a",
4 | "UserType": 1,
5 | "UserName": "",
6 | "PhoneNumber": "99319999",
7 | "Email": "tuva@landro.no",
8 | "PartyId": 50002113,
9 | "Party": {
10 | "PartyTypeName": 1,
11 | "SSN": "01025101037",
12 | "OrgNumber": "",
13 | "Person": {
14 | "SSN": "01025101037",
15 | "Name": "TUVA LANDRO",
16 | "FirstName": "TUVA",
17 | "MiddleName": "",
18 | "LastName": "LANDRO",
19 | "TelephoneNumber": "",
20 | "MobileNumber": "",
21 | "MailingAddress": " Teisenveien 15 0666 OSLO",
22 | "MailingPostalCode": "0666",
23 | "MailingPostalCity": "OSLO",
24 | "AddressMunicipalNumber": "",
25 | "AddressMunicipalName": "",
26 | "AddressStreetName": "",
27 | "AddressHouseNumber": "",
28 | "AddressHouseLetter": "",
29 | "AddressPostalCode": "0666",
30 | "AddressCity": "OSLO"
31 | },
32 | "Organization": null,
33 | "PartyId": 50002113,
34 | "PartyUUID": "1a131a3b-c6c9-4572-86fd-dfe36c3de06a",
35 | "UnitType": null,
36 | "Name": "TUVA LANDRO",
37 | "IsDeleted": false,
38 | "OnlyHierarchyElementWithNoAccess": false,
39 | "ChildParties": null
40 | },
41 | "ProfileSettingPreference": {
42 | "LanguageType": "nb",
43 | "PreSelectedPartyId": 0,
44 | "DoNotPromptForParty": false
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/test/Altinn.Profile.Tests/Testdata/UserProfile/2001607.json:
--------------------------------------------------------------------------------
1 | {
2 | "UserId": 2001607,
3 | "UserUUID": "db8eafc0-6056-43b5-b047-4adcd84f659c",
4 | "UserType": 1,
5 | "UserName": "",
6 | "PhoneNumber": "92019999",
7 | "Email": "tuva@business.com",
8 | "PartyId": 50002113,
9 | "Party": {
10 | "PartyTypeName": 1,
11 | "SSN": "01025101038",
12 | "OrgNumber": "",
13 | "Person": {
14 | "SSN": "01025101038",
15 | "Name": "TUVA LANDRO",
16 | "FirstName": "TUVA",
17 | "MiddleName": "",
18 | "LastName": "LANDRO",
19 | "TelephoneNumber": "",
20 | "MobileNumber": "",
21 | "MailingAddress": " Teisenveien 15 0666 OSLO",
22 | "MailingPostalCode": "0666",
23 | "MailingPostalCity": "OSLO",
24 | "AddressMunicipalNumber": "",
25 | "AddressMunicipalName": "",
26 | "AddressStreetName": "",
27 | "AddressHouseNumber": "",
28 | "AddressHouseLetter": "",
29 | "AddressPostalCode": "0666",
30 | "AddressCity": "OSLO"
31 | },
32 | "Organization": null,
33 | "PartyId": 50002113,
34 | "PartyUUID": "db8eafc0-6056-43b5-b047-4adcd84f659c",
35 | "UnitType": null,
36 | "Name": "TUVA LANDRO",
37 | "IsDeleted": false,
38 | "OnlyHierarchyElementWithNoAccess": false,
39 | "ChildParties": null
40 | },
41 | "ProfileSettingPreference": {
42 | "LanguageType": "ru",
43 | "PreSelectedPartyId": 0,
44 | "DoNotPromptForParty": false
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/ContactRegister/ContactRegisterChangesLog.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Immutable;
2 | using System.Text.Json.Serialization;
3 |
4 | namespace Altinn.Profile.Integrations.ContactRegister;
5 |
6 | ///
7 | /// Represents the changes to a person's contact preferences from the contact register.
8 | ///
9 | public record ContactRegisterChangesLog
10 | {
11 | ///
12 | /// Gets the collection of snapshots representing the changes to a person's contact preferences.
13 | ///
14 | [JsonPropertyName("list")]
15 | public IImmutableList? ContactPreferencesSnapshots { get; init; }
16 |
17 | ///
18 | /// Gets the ending change identifier, which indicates the point at which the system should stop retrieving changes.
19 | ///
20 | [JsonPropertyName("tilEndringsId")]
21 | public long? EndingIdentifier { get; init; }
22 |
23 | ///
24 | /// Gets the most recent change identifier, which represents the last change that was processed by the system.
25 | ///
26 | [JsonPropertyName("sisteEndringsId")]
27 | public long? LatestChangeIdentifier { get; init; }
28 |
29 | ///
30 | /// Gets the starting change identifier indicating the point from which the system begins retrieving changes.
31 | ///
32 | [JsonPropertyName("fraEndringsId")]
33 | public long? StartingIdentifier { get; init; }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Altinn.Profile/Models/PersonContactDetails.cs:
--------------------------------------------------------------------------------
1 | #nullable enable
2 |
3 | using System.Text.Json.Serialization;
4 |
5 | namespace Altinn.Profile.Models;
6 |
7 | ///
8 | /// Represents the contact details for a single person, including the national identity number, mobile phone number, email address, language preference, and an opt-out status for being contacted.
9 | ///
10 | public record PersonContactDetails
11 | {
12 | ///
13 | /// Gets the email address of the person.
14 | ///
15 | [JsonPropertyName("emailAddress")]
16 | public string? EmailAddress { get; init; }
17 |
18 | ///
19 | /// Gets a value indicating whether the person has opted out of being contacted.
20 | ///
21 | [JsonPropertyName("reservation")]
22 | public bool? IsReserved { get; init; }
23 |
24 | ///
25 | /// Gets the language code preferred by the person for communication.
26 | ///
27 | [JsonPropertyName("languageCode")]
28 | public string? LanguageCode { get; init; }
29 |
30 | ///
31 | /// Gets the mobile phone number of the person.
32 | ///
33 | [JsonPropertyName("mobilePhoneNumber")]
34 | public string? MobilePhoneNumber { get; init; }
35 |
36 | ///
37 | /// Gets the national identity number of the person.
38 | ///
39 | [JsonPropertyName("nationalIdentityNumber")]
40 | public required string NationalIdentityNumber { get; init; }
41 | }
42 |
--------------------------------------------------------------------------------
/test/Altinn.Profile.Tests/Testdata/UserProfile/4c3b4909-eb17-45d5-bde1-256e065e196a.json:
--------------------------------------------------------------------------------
1 | {
2 | "UserId": 20000006,
3 | "UserUUID": "4c3b4909-eb17-45d5-bde1-256e065e196a",
4 | "UserType": 1,
5 | "UserName": "",
6 | "ExternalIdentity": "",
7 | "PhoneNumber": null,
8 | "Email": null,
9 | "PartyId": 50002114,
10 | "Party": {
11 | "PartyTypeName": 1,
12 | "SSN": "01025161013",
13 | "OrgNumber": "",
14 | "Person": {
15 | "SSN": "01025161013",
16 | "Name": "ELENA FJAR",
17 | "FirstName": "ELENA",
18 | "MiddleName": "",
19 | "LastName": "FJAR",
20 | "TelephoneNumber": "",
21 | "MobileNumber": "",
22 | "MailingAddress": " Søreidåsen 3 5252 SØREIDGREND",
23 | "MailingPostalCode": "5252",
24 | "MailingPostalCity": "SØREIDGREND",
25 | "AddressMunicipalNumber": "",
26 | "AddressMunicipalName": "",
27 | "AddressStreetName": "",
28 | "AddressHouseNumber": "",
29 | "AddressHouseLetter": "",
30 | "AddressPostalCode": "5252",
31 | "AddressCity": "SØREIDGREND",
32 | "DateOfDeath": null
33 | },
34 | "Organization": null,
35 | "PartyId": 50002114,
36 | "PartyUUID": "4c3b4909-eb17-45d5-bde1-256e065e196a",
37 | "UnitType": null,
38 | "Name": "ELENA FJAR",
39 | "IsDeleted": false,
40 | "OnlyHierarchyElementWithNoAccess": false,
41 | "ChildParties": null
42 | },
43 | "ProfileSettingPreference": {
44 | "Language": "nn",
45 | "PreSelectedPartyId": 0,
46 | "DoNotPromptForParty": false
47 | }
48 | }
--------------------------------------------------------------------------------
/src/Altinn.Profile.Core/ProfessionalNotificationAddresses/UserPartyContactInfoWithIdentity.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Core.ProfessionalNotificationAddresses;
2 |
3 | ///
4 | /// Extended data model for user party contact info that includes user identity information (SSN and Name).
5 | /// Used by Dashboard endpoints to display contact information with user identity.
6 | /// This also includes the organization number the user is acting on behalf of.
7 | ///
8 | public class UserPartyContactInfoWithIdentity
9 | {
10 | ///
11 | /// The national identity number (SSN/D-number) of the user
12 | ///
13 | public string? NationalIdentityNumber { get; set; }
14 |
15 | ///
16 | /// The name of the user
17 | ///
18 | public required string Name { get; set; }
19 |
20 | ///
21 | /// The email address. May be null if no email address is set.
22 | ///
23 | public string? EmailAddress { get; set; }
24 |
25 | ///
26 | /// The phone number. May be null if no phone number is set.
27 | ///
28 | public string? PhoneNumber { get; set; }
29 |
30 | ///
31 | /// The organization number the user is acting on behalf of.
32 | /// May be null if no organization number is set.
33 | ///
34 | public string? OrganizationNumber { get; set; }
35 |
36 | ///
37 | /// Date of last change (UTC)
38 | ///
39 | public DateTime LastChanged { get; set; }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Altinn.Profile/Models/ProfileSettingsPatchRequest.cs:
--------------------------------------------------------------------------------
1 | #nullable enable
2 | using System;
3 |
4 | using Altinn.Profile.Core.Utils;
5 |
6 | namespace Altinn.Profile.Models
7 | {
8 | ///
9 | /// Represents user-specific portal settings and preferences.
10 | ///
11 | public class ProfileSettingsPatchRequest
12 | {
13 | ///
14 | /// The language the user has selected in Altinn portal.
15 | ///
16 | public string? Language { get; set; }
17 |
18 | ///
19 | /// Indicates whether the user should not be prompted for party selection.
20 | /// Can be set without using PreselectedPartyUuid.
21 | ///
22 | public bool? DoNotPromptForParty { get; set; }
23 |
24 | ///
25 | /// The UUID of the preselected party. Optional.
26 | ///
27 | public Optional PreselectedPartyUuid { get; set; } = new();
28 |
29 | ///
30 | /// Indicates whether client units should be shown.
31 | ///
32 | public bool? ShowClientUnits { get; set; }
33 |
34 | ///
35 | /// Indicates whether sub-entities should be shown.
36 | ///
37 | public bool? ShouldShowSubEntities { get; set; }
38 |
39 | ///
40 | /// Indicates whether deleted entities should be shown.
41 | ///
42 | public bool? ShouldShowDeletedEntities { get; set; }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/test/Altinn.Profile.Tests/Testdata/UserProfile/cc86d2c7-1695-44b0-8e82-e633243fdf31.json:
--------------------------------------------------------------------------------
1 | {
2 | "UserId": 20000009,
3 | "UserUUID": "cc86d2c7-1695-44b0-8e82-e633243fdf31",
4 | "UserType": 1,
5 | "UserName": "",
6 | "ExternalIdentity": "",
7 | "PhoneNumber": null,
8 | "Email": null,
9 | "PartyId": 50002117,
10 | "Party": {
11 | "PartyTypeName": 1,
12 | "SSN": "01032730090",
13 | "OrgNumber": "",
14 | "Person": {
15 | "SSN": "01032730090",
16 | "Name": "LEO WILHELMSEN",
17 | "FirstName": "LEO",
18 | "MiddleName": "",
19 | "LastName": "WILHELMSEN",
20 | "TelephoneNumber": "",
21 | "MobileNumber": "",
22 | "MailingAddress": " Farrisvegen 13 3948 PORSGRUNN",
23 | "MailingPostalCode": "3948",
24 | "MailingPostalCity": "PORSGRUNN",
25 | "AddressMunicipalNumber": "",
26 | "AddressMunicipalName": "",
27 | "AddressStreetName": "",
28 | "AddressHouseNumber": "",
29 | "AddressHouseLetter": "",
30 | "AddressPostalCode": "3948",
31 | "AddressCity": "PORSGRUNN",
32 | "DateOfDeath": null
33 | },
34 | "Organization": null,
35 | "PartyId": 50002117,
36 | "PartyUUID": "cc86d2c7-1695-44b0-8e82-e633243fdf31",
37 | "UnitType": null,
38 | "Name": "LEO WILHELMSEN",
39 | "IsDeleted": false,
40 | "OnlyHierarchyElementWithNoAccess": false,
41 | "ChildParties": null
42 | },
43 | "ProfileSettingPreference": {
44 | "Language": "nb",
45 | "PreSelectedPartyId": 0,
46 | "DoNotPromptForParty": false
47 | }
48 | }
--------------------------------------------------------------------------------
/test/Altinn.Profile.Tests/JWTValidationCert.cer:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIID/zCCAuegAwIBAgIQF2ov3ZZUmJVKtoz0a1fabDANBgkqhkiG9w0BAQsFADB/
3 | MRMwEQYKCZImiZPyLGQBGRYDY29tMRcwFQYKCZImiZPyLGQBGRYHY29udG9zbzEU
4 | MBIGCgmSJomT8ixkARkWBGNvcnAxFTATBgNVBAsMDFVzZXJBY2NvdW50czEiMCAG
5 | A1UEAwwZQWx0aW5uIFBsYXRmb3JtIFVuaXQgdGVzdDAgFw0yMDA0MTQwOTMwMTda
6 | GA8yMTIwMDQxNDA5NDAxOFowfzETMBEGCgmSJomT8ixkARkWA2NvbTEXMBUGCgmS
7 | JomT8ixkARkWB2NvbnRvc28xFDASBgoJkiaJk/IsZAEZFgRjb3JwMRUwEwYDVQQL
8 | DAxVc2VyQWNjb3VudHMxIjAgBgNVBAMMGUFsdGlubiBQbGF0Zm9ybSBVbml0IHRl
9 | c3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDCAKc+q5jbYFyQFxM1
10 | xU3v0N477ppnMu03K8qlEkX0+yffRHcR1I0Kku8yg1S+LQjeqh1K42b270myKiIt
11 | vxeuNnanRwdehTZthThembr8RXoGcmzaXfMet7NVDgUa7gNzPXbqjhTFdyWoZzeU
12 | X6TWTgFtciTs5M1F50H+3nieGKX2dvLUIEXWFO7yevj9bqtI8k0b66eLgBjchnjW
13 | 8B7oYOFZW44VDDnqQrvFJ9aMQ44FfLAWWLcy6nBzcDdK+Z+yq9FNVgduyl0J7vRo
14 | 3UtcVazLUvmDdwASLIB3IwB7YmT6fuOyM+6eyw5F1CdjXbc/bhop0pCDY1aAEsZA
15 | CjT9AgMBAAGjdTBzMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcD
16 | AjAtBgNVHREEJjAkoCIGCisGAQQBgjcUAgOgFAwSdGVzdEBhbHRpbm4uc3R1ZGlv
17 | MB0GA1UdDgQWBBTv8Cpf5J7nfmGds20LU/J3bg05XTANBgkqhkiG9w0BAQsFAAOC
18 | AQEAahWeu6ymaiJe9+LiMlQwNsUIV4KaLX+jCsRyF1jUJ0C13aFALGM4k9svqqXR
19 | DzBdCXXr0c1E+Ks3sCwBLfK5yj5fTI+pL26ceEmHahcVyLvzEBljtNb4FnGFs92P
20 | CH0NuCz45hQ2O9/Tv4cZAdgledTznJTKzzQNaF8M6iINmP6sf4kOg0BQx0K71K4f
21 | 7j2oQvYKiT7Zv1e83cdk9pS4ihDe+ZWYiGUM/IuaXNPl6OzVk4rY88PZJAoz7q33
22 | rYjlT+zkcl3dzTc3E0CWzbIWjhaXCRWvlI44cLRtdpmPqJUHI6a/tcGwNb5vWiT4
23 | YfZJ0EZ2iSRQlpU3+jMs8Ci2AA==
24 | -----END CERTIFICATE-----
25 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Core/User.ContactPoints/UserContactPointAvailability.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Core.User.ContactPoints;
2 |
3 | ///
4 | /// Class describing the contact points of a user
5 | ///
6 | public class UserContactPointAvailability
7 | {
8 | ///
9 | /// Gets or sets the ID of the user
10 | ///
11 | public int UserId { get; set; }
12 |
13 | ///
14 | /// Gets or sets the national identity number of the user
15 | ///
16 | public string NationalIdentityNumber { get; set; } = string.Empty;
17 |
18 | ///
19 | /// Gets or sets a boolean indicating whether the user has reserved themselves from electronic communication
20 | ///
21 | public bool IsReserved { get; set; }
22 |
23 | ///
24 | /// Gets or sets a boolean indicating whether the user has registered a mobile number
25 | ///
26 | public bool MobileNumberRegistered { get; set; }
27 |
28 | ///
29 | /// Gets or sets a boolean indicating whether the user has registered an email address
30 | ///
31 | public bool EmailRegistered { get; set; }
32 | }
33 |
34 | ///
35 | /// A list representation of
36 | ///
37 | public class UserContactPointAvailabilityList
38 | {
39 | ///
40 | /// A list containing contact point availability for users
41 | ///
42 | public List AvailabilityList { get; set; } = [];
43 | }
44 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Core/Integrations/IProfileSettingsRepository.cs:
--------------------------------------------------------------------------------
1 | using Altinn.Profile.Core.User.ProfileSettings;
2 |
3 | namespace Altinn.Profile.Core.Integrations
4 | {
5 | ///
6 | /// Provides methods for updating user profile settings in the repository.
7 | ///
8 | public interface IProfileSettingsRepository
9 | {
10 | ///
11 | /// Updates the profile settings for a user.
12 | ///
13 | /// The profile settings to update.
14 | /// A token to monitor for cancellation requests.
15 | /// A task representing the asynchronous operation.
16 | Task UpdateProfileSettings(ProfileSettings profileSettings, CancellationToken cancellationToken);
17 |
18 | ///
19 | /// Patches the profile settings for a user.
20 | ///
21 | /// The profile settings to update.
22 | /// A token to monitor for cancellation requests.
23 | /// A task representing the asynchronous operation.
24 | Task PatchProfileSettings(ProfileSettingsPatchModel profileSettings, CancellationToken cancellationToken);
25 |
26 | ///
27 | /// Gets the local profile settings for a given user ID.
28 | ///
29 | Task GetProfileSettings(int userId);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Events/ProfileSettingsUpdatedEvent.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Integrations.Events
2 | {
3 | ///
4 | /// Event representing an update to profile settings.
5 | ///
6 | /// The user ID associated with the update.
7 | /// The timestamp when the event occurred.
8 | /// The to character language type
9 | /// A value indicating whether the user should not be prompted for party selection.
10 | /// The UUID of the preselected party. Optional.
11 | /// A value indicating whether client units should be shown.
12 | /// A value indicating whether sub-entities should be shown.
13 | /// A value indicating whether deleted entities should be shown.
14 | /// The users last timestamp for ignoring the UnitProfile update.
15 | /// Can be removed when Altinn2 is decommissioned
16 | public record ProfileSettingsUpdatedEvent(
17 | int UserId,
18 | DateTime EventTimestamp,
19 | string LanguageType,
20 | bool DoNotPromptForParty,
21 | Guid? PreselectedPartyUuid,
22 | bool ShowClientUnits,
23 | bool ShouldShowSubEntities,
24 | bool ShouldShowDeletedEntities,
25 | DateTime? IgnoreUnitProfileDateTime);
26 | }
27 |
--------------------------------------------------------------------------------
/test/Bruno/collection.bru:
--------------------------------------------------------------------------------
1 | meta {
2 | name: Altinn 3
3 | }
4 |
5 | auth {
6 | mode: bearer
7 | }
8 |
9 | auth:bearer {
10 | token: {{BearerToken}}
11 | }
12 |
13 | docs {
14 | Collection of request examples and tests for Altinn 3 API.
15 |
16 | # Working test data
17 |
18 | ## Persons
19 |
20 | Test persons with their correct identifiers in each environment. Primarily used for the
21 | AuthN_ variables to generate bearer tokens.
22 |
23 | | Person | Env | UserId | PartyId | PartyUuid |
24 | | ----------- | ---- | -------- | ------- | ------------------------------------ |
25 | | 17902349936 | at22 | 20885478 | 51118947 | b8a3981b-9948-4109-88a9-679d90c4ab37 |
26 | | | at23 | 20462603 | 50891883 | 629fa2c0-27cd-40a2-ac6a-99bdf374dba2 |
27 | | | at24 | 20245418 | 51074789 | 3155a6c7-0967-4c31-9cb3-0afe525d5899 |
28 |
29 | ## Organizations
30 |
31 | Test organizations with their correct identifiers in each environment as well as "CEO" for
32 | the organization. Primarily used for the Party_ variables as "selected" party for the
33 | authenticated user.
34 |
35 | | Organization | Env | PartyId | PartyUuid | "CEO" |
36 | | ------------ | ---- | -------- | ------------------------------------ | ----------- |
37 | | 313605590 | at22 | 51643854 | 8027a287-e3f1-42ad-bb50-57b4c4584f13 | 17902349936 |
38 | | | at23 | 51519644 | 4a1cfef9-82e1-4be9-96b9-b99f116f8350 | |
39 | | | at24 | 51605705 | e0347436-a499-49aa-b651-8c67c3c8d17e | |
40 | }
41 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Core/User.ProfileSettings/LanguageType.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Core.User.ProfileSettings
2 | {
3 | ///
4 | /// Represents the supported language types for the user portal.
5 | ///
6 | public static class LanguageType
7 | {
8 | ///
9 | /// Norwegian Bokmål language.
10 | ///
11 | public const string NB = "nb";
12 |
13 | ///
14 | /// Norwegian Nynorsk language.
15 | ///
16 | public const string NN = "nn";
17 |
18 | ///
19 | /// English language.
20 | ///
21 | public const string EN = "en";
22 |
23 | ///
24 | /// Sami language. Inactive.
25 | ///
26 | public const string SE = "se";
27 |
28 | ///
29 | /// Gets the language code corresponding to the specified Altinn 2 language code.
30 | ///
31 | /// The Altinn 2 language code (e.g., 1044 for Norwegian Bokmål, 2068 for Norwegian Nynorsk, 1033 for English).
32 | /// The language code as a string ("nb", "nn", or "en"). Defaults to "nb" if the code is not recognized.
33 | public static string GetFromAltinn2Code(int altinn2Code)
34 | {
35 | return altinn2Code switch
36 | {
37 | 1044 => NB,
38 | 2068 => NN,
39 | 1033 => EN,
40 | 1083 => SE,
41 | _ => NB
42 | };
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/test/k6/src/config.js:
--------------------------------------------------------------------------------
1 | import { stopIterationOnFail } from "./errorhandler.js";
2 |
3 | // Base URLs for the Altinn platform across different environments.
4 | const baseUrls = {
5 | prod: "altinn.no",
6 | tt02: "tt02.altinn.no",
7 | yt01: "yt01.altinn.cloud",
8 | at22: "at22.altinn.cloud",
9 | at23: "at23.altinn.cloud",
10 | at24: "at24.altinn.cloud"
11 | };
12 |
13 | const environment = __ENV.altinn_env ? __ENV.altinn_env.toLowerCase() : null;
14 | if (!environment) {
15 | stopIterationOnFail("Environment variable 'altinn_env' is not set", false);
16 | }
17 |
18 | const baseUrl = baseUrls[environment];
19 | if (!baseUrl) {
20 | stopIterationOnFail(`Invalid value for environment variable 'altinn_env': '${environment}'.`, false);
21 | }
22 |
23 | // Altinn TestTools token generator URL.
24 | export const tokenGenerator = {
25 | getEnterpriseToken:
26 | "https://altinn-testtools-token-generator.azurewebsites.net/api/GetEnterpriseToken",
27 | getPersonalToken:
28 | "https://altinn-testtools-token-generator.azurewebsites.net/api/GetPersonalToken"
29 | };
30 |
31 | export const profileUrl = {
32 | favorites: `https://platform.${baseUrl}/profile/api/v1/users/current/party-groups/favorites`,
33 | modifyFavorites: (partyUuid) => `https://platform.${baseUrl}/profile/api/v1/users/current/party-groups/favorites/${partyUuid}`,
34 | organization: (orgNo) => `https://platform.${baseUrl}/profile/api/v1/organizations/${orgNo}/notificationaddresses/mandatory`,
35 | personalNotificationAddresses: (partyUuid) => `https://platform.${baseUrl}/profile/api/v1/users/current/notificationsettings/parties/${partyUuid}`,
36 | }
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Handlers/FavoriteAddedEventHandler.cs:
--------------------------------------------------------------------------------
1 | using Altinn.Profile.Integrations.Events;
2 | using Altinn.Profile.Integrations.SblBridge;
3 | using Altinn.Profile.Integrations.SblBridge.User.Favorites;
4 | using Microsoft.Extensions.Options;
5 | using Wolverine.Attributes;
6 |
7 | namespace Altinn.Profile.Integrations.Handlers;
8 |
9 | ///
10 | /// Handler for the event where a party has been added to a user's favorites.
11 | ///
12 | /// The favorites client
13 | /// Config to indicate if the handler should update Altinn 2
14 | /// Can be removed when Altinn2 is decommissioned
15 | public class FavoriteAddedEventHandler(IUserFavoriteClient client, IOptions settings)
16 | {
17 | private readonly IUserFavoriteClient _userFavoriteClient = client;
18 | private readonly bool _updateA2 = settings.Value.UpdateA2Favorites;
19 |
20 | ///
21 | /// Handles the event
22 | ///
23 | [Transactional]
24 | public async Task Handle(FavoriteAddedEvent changeEvent)
25 | {
26 | if (!_updateA2)
27 | {
28 | return;
29 | }
30 |
31 | var request = new FavoriteChangedRequest
32 | {
33 | UserId = changeEvent.UserId,
34 | ChangeType = ChangeType.Insert,
35 | PartyUuid = changeEvent.PartyUuid,
36 | ChangeDateTime = changeEvent.RegistrationTimestamp
37 | };
38 |
39 | // Using SBLBridge to update favorites in A2
40 | await _userFavoriteClient.UpdateFavorites(request);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Persistence/PostgreSQLSettings.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Integrations.Persistence;
2 |
3 | ///
4 | /// Settings for Postgre database
5 | ///
6 | public class PostgreSqlSettings
7 | {
8 | ///
9 | /// Boolean indicating if database should be connected
10 | ///
11 | public bool EnableDBConnection { get; set; } = true;
12 |
13 | ///
14 | /// Path to migration scripts
15 | ///
16 | public string MigrationScriptPath { get; set; } = string.Empty;
17 |
18 | ///
19 | /// Connection string for the admin user of postgre db
20 | ///
21 | public string AdminConnectionString { get; set; } = string.Empty;
22 |
23 | ///
24 | /// Password for admin user for the postgre db
25 | ///
26 | public string ProfileDbAdminPwd { get; set; } = string.Empty;
27 |
28 | ///
29 | /// Connection string for app user the postgre db
30 | ///
31 | public string ConnectionString { get; set; } = string.Empty;
32 |
33 | ///
34 | /// Password for app user for the postgre db
35 | ///
36 | public string ProfileDbPwd { get; set; } = string.Empty;
37 |
38 | ///
39 | /// Gets or sets a value indicating whether to include parameter values in logging/tracing
40 | ///
41 | public bool LogParameters { get; set; } = false;
42 |
43 | ///
44 | /// Boolean indicating if connection to db should be in debug mode
45 | ///
46 | public bool EnableDebug { get; set; } = false;
47 | }
48 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Handlers/FavoriteRemovedEventHandler.cs:
--------------------------------------------------------------------------------
1 | using Altinn.Profile.Integrations.Events;
2 | using Altinn.Profile.Integrations.SblBridge;
3 | using Altinn.Profile.Integrations.SblBridge.User.Favorites;
4 | using Microsoft.Extensions.Options;
5 | using Wolverine.Attributes;
6 |
7 | namespace Altinn.Profile.Integrations.Handlers;
8 |
9 | ///
10 | /// Handler for the event where a party has been removed from a user's favorites.
11 | ///
12 | /// The favorites client
13 | /// Config to indicate if the handler should update Altinn 2
14 | /// Can be removed when Altinn2 is decommissioned
15 | public class FavoriteRemovedEventHandler(IUserFavoriteClient client, IOptions settings)
16 | {
17 | private readonly IUserFavoriteClient _userFavoriteClient = client;
18 | private readonly bool _updateA2 = settings.Value.UpdateA2Favorites;
19 |
20 | ///
21 | /// Handles the event
22 | ///
23 | [Transactional]
24 | public async Task Handle(FavoriteRemovedEvent changeEvent)
25 | {
26 | if (!_updateA2)
27 | {
28 | return;
29 | }
30 |
31 | var request = new FavoriteChangedRequest
32 | {
33 | UserId = changeEvent.UserId,
34 | ChangeType = ChangeType.Delete,
35 | PartyUuid = changeEvent.PartyUuid,
36 | ChangeDateTime = changeEvent.EventTimestamp,
37 | };
38 |
39 | // Using SBLBridge to update favorites in A2
40 | await _userFavoriteClient.UpdateFavorites(request);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/ContactRegister/ContactAndReservationChangesException.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics.CodeAnalysis;
2 |
3 | namespace Altinn.Profile.Integrations.ContactRegister;
4 |
5 | ///
6 | /// Represents errors that occur during order processing operations.
7 | ///
8 | [ExcludeFromCodeCoverage]
9 | public class ContactAndReservationChangesException : Exception
10 | {
11 | ///
12 | /// Initializes a new instance of the class.
13 | ///
14 | public ContactAndReservationChangesException() : base()
15 | {
16 | }
17 |
18 | ///
19 | /// Initializes a new instance of the class
20 | /// with a specified error message.
21 | ///
22 | /// The message that describes the error.
23 | public ContactAndReservationChangesException(string message) : base(message)
24 | {
25 | }
26 |
27 | ///
28 | /// Initializes a new instance of the class
29 | /// with a specified error message and a reference to the inner exception that is the cause of this exception.
30 | ///
31 | /// The error message that explains the reason for the exception.
32 | /// The exception that is the cause of the current exception, or a null reference if no inner exception is specified.
33 | public ContactAndReservationChangesException(string message, Exception inner) : base(message, inner)
34 | {
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Core/User.ProfileSettings/ProfileSettingsPatchModel.cs:
--------------------------------------------------------------------------------
1 | using Altinn.Profile.Core.Utils;
2 |
3 | namespace Altinn.Profile.Core.User.ProfileSettings
4 | {
5 | ///
6 | /// Represents user-specific portal settings and preferences.
7 | ///
8 | public class ProfileSettingsPatchModel
9 | {
10 | ///
11 | /// The id of the user.
12 | ///
13 | public int UserId { get; set; }
14 |
15 | ///
16 | /// The language the user has selected in Altinn portal.
17 | ///
18 | public string? Language { get; set; }
19 |
20 | ///
21 | /// Indicates whether the user should not be prompted for party selection.
22 | /// Can be set without using PreselectedPartyUuid.
23 | ///
24 | public bool? DoNotPromptForParty { get; set; }
25 |
26 | ///
27 | /// The UUID of the preselected party. Optional.
28 | ///
29 | public Optional PreselectedPartyUuid { get; set; } = new();
30 |
31 | ///
32 | /// Indicates whether client units should be shown.
33 | ///
34 | public bool? ShowClientUnits { get; set; }
35 |
36 | ///
37 | /// Indicates whether sub-entities should be shown.
38 | ///
39 | public bool? ShouldShowSubEntities { get; set; }
40 |
41 | ///
42 | /// Indicates whether deleted entities should be shown.
43 | ///
44 | public bool? ShouldShowDeletedEntities { get; set; }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Integrations/Migration/v0.10/01-setup-schema.sql:
--------------------------------------------------------------------------------
1 | CREATE SCHEMA IF NOT EXISTS professional_notification_settings;
2 |
3 | GRANT ALL ON SCHEMA professional_notification_settings TO platform_profile_admin;
4 | GRANT USAGE ON SCHEMA professional_notification_settings TO platform_profile;
5 |
6 | CREATE TABLE professional_notification_settings.user_party_contact_info (
7 | user_party_contact_info_id bigint GENERATED ALWAYS AS IDENTITY,
8 | user_id integer NOT NULL,
9 | party_uuid uuid NOT NULL,
10 | email_address character varying(400),
11 | phone_number character varying(26),
12 | last_changed timestamp with time zone NOT NULL DEFAULT (now()),
13 | CONSTRAINT user_party_contact_info_pkey PRIMARY KEY (user_party_contact_info_id)
14 | );
15 |
16 | CREATE TABLE professional_notification_settings.user_party_contact_info_resources (
17 | user_party_contact_info_resource_id bigint GENERATED ALWAYS AS IDENTITY,
18 | user_party_contact_info_id bigint NOT NULL,
19 | resource_id text NOT NULL,
20 | CONSTRAINT user_party_contact_info_resource_pkey PRIMARY KEY (user_party_contact_info_resource_id),
21 | CONSTRAINT fk_user_party_contact_info_id FOREIGN KEY (user_party_contact_info_id) REFERENCES professional_notification_settings.user_party_contact_info (user_party_contact_info_id) ON DELETE CASCADE
22 | );
23 |
24 | CREATE INDEX ix_user_party_contact_info_party_uuid_user_id ON professional_notification_settings.user_party_contact_info (party_uuid, user_id);
25 |
26 | CREATE INDEX ix_user_party_contact_info_resources_user_party_contact_info_id ON professional_notification_settings.user_party_contact_info_resources (user_party_contact_info_id);
--------------------------------------------------------------------------------
/src/ServiceDefaults.Jobs/JobHostLifecycles.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Hosting;
2 |
3 | namespace Altinn.Authorization.ServiceDefaults.Jobs;
4 |
5 | ///
6 | /// Host lifecycle events that a job can be run at (in addition to the regular scheduled run).
7 | ///
8 | ///
9 | /// This is useful for jobs that for instance want the host to fail to start if the job fails during startup.
10 | ///
11 | [Flags]
12 | public enum JobHostLifecycles
13 | {
14 | ///
15 | /// Do not run the job at any lifecycle events.
16 | ///
17 | None = 0,
18 |
19 | ///
20 | /// Run the job at the point.
21 | ///
22 | Starting = 1 << 0,
23 |
24 | ///
25 | /// Run the job at the point.
26 | ///
27 | Start = 1 << 1,
28 |
29 | ///
30 | /// Run the job at the point.
31 | ///
32 | Started = 1 << 2,
33 |
34 | ///
35 | /// Run the job at the point.
36 | ///
37 | Stopping = 1 << 3,
38 |
39 | ///
40 | /// Run the job at the point.
41 | ///
42 | Stop = 1 << 4,
43 |
44 | ///
45 | /// Run the job at the point.
46 | ///
47 | Stopped = 1 << 5,
48 | }
49 |
--------------------------------------------------------------------------------
/test/Altinn.Profile.Tests/Profile.Core/Utils/OptionalJsonConverterTests.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json;
2 | using Altinn.Profile.Core.Utils;
3 | using Xunit;
4 |
5 | namespace Altinn.Profile.Tests.Profile.Core.Utils;
6 |
7 | public class OptionalJsonConverterTests
8 | {
9 | private readonly JsonSerializerOptions _options;
10 |
11 | public OptionalJsonConverterTests()
12 | {
13 | _options = new JsonSerializerOptions();
14 | _options.Converters.Add(new OptionalJsonConverterFactory());
15 | }
16 |
17 | [Fact]
18 | public void Deserialize_ExplicitNull_ReturnsOptionalWithValueNull()
19 | {
20 | var json = "null";
21 | var result = JsonSerializer.Deserialize>(json, _options);
22 | Assert.True(result.HasValue);
23 | Assert.Null(result.Value);
24 | }
25 |
26 | [Fact]
27 | public void Deserialize_Value_ReturnsOptionalWithValue()
28 | {
29 | var json = "\"hello\"";
30 | var result = JsonSerializer.Deserialize>(json, _options);
31 | Assert.True(result.HasValue);
32 | Assert.Equal("hello", result.Value);
33 | }
34 |
35 | [Fact]
36 | public void Serialize_OptionalWithValue_WritesValue()
37 | {
38 | var optional = new Optional("hello");
39 | var json = JsonSerializer.Serialize(optional, _options);
40 | Assert.Equal("\"hello\"", json);
41 | }
42 |
43 | [Fact]
44 | public void Serialize_OptionalWithoutValue_WritesNull()
45 | {
46 | var optional = new Optional();
47 | var json = JsonSerializer.Serialize(optional, _options);
48 | Assert.Equal("null", json);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/Altinn.Profile.Core/Integrations/INotificationsClient.cs:
--------------------------------------------------------------------------------
1 | namespace Altinn.Profile.Core.Integrations
2 | {
3 | ///
4 | /// Interface for sending notifications such as SMS and email orders.
5 | ///
6 | public interface INotificationsClient
7 | {
8 | ///
9 | /// Sends an SMS order to the specified phone number.
10 | ///
11 | /// The phone number to send the SMS to.
12 | /// The partyUuid for the party the address was changed for
13 | /// The language code for the SMS content.
14 | /// A token to monitor for cancellation requests.
15 | /// A task representing the asynchronous operation.
16 | Task OrderSms(string phoneNumber, Guid partyUuid, string languageCode, CancellationToken cancellationToken);
17 |
18 | ///
19 | /// Sends an email order to the specified email address.
20 | ///
21 | /// The email address to send the email to.
22 | /// The partyUuid for the party the address was changed for
23 | /// The language code for the email content.
24 | /// A token to monitor for cancellation requests.
25 | /// A task representing the asynchronous operation.
26 | Task OrderEmail(string emailAddress, Guid partyUuid, string languageCode, CancellationToken cancellationToken);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Altinn.Profile/Authorization/ClaimsHelper.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using AltinnCore.Authentication.Constants;
3 | using Microsoft.AspNetCore.Http;
4 | using Microsoft.AspNetCore.Mvc;
5 |
6 | namespace Altinn.Profile.Authorization
7 | {
8 | ///
9 | /// Helper class for working with claims in the HTTP context.
10 | ///
11 | public static class ClaimsHelper
12 | {
13 | ///
14 | /// Attempts to retrieve the user ID from the claims in the provided HTTP context.
15 | ///
16 | /// The HTTP context containing the claims.
17 | /// The user ID retrieved from the claims, if successful.
18 | ///
19 | /// A if the user ID is missing or invalid; otherwise, null.
20 | ///
21 | public static BadRequestObjectResult TryGetUserIdFromClaims(HttpContext context, out int userId)
22 | {
23 | userId = 0;
24 | string userIdString = context.User.Claims
25 | .Where(c => c.Type == AltinnCoreClaimTypes.UserId)
26 | .Select(c => c.Value).FirstOrDefault();
27 |
28 | if (string.IsNullOrEmpty(userIdString))
29 | {
30 | return new BadRequestObjectResult("Invalid request context. UserId must be provided in claims.");
31 | }
32 |
33 | if (!int.TryParse(userIdString, out userId))
34 | {
35 | return new BadRequestObjectResult("Invalid user ID format in claims.");
36 | }
37 |
38 | return null; // Success case
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------