5
5
using System . Threading . Tasks ;
6
6
using Microsoft . AspNetCore ;
7
7
using Microsoft . AspNetCore . Authentication ;
8
+ using Microsoft . AspNetCore . Authorization ;
8
9
using Microsoft . AspNetCore . Http ;
9
10
using Microsoft . AspNetCore . Identity ;
10
11
using Microsoft . AspNetCore . Mvc ;
17
18
using VirtoCommerce . Platform . Core . Events ;
18
19
using VirtoCommerce . Platform . Core . Security ;
19
20
using VirtoCommerce . Platform . Core . Security . Events ;
21
+ using VirtoCommerce . Platform . Security ;
22
+ using VirtoCommerce . Platform . Security . Model ;
23
+ using VirtoCommerce . Platform . Security . Services ;
20
24
using VirtoCommerce . Platform . Web . Model . Security ;
21
25
using static OpenIddict . Abstractions . OpenIddictConstants ;
22
26
@@ -30,6 +34,8 @@ public class AuthorizationController : Controller
30
34
private readonly UserManager < ApplicationUser > _userManager ;
31
35
private readonly PasswordLoginOptions _passwordLoginOptions ;
32
36
private readonly IEventPublisher _eventPublisher ;
37
+ private readonly IEnumerable < IUserSignInValidator > _userSignInValidators ;
38
+ private readonly OpenIddictTokenManager < OpenIddictEntityFrameworkCoreToken > _tokenManager ;
33
39
34
40
private UserManager < ApplicationUser > UserManager => _signInManager . UserManager ;
35
41
@@ -39,14 +45,48 @@ public AuthorizationController(
39
45
SignInManager < ApplicationUser > signInManager ,
40
46
UserManager < ApplicationUser > userManager ,
41
47
IOptions < PasswordLoginOptions > passwordLoginOptions ,
42
- IEventPublisher eventPublisher )
48
+ IEventPublisher eventPublisher ,
49
+ IEnumerable < IUserSignInValidator > userSignInValidators ,
50
+ OpenIddictTokenManager < OpenIddictEntityFrameworkCoreToken > tokenManager )
43
51
{
44
52
_applicationManager = applicationManager ;
45
53
_identityOptions = identityOptions . Value ;
46
54
_passwordLoginOptions = passwordLoginOptions . Value ?? new PasswordLoginOptions ( ) ;
47
55
_signInManager = signInManager ;
48
56
_userManager = userManager ;
49
57
_eventPublisher = eventPublisher ;
58
+ _userSignInValidators = userSignInValidators ;
59
+ _tokenManager = tokenManager ;
60
+ }
61
+
62
+ [ Authorize ]
63
+ [ HttpPost ( "~/revoke/token" ) ]
64
+ public async Task < ActionResult > RevokeCurrentUserToken ( )
65
+ {
66
+ var tokenId = HttpContext . User . GetClaim ( "oi_tkn_id" ) ;
67
+ var authId = HttpContext . User . GetClaim ( "oi_au_id" ) ;
68
+
69
+ if ( authId != null )
70
+ {
71
+ var tokens = _tokenManager . FindByAuthorizationIdAsync ( authId ) ;
72
+ await foreach ( var token in tokens )
73
+ {
74
+ await _tokenManager . TryRevokeAsync ( token ) ;
75
+ }
76
+ }
77
+ else if ( tokenId != null )
78
+ {
79
+ var token = await _tokenManager . FindByIdAsync ( tokenId ) ;
80
+ if ( token ? . Authorization != null )
81
+ {
82
+ foreach ( var authorizationToken in token . Authorization . Tokens )
83
+ {
84
+ await _tokenManager . TryRevokeAsync ( authorizationToken ) ;
85
+ }
86
+ }
87
+ }
88
+
89
+ return Ok ( ) ;
50
90
}
51
91
52
92
#region Password, authorization code and refresh token flows
@@ -84,31 +124,39 @@ public async Task<ActionResult> Exchange()
84
124
85
125
if ( user == null )
86
126
{
87
- return BadRequest ( new OpenIddictResponse
88
- {
89
- Error = Errors . InvalidGrant ,
90
- ErrorDescription = "The username/password couple is invalid."
91
- } ) ;
127
+ return BadRequest ( SecurityErrorDescriber . LoginFailed ( ) ) ;
92
128
}
93
129
94
130
if ( ! _passwordLoginOptions . Enabled && ! user . IsAdministrator )
95
131
{
96
- return BadRequest ( new OpenIddictResponse
97
- {
98
- Error = Errors . InvalidGrant ,
99
- ErrorDescription = "The username/password login is disabled."
100
- } ) ;
132
+ return BadRequest ( SecurityErrorDescriber . PasswordLoginDisabled ( ) ) ;
101
133
}
102
134
103
135
// Validate the username/password parameters and ensure the account is not locked out.
104
136
var result = await _signInManager . CheckPasswordSignInAsync ( user , openIdConnectRequest . Password , lockoutOnFailure : true ) ;
105
- if ( ! result . Succeeded )
137
+
138
+ var context = new SignInValidatorContext
139
+ {
140
+ User = user . Clone ( ) as ApplicationUser ,
141
+ DetailedErrors = _passwordLoginOptions . DetailedErrors ,
142
+ IsSucceeded = result . Succeeded ,
143
+ IsLockedOut = result . IsLockedOut ,
144
+ } ;
145
+
146
+ var storeIdParameter = openIdConnectRequest . GetParameter ( "storeId" ) ;
147
+ if ( storeIdParameter != null )
148
+ {
149
+ context . StoreId = ( string ) storeIdParameter . GetValueOrDefault ( ) ;
150
+ }
151
+
152
+ foreach ( var loginValidation in _userSignInValidators . OrderByDescending ( x => x . Priority ) . ThenBy ( x => x . GetType ( ) . Name ) )
106
153
{
107
- return BadRequest ( new OpenIddictResponse
154
+ var validationErrors = await loginValidation . ValidateUserAsync ( context ) ;
155
+ var error = validationErrors . FirstOrDefault ( ) ;
156
+ if ( error != null )
108
157
{
109
- Error = Errors . InvalidGrant ,
110
- ErrorDescription = "The username/password couple is invalid."
111
- } ) ;
158
+ return BadRequest ( error ) ;
159
+ }
112
160
}
113
161
114
162
await _eventPublisher . Publish ( new BeforeUserLoginEvent ( user ) ) ;
@@ -133,27 +181,19 @@ public async Task<ActionResult> Exchange()
133
181
var user = await _userManager . GetUserAsync ( info . Principal ) ;
134
182
if ( user == null )
135
183
{
136
- return BadRequest ( new OpenIddictResponse
137
- {
138
- Error = Errors . InvalidGrant ,
139
- ErrorDescription = "The token is no longer valid."
140
- } ) ;
184
+ return BadRequest ( SecurityErrorDescriber . TokenInvalid ( ) ) ;
141
185
}
142
186
143
187
// Ensure the user is still allowed to sign in.
144
188
if ( ! await _signInManager . CanSignInAsync ( user ) )
145
189
{
146
- return BadRequest ( new OpenIddictResponse
147
- {
148
- Error = Errors . InvalidGrant ,
149
- ErrorDescription = "The user is no longer allowed to sign in."
150
- } ) ;
190
+ return BadRequest ( SecurityErrorDescriber . SignInNotAllowed ( ) ) ;
151
191
}
152
192
153
193
// Create a new authentication ticket, but reuse the properties stored in the
154
194
// authorization code/refresh token, including the scopes originally granted.
155
195
var ticket = await CreateTicketAsync ( openIdConnectRequest , user , info . Properties ) ;
156
- return SignIn ( ticket . Principal , ticket . Properties , ticket . AuthenticationScheme ) ;
196
+ return SignIn ( ticket . Principal , ticket . AuthenticationScheme ) ;
157
197
}
158
198
else if ( openIdConnectRequest . IsClientCredentialsGrantType ( ) )
159
199
{
@@ -162,24 +202,17 @@ public async Task<ActionResult> Exchange()
162
202
var application = await _applicationManager . FindByClientIdAsync ( openIdConnectRequest . ClientId , HttpContext . RequestAborted ) ;
163
203
if ( application == null )
164
204
{
165
- return BadRequest ( new OpenIddictResponse
166
- {
167
- Error = Errors . InvalidClient ,
168
- ErrorDescription = "The client application was not found in the database."
169
- } ) ;
205
+ return BadRequest ( SecurityErrorDescriber . InvalidClient ( ) ) ;
170
206
}
171
207
172
208
// Create a new authentication ticket.
173
209
var ticket = CreateTicket ( application ) ;
174
210
return SignIn ( ticket . Principal , ticket . Properties , ticket . AuthenticationScheme ) ;
175
211
}
176
212
177
- return BadRequest ( new OpenIddictResponse
178
- {
179
- Error = Errors . UnsupportedGrantType ,
180
- ErrorDescription = "The specified grant type is not supported."
181
- } ) ;
213
+ return BadRequest ( SecurityErrorDescriber . UnsupportedGrantType ( ) ) ;
182
214
}
215
+
183
216
#endregion
184
217
185
218
private AuthenticationTicket CreateTicket ( OpenIddictEntityFrameworkCoreApplication application )
0 commit comments