Skip to content

Commit 5631d69

Browse files
authored
Merge pull request #78 from jokk-itu/feature/scope-resource
Feature/scope resource
2 parents 2ca10a3 + aab4479 commit 5631d69

File tree

62 files changed

+1025
-1490
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+1025
-1490
lines changed

src/AuthServer.TestIdentityProvider/Pages/Consent.cshtml.cs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public class ClaimDto
4545
public bool IsGranted { get; set; }
4646
}
4747

48-
public async Task OnGet(string returnUrl, CancellationToken cancellationToken)
48+
public async Task<IActionResult> OnGet(string returnUrl, CancellationToken cancellationToken)
4949
{
5050
ReturnUrl = returnUrl ?? Url.Content("~/");
5151

@@ -59,25 +59,34 @@ public async Task OnGet(string returnUrl, CancellationToken cancellationToken)
5959
var consentGrantDto = await _consentGrantService.GetConsentGrantDto(subject.Subject, clientId, cancellationToken);
6060

6161
var requestedScope = request.Scope.ToList();
62+
var requestedClaims = ClaimsHelper.MapToClaims(requestedScope).ToList();
6263

6364
// Display requested claims, also if they are already consented. This makes sure the end-user can change their full consent.
64-
var requestedClaims = ClaimsHelper.MapToClaims(requestedScope)
65+
var requestedClaimDtos = requestedClaims
6566
.Select(x => new ClaimDto
6667
{
6768
Name = x,
6869
IsGranted = consentGrantDto.ConsentedClaims.Any(y => y == x)
6970
})
7071
.ToList();
7172

73+
if (!consentGrantDto.ClientRequiresConsent)
74+
{
75+
await _consentGrantService.HandleConsent(subject.Subject, clientId, requestedScope, requestedClaims, cancellationToken);
76+
return Redirect(ReturnUrl);
77+
}
78+
7279
Input = new InputModel
7380
{
7481
ClientName = consentGrantDto.ClientName,
7582
ClientUri = consentGrantDto.ClientUri,
7683
ClientLogoUri = consentGrantDto.ClientLogoUri,
7784
Username = consentGrantDto.Username,
7885
RequestedScope = requestedScope,
79-
RequestedClaims = requestedClaims
86+
RequestedClaims = requestedClaimDtos
8087
};
88+
89+
return Page();
8190
}
8291

8392
public async Task<IActionResult> OnPostAccept(string returnUrl, CancellationToken cancellationToken)

src/AuthServer.TestIdentityProvider/Pages/SignIn.cshtml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
ViewData["Title"] = "Sign In";
55
}
66

7-
8-
97
<h1>@ViewData["Title"]</h1>
108
<hr/>
119
<div class="row">

src/AuthServer.TestIdentityProvider/Program.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,13 @@
125125
options.VerificationUri = identity.GetValue<string>("VerificationUri");
126126
});
127127

128+
builder.Services
129+
.AddOptions<TokenValidationOptions>()
130+
.Configure(options =>
131+
{
132+
options.ClockSkew = TimeSpan.FromSeconds(10);
133+
});
134+
128135
builder.Services.AddSingleton<IDistributedCache, InMemoryCache>();
129136
builder.Services.AddScoped<IUserClaimService, UserClaimService>();
130137
builder.Services.AddScoped<IAuthenticatedUserAccessor, AuthenticatedUserAccessor>();

