├── 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 | [![Profile scan](https://github.com/altinn/altinn-profile/actions/workflows/container-scan.yml/badge.svg)](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 | --------------------------------------------------------------------------------