-
Notifications
You must be signed in to change notification settings - Fork 146
/
SignInManager.cs
323 lines (300 loc) · 13.4 KB
/
SignInManager.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
// Copyright (c) Microsoft Corporation, Inc. All rights reserved.
// Licensed under the MIT License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Globalization;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.Owin.Security;
namespace Microsoft.AspNet.Identity.Owin
{
/// <summary>
/// Manages Sign In operations for users
/// </summary>
/// <typeparam name="TUser"></typeparam>
/// <typeparam name="TKey"></typeparam>
public class SignInManager<TUser, TKey> : IDisposable
where TUser : class, IUser<TKey>
where TKey : IEquatable<TKey>
{
/// <summary>
/// Constructor
/// </summary>
/// <param name="userManager"></param>
/// <param name="authenticationManager"></param>
public SignInManager(UserManager<TUser, TKey> userManager, IAuthenticationManager authenticationManager)
{
if (userManager == null)
{
throw new ArgumentNullException("userManager");
}
if (authenticationManager == null)
{
throw new ArgumentNullException("authenticationManager");
}
UserManager = userManager;
AuthenticationManager = authenticationManager;
}
private string _authType;
/// <summary>
/// AuthenticationType that will be used by sign in, defaults to DefaultAuthenticationTypes.ApplicationCookie
/// </summary>
public string AuthenticationType
{
get { return _authType ?? DefaultAuthenticationTypes.ApplicationCookie; }
set { _authType = value; }
}
/// <summary>
/// Used to operate on users
/// </summary>
public UserManager<TUser, TKey> UserManager { get; set; }
/// <summary>
/// Used to sign in identities
/// </summary>
public IAuthenticationManager AuthenticationManager { get; set; }
/// <summary>
/// Called to generate the ClaimsIdentity for the user, override to add additional claims before SignIn
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
public virtual Task<ClaimsIdentity> CreateUserIdentityAsync(TUser user)
{
return UserManager.CreateIdentityAsync(user, AuthenticationType);
}
/// <summary>
/// Convert a TKey userId to a string, by default this just calls ToString()
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public virtual string ConvertIdToString(TKey id)
{
return Convert.ToString(id, CultureInfo.InvariantCulture);
}
/// <summary>
/// Convert a string id to the proper TKey using Convert.ChangeType
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public virtual TKey ConvertIdFromString(string id)
{
if (id == null)
{
return default(TKey);
}
return (TKey)Convert.ChangeType(id, typeof(TKey), CultureInfo.InvariantCulture);
}
/// <summary>
/// Creates a user identity and then signs the identity using the AuthenticationManager
/// </summary>
/// <param name="user"></param>
/// <param name="isPersistent"></param>
/// <param name="rememberBrowser"></param>
/// <returns></returns>
public virtual async Task SignInAsync(TUser user, bool isPersistent, bool rememberBrowser)
{
var userIdentity = await CreateUserIdentityAsync(user).WithCurrentCulture();
// Clear any partial cookies from external or two factor partial sign ins
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie, DefaultAuthenticationTypes.TwoFactorCookie);
if (rememberBrowser)
{
var rememberBrowserIdentity = AuthenticationManager.CreateTwoFactorRememberBrowserIdentity(ConvertIdToString(user.Id));
AuthenticationManager.SignIn(new AuthenticationProperties { IsPersistent = isPersistent }, userIdentity, rememberBrowserIdentity);
}
else
{
AuthenticationManager.SignIn(new AuthenticationProperties { IsPersistent = isPersistent }, userIdentity);
}
}
/// <summary>
/// Send a two factor code to a user
/// </summary>
/// <param name="provider"></param>
/// <returns></returns>
public virtual async Task<bool> SendTwoFactorCodeAsync(string provider)
{
var userId = await GetVerifiedUserIdAsync().WithCurrentCulture();
if (userId == null)
{
return false;
}
var token = await UserManager.GenerateTwoFactorTokenAsync(userId, provider).WithCurrentCulture();
// See IdentityConfig.cs to plug in Email/SMS services to actually send the code
await UserManager.NotifyTwoFactorTokenAsync(userId, provider, token).WithCurrentCulture();
return true;
}
/// <summary>
/// Get the user id that has been verified already or null.
/// </summary>
/// <returns></returns>
public async Task<TKey> GetVerifiedUserIdAsync()
{
var result = await AuthenticationManager.AuthenticateAsync(DefaultAuthenticationTypes.TwoFactorCookie).WithCurrentCulture();
if (result != null && result.Identity != null && !String.IsNullOrEmpty(result.Identity.GetUserId()))
{
return ConvertIdFromString(result.Identity.GetUserId());
}
return default(TKey);
}
/// <summary>
/// Has the user been verified (ie either via password or external login)
/// </summary>
/// <returns></returns>
public async Task<bool> HasBeenVerifiedAsync()
{
return await GetVerifiedUserIdAsync().WithCurrentCulture() != null;
}
/// <summary>
/// Two factor verification step
/// </summary>
/// <param name="provider"></param>
/// <param name="code"></param>
/// <param name="isPersistent"></param>
/// <param name="rememberBrowser"></param>
/// <returns></returns>
public virtual async Task<SignInStatus> TwoFactorSignInAsync(string provider, string code, bool isPersistent, bool rememberBrowser)
{
var userId = await GetVerifiedUserIdAsync().WithCurrentCulture();
if (userId == null)
{
return SignInStatus.Failure;
}
var user = await UserManager.FindByIdAsync(userId).WithCurrentCulture();
if (user == null)
{
return SignInStatus.Failure;
}
if (await UserManager.IsLockedOutAsync(user.Id).WithCurrentCulture())
{
return SignInStatus.LockedOut;
}
if (await UserManager.VerifyTwoFactorTokenAsync(user.Id, provider, code).WithCurrentCulture())
{
// When token is verified correctly, clear the access failed count used for lockout
var resetLockoutResult = await UserManager.ResetAccessFailedCountAsync(user.Id).WithCurrentCulture();
if (!resetLockoutResult.Succeeded)
{
// ResetLockout got an unsuccessful result that could be caused by concurrency failures indicating an
// attacker could be trying to bypass the MaxFailedAccessAttempts limit. Return the same failure we do
// when failing to increment the lockout to avoid giving an attacker extra guesses at the two factor code.
return SignInStatus.Failure;
}
await SignInAsync(user, isPersistent, rememberBrowser).WithCurrentCulture();
return SignInStatus.Success;
}
// If the token is incorrect, record the failure which also may cause the user to be locked out
var incrementLockoutResult = await UserManager.AccessFailedAsync(user.Id).WithCurrentCulture();
if (!incrementLockoutResult.Succeeded)
{
// Return the same failure we do when resetting the lockout fails after a correct two factor code.
// This is currently redundant, but it's here in case the code gets copied elsewhere.
return SignInStatus.Failure;
}
return SignInStatus.Failure;
}
/// <summary>
/// Sign the user in using an associated external login
/// </summary>
/// <param name="loginInfo"></param>
/// <param name="isPersistent"></param>
/// <returns></returns>
public async Task<SignInStatus> ExternalSignInAsync(ExternalLoginInfo loginInfo, bool isPersistent)
{
var user = await UserManager.FindAsync(loginInfo.Login).WithCurrentCulture();
if (user == null)
{
return SignInStatus.Failure;
}
if (await UserManager.IsLockedOutAsync(user.Id).WithCurrentCulture())
{
return SignInStatus.LockedOut;
}
return await SignInOrTwoFactor(user, isPersistent).WithCurrentCulture();
}
private async Task<bool> IsTwoFactorEnabled(TUser user)
{
return await UserManager.GetTwoFactorEnabledAsync(user.Id).WithCurrentCulture()
&& (await UserManager.GetValidTwoFactorProvidersAsync(user.Id).WithCurrentCulture()).Count > 0;
}
private async Task<SignInStatus> SignInOrTwoFactor(TUser user, bool isPersistent)
{
var id = Convert.ToString(user.Id);
if (await IsTwoFactorEnabled(user) && !await AuthenticationManager.TwoFactorBrowserRememberedAsync(id).WithCurrentCulture())
{
var identity = new ClaimsIdentity(DefaultAuthenticationTypes.TwoFactorCookie);
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, id));
AuthenticationManager.SignIn(identity);
return SignInStatus.RequiresVerification;
}
await SignInAsync(user, isPersistent, false).WithCurrentCulture();
return SignInStatus.Success;
}
/// <summary>
/// Sign in the user in using the user name and password
/// </summary>
/// <param name="userName"></param>
/// <param name="password"></param>
/// <param name="isPersistent"></param>
/// <param name="shouldLockout"></param>
/// <returns></returns>
public virtual async Task<SignInStatus> PasswordSignInAsync(string userName, string password, bool isPersistent, bool shouldLockout)
{
if (UserManager == null)
{
return SignInStatus.Failure;
}
var user = await UserManager.FindByNameAsync(userName).WithCurrentCulture();
if (user == null)
{
return SignInStatus.Failure;
}
if (await UserManager.IsLockedOutAsync(user.Id).WithCurrentCulture())
{
return SignInStatus.LockedOut;
}
if (await UserManager.CheckPasswordAsync(user, password).WithCurrentCulture())
{
if (!await IsTwoFactorEnabled(user))
{
var resetLockoutResult = await UserManager.ResetAccessFailedCountAsync(user.Id).WithCurrentCulture();
if (!resetLockoutResult.Succeeded)
{
// ResetLockout got an unsuccessful result that could be caused by concurrency failures indicating an
// attacker could be trying to bypass the MaxFailedAccessAttempts limit. Return the same failure we do
// when failing to increment the lockout to avoid giving an attacker extra guesses at the password.
return SignInStatus.Failure;
}
}
return await SignInOrTwoFactor(user, isPersistent).WithCurrentCulture();
}
if (shouldLockout)
{
// If lockout is requested, increment access failed count which might lock out the user
var incrementLockoutResult = await UserManager.AccessFailedAsync(user.Id).WithCurrentCulture();
if (!incrementLockoutResult.Succeeded)
{
// Return the same failure we do when resetting the lockout fails after a correct password.
return SignInStatus.Failure;
}
if (await UserManager.IsLockedOutAsync(user.Id).WithCurrentCulture())
{
return SignInStatus.LockedOut;
}
}
return SignInStatus.Failure;
}
/// <summary>
/// Dispose
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// If disposing, calls dispose on the Context. Always nulls out the Context
/// </summary>
/// <param name="disposing"></param>
protected virtual void Dispose(bool disposing)
{
}
}
}