src/AuthServer/Authentication/Abstractions/IAuthenticatedUserAccessor.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ public interface IAuthenticatedUserAccessor
66
{
77
Task<AuthenticatedUser?> GetAuthenticatedUser();
88
Task<int> CountAuthenticatedUsers();
9+
Task ClearAuthenticatedUser();
910
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using AuthServer.Authorization.Models;
2+
3+
namespace AuthServer.Authorization.Abstractions;
4+
5+
internal interface IScopeResourceService
6+
{
7+
/// <summary>
8+
///
9+
/// </summary>
10+
/// <param name="scopes"></param>
11+
/// <param name="resources"></param>
12+
/// <param name="authorizationGrantId"></param>
13+
/// <param name="cancellationToken"></param>
14+
/// <returns></returns>
15+
Task<ScopeResourceValidationResult> ValidateScopeResourceForGrant(
16+
IReadOnlyCollection<string> scopes,
17+
IReadOnlyCollection<string> resources,
18+
string authorizationGrantId,
19+
CancellationToken cancellationToken);
20+
21+
/// <summary>
22+
///
23+
/// </summary>
24+
/// <param name="scopes"></param>
25+
/// <param name="resources"></param>
26+
/// <param name="clientId"></param>
27+
/// <param name="cancellationToken"></param>
28+
/// <returns></returns>
29+
Task<ScopeResourceValidationResult> ValidateScopeResourceForClient(
30+
IReadOnlyCollection<string> scopes,
31+
IReadOnlyCollection<string> resources,
32+
string clientId,
33+
CancellationToken cancellationToken);
34+
}

src/AuthServer/Authorization/BaseAuthorizeValidator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ protected async Task<bool> HasUniqueNonce(string? nonce, CancellationToken cance
8484
=> string.IsNullOrEmpty(nonce) || !await _nonceRepository.IsNonceReplay(nonce, cancellationToken);
8585

8686
protected async Task<bool> HasValidResource(IReadOnlyCollection<string> resources, IReadOnlyCollection<string> scopes, CancellationToken cancellationToken)
87-
=> resources.Count != 0 && await _clientRepository.DoesResourcesExist(resources, scopes, cancellationToken);
87+
=> resources.Count != 0 && await _clientRepository.AreResourcesAuthorizedForScope(resources, scopes, cancellationToken);
8888

8989
protected bool HasValidEmptyRequest(string? requestObject, string? requestUri, bool isRequiredByClient)
9090
=> !string.IsNullOrEmpty(requestObject) || !string.IsNullOrEmpty(requestUri) || (!isRequiredByClient && !_discoveryDocumentOptions.Value.RequireSignedRequestObject);
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace AuthServer.Authorization.Models;
2+
3+
internal enum ScopeResourceError
4+
{
5+
ConsentNotFound,
6+
ScopeExceedsConsent,
7+
ResourceExceedsConsent,
8+
UnauthorizedClientForScope,
9+
UnauthorizedResourceForScope
10+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
namespace AuthServer.Authorization.Models;
2+
3+
internal class ScopeResourceValidationResult
4+
{
5+
public IReadOnlyCollection<string> Scopes { get; init; } = [];
6+
7+
public IReadOnlyCollection<string> Resources { get; init; } = [];
8+
9+
public ScopeResourceError? Error { get; init; }
10+
11+
public bool IsValid => Error is null;
12+
};
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
using AuthServer.Authorization.Abstractions;
2+
using AuthServer.Authorization.Models;
3+
using AuthServer.Cache.Abstractions;
4+
using AuthServer.Extensions;
5+
using AuthServer.Repositories.Abstractions;
6+
using Microsoft.Extensions.Logging;
7+
8+
namespace AuthServer.Authorization;
9+
10+
internal class ScopeResourceService : IScopeResourceService
11+
{
12+
private readonly ICachedClientStore _cachedClientStore;
13+
private readonly IConsentRepository _consentRepository;
14+
private readonly IClientRepository _clientRepository;
15+
private readonly ILogger<ScopeResourceService> _logger;
16+
17+
public ScopeResourceService(
18+
ICachedClientStore cachedClientStore,
19+
IConsentRepository consentRepository,
20+
IClientRepository clientRepository,
21+
ILogger<ScopeResourceService> logger)
22+
{
23+
_cachedClientStore = cachedClientStore;
24+
_consentRepository = consentRepository;
25+
_clientRepository = clientRepository;
26+
_logger = logger;
27+
}
28+
29+
/// <inheritdocs/>
30+
public async Task<ScopeResourceValidationResult> ValidateScopeResourceForGrant(
31+
IReadOnlyCollection<string> scopes,
32+
IReadOnlyCollection<string> resources,
33+
string authorizationGrantId,
34+
CancellationToken cancellationToken)
35+
{
36+
var grantConsentScopes = await _consentRepository.GetGrantConsentedScopes(authorizationGrantId, cancellationToken);
37+
if (grantConsentScopes.Count == 0)
38+
{
39+
return new ScopeResourceValidationResult
40+
{
41+
Error = ScopeResourceError.ConsentNotFound
42+
};
43+
}
44+
45+
var requestedScopes = scopes.Count != 0
46+
? scopes
47+
: grantConsentScopes
48+
.Select(x => x.Name)
49+
.ToList();
50+
51+
var requestedResources = resources.Count != 0
52+
? resources
53+
: grantConsentScopes
54+
.Select(x => x.Resource)
55+
.Distinct()
56+
.ToList();
57+
58+
_logger.LogDebug(
59+
"Scopes {@Scopes} and Resources {@Resource} deduced for grant {AuthorizationGrantId}",
60+
requestedScopes,
61+
requestedResources,
62+
authorizationGrantId);
63+
64+
if (requestedScopes.IsNotSubset(grantConsentScopes.Select(x => x.Name)))
65+
{
66+
return new ScopeResourceValidationResult
67+
{
68+
Error = ScopeResourceError.ScopeExceedsConsent
69+
};
70+
}
71+
72+
if (requestedResources.IsNotSubset(grantConsentScopes.Select(x => x.Resource)))
73+
{
74+
return new ScopeResourceValidationResult
75+
{
76+
Error = ScopeResourceError.ResourceExceedsConsent
77+
};
78+
}
79+
80+
var areResourcesAuthorizedForScope = await _clientRepository.AreResourcesAuthorizedForScope(
81+
requestedResources,
82+
requestedScopes,
83+
cancellationToken);
84+
85+
if (!areResourcesAuthorizedForScope)
86+
{
87+
return new ScopeResourceValidationResult
88+
{
89+
Error = ScopeResourceError.UnauthorizedResourceForScope
90+
};
91+
}
92+
93+
return new ScopeResourceValidationResult
94+
{
95+
Scopes = requestedScopes,
96+
Resources = requestedResources
97+
};
98+
}
99+
100+
/// <inheritdocs/>
101+
public async Task<ScopeResourceValidationResult> ValidateScopeResourceForClient(
102+
IReadOnlyCollection<string> scopes,
103+
IReadOnlyCollection<string> resources,
104+
string clientId,
105+
CancellationToken cancellationToken)
106+
{
107+
var cachedClient = await _cachedClientStore.Get(clientId, cancellationToken);
108+
var requestedScopes = scopes.Count == 0 ? cachedClient.Scopes : scopes;
109+
if (requestedScopes.IsNotSubset(cachedClient.Scopes))
110+
{
111+
return new ScopeResourceValidationResult
112+
{
113+
Error = ScopeResourceError.UnauthorizedClientForScope
114+
};
115+
}
116+
117+
if (resources.Count == 0)
118+
{
119+
throw new ArgumentException("resources must be provided");
120+
}
121+
122+
var areResourcesAuthorizedForScope = await _clientRepository.AreResourcesAuthorizedForScope(resources, requestedScopes, cancellationToken);
123+
if (!areResourcesAuthorizedForScope)
124+
{
125+
return new ScopeResourceValidationResult
126+
{
127+
Error = ScopeResourceError.UnauthorizedResourceForScope
128+
};
129+
}
130+
131+
return new ScopeResourceValidationResult
132+
{
133+
Scopes = requestedScopes,
134+
Resources = resources
135+
};
136+
}
137+
}

src/AuthServer/Authorize/AuthorizeInteractionService.cs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -139,12 +139,6 @@ private async Task<InteractionResult> GetPrompt(AuthorizeUser authorizeUser, Aut
139139
return grantIdPrompt;
140140
}
141141

142-
if (!authorizationGrant.Client.RequireConsent)
143-
{
144-
_logger.LogDebug("Client {ClientId} does not require consent, deducing prompt {Prompt}", authorizeRequest.ClientId, PromptConstants.None);
145-
return InteractionResult.Success(authorizeUser.SubjectIdentifier, authorizeUser.AuthorizationGrantId);
146-
}
147-
148142
var consentedScope = await _consentGrantRepository.GetClientConsentedScopes(authorizeUser.SubjectIdentifier, authorizeRequest.ClientId!, cancellationToken);
149143
if (authorizeRequest.Scope.IsNotSubset(consentedScope))
150144
{

0 commit comments

Comments
 (0)