Skip to content

Commit 73f01ae

Browse files
VCST-1353: Add validators and custom calim providers for token requests (#2810)
1 parent a185c69 commit 73f01ae

14 files changed

+200
-147
lines changed

VirtoCommerce.Platform.sln.DotSettings

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
1+
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
22
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=UI/@EntryIndexedValue">UI</s:String>
33
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticReadonly/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /&gt;</s:String>
44
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=15b5b1f1_002D457c_002D4ca6_002Db278_002D5615aedc07d3/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static readonly fields (private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="READONLY_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
55
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EPredefinedNamingRulesToUserRulesUpgrade/@EntryIndexedValue">True</s:Boolean>
66
<s:Boolean x:Key="/Default/UserDictionary/Words/=auditable/@EntryIndexedValue">True</s:Boolean>
77
<s:Boolean x:Key="/Default/UserDictionary/Words/=hangfire/@EntryIndexedValue">True</s:Boolean>
8+
<s:Boolean x:Key="/Default/UserDictionary/Words/=openIddict/@EntryIndexedValue">True</s:Boolean>
89
<s:Boolean x:Key="/Default/UserDictionary/Words/=postgre/@EntryIndexedValue">True</s:Boolean>
910
<s:Boolean x:Key="/Default/UserDictionary/Words/=prerelease/@EntryIndexedValue">True</s:Boolean>
1011
<s:Boolean x:Key="/Default/UserDictionary/Words/=virto/@EntryIndexedValue">True</s:Boolean>

src/VirtoCommerce.Platform.Security/Extensions/ClaimsPrincipalExtensions.cs

+16
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Collections.Generic;
12
using System.Security.Claims;
23
using OpenIddict.Abstractions;
34

@@ -14,5 +15,20 @@ public static bool IsSsoAuthenticationMethod(this ClaimsPrincipal claimsPrincipa
1415
{
1516
return claimsPrincipal?.GetClaim(ClaimTypes.AuthenticationMethod) != null;
1617
}
18+
19+
public static ClaimsPrincipal SetClaimWithDestinations(this ClaimsPrincipal claimsPrincipal, string type, string value, IList<string> destinations)
20+
{
21+
claimsPrincipal.SetClaim(type, value);
22+
23+
foreach (var claim in claimsPrincipal.Claims)
24+
{
25+
if (claim.Type == type && claim.Value == value)
26+
{
27+
claim.SetDestinations(destinations);
28+
}
29+
}
30+
31+
return claimsPrincipal;
32+
}
1733
}
1834
}

src/VirtoCommerce.Platform.Security/Model/SignInValidatorContext.cs

-21
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Threading.Tasks;
4+
5+
namespace VirtoCommerce.Platform.Security.OpenIddict
6+
{
7+
public class BaseUserSignInValidator : ITokenRequestValidator
8+
{
9+
public int Priority { get; set; }
10+
11+
public Task<IList<TokenResponse>> ValidateAsync(TokenRequestContext context)
12+
{
13+
IList<TokenResponse> result = [];
14+
15+
if (context.SignInResult != null && !context.SignInResult.Succeeded)
16+
{
17+
TokenResponse error;
18+
19+
if (context.DetailedErrors && context.SignInResult.IsLockedOut)
20+
{
21+
var permanentLockOut = context.User.LockoutEnd == DateTime.MaxValue.ToUniversalTime();
22+
error = permanentLockOut
23+
? SecurityErrorDescriber.UserIsLockedOut()
24+
: SecurityErrorDescriber.UserIsTemporaryLockedOut();
25+
}
26+
else
27+
{
28+
error = SecurityErrorDescriber.LoginFailed();
29+
}
30+
31+
result.Add(error);
32+
}
33+
34+
return Task.FromResult(result);
35+
}
36+
}
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using System.Security.Claims;
2+
using System.Threading.Tasks;
3+
4+
namespace VirtoCommerce.Platform.Security.OpenIddict;
5+
6+
public interface ITokenClaimProvider
7+
{
8+
Task SetClaimsAsync(ClaimsPrincipal principal, TokenRequestContext context);
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System.Collections.Generic;
2+
using System.Threading.Tasks;
3+
4+
namespace VirtoCommerce.Platform.Security.OpenIddict
5+
{
6+
public interface ITokenRequestValidator
7+
{
8+
public int Priority { get; set; }
9+
10+
Task<IList<TokenResponse>> ValidateAsync(TokenRequestContext context);
11+
}
12+
}

src/VirtoCommerce.Platform.Security/SecurityErrorDescriber.cs src/VirtoCommerce.Platform.Security/OpenIddict/SecurityErrorDescriber.cs

+10-11
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,67 @@
11
using VirtoCommerce.Platform.Core.Common;
2-
using VirtoCommerce.Platform.Security.Model;
32
using static OpenIddict.Abstractions.OpenIddictConstants;
43

5-
namespace VirtoCommerce.Platform.Security
4+
namespace VirtoCommerce.Platform.Security.OpenIddict
65
{
76
public static class SecurityErrorDescriber
87
{
9-
public static TokenLoginResponse LoginFailed() => new()
8+
public static TokenResponse LoginFailed() => new()
109
{
1110
Error = Errors.InvalidGrant,
1211
Code = nameof(LoginFailed).ToSnakeCase(),
1312
ErrorDescription = "Login attempt failed. Please check your credentials."
1413
};
1514

16-
public static TokenLoginResponse UserIsLockedOut() => new()
15+
public static TokenResponse UserIsLockedOut() => new()
1716
{
1817
Error = Errors.InvalidGrant,
1918
Code = nameof(UserIsLockedOut).ToSnakeCase(),
2019
ErrorDescription = "Your account has been locked. Please contact support for assistance."
2120
};
2221

23-
public static TokenLoginResponse UserIsTemporaryLockedOut() => new()
22+
public static TokenResponse UserIsTemporaryLockedOut() => new()
2423
{
2524
Error = Errors.InvalidGrant,
2625
Code = nameof(UserIsLockedOut).ToSnakeCase(),
2726
ErrorDescription = "Your account has been temporarily locked. Please try again after some time."
2827
};
2928

30-
public static TokenLoginResponse PasswordExpired() => new()
29+
public static TokenResponse PasswordExpired() => new()
3130
{
3231
Error = Errors.InvalidGrant,
3332
Code = nameof(PasswordExpired).ToSnakeCase(),
3433
ErrorDescription = "Your password has been expired and must be changed.",
3534
};
3635

37-
public static TokenLoginResponse PasswordLoginDisabled() => new()
36+
public static TokenResponse PasswordLoginDisabled() => new()
3837
{
3938
Error = Errors.InvalidGrant,
4039
Code = nameof(PasswordLoginDisabled).ToSnakeCase(),
4140
ErrorDescription = "The username/password login is disabled."
4241
};
4342

44-
public static TokenLoginResponse TokenInvalid() => new()
43+
public static TokenResponse TokenInvalid() => new()
4544
{
4645
Error = Errors.InvalidGrant,
4746
Code = nameof(TokenInvalid).ToSnakeCase(),
4847
ErrorDescription = "The token is no longer valid."
4948
};
5049

51-
public static TokenLoginResponse SignInNotAllowed() => new()
50+
public static TokenResponse SignInNotAllowed() => new()
5251
{
5352
Error = Errors.InvalidGrant,
5453
Code = nameof(SignInNotAllowed).ToSnakeCase(),
5554
ErrorDescription = "The user is no longer allowed to sign in."
5655
};
5756

58-
public static TokenLoginResponse InvalidClient() => new()
57+
public static TokenResponse InvalidClient() => new()
5958
{
6059
Error = Errors.InvalidClient,
6160
Code = nameof(InvalidClient).ToSnakeCase(),
6261
ErrorDescription = "The client application was not found in the database."
6362
};
6463

65-
public static TokenLoginResponse UnsupportedGrantType() => new()
64+
public static TokenResponse UnsupportedGrantType() => new()
6665
{
6766
Error = Errors.UnsupportedGrantType,
6867
Code = nameof(UnsupportedGrantType).ToSnakeCase(),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Security.Claims;
4+
using Microsoft.AspNetCore.Authentication;
5+
using Microsoft.AspNetCore.Identity;
6+
using OpenIddict.Abstractions;
7+
using VirtoCommerce.Platform.Core.Security;
8+
9+
namespace VirtoCommerce.Platform.Security.OpenIddict
10+
{
11+
public class TokenRequestContext
12+
{
13+
public string AuthenticationScheme { get; set; }
14+
15+
public OpenIddictRequest Request { get; set; }
16+
17+
public SignInResult SignInResult { get; set; }
18+
19+
public ApplicationUser User { get; set; }
20+
21+
public ClaimsPrincipal Principal { get; set; }
22+
23+
public AuthenticationProperties Properties { get; set; }
24+
25+
public bool DetailedErrors { get; set; }
26+
27+
public IDictionary<string, object> AdditionalParameters { get; set; } = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
28+
}
29+
}

src/VirtoCommerce.Platform.Security/Model/TokenLoginResponse.cs src/VirtoCommerce.Platform.Security/OpenIddict/TokenResponse.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
using Microsoft.AspNetCore.Identity;
33
using OpenIddict.Abstractions;
44

5-
namespace VirtoCommerce.Platform.Security.Model
5+
namespace VirtoCommerce.Platform.Security.OpenIddict
66
{
7-
public class TokenLoginResponse : OpenIddictResponse
7+
public class TokenResponse : OpenIddictResponse
88
{
99
public string UserId { get; set; }
1010

src/VirtoCommerce.Platform.Security/Services/BaseUserSignInValidator.cs

-32
This file was deleted.

src/VirtoCommerce.Platform.Security/Services/IUserSignInValidator.cs

-13
This file was deleted.

0 commit comments

Comments
 (0)