Skip to content

Commit 050ccbb

Browse files
elsandoskogstad
andauthoredApr 25, 2024··
feat: Authorized parties endpoint in enduser API (#661)
## Description This adds a parties endpoint in the enduser-API, proxying requests to access-management and returning a custom DTO for all parties that the user has some sort of access relation to. ## Related Issue(s) - #660 ## Verification - [x] **Your** code builds clean without any errors or warnings - [x] Manual testing done (required) - [x] Relevant automated test added (if you find this hard, leave it and we'll help out) ## Documentation - [ ] Documentation is updated (either in `docs`-directory, Altinnpedia or a separate linked PR in [altinn-studio-docs.](https://github.com/Altinn/altinn-studio-docs), if applicable) --------- Co-authored-by: Ole Jørgen Skogstad <skogstad@softis.net>
1 parent 771fe15 commit 050ccbb

File tree

24 files changed

+507
-34
lines changed

24 files changed

+507
-34
lines changed
 

‎docs/schema/V1/schema.verified.graphql

+20-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
schema {
2-
query: DialogQueries
2+
query: Queries
33
}
44

55
type Activity {
@@ -34,6 +34,18 @@ type ApiActionEndpoint {
3434
sunsetAt: DateTime
3535
}
3636

37+
type AuthorizedParty {
38+
party: String!
39+
name: String!
40+
partyType: String!
41+
isDeleted: Boolean!
42+
hasKeyRole: Boolean!
43+
isMainAdministrator: Boolean!
44+
isAccessManager: Boolean!
45+
hasOnlyAccessToSubParties: Boolean!
46+
subParties: [AuthorizedParty!]
47+
}
48+
3749
type Content {
3850
type: ContentType!
3951
value: [Localization!]!
@@ -63,11 +75,6 @@ type Dialog {
6375
seenSinceLastUpdate: [SeenLog!]!
6476
}
6577

66-
type DialogQueries @authorize(policy: "enduser") {
67-
dialogById(dialogId: UUID!): Dialog!
68-
searchDialogs(input: SearchDialogInput!): SearchDialogsPayload!
69-
}
70-
7178
type Element {
7279
id: UUID!
7380
type: URL
@@ -103,6 +110,12 @@ type Localization {
103110
cultureCode: String!
104111
}
105112

113+
type Queries @authorize(policy: "enduser") {
114+
dialogById(dialogId: UUID!): Dialog!
115+
searchDialogs(input: SearchDialogInput!): SearchDialogsPayload!
116+
parties: [AuthorizedParty!]!
117+
}
118+
106119
type SearchDialog {
107120
id: UUID!
108121
org: String!
@@ -239,4 +252,4 @@ scalar DateTime @specifiedBy(url: "https:\/\/www.graphql-scalars.com\/date-time"
239252

240253
scalar URL @specifiedBy(url: "https:\/\/tools.ietf.org\/html\/rfc3986")
241254

242-
scalar UUID @specifiedBy(url: "https:\/\/tools.ietf.org\/html\/rfc4122")
255+
scalar UUID @specifiedBy(url: "https:\/\/tools.ietf.org\/html\/rfc4122")

‎docs/schema/V1/swagger.verified.json

+86-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{
1+
{
22
"openapi": "3.0.0",
33
"info": {
44
"title": "Dialogporten",
@@ -1606,6 +1606,42 @@
16061606
]
16071607
}
16081608
},
1609+
"/api/v1/enduser/parties": {
1610+
"get": {
1611+
"tags": [
1612+
"Enduser"
1613+
],
1614+
"summary": "Gets the list of authorized parties for the end user",
1615+
"description": "Gets the list of authorized parties for the end user. For more information see the documentation (link TBD).",
1616+
"operationId": "GetParties",
1617+
"responses": {
1618+
"200": {
1619+
"description": "The list of authorized parties for the end user",
1620+
"content": {
1621+
"application/json": {
1622+
"schema": {
1623+
"type": "array",
1624+
"items": {
1625+
"$ref": "#/components/schemas/GetPartiesDto"
1626+
}
1627+
}
1628+
}
1629+
}
1630+
},
1631+
"401": {
1632+
"description": "Unauthorized"
1633+
},
1634+
"403": {
1635+
"description": "Forbidden"
1636+
}
1637+
},
1638+
"security": [
1639+
{
1640+
"JWTBearerAuth": []
1641+
}
1642+
]
1643+
}
1644+
},
16091645
"/api/v1/enduser/dialogs/{dialogId}/seenlog": {
16101646
"get": {
16111647
"tags": [
@@ -3732,6 +3768,55 @@
37323768
}
37333769
}
37343770
},
3771+
"GetPartiesDto": {
3772+
"type": "object",
3773+
"additionalProperties": false,
3774+
"properties": {
3775+
"authorizedParties": {
3776+
"type": "array",
3777+
"items": {
3778+
"$ref": "#/components/schemas/AuthorizedPartyDto"
3779+
}
3780+
}
3781+
}
3782+
},
3783+
"AuthorizedPartyDto": {
3784+
"type": "object",
3785+
"additionalProperties": false,
3786+
"properties": {
3787+
"party": {
3788+
"type": "string"
3789+
},
3790+
"name": {
3791+
"type": "string"
3792+
},
3793+
"partyType": {
3794+
"type": "string"
3795+
},
3796+
"isDeleted": {
3797+
"type": "boolean"
3798+
},
3799+
"hasKeyRole": {
3800+
"type": "boolean"
3801+
},
3802+
"isMainAdministrator": {
3803+
"type": "boolean"
3804+
},
3805+
"isAccessManager": {
3806+
"type": "boolean"
3807+
},
3808+
"hasOnlyAccessToSubParties": {
3809+
"type": "boolean"
3810+
},
3811+
"subParties": {
3812+
"type": "array",
3813+
"nullable": true,
3814+
"items": {
3815+
"$ref": "#/components/schemas/AuthorizedPartyDto"
3816+
}
3817+
}
3818+
}
3819+
},
37353820
"SearchDialogSeenLogDto": {
37363821
"type": "object",
37373822
"additionalProperties": false,

‎src/Digdir.Domain.Dialogporten.Application/ApplicationExtensions.cs

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ public static IServiceCollection AddApplication(this IServiceCollection services
4444
.AddTransient<IUserOrganizationRegistry, UserOrganizationRegistry>()
4545
.AddTransient<IUserResourceRegistry, UserResourceRegistry>()
4646
.AddTransient<IUserNameRegistry, UserNameRegistry>()
47+
.AddTransient<IUserParties, UserParties>()
4748
.AddTransient<IDialogActivityService, DialogActivityService>()
4849
.AddTransient<IClock, Clock>()
4950
.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehaviour<,>))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using Digdir.Domain.Dialogporten.Application.Common.Extensions;
2+
using Digdir.Domain.Dialogporten.Application.Externals.AltinnAuthorization;
3+
using Digdir.Domain.Dialogporten.Application.Externals.Presentation;
4+
using Digdir.Domain.Dialogporten.Domain.Parties;
5+
6+
namespace Digdir.Domain.Dialogporten.Application.Common;
7+
8+
public interface IUserParties
9+
{
10+
public Task<AuthorizedPartiesResult> GetUserParties(CancellationToken cancellationToken = default);
11+
}
12+
13+
public class UserParties : IUserParties
14+
{
15+
private readonly IUser _user;
16+
private readonly IAltinnAuthorization _altinnAuthorization;
17+
18+
public UserParties(IUser user, IAltinnAuthorization altinnAuthorization)
19+
{
20+
_user = user ?? throw new ArgumentNullException(nameof(user));
21+
_altinnAuthorization = altinnAuthorization ?? throw new ArgumentNullException(nameof(altinnAuthorization));
22+
}
23+
24+
public Task<AuthorizedPartiesResult> GetUserParties(CancellationToken cancellationToken = default) =>
25+
_user.TryGetPid(out var pid) &&
26+
NorwegianPersonIdentifier.TryParse(NorwegianPersonIdentifier.PrefixWithSeparator + pid,
27+
out var partyIdentifier)
28+
? _altinnAuthorization.GetAuthorizedParties(partyIdentifier, cancellationToken)
29+
: Task.FromResult(new AuthorizedPartiesResult());
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using Digdir.Domain.Dialogporten.Domain.Parties.Abstractions;
2+
3+
namespace Digdir.Domain.Dialogporten.Application.Externals.AltinnAuthorization;
4+
5+
public class AuthorizedPartiesResult
6+
{
7+
public List<AuthorizedParty> AuthorizedParties { get; init; } = new();
8+
}
9+
10+
public class AuthorizedParty
11+
{
12+
public string Party { get; init; } = null!;
13+
public string Name { get; init; } = null!;
14+
public AuthorizedPartyType PartyType { get; init; }
15+
public bool IsDeleted { get; init; }
16+
public bool HasKeyRole { get; init; }
17+
public bool IsMainAdministrator { get; init; }
18+
public bool IsAccessManager { get; init; }
19+
public bool HasOnlyAccessToSubParties { get; init; }
20+
public List<string> AuthorizedResources { get; init; } = new();
21+
public List<AuthorizedParty>? SubParties { get; init; }
22+
}
23+
24+
public enum AuthorizedPartyType
25+
{
26+
Person,
27+
Organization
28+
}

‎src/Digdir.Domain.Dialogporten.Application/Externals/AltinnAuthorization/IAltinnAuthorization.cs

+4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities;
2+
using Digdir.Domain.Dialogporten.Domain.Parties.Abstractions;
23

34
namespace Digdir.Domain.Dialogporten.Application.Externals.AltinnAuthorization;
45

@@ -13,4 +14,7 @@ public Task<DialogSearchAuthorizationResult> GetAuthorizedResourcesForSearch(
1314
List<string> constraintServiceResources,
1415
string? endUserId = null,
1516
CancellationToken cancellationToken = default);
17+
18+
public Task<AuthorizedPartiesResult> GetAuthorizedParties(IPartyIdentifier authenticatedParty,
19+
CancellationToken cancellationToken = default);
1620
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
namespace Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Parties.Queries.Get;
2+
3+
public class GetPartiesDto
4+
{
5+
public List<AuthorizedPartyDto> AuthorizedParties { get; init; } = new();
6+
}
7+
8+
public class AuthorizedPartyDto
9+
{
10+
public string Party { get; init; } = null!;
11+
public string Name { get; init; } = null!;
12+
public string PartyType { get; init; } = null!;
13+
public bool IsDeleted { get; init; }
14+
public bool HasKeyRole { get; init; }
15+
public bool IsMainAdministrator { get; init; }
16+
public bool IsAccessManager { get; init; }
17+
public bool HasOnlyAccessToSubParties { get; init; }
18+
public List<AuthorizedPartyDto>? SubParties { get; init; }
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using AutoMapper;
2+
using Digdir.Domain.Dialogporten.Application.Common;
3+
using MediatR;
4+
5+
namespace Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Parties.Queries.Get;
6+
7+
public sealed class GetPartiesQuery : IRequest<GetPartiesDto>;
8+
9+
internal sealed class GetPartiesQueryHandler : IRequestHandler<GetPartiesQuery, GetPartiesDto>
10+
{
11+
private readonly IUserParties _userParties;
12+
private readonly IMapper _mapper;
13+
14+
public GetPartiesQueryHandler(IUserParties userParties, IMapper mapper)
15+
{
16+
_userParties = userParties;
17+
_mapper = mapper;
18+
}
19+
20+
public async Task<GetPartiesDto> Handle(GetPartiesQuery request, CancellationToken cancellationToken)
21+
{
22+
var authorizedPartiesResult = await _userParties.GetUserParties(cancellationToken);
23+
return _mapper.Map<GetPartiesDto>(authorizedPartiesResult);
24+
}
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using AutoMapper;
2+
using Digdir.Domain.Dialogporten.Application.Externals.AltinnAuthorization;
3+
4+
namespace Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Parties.Queries.Get;
5+
6+
internal sealed class MappingProfile : Profile
7+
{
8+
public MappingProfile()
9+
{
10+
CreateMap<AuthorizedPartiesResult, GetPartiesDto>();
11+
CreateMap<AuthorizedParty, AuthorizedPartyDto>();
12+
}
13+
}

‎src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogQueries.cs

+1-4
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11
using AutoMapper;
22
using Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Dialogs.Queries.Get;
33
using Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Dialogs.Queries.Search;
4-
using Digdir.Domain.Dialogporten.GraphQL.Common.Authorization;
54
using Digdir.Domain.Dialogporten.GraphQL.EndUser.DialogById;
65
using Digdir.Domain.Dialogporten.GraphQL.EndUser.SearchDialogs;
7-
using HotChocolate.Authorization;
86
using MediatR;
97

108
namespace Digdir.Domain.Dialogporten.GraphQL.EndUser;
119

12-
[Authorize(Policy = AuthorizationPolicy.EndUser)]
13-
public class DialogQueries
10+
public partial class Queries
1411
{
1512
public async Task<Dialog> GetDialogById(
1613
[Service] ISender mediator,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using AutoMapper;
2+
using Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Parties.Queries.Get;
3+
4+
namespace Digdir.Domain.Dialogporten.GraphQL.EndUser.Parties;
5+
6+
public class MappingProfile : Profile
7+
{
8+
public MappingProfile()
9+
{
10+
CreateMap<AuthorizedPartyDto, AuthorizedParty>();
11+
}
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
namespace Digdir.Domain.Dialogporten.GraphQL.EndUser.Parties;
2+
3+
public class AuthorizedParty
4+
{
5+
public string Party { get; init; } = null!;
6+
public string Name { get; init; } = null!;
7+
public string PartyType { get; init; } = null!;
8+
public bool IsDeleted { get; init; }
9+
public bool HasKeyRole { get; init; }
10+
public bool IsMainAdministrator { get; init; }
11+
public bool IsAccessManager { get; init; }
12+
public bool HasOnlyAccessToSubParties { get; init; }
13+
public List<AuthorizedParty>? SubParties { get; init; }
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using AutoMapper;
2+
using Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Parties.Queries.Get;
3+
using Digdir.Domain.Dialogporten.GraphQL.EndUser.Parties;
4+
using MediatR;
5+
6+
namespace Digdir.Domain.Dialogporten.GraphQL.EndUser;
7+
8+
public partial class Queries
9+
{
10+
public async Task<List<AuthorizedParty>> GetParties(
11+
[Service] ISender mediator,
12+
[Service] IMapper mapper,
13+
CancellationToken cancellationToken)
14+
{
15+
var request = new GetPartiesQuery();
16+
var result = await mediator.Send(request, cancellationToken);
17+
18+
return mapper.Map<List<AuthorizedParty>>(result.AuthorizedParties);
19+
}
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
using Digdir.Domain.Dialogporten.GraphQL.Common.Authorization;
2+
using HotChocolate.Authorization;
3+
4+
namespace Digdir.Domain.Dialogporten.GraphQL.EndUser;
5+
6+
[Authorize(Policy = AuthorizationPolicy.EndUser)]
7+
public partial class Queries;

‎src/Digdir.Domain.Dialogporten.GraphQL/ServiceCollectionExtensions.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public static IServiceCollection AddDialogportenGraphQl(
1313
.AddAuthorization()
1414
.RegisterDbContext<DialogDbContext>()
1515
.AddDiagnosticEventListener<ApplicationInsightEventListener>()
16-
.AddQueryType<DialogQueries>()
16+
.AddQueryType<Queries>()
1717
.Services;
1818
}
1919
}

0 commit comments

Comments
 (0)
Please sign in to comment.