diff --git a/src/Microsoft.AspNet.Identity/IdentityResult.cs b/src/Microsoft.AspNet.Identity/IdentityResult.cs index dd3fe07de6..043c605fef 100644 --- a/src/Microsoft.AspNet.Identity/IdentityResult.cs +++ b/src/Microsoft.AspNet.Identity/IdentityResult.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; +using Microsoft.Framework.Logging; namespace Microsoft.AspNet.Identity { @@ -48,5 +49,23 @@ namespace Microsoft.AspNet.Identity } return result; } + + /// + /// Log Identity result + /// + /// + /// + public virtual void Log(ILogger logger, string message) + { + // TODO: Take logging level as a parameter + if (Succeeded) + { + logger.WriteInformation(Resources.FormatLogIdentityResultSuccess(message)); + } + else + { + logger.WriteWarning(Resources.FormatLogIdentityResultFailure(message, string.Join(",", Errors.Select(x => x.Code).ToList()))); + } + } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity/Microsoft.AspNet.Identity.kproj b/src/Microsoft.AspNet.Identity/Microsoft.AspNet.Identity.kproj index 2c8bf644b8..61c02b9a59 100644 --- a/src/Microsoft.AspNet.Identity/Microsoft.AspNet.Identity.kproj +++ b/src/Microsoft.AspNet.Identity/Microsoft.AspNet.Identity.kproj @@ -13,6 +13,9 @@ 2.0 + + True + diff --git a/src/Microsoft.AspNet.Identity/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Identity/Properties/Resources.Designer.cs index 11169b63ea..2aa5369096 100644 --- a/src/Microsoft.AspNet.Identity/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNet.Identity/Properties/Resources.Designer.cs @@ -59,7 +59,7 @@ namespace Microsoft.AspNet.Identity } /// - /// Security Code + /// Security code /// internal static string DefaultEmailTokenProviderSubject { @@ -67,7 +67,7 @@ namespace Microsoft.AspNet.Identity } /// - /// Security Code + /// Security code /// internal static string FormatDefaultEmailTokenProviderSubject() { @@ -123,7 +123,7 @@ namespace Microsoft.AspNet.Identity } /// - /// DefaultTokenProvider + /// Default Token Provider /// internal static string DefaultTokenProvider { @@ -131,7 +131,7 @@ namespace Microsoft.AspNet.Identity } /// - /// DefaultTokenProvider + /// Default Token Provider /// internal static string FormatDefaultTokenProvider() { @@ -155,7 +155,7 @@ namespace Microsoft.AspNet.Identity } /// - /// Role Name '{0}' is already taken. + /// Role name '{0}' is already taken. /// internal static string DuplicateRoleName { @@ -163,7 +163,7 @@ namespace Microsoft.AspNet.Identity } /// - /// Role Name '{0}' is already taken. + /// Role name '{0}' is already taken. /// internal static string FormatDuplicateRoleName(object p0) { @@ -171,7 +171,7 @@ namespace Microsoft.AspNet.Identity } /// - /// UserName '{0}' is already taken. + /// User name '{0}' is already taken. /// internal static string DuplicateUserName { @@ -179,7 +179,7 @@ namespace Microsoft.AspNet.Identity } /// - /// UserName '{0}' is already taken. + /// User name '{0}' is already taken. /// internal static string FormatDuplicateUserName(object p0) { @@ -746,6 +746,86 @@ namespace Microsoft.AspNet.Identity return string.Format(CultureInfo.CurrentCulture, GetString("UserNotInRole"), p0); } + /// + /// {0} : Failed : {1} + /// + internal static string LogIdentityResultFailure + { + get { return GetString("LogIdentityResultFailure"); } + } + + /// + /// {0} : Failed : {1} + /// + internal static string FormatLogIdentityResultFailure(object p0, object p1) + { + return string.Format(CultureInfo.CurrentCulture, GetString("LogIdentityResultFailure"), p0, p1); + } + + /// + /// {0} : Success + /// + internal static string LogIdentityResultSuccess + { + get { return GetString("LogIdentityResultSuccess"); } + } + + /// + /// {0} : Success + /// + internal static string FormatLogIdentityResultSuccess(object p0) + { + return string.Format(CultureInfo.CurrentCulture, GetString("LogIdentityResultSuccess"), p0); + } + + /// + /// {0} : Result : {1} + /// + internal static string LoggingSigninResult + { + get { return GetString("LoggingSigninResult"); } + } + + /// + /// {0} : Result : {1} + /// + internal static string FormatLoggingSigninResult(object p0, object p1) + { + return string.Format(CultureInfo.CurrentCulture, GetString("LoggingSigninResult"), p0, p1); + } + + /// + /// {0} for user: {1} + /// + internal static string LoggingResultMessage + { + get { return GetString("LoggingResultMessage"); } + } + + /// + /// {0} for user: {1} + /// + internal static string FormatLoggingResultMessage(object p0, object p1) + { + return string.Format(CultureInfo.CurrentCulture, GetString("LoggingResultMessage"), p0, p1); + } + + /// + /// {0} for role: {1} + /// + internal static string LoggingResultMessageForRole + { + get { return GetString("LoggingResultMessageForRole"); } + } + + /// + /// {0} for role: {1} + /// + internal static string FormatLoggingResultMessageForRole(object p0, object p1) + { + return string.Format(CultureInfo.CurrentCulture, GetString("LoggingResultMessageForRole"), p0, p1); + } + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/src/Microsoft.AspNet.Identity/Resources.resx b/src/Microsoft.AspNet.Identity/Resources.resx index 2592527afb..2351b1e7aa 100644 --- a/src/Microsoft.AspNet.Identity/Resources.resx +++ b/src/Microsoft.AspNet.Identity/Resources.resx @@ -130,7 +130,7 @@ Default name for the email token provider - Security Code + Security code Default subject for the email @@ -146,24 +146,24 @@ Default name for the phone number token provider - DefaultTokenProvider + Default Token Provider Name of the default token provider Email '{0}' is already taken. - error for duplicate emails + Error for duplicate emails - Role Name '{0}' is already taken. - error for duplicate usernames + Role name '{0}' is already taken. + Error for duplicate user names - UserName '{0}' is already taken. - error for duplicate usernames + User name '{0}' is already taken. + Error for duplicate user names Email '{0}' is invalid. - invalid email + Invalid email The provided PasswordHasherCompatibilityMode is invalid. @@ -175,7 +175,7 @@ Role name '{0}' is invalid. - error for invalid role names + Error for invalid role names Invalid token. @@ -183,7 +183,7 @@ User name '{0}' is invalid, can only contain letters or digits. - usernames can only contain letters or digits + User names can only contain letters or digits A user with this login already exists. @@ -223,63 +223,63 @@ Role {0} does not exist. - error when a role does not exist + Error when a role does not exist Store does not implement IQueryableRoleStore<TRole>. - error when the store does not implement this interface + Error when the store does not implement this interface Store does not implement IQueryableUserStore<TUser>. - error when the store does not implement this interface + Error when the store does not implement this interface Store does not implement IRoleClaimStore<TRole>. - error when the store does not implement this interface + Error when the store does not implement this interface Store does not implement IUserClaimStore<TUser>. - error when the store does not implement this interface + Error when the store does not implement this interface Store does not implement IUserConfirmationStore<TUser>. - error when the store does not implement this interface + Error when the store does not implement this interface Store does not implement IUserEmailStore<TUser>. - error when the store does not implement this interface + Error when the store does not implement this interface Store does not implement IUserLockoutStore<TUser>. - error when the store does not implement this interface + Error when the store does not implement this interface Store does not implement IUserLoginStore<TUser>. - error when the store does not implement this interface + Error when the store does not implement this interface Store does not implement IUserPasswordStore<TUser>. - error when the store does not implement this interface + Error when the store does not implement this interface Store does not implement IUserPhoneNumberStore<TUser>. - error when the store does not implement this interface + Error when the store does not implement this interface Store does not implement IUserRoleStore<TUser>. - error when the store does not implement this interface + Error when the store does not implement this interface Store does not implement IUserSecurityStampStore<TUser>. - error when the store does not implement this interface + Error when the store does not implement this interface Store does not implement IUserTwoFactorStore<TUser>. - error when the store does not implement this interface + Error when the store does not implement this interface User already has a password set. - error when AddPasswordAsync called when a user already has a password + Error when AddPasswordAsync called when a user already has a password User already in role '{0}'. @@ -291,14 +291,34 @@ Lockout is not enabled for this user. - error when lockout is not enabled + Error when lockout is not enabled User {0} does not exist. - error when a user does not exist + Error when a user does not exist User is not in role '{0}'. Error when a user is not in the role + + {0} : Failed : {1} + Logging method execution failure + + + {0} : Success + Logging method execution success + + + {0} : Result : {1} + Logging statement for SignInManager + + + {0} for user: {1} + Message prefix for Identity result + + + {0} for role: {1} + Message prefix for Identity result for role operation + \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity/RoleManager.cs b/src/Microsoft.AspNet.Identity/RoleManager.cs index 7b23ca8ab2..98b7db3990 100644 --- a/src/Microsoft.AspNet.Identity/RoleManager.cs +++ b/src/Microsoft.AspNet.Identity/RoleManager.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Security.Claims; using System.Threading; using System.Threading.Tasks; +using Microsoft.Framework.Logging; namespace Microsoft.AspNet.Identity { @@ -23,10 +24,11 @@ namespace Microsoft.AspNet.Identity /// /// The IRoleStore commits changes via the UpdateAsync/CreateAsync methods /// - public RoleManager(IRoleStore store, + public RoleManager(IRoleStore store, IEnumerable> roleValidators = null, ILookupNormalizer keyNormalizer = null, - IdentityErrorDescriber errors = null) + IdentityErrorDescriber errors = null, + ILoggerFactory loggerFactory = null) { if (store == null) { @@ -43,6 +45,9 @@ namespace Microsoft.AspNet.Identity RoleValidators.Add(v); } } + + loggerFactory = loggerFactory ?? new LoggerFactory(); + Logger = loggerFactory.Create(nameof(RoleManager)); } /// @@ -60,6 +65,11 @@ namespace Microsoft.AspNet.Identity /// public IdentityErrorDescriber ErrorDescriber { get; set; } + /// + /// Used to log results + /// + public ILogger Logger { get; set; } + /// /// Used to normalize user names, role names, emails for uniqueness /// @@ -134,7 +144,7 @@ namespace Microsoft.AspNet.Identity /// /// /// - public virtual async Task CreateAsync(TRole role, + public virtual async Task CreateAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); @@ -149,7 +159,7 @@ namespace Microsoft.AspNet.Identity return result; } await UpdateNormalizedRoleNameAsync(role, cancellationToken); - return await Store.CreateAsync(role, cancellationToken); + return await LogResultAsync(await Store.CreateAsync(role, cancellationToken), role); } /// @@ -172,7 +182,7 @@ namespace Microsoft.AspNet.Identity /// /// /// - public virtual async Task UpdateAsync(TRole role, + public virtual async Task UpdateAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); @@ -181,6 +191,12 @@ namespace Microsoft.AspNet.Identity throw new ArgumentNullException("role"); } + return await LogResultAsync(await UpdateRoleAsync(role, cancellationToken), role); + } + + private async Task UpdateRoleAsync(TRole role, + CancellationToken cancellationToken = default(CancellationToken)) + { var result = await ValidateRoleInternal(role, cancellationToken); if (!result.Succeeded) { @@ -196,7 +212,7 @@ namespace Microsoft.AspNet.Identity /// /// /// - public virtual async Task DeleteAsync(TRole role, + public virtual async Task DeleteAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); @@ -204,7 +220,7 @@ namespace Microsoft.AspNet.Identity { throw new ArgumentNullException("role"); } - return await Store.DeleteAsync(role, cancellationToken); + return await LogResultAsync(await Store.DeleteAsync(role, cancellationToken), role); } /// @@ -213,7 +229,7 @@ namespace Microsoft.AspNet.Identity /// /// /// - public virtual async Task RoleExistsAsync(string roleName, + public virtual async Task RoleExistsAsync(string roleName, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); @@ -242,7 +258,7 @@ namespace Microsoft.AspNet.Identity /// /// /// - public virtual async Task FindByIdAsync(string roleId, + public virtual async Task FindByIdAsync(string roleId, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); @@ -255,7 +271,7 @@ namespace Microsoft.AspNet.Identity /// /// /// - public virtual async Task GetRoleNameAsync(TRole role, + public virtual async Task GetRoleNameAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); @@ -269,13 +285,13 @@ namespace Microsoft.AspNet.Identity /// /// /// - public virtual async Task SetRoleNameAsync(TRole role, string name, + public virtual async Task SetRoleNameAsync(TRole role, string name, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); await Store.SetRoleNameAsync(role, name, cancellationToken); await UpdateNormalizedRoleNameAsync(role, cancellationToken); - return IdentityResult.Success; + return await LogResultAsync(IdentityResult.Success, role); } /// @@ -284,7 +300,7 @@ namespace Microsoft.AspNet.Identity /// /// /// - public virtual async Task GetRoleIdAsync(TRole role, + public virtual async Task GetRoleIdAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); @@ -297,7 +313,7 @@ namespace Microsoft.AspNet.Identity /// /// /// - public virtual async Task FindByNameAsync(string roleName, + public virtual async Task FindByNameAsync(string roleName, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); @@ -327,7 +343,7 @@ namespace Microsoft.AspNet.Identity /// /// /// - public virtual async Task AddClaimAsync(TRole role, Claim claim, + public virtual async Task AddClaimAsync(TRole role, Claim claim, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); @@ -341,7 +357,7 @@ namespace Microsoft.AspNet.Identity throw new ArgumentNullException("role"); } await claimStore.AddClaimAsync(role, claim, cancellationToken); - return await UpdateAsync(role, cancellationToken); + return await LogResultAsync(await UpdateRoleAsync(role, cancellationToken), role); } /// @@ -351,7 +367,7 @@ namespace Microsoft.AspNet.Identity /// /// /// - public virtual async Task RemoveClaimAsync(TRole role, Claim claim, + public virtual async Task RemoveClaimAsync(TRole role, Claim claim, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); @@ -361,7 +377,7 @@ namespace Microsoft.AspNet.Identity throw new ArgumentNullException("role"); } await claimStore.RemoveClaimAsync(role, claim, cancellationToken); - return await UpdateAsync(role, cancellationToken); + return await LogResultAsync(await UpdateRoleAsync(role, cancellationToken), role); } /// @@ -370,7 +386,7 @@ namespace Microsoft.AspNet.Identity /// /// /// - public virtual async Task> GetClaimsAsync(TRole role, + public virtual async Task> GetClaimsAsync(TRole role, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); @@ -382,6 +398,21 @@ namespace Microsoft.AspNet.Identity return await claimStore.GetClaimsAsync(role, cancellationToken); } + /// + /// Logs the current Identity Result and returns result object + /// + /// + /// + /// + /// + protected async Task LogResultAsync(IdentityResult result, + TRole role, [System.Runtime.CompilerServices.CallerMemberName] string methodName = "") + { + result.Log(Logger, Resources.FormatLoggingResultMessageForRole(methodName, await GetRoleIdAsync(role))); + + return result; + } + private void ThrowIfDisposed() { if (_disposed) diff --git a/src/Microsoft.AspNet.Identity/SignInManager.cs b/src/Microsoft.AspNet.Identity/SignInManager.cs index fb170b95e7..d6fd276950 100644 --- a/src/Microsoft.AspNet.Identity/SignInManager.cs +++ b/src/Microsoft.AspNet.Identity/SignInManager.cs @@ -11,6 +11,7 @@ using System.Threading.Tasks; using Microsoft.AspNet.Hosting; using Microsoft.AspNet.Http; using Microsoft.AspNet.Http.Security; +using Microsoft.Framework.Logging; using Microsoft.Framework.OptionsModel; namespace Microsoft.AspNet.Identity @@ -21,10 +22,11 @@ namespace Microsoft.AspNet.Identity /// public class SignInManager where TUser : class { - public SignInManager(UserManager userManager, - IHttpContextAccessor contextAccessor, - IClaimsIdentityFactory claimsFactory, - IOptions optionsAccessor = null) + public SignInManager(UserManager userManager, + IHttpContextAccessor contextAccessor, + IClaimsIdentityFactory claimsFactory, + IOptions optionsAccessor = null, + ILoggerFactory loggerFactory = null) { if (userManager == null) { @@ -38,16 +40,21 @@ namespace Microsoft.AspNet.Identity { throw new ArgumentNullException(nameof(claimsFactory)); } + UserManager = userManager; Context = contextAccessor.Value; ClaimsFactory = claimsFactory; Options = optionsAccessor?.Options ?? new IdentityOptions(); + + loggerFactory = loggerFactory ?? new LoggerFactory(); + Logger = loggerFactory.Create(nameof(SignInManager)); } public UserManager UserManager { get; private set; } public HttpContext Context { get; private set; } public IClaimsIdentityFactory ClaimsFactory { get; private set; } public IdentityOptions Options { get; private set; } + public ILogger Logger { get; set; } // Should this be a func? public virtual async Task CreateUserIdentityAsync(TUser user, @@ -61,13 +68,13 @@ namespace Microsoft.AspNet.Identity { if (Options.SignIn.RequireConfirmedEmail && !(await UserManager.IsEmailConfirmedAsync(user, cancellationToken))) { - return false; + return await LogResultAsync(false, user); } if (Options.SignIn.RequireConfirmedPhoneNumber && !(await UserManager.IsPhoneNumberConfirmedAsync(user, cancellationToken))) { - return false; + return await LogResultAsync(false, user); } - return true; + return await LogResultAsync(true, user); } public virtual async Task SignInAsync(TUser user, bool isPersistent, string authenticationMethod = null, @@ -139,7 +146,7 @@ namespace Microsoft.AspNet.Identity return null; } - public virtual async Task PasswordSignInAsync(TUser user, string password, + public virtual async Task PasswordSignInAsync(TUser user, string password, bool isPersistent, bool shouldLockout, CancellationToken cancellationToken = default(CancellationToken)) { if (user == null) @@ -149,16 +156,16 @@ namespace Microsoft.AspNet.Identity var error = await PreSignInCheck(user, cancellationToken); if (error != null) { - return error; + return await LogResultAsync(error, user); } if (await IsLockedOut(user, cancellationToken)) { - return SignInResult.LockedOut; + return await LogResultAsync(SignInResult.LockedOut, user); } if (await UserManager.CheckPasswordAsync(user, password, cancellationToken)) { await ResetLockout(user, cancellationToken); - return await SignInOrTwoFactorAsync(user, isPersistent, cancellationToken); + return await LogResultAsync(await SignInOrTwoFactorAsync(user, isPersistent, cancellationToken), user); } if (UserManager.SupportsUserLockout && shouldLockout) { @@ -166,10 +173,11 @@ namespace Microsoft.AspNet.Identity await UserManager.AccessFailedAsync(user, cancellationToken); if (await UserManager.IsLockedOutAsync(user, cancellationToken)) { - return SignInResult.LockedOut; + + return await LogResultAsync(SignInResult.LockedOut, user); } } - return SignInResult.Failed; + return await LogResultAsync(SignInResult.Failed, user); } public virtual async Task PasswordSignInAsync(string userName, string password, @@ -214,7 +222,7 @@ namespace Microsoft.AspNet.Identity } var token = await UserManager.GenerateTwoFactorTokenAsync(user, provider, cancellationToken); await UserManager.NotifyTwoFactorTokenAsync(user, provider, token, cancellationToken); - return true; + return await LogResultAsync(true, user); } public async Task IsTwoFactorClientRememberedAsync(TUser user, @@ -257,7 +265,7 @@ namespace Microsoft.AspNet.Identity var error = await PreSignInCheck(user, cancellationToken); if (error != null) { - return error; + return await LogResultAsync(error, user); } if (await UserManager.VerifyTwoFactorTokenAsync(user, provider, code, cancellationToken)) { @@ -275,11 +283,11 @@ namespace Microsoft.AspNet.Identity } await UserManager.ResetAccessFailedCountAsync(user, cancellationToken); await SignInAsync(user, isPersistent); - return SignInResult.Success; + return await LogResultAsync(SignInResult.Success, user); } // If the token is incorrect, record the failure which also may cause the user to be locked out await UserManager.AccessFailedAsync(user, cancellationToken); - return SignInResult.Failed; + return await LogResultAsync(SignInResult.Failed, user); } /// @@ -310,9 +318,9 @@ namespace Microsoft.AspNet.Identity var error = await PreSignInCheck(user, cancellationToken); if (error != null) { - return error; + return await LogResultAsync(error, user); } - return await SignInOrTwoFactorAsync(user, isPersistent, cancellationToken, loginProvider); + return await LogResultAsync(await SignInOrTwoFactorAsync(user, isPersistent, cancellationToken, loginProvider), user); } private const string LoginProviderKey = "LoginProvider"; @@ -323,7 +331,7 @@ namespace Microsoft.AspNet.Identity return Context.GetAuthenticationTypes().Where(d => !string.IsNullOrEmpty(d.Caption)); } - public virtual async Task GetExternalLoginInfoAsync(string expectedXsrf = null, + public virtual async Task GetExternalLoginInfoAsync(string expectedXsrf = null, CancellationToken cancellationToken = default(CancellationToken)) { var auth = await Context.AuthenticateAsync(IdentityOptions.ExternalCookieAuthenticationType); @@ -368,7 +376,7 @@ namespace Microsoft.AspNet.Identity private async Task SignInOrTwoFactorAsync(TUser user, bool isPersistent, CancellationToken cancellationToken, string loginProvider = null) { - if (UserManager.SupportsUserTwoFactor && + if (UserManager.SupportsUserTwoFactor && await UserManager.GetTwoFactorEnabledAsync(user, cancellationToken) && (await UserManager.GetValidTwoFactorProvidersAsync(user, cancellationToken)).Count > 0) { @@ -403,6 +411,35 @@ namespace Microsoft.AspNet.Identity return null; } + /// + /// Log boolean result for user and return result + /// + /// + /// + /// + /// + protected async virtual Task LogResultAsync(bool result, TUser user, [System.Runtime.CompilerServices.CallerMemberName] string methodName = "") + { + Logger.WriteInformation(Resources.FormatLoggingSigninResult(Resources.FormatLoggingResultMessage(methodName, + await UserManager.GetUserIdAsync(user)), result)); + + return result; + } + + /// + /// Log SignInStatus for user and return SignInStatus + /// + /// + /// + /// + /// + protected async virtual Task LogResultAsync(SignInResult status, TUser user, [System.Runtime.CompilerServices.CallerMemberName] string methodName = "") + { + status.Log(Logger, Resources.FormatLoggingResultMessage(methodName, await UserManager.GetUserIdAsync(user))); + + return status; + } + internal static ClaimsIdentity StoreTwoFactorInfo(string userId, string loginProvider) { var identity = new ClaimsIdentity(IdentityOptions.TwoFactorUserIdCookieAuthenticationType); diff --git a/src/Microsoft.AspNet.Identity/SignInResult.cs b/src/Microsoft.AspNet.Identity/SignInResult.cs index 9c88817de2..7ef69a60bb 100644 --- a/src/Microsoft.AspNet.Identity/SignInResult.cs +++ b/src/Microsoft.AspNet.Identity/SignInResult.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using Microsoft.Framework.Logging; + namespace Microsoft.AspNet.Identity { /// @@ -78,5 +80,34 @@ namespace Microsoft.AspNet.Identity { get { return _twoFactorRequired; } } + + /// + /// Log result based on properties + /// + /// + /// + public virtual void Log(ILogger logger, string message) + { + if (IsLockedOut) + { + logger.WriteInformation(Resources.FormatLoggingSigninResult(message, "Lockedout")); + } + else if (IsNotAllowed) + { + logger.WriteInformation(Resources.FormatLoggingSigninResult(message, "NotAllowed")); + } + else if (RequiresTwoFactor) + { + logger.WriteInformation(Resources.FormatLoggingSigninResult(message, "RequiresTwoFactor")); + } + else if (Succeeded) + { + logger.WriteInformation(Resources.FormatLoggingSigninResult(message, "Succeeded")); + } + else + { + logger.WriteInformation(Resources.FormatLoggingSigninResult(message, "Failed")); + } + } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Identity/UserManager.cs b/src/Microsoft.AspNet.Identity/UserManager.cs index e7897a7e1a..fa63e7d5ff 100644 --- a/src/Microsoft.AspNet.Identity/UserManager.cs +++ b/src/Microsoft.AspNet.Identity/UserManager.cs @@ -9,6 +9,7 @@ using System.Security.Claims; using System.Text; using System.Threading; using System.Threading.Tasks; +using Microsoft.Framework.Logging; using Microsoft.Framework.OptionsModel; namespace Microsoft.AspNet.Identity @@ -41,15 +42,17 @@ namespace Microsoft.AspNet.Identity /// /// /// - public UserManager(IUserStore store, + /// + public UserManager(IUserStore store, IOptions optionsAccessor = null, - IPasswordHasher passwordHasher = null, + IPasswordHasher passwordHasher = null, IEnumerable> userValidators = null, - IEnumerable> passwordValidators = null, + IEnumerable> passwordValidators = null, ILookupNormalizer keyNormalizer = null, IdentityErrorDescriber errors = null, - IEnumerable> tokenProviders = null, - IEnumerable msgProviders = null) + IEnumerable> tokenProviders = null, + IEnumerable msgProviders = null, + ILoggerFactory loggerFactory = null) { if (store == null) { @@ -60,6 +63,7 @@ namespace Microsoft.AspNet.Identity PasswordHasher = passwordHasher ?? new PasswordHasher(); KeyNormalizer = keyNormalizer ?? new UpperInvariantLookupNormalizer(); ErrorDescriber = errors ?? new IdentityErrorDescriber(); + if (userValidators != null) { foreach (var v in userValidators) @@ -74,6 +78,10 @@ namespace Microsoft.AspNet.Identity PasswordValidators.Add(v); } } + + loggerFactory = loggerFactory ?? new LoggerFactory(); + Logger = loggerFactory.Create(nameof(UserManager)); + if (tokenProviders != null) { foreach (var tokenProvider in tokenProviders) @@ -138,6 +146,11 @@ namespace Microsoft.AspNet.Identity /// public IdentityErrorDescriber ErrorDescriber { get; set; } + /// + /// Used to log IdentityResult + /// + public ILogger Logger { get; set; } + public IdentityOptions Options { get @@ -329,12 +342,31 @@ namespace Microsoft.AspNet.Identity return errors.Count > 0 ? IdentityResult.Failed(errors.ToArray()) : IdentityResult.Success; } - public virtual Task GenerateConcurrencyStampAsync(TUser user, + public virtual Task GenerateConcurrencyStampAsync(TUser user, CancellationToken token = default(CancellationToken)) { return Task.FromResult(Guid.NewGuid().ToString()); } + /// + /// Validate user and update. Called by other UserManager methods + /// + /// + /// + /// + private async Task UpdateUserAsync(TUser user, + CancellationToken cancellationToken = default(CancellationToken)) + { + var result = await ValidateUserInternal(user, cancellationToken); + if (!result.Succeeded) + { + return result; + } + await UpdateNormalizedUserNameAsync(user, cancellationToken); + await UpdateNormalizedEmailAsync(user, cancellationToken); + return await Store.UpdateAsync(user, cancellationToken); + } + /// /// Create a user with no password /// @@ -357,7 +389,7 @@ namespace Microsoft.AspNet.Identity } await UpdateNormalizedUserNameAsync(user, cancellationToken); await UpdateNormalizedEmailAsync(user, cancellationToken); - return await Store.CreateAsync(user, cancellationToken); + return await LogResultAsync(await Store.CreateAsync(user, cancellationToken), user); } /// @@ -374,14 +406,7 @@ namespace Microsoft.AspNet.Identity { throw new ArgumentNullException("user"); } - var result = await ValidateUserInternal(user, cancellationToken); - if (!result.Succeeded) - { - return result; - } - await UpdateNormalizedUserNameAsync(user, cancellationToken); - await UpdateNormalizedEmailAsync(user, cancellationToken); - return await Store.UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } /// @@ -398,7 +423,7 @@ namespace Microsoft.AspNet.Identity { throw new ArgumentNullException("user"); } - return await Store.DeleteAsync(user, cancellationToken); + return await LogResultAsync(await Store.DeleteAsync(user, cancellationToken), user); } /// @@ -527,7 +552,7 @@ namespace Microsoft.AspNet.Identity throw new ArgumentNullException("user"); } await UpdateUserName(user, userName, cancellationToken); - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } private async Task UpdateUserName(TUser user, string userName, CancellationToken cancellationToken) @@ -566,11 +591,13 @@ namespace Microsoft.AspNet.Identity return false; } var result = await VerifyPasswordAsync(passwordStore, user, password, cancellationToken); - if (result == PasswordVerificationResult.SuccessRehashNeeded) { + if (result == PasswordVerificationResult.SuccessRehashNeeded) + { await UpdatePasswordHash(passwordStore, user, password, cancellationToken, validatePassword: false); - await UpdateAsync(user, cancellationToken); + await UpdateUserAsync(user, cancellationToken); } - return result != PasswordVerificationResult.Failed; + + return await LogResultAsync(result != PasswordVerificationResult.Failed, user); } /// @@ -588,7 +615,7 @@ namespace Microsoft.AspNet.Identity { throw new ArgumentNullException("user"); } - return await passwordStore.HasPasswordAsync(user, cancellationToken); + return await LogResultAsync(await passwordStore.HasPasswordAsync(user, cancellationToken), user); } /// @@ -610,14 +637,14 @@ namespace Microsoft.AspNet.Identity var hash = await passwordStore.GetPasswordHashAsync(user, cancellationToken); if (hash != null) { - return IdentityResult.Failed(ErrorDescriber.UserAlreadyHasPassword()); + return await LogResultAsync(IdentityResult.Failed(ErrorDescriber.UserAlreadyHasPassword()), user); } var result = await UpdatePasswordHash(passwordStore, user, password, cancellationToken); if (!result.Succeeded) { - return result; + return await LogResultAsync(result, user); } - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } /// @@ -642,11 +669,11 @@ namespace Microsoft.AspNet.Identity var result = await UpdatePasswordHash(passwordStore, user, newPassword, cancellationToken); if (!result.Succeeded) { - return result; + return await LogResultAsync(result, user); } - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } - return IdentityResult.Failed(ErrorDescriber.PasswordMismatch()); + return await LogResultAsync(IdentityResult.Failed(ErrorDescriber.PasswordMismatch()), user); } /// @@ -665,13 +692,13 @@ namespace Microsoft.AspNet.Identity throw new ArgumentNullException("user"); } await UpdatePasswordHash(passwordStore, user, null, cancellationToken, validatePassword: false); - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } internal async Task UpdatePasswordHash(IUserPasswordStore passwordStore, TUser user, string newPassword, CancellationToken cancellationToken, bool validatePassword = true) { - if (validatePassword) + if (validatePassword) { var validate = await ValidatePasswordInternal(user, newPassword, cancellationToken); if (!validate.Succeeded) @@ -746,7 +773,7 @@ namespace Microsoft.AspNet.Identity throw new ArgumentNullException("user"); } await UpdateSecurityStampInternal(user, cancellationToken); - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } /// @@ -759,7 +786,10 @@ namespace Microsoft.AspNet.Identity CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); - return await GenerateUserTokenAsync(user, Options.PasswordResetTokenProvider, "ResetPassword", cancellationToken); + var token = await GenerateUserTokenAsync(user, Options.PasswordResetTokenProvider, "ResetPassword", cancellationToken); + await LogResultAsync(IdentityResult.Success, user); + + return token; } /// @@ -781,15 +811,15 @@ namespace Microsoft.AspNet.Identity // Make sure the token is valid and the stamp matches if (!await VerifyUserTokenAsync(user, Options.PasswordResetTokenProvider, "ResetPassword", token, cancellationToken)) { - return IdentityResult.Failed(ErrorDescriber.InvalidToken()); + return await LogResultAsync(IdentityResult.Failed(ErrorDescriber.InvalidToken()), user); } var passwordStore = GetPasswordStore(); var result = await UpdatePasswordHash(passwordStore, user, newPassword, cancellationToken); if (!result.Succeeded) { - return result; + return await LogResultAsync(result, user); } - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } // Update the security stamp if the store supports it @@ -866,7 +896,7 @@ namespace Microsoft.AspNet.Identity } await loginStore.RemoveLoginAsync(user, loginProvider, providerKey, cancellationToken); await UpdateSecurityStampInternal(user, cancellationToken); - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } /// @@ -892,10 +922,10 @@ namespace Microsoft.AspNet.Identity var existingUser = await FindByLoginAsync(login.LoginProvider, login.ProviderKey, cancellationToken); if (existingUser != null) { - return IdentityResult.Failed(ErrorDescriber.LoginAlreadyAssociated()); + return await LogResultAsync(IdentityResult.Failed(ErrorDescriber.LoginAlreadyAssociated()), user); } await loginStore.AddLoginAsync(user, login, cancellationToken); - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } /// @@ -971,7 +1001,7 @@ namespace Microsoft.AspNet.Identity throw new ArgumentNullException("user"); } await claimStore.AddClaimsAsync(user, claims, cancellationToken); - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } /// @@ -1000,7 +1030,7 @@ namespace Microsoft.AspNet.Identity throw new ArgumentNullException("user"); } await claimStore.ReplaceClaimAsync(user, claim, newClaim, cancellationToken); - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } /// @@ -1047,7 +1077,7 @@ namespace Microsoft.AspNet.Identity throw new ArgumentNullException("claims"); } await claimStore.RemoveClaimsAsync(user, claims, cancellationToken); - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } /// @@ -1097,10 +1127,10 @@ namespace Microsoft.AspNet.Identity var userRoles = await userRoleStore.GetRolesAsync(user, cancellationToken); if (userRoles.Contains(role)) { - return IdentityResult.Failed(ErrorDescriber.UserAlreadyInRole(role)); + return await LogResultAsync(IdentityResult.Failed(ErrorDescriber.UserAlreadyInRole(role)), user); } await userRoleStore.AddToRoleAsync(user, role, cancellationToken); - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } /// @@ -1128,11 +1158,11 @@ namespace Microsoft.AspNet.Identity { if (userRoles.Contains(role)) { - return IdentityResult.Failed(ErrorDescriber.UserAlreadyInRole(role)); + return await LogResultAsync(IdentityResult.Failed(ErrorDescriber.UserAlreadyInRole(role)), user); } await userRoleStore.AddToRoleAsync(user, role, cancellationToken); } - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } /// @@ -1153,10 +1183,10 @@ namespace Microsoft.AspNet.Identity } if (!await userRoleStore.IsInRoleAsync(user, role, cancellationToken)) { - return IdentityResult.Failed(ErrorDescriber.UserNotInRole(role)); + return await LogResultAsync(IdentityResult.Failed(ErrorDescriber.UserNotInRole(role)), user); } await userRoleStore.RemoveFromRoleAsync(user, role, cancellationToken); - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } /// @@ -1183,11 +1213,11 @@ namespace Microsoft.AspNet.Identity { if (!await userRoleStore.IsInRoleAsync(user, role, cancellationToken)) { - return IdentityResult.Failed(ErrorDescriber.UserNotInRole(role)); + return await LogResultAsync(IdentityResult.Failed(ErrorDescriber.UserNotInRole(role)), user); } await userRoleStore.RemoveFromRoleAsync(user, role, cancellationToken); } - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } /// @@ -1276,7 +1306,7 @@ namespace Microsoft.AspNet.Identity await store.SetEmailAsync(user, email, cancellationToken); await store.SetEmailConfirmedAsync(user, false, cancellationToken); await UpdateSecurityStampInternal(user, cancellationToken); - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } /// @@ -1321,11 +1351,14 @@ namespace Microsoft.AspNet.Identity /// /// /// - public virtual Task GenerateEmailConfirmationTokenAsync(TUser user, + public async virtual Task GenerateEmailConfirmationTokenAsync(TUser user, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); - return GenerateUserTokenAsync(user, Options.EmailConfirmationTokenProvider, "Confirmation", cancellationToken); + var token = await GenerateUserTokenAsync(user, Options.EmailConfirmationTokenProvider, "Confirmation", cancellationToken); + await LogResultAsync(IdentityResult.Success, user); + + return token; } /// @@ -1346,10 +1379,10 @@ namespace Microsoft.AspNet.Identity } if (!await VerifyUserTokenAsync(user, Options.EmailConfirmationTokenProvider, "Confirmation", token, cancellationToken)) { - return IdentityResult.Failed(ErrorDescriber.InvalidToken()); + return await LogResultAsync(IdentityResult.Failed(ErrorDescriber.InvalidToken()), user); } await store.SetEmailConfirmedAsync(user, true, cancellationToken); - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } /// @@ -1385,7 +1418,10 @@ namespace Microsoft.AspNet.Identity CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); - return await GenerateUserTokenAsync(user, Options.ChangeEmailTokenProvider, GetChangeEmailPurpose(newEmail), cancellationToken); + var token = await GenerateUserTokenAsync(user, Options.ChangeEmailTokenProvider, GetChangeEmailPurpose(newEmail), cancellationToken); + await LogResultAsync(IdentityResult.Success, user); + + return token; } /// @@ -1407,13 +1443,13 @@ namespace Microsoft.AspNet.Identity // Make sure the token is valid and the stamp matches if (!await VerifyUserTokenAsync(user, Options.ChangeEmailTokenProvider, GetChangeEmailPurpose(newEmail), token, cancellationToken)) { - return IdentityResult.Failed(ErrorDescriber.InvalidToken()); + return await LogResultAsync(IdentityResult.Failed(ErrorDescriber.InvalidToken()), user); } var store = GetEmailStore(); await store.SetEmailAsync(user, newEmail, cancellationToken); await store.SetEmailConfirmedAsync(user, true, cancellationToken); await UpdateSecurityStampInternal(user, cancellationToken); - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } // IUserPhoneNumberStore methods @@ -1464,7 +1500,7 @@ namespace Microsoft.AspNet.Identity await store.SetPhoneNumberAsync(user, phoneNumber, cancellationToken); await store.SetPhoneNumberConfirmedAsync(user, false, cancellationToken); await UpdateSecurityStampInternal(user, cancellationToken); - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } /// @@ -1486,12 +1522,12 @@ namespace Microsoft.AspNet.Identity } if (!await VerifyChangePhoneNumberTokenAsync(user, token, phoneNumber, cancellationToken)) { - return IdentityResult.Failed(ErrorDescriber.InvalidToken()); + return await LogResultAsync(IdentityResult.Failed(ErrorDescriber.InvalidToken()), user); } await store.SetPhoneNumberAsync(user, phoneNumber, cancellationToken); await store.SetPhoneNumberConfirmedAsync(user, true, cancellationToken); await UpdateSecurityStampInternal(user, cancellationToken); - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } /// @@ -1530,9 +1566,12 @@ namespace Microsoft.AspNet.Identity CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); - return Rfc6238AuthenticationService.GenerateCode( + var token = Rfc6238AuthenticationService.GenerateCode( await CreateSecurityTokenAsync(user, cancellationToken), phoneNumber) .ToString(CultureInfo.InvariantCulture); + + await LogResultAsync(IdentityResult.Success, user); + return token; } /// @@ -1550,8 +1589,13 @@ namespace Microsoft.AspNet.Identity int code; if (securityToken != null && Int32.TryParse(token, out code)) { - return Rfc6238AuthenticationService.ValidateCode(securityToken, code, phoneNumber); + if (Rfc6238AuthenticationService.ValidateCode(securityToken, code, phoneNumber)) + { + await LogResultAsync(IdentityResult.Success, user); + return true; + } } + await LogResultAsync(IdentityResult.Failed(ErrorDescriber.InvalidToken()), user); return false; } @@ -1580,7 +1624,18 @@ namespace Microsoft.AspNet.Identity throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, Resources.NoTokenProvider, tokenProvider)); } // Make sure the token is valid - return await _tokenProviders[tokenProvider].ValidateAsync(purpose, token, this, user, cancellationToken); + var result = await _tokenProviders[tokenProvider].ValidateAsync(purpose, token, this, user, cancellationToken); + + if (result) + { + await LogResultAsync(IdentityResult.Success, user); + } + else + { + await LogResultAsync(IdentityResult.Failed(ErrorDescriber.InvalidToken()), user); + } + + return result; } /// @@ -1606,7 +1661,11 @@ namespace Microsoft.AspNet.Identity { throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, Resources.NoTokenProvider, tokenProvider)); } - return await _tokenProviders[tokenProvider].GenerateAsync(purpose, this, user, cancellationToken); + + var token = await _tokenProviders[tokenProvider].GenerateAsync(purpose, this, user, cancellationToken); + await LogResultAsync(IdentityResult.Success, user); + + return token; } /// @@ -1684,7 +1743,18 @@ namespace Microsoft.AspNet.Identity Resources.NoTokenProvider, tokenProvider)); } // Make sure the token is valid - return await _tokenProviders[tokenProvider].ValidateAsync("TwoFactor", token, this, user, cancellationToken); + var result = await _tokenProviders[tokenProvider].ValidateAsync("TwoFactor", token, this, user, cancellationToken); + + if (result) + { + await LogResultAsync(IdentityResult.Success, user); + } + else + { + await LogResultAsync(IdentityResult.Failed(ErrorDescriber.InvalidToken()), user); + } + + return result; } /// @@ -1707,7 +1777,10 @@ namespace Microsoft.AspNet.Identity throw new NotSupportedException(String.Format(CultureInfo.CurrentCulture, Resources.NoTokenProvider, tokenProvider)); } - return await _tokenProviders[tokenProvider].GenerateAsync("TwoFactor", this, user, cancellationToken); + var token = await _tokenProviders[tokenProvider].GenerateAsync("TwoFactor", this, user, cancellationToken); + await LogResultAsync(IdentityResult.Success, user); + + return token; } /// @@ -1736,7 +1809,7 @@ namespace Microsoft.AspNet.Identity Resources.NoTokenProvider, tokenProvider)); } await _tokenProviders[tokenProvider].NotifyAsync(token, this, user, cancellationToken); - return IdentityResult.Success; + return await LogResultAsync(IdentityResult.Success, user); } // IUserFactorStore methods @@ -1786,7 +1859,7 @@ namespace Microsoft.AspNet.Identity } await store.SetTwoFactorEnabledAsync(user, enabled, cancellationToken); await UpdateSecurityStampInternal(user, cancellationToken); - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } // Messaging methods @@ -1798,7 +1871,7 @@ namespace Microsoft.AspNet.Identity /// /// /// - public virtual async Task SendMessageAsync(string messageProvider, IdentityMessage message, + public virtual async Task SendMessageAsync(string messageProvider, IdentityMessage message, CancellationToken cancellationToken = default(CancellationToken)) { ThrowIfDisposed(); @@ -1866,7 +1939,7 @@ namespace Microsoft.AspNet.Identity throw new ArgumentNullException("user"); } await store.SetLockoutEnabledAsync(user, enabled, cancellationToken); - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } /// @@ -1923,10 +1996,10 @@ namespace Microsoft.AspNet.Identity } if (!await store.GetLockoutEnabledAsync(user, cancellationToken).ConfigureAwait((false))) { - return IdentityResult.Failed(ErrorDescriber.UserLockoutNotEnabled()); + return await LogResultAsync(IdentityResult.Failed(ErrorDescriber.UserLockoutNotEnabled()), user); } await store.SetLockoutEndDateAsync(user, lockoutEnd, cancellationToken); - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } /// @@ -1950,12 +2023,12 @@ namespace Microsoft.AspNet.Identity var count = await store.IncrementAccessFailedCountAsync(user, cancellationToken); if (count < Options.Lockout.MaxFailedAccessAttempts) { - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } await store.SetLockoutEndDateAsync(user, DateTimeOffset.UtcNow.Add(Options.Lockout.DefaultLockoutTimeSpan), cancellationToken); await store.ResetAccessFailedCountAsync(user, cancellationToken); - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } /// @@ -1974,7 +2047,7 @@ namespace Microsoft.AspNet.Identity throw new ArgumentNullException("user"); } await store.ResetAccessFailedCountAsync(user, cancellationToken); - return await UpdateAsync(user, cancellationToken); + return await LogResultAsync(await UpdateUserAsync(user, cancellationToken), user); } /// @@ -2026,6 +2099,44 @@ namespace Microsoft.AspNet.Identity return store.GetUsersInRoleAsync(roleName, cancellationToken); } + /// + /// Logs the current Identity Result and returns result object + /// + /// + /// + /// + /// + protected async Task LogResultAsync(IdentityResult result, + TUser user, [System.Runtime.CompilerServices.CallerMemberName] string methodName = "") + { + result.Log(Logger, Resources.FormatLoggingResultMessage(methodName, await GetUserIdAsync(user))); + + return result; + } + + /// + /// Logs result of operation being true/false + /// + /// + /// + /// + /// result + protected async Task LogResultAsync(bool result, + TUser user, [System.Runtime.CompilerServices.CallerMemberName] string methodName = "") + { + var baseMessage = Resources.FormatLoggingResultMessage(methodName, await GetUserIdAsync(user)); + if (result) + { + Logger.WriteInformation(string.Format("{0} : {1}", baseMessage, result.ToString())); + } + else + { + Logger.WriteWarning(string.Format("{0} : {1}", baseMessage, result.ToString())); + } + + return result; + } + private void ThrowIfDisposed() { if (_disposed) diff --git a/src/Microsoft.AspNet.Identity/project.json b/src/Microsoft.AspNet.Identity/project.json index 0ef1603874..ad83945ac3 100644 --- a/src/Microsoft.AspNet.Identity/project.json +++ b/src/Microsoft.AspNet.Identity/project.json @@ -7,7 +7,8 @@ "Microsoft.AspNet.Security.DataProtection": "1.0.0-*", "Microsoft.Framework.ConfigurationModel": "1.0.0-*", "Microsoft.Framework.DependencyInjection" : "1.0.0-*", - "Microsoft.Framework.OptionsModel": "1.0.0-*" + "Microsoft.Framework.OptionsModel": "1.0.0-*", + "Microsoft.Framework.Logging": "1.0.0-*" }, "frameworks": { "aspnet50": {}, diff --git a/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/InMemoryEFUserStoreTest.cs b/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/InMemoryEFUserStoreTest.cs index 66c01e5998..758e799ff9 100644 --- a/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/InMemoryEFUserStoreTest.cs +++ b/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/InMemoryEFUserStoreTest.cs @@ -1,12 +1,13 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using Microsoft.AspNet.Identity.Test; using Microsoft.Framework.DependencyInjection; namespace Microsoft.AspNet.Identity.EntityFramework.InMemory.Test { - public class InMemoryEFUserStoreTest : UserManagerTestBase + public class InMemoryEFUserStoreTest : UserManagerTestBase,IDisposable { protected override object CreateTestContext() { @@ -23,5 +24,10 @@ namespace Microsoft.AspNet.Identity.EntityFramework.InMemory.Test var store = new RoleStore((InMemoryContext)context); services.AddInstance>(store); } + + public void Dispose() + { + loggerFactory.Dispose(); + } } } diff --git a/test/Microsoft.AspNet.Identity.EntityFramework.Test/SqlStoreTestBase.cs b/test/Microsoft.AspNet.Identity.EntityFramework.Test/SqlStoreTestBase.cs index b7e52134aa..3b6aca6f78 100644 --- a/test/Microsoft.AspNet.Identity.EntityFramework.Test/SqlStoreTestBase.cs +++ b/test/Microsoft.AspNet.Identity.EntityFramework.Test/SqlStoreTestBase.cs @@ -7,10 +7,7 @@ using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNet.Builder; using Microsoft.AspNet.Identity.Test; -using Microsoft.Data.Entity; using Microsoft.Framework.DependencyInjection; -using Microsoft.Framework.DependencyInjection.Fallback; -using Microsoft.Framework.OptionsModel; using Microsoft.Framework.Runtime.Infrastructure; using Xunit; @@ -37,6 +34,7 @@ namespace Microsoft.AspNet.Identity.EntityFramework.Test public void DropDatabaseDone() { DropDb(); + loggerFactory.Dispose(); } public void DropDb() diff --git a/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryStoreTest.cs b/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryStoreTest.cs index 9fbc2536dc..7cca942833 100644 --- a/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryStoreTest.cs +++ b/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryStoreTest.cs @@ -1,12 +1,13 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using Microsoft.AspNet.Identity.Test; using Microsoft.Framework.DependencyInjection; namespace Microsoft.AspNet.Identity.InMemory.Test { - public class InMemoryStoreTest : UserManagerTestBase + public class InMemoryStoreTest : UserManagerTestBase, IDisposable { protected override object CreateTestContext() { @@ -22,5 +23,10 @@ namespace Microsoft.AspNet.Identity.InMemory.Test { services.AddSingleton, InMemoryRoleStore>(); } + + public void Dispose() + { + loggerFactory.Dispose(); + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Identity.Test/IdentityBuilderTest.cs b/test/Microsoft.AspNet.Identity.Test/IdentityBuilderTest.cs index 488fcc7545..ee4b210d7b 100644 --- a/test/Microsoft.AspNet.Identity.Test/IdentityBuilderTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/IdentityBuilderTest.cs @@ -10,6 +10,7 @@ using System; using System.Threading; using System.Threading.Tasks; using System.Linq; +using Microsoft.Framework.Logging; namespace Microsoft.AspNet.Identity.Test { @@ -236,13 +237,13 @@ namespace Microsoft.AspNet.Identity.Test private class MyUserManager : UserManager { - public MyUserManager(IUserStore store) : base(store) { } + public MyUserManager(IUserStore store) : base(store) { } } private class MyRoleManager : RoleManager { public MyRoleManager(IRoleStore store, - IEnumerable> roleValidators) : base(store, roleValidators) + IEnumerable> roleValidators) : base(store) { } diff --git a/test/Microsoft.AspNet.Identity.Test/IdentityResultTest.cs b/test/Microsoft.AspNet.Identity.Test/IdentityResultTest.cs index 7d24b52bce..f65ab23cfb 100644 --- a/test/Microsoft.AspNet.Identity.Test/IdentityResultTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/IdentityResultTest.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Linq; +using System.Text; using Xunit; namespace Microsoft.AspNet.Identity.Test @@ -23,5 +24,29 @@ namespace Microsoft.AspNet.Identity.Test Assert.False(result.Succeeded); Assert.Equal(0, result.Errors.Count()); } + + [Fact] + public void VerifySuccessResultLog() + { + var result = IdentityResult.Success; + var logMessage = new StringBuilder(); + var logger = MockHelpers.MockILogger(logMessage); + + result.Log(logger.Object, "Operation"); + + Assert.Equal("Operation : Success", logMessage.ToString()); + } + + [Fact] + public void VerifyFailureResultLog() + { + var result = IdentityResult.Failed(new IdentityError() { Code = "Foo" }, new IdentityError() { Code = "Bar" }); + var logMessage = new StringBuilder(); + var logger = MockHelpers.MockILogger(logMessage); + + result.Log(logger.Object, "Operation"); + + Assert.Equal("Operation : Failed : Foo,Bar", logMessage.ToString()); + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Identity.Test/Microsoft.AspNet.Identity.Test.kproj b/test/Microsoft.AspNet.Identity.Test/Microsoft.AspNet.Identity.Test.kproj index 1bbfe3ac3e..3c06d031ab 100644 --- a/test/Microsoft.AspNet.Identity.Test/Microsoft.AspNet.Identity.Test.kproj +++ b/test/Microsoft.AspNet.Identity.Test/Microsoft.AspNet.Identity.Test.kproj @@ -1,4 +1,4 @@ - + 14.0 @@ -14,4 +14,9 @@ 2.0 - + + + + + + \ No newline at end of file diff --git a/test/Microsoft.AspNet.Identity.Test/RoleManagerTest.cs b/test/Microsoft.AspNet.Identity.Test/RoleManagerTest.cs index cab11a83df..99e35a31fb 100644 --- a/test/Microsoft.AspNet.Identity.Test/RoleManagerTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/RoleManagerTest.cs @@ -124,7 +124,7 @@ namespace Microsoft.AspNet.Identity.Test public async Task RoleManagerPublicNullChecks() { Assert.Throws("store", - () => new RoleManager(null, null)); + () => new RoleManager(null, null, null)); var manager = CreateRoleManager(new NotImplementedStore()); await Assert.ThrowsAsync("role", async () => await manager.CreateAsync(null)); await Assert.ThrowsAsync("role", async () => await manager.UpdateAsync(null)); @@ -150,7 +150,7 @@ namespace Microsoft.AspNet.Identity.Test { var v = new List>(); v.Add(new RoleValidator()); - return new RoleManager(roleStore, v); + return new RoleManager(roleStore); } private class NotImplementedStore : IRoleStore diff --git a/test/Microsoft.AspNet.Identity.Test/RoleValidatorTest.cs b/test/Microsoft.AspNet.Identity.Test/RoleValidatorTest.cs index 75a84c24cb..f1b0aa48fa 100644 --- a/test/Microsoft.AspNet.Identity.Test/RoleValidatorTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/RoleValidatorTest.cs @@ -14,7 +14,7 @@ namespace Microsoft.AspNet.Identity.Test { // Setup var validator = new RoleValidator(); - var manager = new RoleManager(new NoopRoleStore(), null); + var manager = new RoleManager(new NoopRoleStore()); // Act // Assert @@ -29,7 +29,7 @@ namespace Microsoft.AspNet.Identity.Test { // Setup var validator = new RoleValidator(); - var manager = new RoleManager(new NoopRoleStore(), null); + var manager = new RoleManager(new NoopRoleStore()); var user = new TestRole {Name = input}; // Act diff --git a/test/Microsoft.AspNet.Identity.Test/SecurityStampValidatorTest.cs b/test/Microsoft.AspNet.Identity.Test/SecurityStampValidatorTest.cs index 1528665dde..4bad958493 100644 --- a/test/Microsoft.AspNet.Identity.Test/SecurityStampValidatorTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/SecurityStampValidatorTest.cs @@ -47,7 +47,7 @@ namespace Microsoft.AspNet.Identity.Test var contextAccessor = new Mock(); contextAccessor.Setup(a => a.Value).Returns(httpContext.Object); var signInManager = new Mock>(userManager.Object, - contextAccessor.Object, claimsManager.Object, options.Object); + contextAccessor.Object, claimsManager.Object, options.Object, null); signInManager.Setup(s => s.ValidateSecurityStampAsync(It.IsAny(), user.Id, CancellationToken.None)).ReturnsAsync(user).Verifiable(); signInManager.Setup(s => s.SignInAsync(user, isPersistent, null, CancellationToken.None)).Returns(Task.FromResult(0)).Verifiable(); var services = new ServiceCollection(); @@ -82,7 +82,7 @@ namespace Microsoft.AspNet.Identity.Test var contextAccessor = new Mock(); contextAccessor.Setup(a => a.Value).Returns(httpContext.Object); var signInManager = new Mock>(userManager.Object, - contextAccessor.Object, claimsManager.Object, options.Object); + contextAccessor.Object, claimsManager.Object, options.Object, null); signInManager.Setup(s => s.ValidateSecurityStampAsync(It.IsAny(), user.Id, CancellationToken.None)).ReturnsAsync(null).Verifiable(); var services = new ServiceCollection(); services.AddInstance(options.Object); @@ -116,7 +116,7 @@ namespace Microsoft.AspNet.Identity.Test var contextAccessor = new Mock(); contextAccessor.Setup(a => a.Value).Returns(httpContext.Object); var signInManager = new Mock>(userManager.Object, - contextAccessor.Object, claimsManager.Object, options.Object); + contextAccessor.Object, claimsManager.Object, options.Object, null); signInManager.Setup(s => s.ValidateSecurityStampAsync(It.IsAny(), user.Id, CancellationToken.None)).ReturnsAsync(null).Verifiable(); var services = new ServiceCollection(); services.AddInstance(options.Object); @@ -150,7 +150,7 @@ namespace Microsoft.AspNet.Identity.Test var contextAccessor = new Mock(); contextAccessor.Setup(a => a.Value).Returns(httpContext.Object); var signInManager = new Mock>(userManager.Object, - contextAccessor.Object, claimsManager.Object, options.Object); + contextAccessor.Object, claimsManager.Object, options.Object, null); signInManager.Setup(s => s.ValidateSecurityStampAsync(It.IsAny(), user.Id, CancellationToken.None)).Throws(new Exception("Shouldn't be called")); signInManager.Setup(s => s.SignInAsync(user, false, null, CancellationToken.None)).Throws(new Exception("Shouldn't be called")); var services = new ServiceCollection(); diff --git a/test/Microsoft.AspNet.Identity.Test/SignInManagerTest.cs b/test/Microsoft.AspNet.Identity.Test/SignInManagerTest.cs index 7d1d1d6c90..401d4cf384 100644 --- a/test/Microsoft.AspNet.Identity.Test/SignInManagerTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/SignInManagerTest.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Security.Claims; using System.Security.Principal; +using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNet.Hosting; @@ -68,7 +69,7 @@ namespace Microsoft.AspNet.Identity.Test [Fact] public void ConstructorNullChecks() { - Assert.Throws("userManager", () => new SignInManager(null, null, null, null)); + Assert.Throws("userManager", () => new SignInManager(null, null, null, null, null)); var userManager = MockHelpers.MockUserManager().Object; Assert.Throws("contextAccessor", () => new SignInManager(userManager, null, null, null)); var contextAccessor = new Mock(); @@ -117,6 +118,8 @@ namespace Microsoft.AspNet.Identity.Test manager.Setup(m => m.SupportsUserLockout).Returns(true).Verifiable(); manager.Setup(m => m.IsLockedOutAsync(user, CancellationToken.None)).ReturnsAsync(true).Verifiable(); manager.Setup(m => m.FindByNameAsync(user.UserName, CancellationToken.None)).ReturnsAsync(user).Verifiable(); + manager.Setup(m => m.GetUserIdAsync(user, CancellationToken.None)).ReturnsAsync(user.Id.ToString()).Verifiable(); + var context = new Mock(); var contextAccessor = new Mock(); contextAccessor.Setup(a => a.Value).Returns(context.Object); @@ -125,7 +128,10 @@ namespace Microsoft.AspNet.Identity.Test var options = new Mock>(); options.Setup(a => a.Options).Returns(identityOptions); var claimsFactory = new Mock>(manager.Object, roleManager.Object, options.Object); - var helper = new SignInManager(manager.Object, contextAccessor.Object, claimsFactory.Object, options.Object); + var logStore = new StringBuilder(); + var loggerFactory = MockHelpers.MockILoggerFactory(MockHelpers.MockILogger(logStore).Object); + var helper = new SignInManager(manager.Object, contextAccessor.Object, claimsFactory.Object, options.Object, loggerFactory.Object); + string expected = string.Format("{0} for user: {1} : Result : {2}", "PasswordSignInAsync", user.Id, "Lockedout"); // Act var result = await helper.PasswordSignInAsync(user.UserName, "bogus", false, false); @@ -133,9 +139,10 @@ namespace Microsoft.AspNet.Identity.Test // Assert Assert.False(result.Succeeded); Assert.True(result.IsLockedOut); + Assert.NotEqual(-1, logStore.ToString().IndexOf(expected)); manager.VerifyAll(); } - + [Theory] [InlineData(true)] [InlineData(false)] @@ -149,6 +156,8 @@ namespace Microsoft.AspNet.Identity.Test manager.Setup(m => m.FindByNameAsync(user.UserName, CancellationToken.None)).ReturnsAsync(user).Verifiable(); manager.Setup(m => m.CheckPasswordAsync(user, "password", CancellationToken.None)).ReturnsAsync(true).Verifiable(); manager.Setup(m => m.ResetAccessFailedCountAsync(user, CancellationToken.None)).ReturnsAsync(IdentityResult.Success).Verifiable(); + manager.Setup(m => m.GetUserIdAsync(user, CancellationToken.None)).ReturnsAsync(user.Id.ToString()).Verifiable(); + var context = new Mock(); var response = new Mock(); context.Setup(c => c.Response).Returns(response.Object).Verifiable(); @@ -161,13 +170,17 @@ namespace Microsoft.AspNet.Identity.Test options.Setup(a => a.Options).Returns(identityOptions); var claimsFactory = new Mock>(manager.Object, roleManager.Object, options.Object); claimsFactory.Setup(m => m.CreateAsync(user, CancellationToken.None)).ReturnsAsync(new ClaimsIdentity("Microsoft.AspNet.Identity")).Verifiable(); - var helper = new SignInManager(manager.Object, contextAccessor.Object, claimsFactory.Object, options.Object); + var logStore = new StringBuilder(); + var loggerFactory = MockHelpers.MockILoggerFactory(MockHelpers.MockILogger(logStore).Object); + var helper = new SignInManager(manager.Object, contextAccessor.Object, claimsFactory.Object, options.Object, loggerFactory.Object); + string expected = string.Format("{0} for user: {1} : Result : {2}", "PasswordSignInAsync", user.Id, "Succeeded"); // Act var result = await helper.PasswordSignInAsync(user.UserName, "password", isPersistent, false); // Assert Assert.True(result.Succeeded); + Assert.NotEqual(-1, logStore.ToString().IndexOf(expected)); manager.VerifyAll(); context.VerifyAll(); response.VerifyAll(); @@ -186,6 +199,8 @@ namespace Microsoft.AspNet.Identity.Test manager.Setup(m => m.FindByNameAsync(user.UserName, CancellationToken.None)).ReturnsAsync(user).Verifiable(); manager.Setup(m => m.CheckPasswordAsync(user, "password", CancellationToken.None)).ReturnsAsync(true).Verifiable(); manager.Setup(m => m.ResetAccessFailedCountAsync(user, CancellationToken.None)).ReturnsAsync(IdentityResult.Success).Verifiable(); + manager.Setup(m => m.GetUserIdAsync(user, CancellationToken.None)).ReturnsAsync(user.Id.ToString()).Verifiable(); + var context = new Mock(); var response = new Mock(); response.Setup(r => r.SignIn(It.IsAny(), It.IsAny())).Verifiable(); @@ -245,7 +260,10 @@ namespace Microsoft.AspNet.Identity.Test var identityOptions = new IdentityOptions(); var options = new Mock>(); options.Setup(a => a.Options).Returns(identityOptions); - var helper = new SignInManager(manager.Object, contextAccessor.Object, new ClaimsIdentityFactory(manager.Object, roleManager.Object, options.Object), options.Object); + var logStore = new StringBuilder(); + var loggerFactory = MockHelpers.MockILoggerFactory(MockHelpers.MockILogger(logStore).Object); + var helper = new SignInManager(manager.Object, contextAccessor.Object, new ClaimsIdentityFactory(manager.Object, roleManager.Object, options.Object), options.Object, loggerFactory.Object); + string expected = string.Format("{0} for user: {1} : Result : {2}", "PasswordSignInAsync", user.Id, "RequiresTwoFactor"); // Act var result = await helper.PasswordSignInAsync(user.UserName, "password", false, false); @@ -253,6 +271,7 @@ namespace Microsoft.AspNet.Identity.Test // Assert Assert.False(result.Succeeded); Assert.True(result.RequiresTwoFactor); + Assert.NotEqual(-1, logStore.ToString().IndexOf(expected)); manager.VerifyAll(); context.VerifyAll(); response.VerifyAll(); @@ -277,6 +296,8 @@ namespace Microsoft.AspNet.Identity.Test manager.Setup(m => m.IsLockedOutAsync(user, CancellationToken.None)).ReturnsAsync(false).Verifiable(); } manager.Setup(m => m.FindByLoginAsync(loginProvider, providerKey, CancellationToken.None)).ReturnsAsync(user).Verifiable(); + manager.Setup(m => m.GetUserIdAsync(user, CancellationToken.None)).ReturnsAsync(user.Id.ToString()).Verifiable(); + var context = new Mock(); var response = new Mock(); context.Setup(c => c.Response).Returns(response.Object).Verifiable(); @@ -292,13 +313,17 @@ namespace Microsoft.AspNet.Identity.Test options.Setup(a => a.Options).Returns(identityOptions); var claimsFactory = new Mock>(manager.Object, roleManager.Object, options.Object); claimsFactory.Setup(m => m.CreateAsync(user, CancellationToken.None)).ReturnsAsync(new ClaimsIdentity("Microsoft.AspNet.Identity")).Verifiable(); - var helper = new SignInManager(manager.Object, contextAccessor.Object, claimsFactory.Object, options.Object); + var logStore = new StringBuilder(); + var loggerFactory = MockHelpers.MockILoggerFactory(MockHelpers.MockILogger(logStore).Object); + var helper = new SignInManager(manager.Object, contextAccessor.Object, claimsFactory.Object, options.Object, loggerFactory.Object); + string expected = string.Format("{0} for user: {1} : Result : {2}", "ExternalLoginSignInAsync", user.Id.ToString(), "Succeeded"); // Act var result = await helper.ExternalLoginSignInAsync(loginProvider, providerKey, isPersistent); // Assert Assert.True(result.Succeeded); + Assert.NotEqual(-1, logStore.ToString().IndexOf(expected)); manager.VerifyAll(); context.VerifyAll(); response.VerifyAll(); @@ -323,7 +348,6 @@ namespace Microsoft.AspNet.Identity.Test [InlineData(false, true, false, false)] [InlineData(false, false, true, false)] [InlineData(false, false, false, false)] - public async Task CanTwoFactorSignIn(bool isPersistent, bool supportsLockout, bool externalLogin, bool rememberClient) { // Setup @@ -377,13 +401,17 @@ namespace Microsoft.AspNet.Identity.Test context.Setup(c => c.Response).Returns(response.Object).Verifiable(); context.Setup(c => c.AuthenticateAsync(IdentityOptions.TwoFactorUserIdCookieAuthenticationType)).ReturnsAsync(authResult).Verifiable(); contextAccessor.Setup(a => a.Value).Returns(context.Object); - var helper = new SignInManager(manager.Object, contextAccessor.Object, claimsFactory, options.Object); + var logStore = new StringBuilder(); + var loggerFactory = MockHelpers.MockILoggerFactory(MockHelpers.MockILogger(logStore).Object); + var helper = new SignInManager(manager.Object, contextAccessor.Object, claimsFactory, options.Object, loggerFactory.Object); + string expected = string.Format("{0} for user: {1} : Result : {2}", "TwoFactorSignInAsync", user.Id.ToString(), "Succeeded"); // Act var result = await helper.TwoFactorSignInAsync(provider, code, isPersistent, rememberClient); // Assert Assert.True(result.Succeeded); + Assert.NotEqual(-1, logStore.ToString().IndexOf(expected)); manager.VerifyAll(); context.VerifyAll(); response.VerifyAll(); @@ -496,7 +524,9 @@ namespace Microsoft.AspNet.Identity.Test options.Setup(a => a.Options).Returns(identityOptions); IdentityOptions.ApplicationCookieAuthenticationType = authenticationType; var claimsFactory = new Mock>(manager.Object, roleManager.Object, options.Object); - var helper = new SignInManager(manager.Object, contextAccessor.Object, claimsFactory.Object, options.Object); + var logStore = new StringBuilder(); + var loggerFactory = MockHelpers.MockILoggerFactory(MockHelpers.MockILogger(logStore).Object); + var helper = new SignInManager(manager.Object, contextAccessor.Object, claimsFactory.Object, options.Object, loggerFactory.Object); // Act helper.SignOut(); @@ -518,6 +548,7 @@ namespace Microsoft.AspNet.Identity.Test manager.Setup(m => m.IsLockedOutAsync(user, CancellationToken.None)).ReturnsAsync(false).Verifiable(); manager.Setup(m => m.FindByNameAsync(user.UserName, CancellationToken.None)).ReturnsAsync(user).Verifiable(); manager.Setup(m => m.CheckPasswordAsync(user, "bogus", CancellationToken.None)).ReturnsAsync(false).Verifiable(); + manager.Setup(m => m.GetUserIdAsync(user, CancellationToken.None)).ReturnsAsync(user.Id.ToString()).Verifiable(); var context = new Mock(); var contextAccessor = new Mock(); contextAccessor.Setup(a => a.Value).Returns(context.Object); @@ -526,12 +557,16 @@ namespace Microsoft.AspNet.Identity.Test var options = new Mock>(); options.Setup(a => a.Options).Returns(identityOptions); var claimsFactory = new Mock>(manager.Object, roleManager.Object, options.Object); - var helper = new SignInManager(manager.Object, contextAccessor.Object, claimsFactory.Object, options.Object); + var logStore = new StringBuilder(); + var loggerFactory = MockHelpers.MockILoggerFactory(MockHelpers.MockILogger(logStore).Object); + var helper = new SignInManager(manager.Object, contextAccessor.Object, claimsFactory.Object, options.Object, loggerFactory.Object); + string expected = string.Format("{0} for user: {1} : Result : {2}", "PasswordSignInAsync", user.Id.ToString(), "Failed"); // Act var result = await helper.PasswordSignInAsync(user.UserName, "bogus", false, false); // Assert Assert.False(result.Succeeded); + Assert.NotEqual(-1, logStore.ToString().IndexOf(expected)); manager.VerifyAll(); context.VerifyAll(); contextAccessor.VerifyAll(); @@ -611,6 +646,7 @@ namespace Microsoft.AspNet.Identity.Test { manager.Setup(m => m.CheckPasswordAsync(user, "password", CancellationToken.None)).ReturnsAsync(true).Verifiable(); } + manager.Setup(m => m.GetUserIdAsync(user, CancellationToken.None)).ReturnsAsync(user.Id.ToString()).Verifiable(); var context = new Mock(); var response = new Mock(); if (confirmed) @@ -627,7 +663,10 @@ namespace Microsoft.AspNet.Identity.Test var options = new Mock>(); options.Setup(a => a.Options).Returns(identityOptions); var claimsFactory = new Mock>(manager.Object, roleManager.Object, options.Object); - var helper = new SignInManager(manager.Object, contextAccessor.Object, claimsFactory.Object, options.Object); + var logStore = new StringBuilder(); + var loggerFactory = MockHelpers.MockILoggerFactory(MockHelpers.MockILogger(logStore).Object); + var helper = new SignInManager(manager.Object, contextAccessor.Object, claimsFactory.Object, options.Object, loggerFactory.Object); + string expected = string.Format("{0} for user: {1} : Result : {2}", "CanSignInAsync", user.Id.ToString(), confirmed.ToString()); // Act var result = await helper.PasswordSignInAsync(user, "password", false, false); @@ -636,6 +675,7 @@ namespace Microsoft.AspNet.Identity.Test Assert.Equal(confirmed, result.Succeeded); Assert.NotEqual(confirmed, result.IsNotAllowed); + Assert.NotEqual(-1, logStore.ToString().IndexOf(expected)); manager.VerifyAll(); context.VerifyAll(); response.VerifyAll(); @@ -650,7 +690,8 @@ namespace Microsoft.AspNet.Identity.Test // Setup var user = new TestUser { UserName = "Foo" }; var manager = MockHelpers.MockUserManager(); - manager.Setup(m => m.IsEmailConfirmedAsync(user, CancellationToken.None)).ReturnsAsync(confirmed).Verifiable(); + manager.Setup(m => m.IsPhoneNumberConfirmedAsync(user, CancellationToken.None)).ReturnsAsync(confirmed).Verifiable(); + manager.Setup(m => m.GetUserIdAsync(user, CancellationToken.None)).ReturnsAsync(user.Id.ToString()).Verifiable(); var context = new Mock(); var response = new Mock(); if (confirmed) @@ -664,11 +705,14 @@ namespace Microsoft.AspNet.Identity.Test contextAccessor.Setup(a => a.Value).Returns(context.Object); var roleManager = MockHelpers.MockRoleManager(); var identityOptions = new IdentityOptions(); - identityOptions.SignIn.RequireConfirmedEmail = true; + identityOptions.SignIn.RequireConfirmedPhoneNumber = true; var options = new Mock>(); options.Setup(a => a.Options).Returns(identityOptions); var claimsFactory = new Mock>(manager.Object, roleManager.Object, options.Object); - var helper = new SignInManager(manager.Object, contextAccessor.Object, claimsFactory.Object, options.Object); + var logStore = new StringBuilder(); + var loggerFactory = MockHelpers.MockILoggerFactory(MockHelpers.MockILogger(logStore).Object); + var helper = new SignInManager(manager.Object, contextAccessor.Object, claimsFactory.Object, options.Object, loggerFactory.Object); + string expected = string.Format("{0} for user: {1} : Result : {2}", "CanSignInAsync", user.Id.ToString(), confirmed.ToString()); // Act var result = await helper.PasswordSignInAsync(user, "password", false, false); @@ -676,6 +720,7 @@ namespace Microsoft.AspNet.Identity.Test // Assert Assert.Equal(confirmed, result.Succeeded); Assert.NotEqual(confirmed, result.IsNotAllowed); + Assert.NotEqual(-1, logStore.ToString().IndexOf(expected)); manager.VerifyAll(); context.VerifyAll(); response.VerifyAll(); diff --git a/test/Microsoft.AspNet.Identity.Test/SignInResultTest.cs b/test/Microsoft.AspNet.Identity.Test/SignInResultTest.cs new file mode 100644 index 0000000000..ce7a522937 --- /dev/null +++ b/test/Microsoft.AspNet.Identity.Test/SignInResultTest.cs @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Text; +using Xunit; + +namespace Microsoft.AspNet.Identity.Test +{ + public class SignInResultTest + { + [Fact] + public void VerifyLogSuccess() + { + var result = SignInResult.Success; + var logMessage = new StringBuilder(); + var logger = MockHelpers.MockILogger(logMessage); + + result.Log(logger.Object, "Operation"); + + Assert.Equal("Operation : Result : Succeeded", logMessage.ToString()); + } + + [Fact] + public void VerifyLogLockedOut() + { + var result = SignInResult.LockedOut; + var logMessage = new StringBuilder(); + var logger = MockHelpers.MockILogger(logMessage); + + result.Log(logger.Object, "Operation"); + + Assert.Equal("Operation : Result : Lockedout", logMessage.ToString()); + } + + [Fact] + public void VerifyLogNotAllowed() + { + var result = SignInResult.NotAllowed; + var logMessage = new StringBuilder(); + var logger = MockHelpers.MockILogger(logMessage); + + result.Log(logger.Object, "Operation"); + + Assert.Equal("Operation : Result : NotAllowed", logMessage.ToString()); + } + + [Fact] + public void VerifyLogRequiresTwoFactor() + { + var result = SignInResult.TwoFactorRequired; + var logMessage = new StringBuilder(); + var logger = MockHelpers.MockILogger(logMessage); + + result.Log(logger.Object, "Operation"); + + Assert.Equal("Operation : Result : RequiresTwoFactor", logMessage.ToString()); + } + + [Fact] + public void VerifyLogRequiresFailed() + { + var result = SignInResult.Failed; + var logMessage = new StringBuilder(); + var logger = MockHelpers.MockILogger(logMessage); + + result.Log(logger.Object, "Operation"); + + Assert.Equal("Operation : Result : Failed", logMessage.ToString()); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs b/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs index e4cf94ecd5..62f1e77917 100644 --- a/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs +++ b/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs @@ -175,7 +175,7 @@ namespace Microsoft.AspNet.Identity.Test { // Setup var store = new Mock>(); - var user = new TestUser {UserName="Foo"}; + var user = new TestUser { UserName = "Foo" }; store.Setup(s => s.FindByNameAsync(user.UserName.ToUpperInvariant(), CancellationToken.None)).Returns(Task.FromResult(user)).Verifiable(); var userManager = MockHelpers.TestUserManager(store.Object); @@ -192,7 +192,7 @@ namespace Microsoft.AspNet.Identity.Test { // Setup var store = new Mock>(); - var user = new TestUser {UserName="Foo"}; + var user = new TestUser { UserName = "Foo" }; store.Setup(s => s.FindByNameAsync(user.UserName, CancellationToken.None)).Returns(Task.FromResult(user)).Verifiable(); var userManager = MockHelpers.TestUserManager(store.Object); userManager.KeyNormalizer = null; @@ -246,7 +246,7 @@ namespace Microsoft.AspNet.Identity.Test // Setup var store = new Mock>(); var user = new TestUser { UserName = "Foo" }; - var roles = new string[] {"A", "B", "C"}; + var roles = new string[] { "A", "B", "C" }; store.Setup(s => s.AddToRoleAsync(user, "A", CancellationToken.None)) .Returns(Task.FromResult(0)) .Verifiable(); @@ -642,7 +642,7 @@ namespace Microsoft.AspNet.Identity.Test var store = new NotImplementedStore(); Assert.Throws("store", - () => new UserManager(null)); + () => new UserManager(null, null)); var manager = new UserManager(store); @@ -682,7 +682,7 @@ namespace Microsoft.AspNet.Identity.Test await Assert.ThrowsAsync("user", async () => await manager.AddClaimAsync(null, new Claim("a", "b"))); await Assert.ThrowsAsync("user", - async () => await manager.AddLoginAsync(null, new UserLoginInfo("","",""))); + async () => await manager.AddLoginAsync(null, new UserLoginInfo("", "", ""))); await Assert.ThrowsAsync("user", async () => await manager.AddPasswordAsync(null, null)); await Assert.ThrowsAsync("user", diff --git a/test/Shared/IdentityResultAssert.cs b/test/Shared/IdentityResultAssert.cs index 617e5c0c6b..907aacecb1 100644 --- a/test/Shared/IdentityResultAssert.cs +++ b/test/Shared/IdentityResultAssert.cs @@ -1,7 +1,9 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.IO; using System.Linq; +using Microsoft.Framework.Logging; using Xunit; namespace Microsoft.AspNet.Identity.Test @@ -35,5 +37,69 @@ namespace Microsoft.AspNet.Identity.Test Assert.Equal(error.Code, result.Errors.First().Code); } + public static void VerifyUserManagerFailureLog(ILogger logger, string methodName, string userId, params IdentityError[] errors) + { + VerifyFailureLog(logger, "UserManager", methodName, userId, "user", errors); + } + + public static void VerifyRoleManagerFailureLog(ILogger logger, string methodName, string roleId, params IdentityError[] errors) + { + VerifyFailureLog(logger, "RoleManager", methodName, roleId, "role", errors); + } + + public static void VerifyUserManagerSuccessLog(ILogger logger, string methodName, string userId) + { + VerifySuccessLog(logger, "UserManager", methodName, userId, "user"); + + } + + public static void VerifyRoleManagerSuccessLog(ILogger logger, string methodName, string roleId) + { + VerifySuccessLog(logger, "RoleManager", methodName, roleId, "role"); + + } + private static void VerifySuccessLog(ILogger logger, string className, string methodName, string id, string userOrRole = "user") + { + if (logger is TestFileLogger) + { + var fileLogger = logger as TestFileLogger; + string expected = string.Format("{0} for {1}: {2} : Success", methodName, userOrRole, id); + + Assert.True(File.ReadAllText(fileLogger.FileName).Contains(expected)); + } + else + { + Assert.True(true, "No logger registered"); + } + } + + public static void VerifyLogMessage(ILogger logger, string expectedLog) + { + if (logger is TestFileLogger) + { + var fileLogger = logger as TestFileLogger; + Assert.True(File.ReadAllText(fileLogger.FileName).Contains(expectedLog)); + } + else + { + Assert.True(true, "No logger registered"); + } + } + + private static void VerifyFailureLog(ILogger logger, string className, string methodName, string userId, string userOrRole = "user", params IdentityError[] errors) + { + if (logger is TestFileLogger) + { + var fileLogger = logger as TestFileLogger; + errors = errors ?? new IdentityError[] { new IdentityError() }; + string expected = string.Format("{0} for {1}: {2} : Failed : {3}", methodName, userOrRole, userId, string.Join(",", errors.Select(x => x.Code).ToList())); + + Assert.True(File.ReadAllText(fileLogger.FileName).Contains(expected)); + } + else + { + Assert.True(true, "No logger registered"); + } + } } } \ No newline at end of file diff --git a/test/Shared/MockHelpers.cs b/test/Shared/MockHelpers.cs index f70541bec0..36f2690c60 100644 --- a/test/Shared/MockHelpers.cs +++ b/test/Shared/MockHelpers.cs @@ -1,20 +1,24 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Microsoft.Framework.OptionsModel; +using Microsoft.Framework.Logging; using Moq; +using System.Text; namespace Microsoft.AspNet.Identity.Test { public static class MockHelpers { + public static StringBuilder LogMessage = new StringBuilder(); + public static Mock> MockUserManager() where TUser : class { var store = new Mock>(); - var mgr = new Mock>(store.Object, null, null, null, null, null, null, null, null); + var mgr = new Mock>(store.Object, null, null, null, null, null, null, null, null, null); mgr.Object.UserValidators.Add(new UserValidator()); mgr.Object.PasswordValidators.Add(new PasswordValidator()); return mgr; @@ -25,7 +29,37 @@ namespace Microsoft.AspNet.Identity.Test store = store ?? new Mock>().Object; var roles = new List>(); roles.Add(new RoleValidator()); - return new Mock>(store, roles, null, null); + return new Mock>(store, roles, null, null,null); + } + + public static Mock MockILogger(StringBuilder logStore = null) + { + logStore = logStore ?? LogMessage; + var logger = new Mock(); + logger.Setup(x => x.Write(It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny>())) + .Callback((LogLevel logLevel, int eventId, object state, Exception exception, Func formatter) => + { logStore.Append(state.ToString()); }); + logger.Setup(x => x.IsEnabled(LogLevel.Information)).Returns(true); + logger.Setup(x => x.IsEnabled(LogLevel.Warning)).Returns(true); + + return logger; + } + + public static Mock MockILoggerFactory(ILogger logger = null) + { + logger = logger ?? MockILogger().Object; + var loggerFactory = new Mock(); + loggerFactory.Setup(x => x.Create(It.IsAny())).Returns(logger); + return loggerFactory; + } + + public static UserManager UserManagerWithMockLogger(ILoggerFactory loggerFactory = null) where TUser : class + { + var userstore = new Mock>().Object; + var userManager = new UserManager(userstore, loggerFactory: loggerFactory ?? MockILoggerFactory().Object); + + return userManager; } public static UserManager TestUserManager(IUserStore store = null) where TUser : class diff --git a/test/Shared/TestFileLogger.cs b/test/Shared/TestFileLogger.cs new file mode 100644 index 0000000000..8dc93126d2 --- /dev/null +++ b/test/Shared/TestFileLogger.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using Microsoft.Framework.Logging; + +namespace Microsoft.AspNet.Identity.Test +{ + public class TestFileLogger : ILogger + { + public string FileName { get; set; } + + public object FileLock { get; private set; } = new object(); + + public TestFileLogger(string name) + { + var directory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "IdentityTests"); + Directory.CreateDirectory(directory); + FileName = Path.Combine(directory, (name + DateTime.Now.Ticks + "log.txt")); + if (!File.Exists(FileName)) + { + File.Create(FileName).Close(); + } + } + + public IDisposable BeginScope(object state) + { + throw new NotImplementedException(); + } + + public bool IsEnabled(LogLevel logLevel) + { + return true; + } + + public void Write(LogLevel logLevel, int eventId, object state, Exception exception, Func formatter) + { + lock (FileLock) + { + File.AppendAllLines(FileName, new string[] { state.ToString() }); + } + } + } +} \ No newline at end of file diff --git a/test/Shared/TestFileLoggerFactory.cs b/test/Shared/TestFileLoggerFactory.cs new file mode 100644 index 0000000000..eb6f8f86f4 --- /dev/null +++ b/test/Shared/TestFileLoggerFactory.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.IO; +using Microsoft.Framework.Logging; + +namespace Microsoft.AspNet.Identity.Test +{ + public class TestFileLoggerFactory : ILoggerFactory, IDisposable + { + private static Dictionary _loggers; + + static TestFileLoggerFactory() + { + _loggers = new Dictionary(); + } + + public void AddProvider(ILoggerProvider provider) + { + + } + + public ILogger Create(string name) + { + if (!_loggers.ContainsKey(name)) + { + _loggers.Add(name, new TestFileLogger(name)); + } + + return _loggers[name]; + } + + public void Dispose() + { + Parallel.ForEach(_loggers.Values, l => + { + if(l is TestFileLogger) + { + var logger = l as TestFileLogger; + File.Delete(logger.FileName); + } + }); + } + } +} diff --git a/test/Shared/UserManagerTestBase.cs b/test/Shared/UserManagerTestBase.cs index 1be1741852..037a8b1ca9 100644 --- a/test/Shared/UserManagerTestBase.cs +++ b/test/Shared/UserManagerTestBase.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using Microsoft.AspNet.Testing; using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.DependencyInjection.Fallback; +using Microsoft.Framework.Logging; using Xunit; namespace Microsoft.AspNet.Identity.Test @@ -20,17 +21,25 @@ namespace Microsoft.AspNet.Identity.Test where TRole : IdentityRole, new() { } - public abstract class UserManagerTestBase - where TUser: IdentityUser, new() - where TRole: IdentityRole, new() + public abstract class UserManagerTestBase + where TUser : IdentityUser, new() + where TRole : IdentityRole, new() where TKey : IEquatable { + protected TestFileLoggerFactory loggerFactory; + + public UserManagerTestBase() + { + loggerFactory = new TestFileLoggerFactory(); + } + protected virtual void SetupIdentityServices(IServiceCollection services, object context = null) { services.AddHosting(); services.AddIdentity().AddDefaultTokenProviders(); AddUserStore(services, context); AddRoleStore(services, context); + services.AddInstance(loggerFactory); services.ConfigureIdentity(options => { options.Password.RequireDigit = false; @@ -74,11 +83,13 @@ namespace Microsoft.AspNet.Identity.Test protected abstract void AddUserStore(IServiceCollection services, object context = null); protected abstract void AddRoleStore(IServiceCollection services, object context = null); - protected TUser CreateTestUser(string namePrefix = "") { + protected TUser CreateTestUser(string namePrefix = "") + { return new TUser() { UserName = namePrefix + Guid.NewGuid().ToString() }; } - protected TRole CreateRole(string namePrefix = "") { + protected TRole CreateRole(string namePrefix = "") + { return new TRole() { Name = namePrefix + Guid.NewGuid().ToString() }; } @@ -88,7 +99,11 @@ namespace Microsoft.AspNet.Identity.Test var manager = CreateManager(); var user = CreateTestUser(); IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "CreateAsync", user.Id.ToString()); + IdentityResultAssert.IsSuccess(await manager.DeleteAsync(user)); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "DeleteAsync", user.Id.ToString()); + Assert.Null(await manager.FindByIdAsync(user.Id.ToString())); } @@ -103,6 +118,7 @@ namespace Microsoft.AspNet.Identity.Test Assert.Null(await manager.FindByNameAsync(newName)); user.UserName = newName; IdentityResultAssert.IsSuccess(await manager.UpdateAsync(user)); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "UpdateAsync", user.Id.ToString()); Assert.NotNull(await manager.FindByNameAsync(newName)); Assert.Null(await manager.FindByNameAsync(name)); } @@ -126,9 +142,14 @@ namespace Microsoft.AspNet.Identity.Test var user = new TUser() { UserName = "UpdatePassword" }; IdentityResultAssert.IsSuccess(await manager.CreateAsync(user, "password")); Assert.True(await manager.CheckPasswordAsync(user, "password")); + string expectedLog = string.Format("{0} for user: {1} : {2}", "CheckPasswordAsync", user.Id.ToString(), true.ToString()); + IdentityResultAssert.VerifyLogMessage(manager.Logger, expectedLog); + user.PasswordHash = manager.PasswordHasher.HashPassword(user, "New"); IdentityResultAssert.IsSuccess(await manager.UpdateAsync(user)); Assert.False(await manager.CheckPasswordAsync(user, "password")); + expectedLog = string.Format("{0} for user: {1} : {2}", "CheckPasswordAsync", user.Id.ToString(), false.ToString()); + IdentityResultAssert.VerifyLogMessage(manager.Logger, expectedLog); Assert.True(await manager.CheckPasswordAsync(user, "New")); } @@ -160,6 +181,7 @@ namespace Microsoft.AspNet.Identity.Test manager.UserValidators.Clear(); manager.UserValidators.Add(new AlwaysBadValidator()); IdentityResultAssert.IsFailure(await manager.UpdateAsync(user), AlwaysBadValidator.ErrorMessage); + IdentityResultAssert.VerifyUserManagerFailureLog(manager.Logger, "UpdateAsync", user.Id.ToString(), AlwaysBadValidator.ErrorMessage); } [Fact] @@ -208,6 +230,7 @@ namespace Microsoft.AspNet.Identity.Test manager.PasswordValidators.Add(new AlwaysBadValidator()); IdentityResultAssert.IsFailure(await manager.AddPasswordAsync(user, "password"), AlwaysBadValidator.ErrorMessage); + IdentityResultAssert.VerifyUserManagerFailureLog(manager.Logger, "AddPasswordAsync", user.Id.ToString(), AlwaysBadValidator.ErrorMessage); } [Fact] @@ -234,6 +257,17 @@ namespace Microsoft.AspNet.Identity.Test manager.PasswordValidators.Add(new AlwaysBadValidator()); IdentityResultAssert.IsFailure(await manager.ChangePasswordAsync(user, "password", "new"), AlwaysBadValidator.ErrorMessage); + IdentityResultAssert.VerifyUserManagerFailureLog(manager.Logger, "ChangePasswordAsync", user.Id.ToString(), AlwaysBadValidator.ErrorMessage); + } + + [Fact] + public async Task PasswordValidatorCanBlockCreateUser() + { + var manager = CreateManager(); + var user = CreateTestUser(); + manager.PasswordValidators.Clear(); + manager.PasswordValidators.Add(new AlwaysBadValidator()); + IdentityResultAssert.IsFailure(await manager.CreateAsync(user, "password"), AlwaysBadValidator.ErrorMessage); } [Fact] @@ -260,6 +294,7 @@ namespace Microsoft.AspNet.Identity.Test IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); user = await manager.FindByNameAsync(user.UserName); IdentityResultAssert.IsSuccess(await manager.AddLoginAsync(user, new UserLoginInfo(provider, providerKey, display))); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "AddLoginAsync", user.Id.ToString()); var logins = await manager.GetLoginsAsync(user); Assert.NotNull(logins); Assert.Equal(1, logins.Count()); @@ -278,6 +313,7 @@ namespace Microsoft.AspNet.Identity.Test IdentityResultAssert.IsSuccess(await manager.AddLoginAsync(user, login)); Assert.False(await manager.HasPasswordAsync(user)); IdentityResultAssert.IsSuccess(await manager.AddPasswordAsync(user, "password")); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "AddPasswordAsync", user.Id.ToString()); Assert.True(await manager.HasPasswordAsync(user)); var logins = await manager.GetLoginsAsync(user); Assert.NotNull(logins); @@ -295,6 +331,7 @@ namespace Microsoft.AspNet.Identity.Test Assert.True(await manager.HasPasswordAsync(user)); IdentityResultAssert.IsFailure(await manager.AddPasswordAsync(user, "password"), "User already has a password set."); + IdentityResultAssert.VerifyUserManagerFailureLog(manager.Logger, "AddPasswordAsync", user.Id.ToString(), IdentityErrorDescriber.Default.UserAlreadyHasPassword()); } [Fact] @@ -316,6 +353,7 @@ namespace Microsoft.AspNet.Identity.Test Assert.Equal(login.ProviderDisplayName, logins.Last().ProviderDisplayName); var stamp = user.SecurityStamp; IdentityResultAssert.IsSuccess(await manager.RemoveLoginAsync(user, login.LoginProvider, login.ProviderKey)); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "RemoveLoginAsync", user.Id.ToString()); Assert.Null(await manager.FindByLoginAsync(login.LoginProvider, login.ProviderKey)); logins = await manager.GetLoginsAsync(user); Assert.NotNull(logins); @@ -332,6 +370,7 @@ namespace Microsoft.AspNet.Identity.Test IdentityResultAssert.IsSuccess(await manager.CreateAsync(user, password)); var stamp = user.SecurityStamp; IdentityResultAssert.IsSuccess(await manager.RemovePasswordAsync(user)); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "RemovePasswordAsync", user.Id.ToString()); var u = await manager.FindByNameAsync(user.UserName); Assert.NotNull(u); Assert.Null(u.PasswordHash); @@ -351,6 +390,7 @@ namespace Microsoft.AspNet.Identity.Test IdentityResultAssert.IsSuccess(await manager.ChangePasswordAsync(user, password, newPassword)); Assert.False(await manager.CheckPasswordAsync(user, password)); Assert.True(await manager.CheckPasswordAsync(user, newPassword)); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "ChangePasswordAsync", user.Id.ToString()); Assert.NotEqual(stamp, user.SecurityStamp); } @@ -365,9 +405,11 @@ namespace Microsoft.AspNet.Identity.Test { IdentityResultAssert.IsSuccess(await manager.AddClaimAsync(user, c)); } + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "AddClaimsAsync", user.Id.ToString()); var userClaims = await manager.GetClaimsAsync(user); Assert.Equal(3, userClaims.Count); IdentityResultAssert.IsSuccess(await manager.RemoveClaimAsync(user, claims[0])); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "RemoveClaimsAsync", user.Id.ToString()); userClaims = await manager.GetClaimsAsync(user); Assert.Equal(2, userClaims.Count); IdentityResultAssert.IsSuccess(await manager.RemoveClaimAsync(user, claims[1])); @@ -419,6 +461,7 @@ namespace Microsoft.AspNet.Identity.Test Claim claim = new Claim("c", "b"); Claim oldClaim = userClaims.FirstOrDefault(); IdentityResultAssert.IsSuccess(await manager.ReplaceClaimAsync(user, oldClaim, claim)); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "ReplaceClaimAsync", user.Id.ToString()); var newUserClaims = await manager.GetClaimsAsync(user); Assert.Equal(1, newUserClaims.Count); Claim newClaim = newUserClaims.FirstOrDefault(); @@ -463,6 +506,7 @@ namespace Microsoft.AspNet.Identity.Test IdentityResultAssert.IsSuccess(await manager.CreateAsync(user, "password")); var result = await manager.ChangePasswordAsync(user, "bogus", "newpassword"); IdentityResultAssert.IsFailure(result, "Incorrect password."); + IdentityResultAssert.VerifyUserManagerFailureLog(manager.Logger, "ChangePasswordAsync", user.Id.ToString(), IdentityErrorDescriber.Default.PasswordMismatch()); } [Fact] @@ -511,6 +555,7 @@ namespace Microsoft.AspNet.Identity.Test var stamp = user.SecurityStamp; Assert.NotNull(stamp); IdentityResultAssert.IsSuccess(await manager.UpdateSecurityStampAsync(user)); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "UpdateSecurityStampAsync", user.Id.ToString()); Assert.NotEqual(stamp, user.SecurityStamp); } @@ -524,6 +569,7 @@ namespace Microsoft.AspNet.Identity.Test IdentityResultAssert.IsSuccess(await manager.AddLoginAsync(user, login)); var result = await manager.AddLoginAsync(user, login); IdentityResultAssert.IsFailure(result, IdentityErrorDescriber.Default.LoginAlreadyAssociated()); + IdentityResultAssert.VerifyUserManagerFailureLog(manager.Logger, "AddLoginAsync", user.Id.ToString(), IdentityErrorDescriber.Default.LoginAlreadyAssociated()); } // Email tests @@ -607,7 +653,9 @@ namespace Microsoft.AspNet.Identity.Test Assert.NotNull(stamp); var token = await manager.GeneratePasswordResetTokenAsync(user); Assert.NotNull(token); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "GeneratePasswordResetTokenAsync", user.Id.ToString()); IdentityResultAssert.IsSuccess(await manager.ResetPasswordAsync(user, token, newPassword)); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "ResetPasswordAsync", user.Id.ToString()); Assert.False(await manager.CheckPasswordAsync(user, password)); Assert.True(await manager.CheckPasswordAsync(user, newPassword)); Assert.NotEqual(stamp, user.SecurityStamp); @@ -630,6 +678,7 @@ namespace Microsoft.AspNet.Identity.Test manager.PasswordValidators.Add(new AlwaysBadValidator()); IdentityResultAssert.IsFailure(await manager.ResetPasswordAsync(user, token, newPassword), AlwaysBadValidator.ErrorMessage); + IdentityResultAssert.VerifyUserManagerFailureLog(manager.Logger, "ResetPasswordAsync", user.Id.ToString(), AlwaysBadValidator.ErrorMessage); Assert.True(await manager.CheckPasswordAsync(user, password)); Assert.Equal(stamp, user.SecurityStamp); } @@ -647,6 +696,7 @@ namespace Microsoft.AspNet.Identity.Test var stamp = user.SecurityStamp; Assert.NotNull(stamp); IdentityResultAssert.IsFailure(await manager.ResetPasswordAsync(user, "bogus", newPassword), "Invalid token."); + IdentityResultAssert.VerifyUserManagerFailureLog(manager.Logger, "ResetPasswordAsync", user.Id.ToString(), IdentityErrorDescriber.Default.InvalidToken()); Assert.True(await manager.CheckPasswordAsync(user, password)); Assert.Equal(stamp, user.SecurityStamp); } @@ -661,8 +711,14 @@ namespace Microsoft.AspNet.Identity.Test IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); IdentityResultAssert.IsSuccess(await manager.CreateAsync(user2)); var token = await manager.GenerateUserTokenAsync(user, "Static", "test"); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "GenerateUserTokenAsync", user.Id.ToString()); + Assert.True(await manager.VerifyUserTokenAsync(user, "Static", "test", token)); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "VerifyUserTokenAsync", user.Id.ToString()); + Assert.False(await manager.VerifyUserTokenAsync(user, "Static", "test2", token)); + IdentityResultAssert.VerifyUserManagerFailureLog(manager.Logger, "VerifyUserTokenAsync", user.Id.ToString(), IdentityErrorDescriber.Default.InvalidToken()); + Assert.False(await manager.VerifyUserTokenAsync(user, "Static", "test", token + "a")); Assert.False(await manager.VerifyUserTokenAsync(user2, "Static", "test", token)); } @@ -678,7 +734,9 @@ namespace Microsoft.AspNet.Identity.Test IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); var token = await manager.GenerateEmailConfirmationTokenAsync(user); Assert.NotNull(token); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "GenerateEmailConfirmationTokenAsync", user.Id.ToString()); IdentityResultAssert.IsSuccess(await manager.ConfirmEmailAsync(user, token)); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "ConfirmEmailAsync", user.Id.ToString()); Assert.True(await manager.IsEmailConfirmedAsync(user)); IdentityResultAssert.IsSuccess(await manager.SetEmailAsync(user, null)); Assert.False(await manager.IsEmailConfirmedAsync(user)); @@ -695,6 +753,7 @@ namespace Microsoft.AspNet.Identity.Test IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); IdentityResultAssert.IsFailure(await manager.ConfirmEmailAsync(user, "bogus"), "Invalid token."); Assert.False(await manager.IsEmailConfirmedAsync(user)); + IdentityResultAssert.VerifyUserManagerFailureLog(manager.Logger, "ConfirmEmailAsync", user.Id.ToString(), IdentityErrorDescriber.Default.InvalidToken()); } [Fact] @@ -770,6 +829,7 @@ namespace Microsoft.AspNet.Identity.Test Assert.False(await mgr.GetLockoutEndDateAsync(user) > DateTimeOffset.UtcNow.AddMinutes(55)); Assert.Equal(1, await mgr.GetAccessFailedCountAsync(user)); IdentityResultAssert.IsSuccess(await mgr.ResetAccessFailedCountAsync(user)); + IdentityResultAssert.VerifyUserManagerSuccessLog(mgr.Logger, "ResetAccessFailedCountAsync", user.Id.ToString()); Assert.Equal(0, await mgr.GetAccessFailedCountAsync(user)); Assert.False(await mgr.IsLockedOutAsync(user)); Assert.False(await mgr.GetLockoutEndDateAsync(user) > DateTimeOffset.UtcNow.AddMinutes(55)); @@ -792,8 +852,10 @@ namespace Microsoft.AspNet.Identity.Test IdentityResultAssert.IsSuccess(await mgr.SetLockoutEnabledAsync(user, true)); Assert.True(await mgr.GetLockoutEnabledAsync(user)); Assert.True(user.LockoutEnabled); + IdentityResultAssert.VerifyUserManagerSuccessLog(mgr.Logger, "SetLockoutEnabledAsync", user.Id.ToString()); Assert.False(await mgr.IsLockedOutAsync(user)); IdentityResultAssert.IsSuccess(await mgr.AccessFailedAsync(user)); + IdentityResultAssert.VerifyUserManagerSuccessLog(mgr.Logger, "AccessFailedAsync", user.Id.ToString()); Assert.False(await mgr.IsLockedOutAsync(user)); Assert.False(await mgr.GetLockoutEndDateAsync(user) > DateTimeOffset.UtcNow.AddMinutes(55)); Assert.Equal(1, await mgr.GetAccessFailedCountAsync(user)); @@ -814,6 +876,7 @@ namespace Microsoft.AspNet.Identity.Test Assert.True(user.LockoutEnabled); IdentityResultAssert.IsSuccess(await mgr.SetLockoutEndDateAsync(user, new DateTimeOffset())); Assert.False(await mgr.IsLockedOutAsync(user)); + IdentityResultAssert.VerifyUserManagerSuccessLog(mgr.Logger, "SetLockoutEndDateAsync", user.Id.ToString()); Assert.Equal(new DateTimeOffset(), await mgr.GetLockoutEndDateAsync(user)); Assert.Equal(new DateTimeOffset(), user.LockoutEnd); } @@ -900,7 +963,7 @@ namespace Microsoft.AspNet.Identity.Test private class AlwaysBadValidator : IUserValidator, IRoleValidator, IPasswordValidator { - public static readonly IdentityError ErrorMessage = new IdentityError { Description = "I'm Bad." }; + public static readonly IdentityError ErrorMessage = new IdentityError { Description = "I'm Bad.", Code = "BadValidator" }; public Task ValidateAsync(UserManager manager, TUser user, string password, CancellationToken cancellationToken = default(CancellationToken)) { @@ -950,6 +1013,7 @@ namespace Microsoft.AspNet.Identity.Test manager.RoleValidators.Clear(); manager.RoleValidators.Add(new AlwaysBadValidator()); IdentityResultAssert.IsFailure(await manager.UpdateAsync(role), error); + IdentityResultAssert.VerifyRoleManagerFailureLog(manager.Logger, "UpdateAsync", role.Id.ToString(), AlwaysBadValidator.ErrorMessage); } [Fact] @@ -961,6 +1025,7 @@ namespace Microsoft.AspNet.Identity.Test IdentityResultAssert.IsSuccess(await manager.CreateAsync(role)); IdentityResultAssert.IsSuccess(await manager.DeleteAsync(role)); Assert.False(await manager.RoleExistsAsync(role.Name)); + IdentityResultAssert.VerifyRoleManagerSuccessLog(manager.Logger, "DeleteAsync", role.Id.ToString()); } [Fact] @@ -974,6 +1039,7 @@ namespace Microsoft.AspNet.Identity.Test { IdentityResultAssert.IsSuccess(await manager.AddClaimAsync(role, c)); } + IdentityResultAssert.VerifyRoleManagerSuccessLog(manager.Logger, "AddClaimAsync", role.Id.ToString()); var roleClaims = await manager.GetClaimsAsync(role); Assert.Equal(3, roleClaims.Count); IdentityResultAssert.IsSuccess(await manager.RemoveClaimAsync(role, claims[0])); @@ -985,6 +1051,8 @@ namespace Microsoft.AspNet.Identity.Test IdentityResultAssert.IsSuccess(await manager.RemoveClaimAsync(role, claims[2])); roleClaims = await manager.GetClaimsAsync(role); Assert.Equal(0, roleClaims.Count); + + IdentityResultAssert.VerifyRoleManagerSuccessLog(manager.Logger, "RemoveClaimAsync", role.Id.ToString()); } [Fact] @@ -1018,6 +1086,7 @@ namespace Microsoft.AspNet.Identity.Test Assert.True(await manager.RoleExistsAsync(role.Name)); IdentityResultAssert.IsSuccess(await manager.SetRoleNameAsync(role, "Changed")); IdentityResultAssert.IsSuccess(await manager.UpdateAsync(role)); + IdentityResultAssert.VerifyRoleManagerSuccessLog(manager.Logger, "UpdateAsync", role.Id.ToString()); Assert.False(await manager.RoleExistsAsync("update")); Assert.Equal(role, await manager.FindByNameAsync("Changed")); } @@ -1085,6 +1154,7 @@ namespace Microsoft.AspNet.Identity.Test var role = CreateRole("dupeRole"); Assert.False(await manager.RoleExistsAsync(role.Name)); IdentityResultAssert.IsSuccess(await manager.CreateAsync(role)); + IdentityResultAssert.VerifyRoleManagerSuccessLog(manager.Logger, "CreateAsync", role.Id.ToString()); Assert.True(await manager.RoleExistsAsync(role.Name)); var role2 = CreateRole(); role2.Name = role.Name; @@ -1109,6 +1179,8 @@ namespace Microsoft.AspNet.Identity.Test IdentityResultAssert.IsSuccess(await manager.CreateAsync(u)); IdentityResultAssert.IsSuccess(await manager.AddToRoleAsync(u, role.Name)); Assert.True(await manager.IsInRoleAsync(u, role.Name)); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "AddToRoleAsync", u.Id.ToString()); + } } @@ -1161,6 +1233,7 @@ namespace Microsoft.AspNet.Identity.Test Assert.True(await userManager.IsInRoleAsync(user, r.Name)); } IdentityResultAssert.IsSuccess(await userManager.RemoveFromRoleAsync(user, roles[2].Name)); + IdentityResultAssert.VerifyUserManagerSuccessLog(userManager.Logger, "RemoveFromRoleAsync", user.Id.ToString()); Assert.False(await userManager.IsInRoleAsync(user, roles[2].Name)); } @@ -1201,6 +1274,7 @@ namespace Microsoft.AspNet.Identity.Test IdentityResultAssert.IsSuccess(await roleMgr.CreateAsync(role)); var result = await userMgr.RemoveFromRoleAsync(user, role.Name); IdentityResultAssert.IsFailure(result, IdentityErrorDescriber.Default.UserNotInRole(role.Name)); + IdentityResultAssert.VerifyUserManagerFailureLog(userMgr.Logger, "RemoveFromRoleAsync", user.Id.ToString(), IdentityErrorDescriber.Default.UserNotInRole(role.Name)); } [Fact] @@ -1216,6 +1290,7 @@ namespace Microsoft.AspNet.Identity.Test IdentityResultAssert.IsSuccess(await userMgr.AddToRoleAsync(user, role.Name)); Assert.True(await userMgr.IsInRoleAsync(user, role.Name)); IdentityResultAssert.IsFailure(await userMgr.AddToRoleAsync(user, role.Name), IdentityErrorDescriber.Default.UserAlreadyInRole(role.Name)); + IdentityResultAssert.VerifyUserManagerFailureLog(userMgr.Logger, "AddToRoleAsync", user.Id.ToString(), IdentityErrorDescriber.Default.UserAlreadyInRole(role.Name)); } [Fact] @@ -1261,6 +1336,7 @@ namespace Microsoft.AspNet.Identity.Test var stamp = await manager.GetSecurityStampAsync(user); var token1 = await manager.GenerateChangePhoneNumberTokenAsync(user, "111-111-1111"); IdentityResultAssert.IsSuccess(await manager.ChangePhoneNumberAsync(user, "111-111-1111", token1)); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "ChangePhoneNumberAsync", user.Id.ToString()); Assert.True(await manager.IsPhoneNumberConfirmedAsync(user)); Assert.Equal(await manager.GetPhoneNumberAsync(user), "111-111-1111"); Assert.NotEqual(stamp, user.SecurityStamp); @@ -1277,6 +1353,7 @@ namespace Microsoft.AspNet.Identity.Test var stamp = await manager.GetSecurityStampAsync(user); IdentityResultAssert.IsFailure(await manager.ChangePhoneNumberAsync(user, "111-111-1111", "bogus"), "Invalid token."); + IdentityResultAssert.VerifyUserManagerFailureLog(manager.Logger, "ChangePhoneNumberAsync", user.Id.ToString(), IdentityErrorDescriber.Default.InvalidToken()); Assert.False(await manager.IsPhoneNumberConfirmedAsync(user)); Assert.Equal(await manager.GetPhoneNumberAsync(user), "123-456-7890"); Assert.Equal(stamp, user.SecurityStamp); @@ -1308,12 +1385,16 @@ namespace Microsoft.AspNet.Identity.Test const string num1 = "111-123-4567"; const string num2 = "111-111-1111"; var token1 = await manager.GenerateChangePhoneNumberTokenAsync(user, num1); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "GenerateChangePhoneNumberTokenAsync", user.Id.ToString()); + var token2 = await manager.GenerateChangePhoneNumberTokenAsync(user, num2); Assert.NotEqual(token1, token2); Assert.True(await manager.VerifyChangePhoneNumberTokenAsync(user, token1, num1)); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "VerifyChangePhoneNumberTokenAsync", user.Id.ToString()); Assert.True(await manager.VerifyChangePhoneNumberTokenAsync(user, token2, num2)); Assert.False(await manager.VerifyChangePhoneNumberTokenAsync(user, token2, num1)); Assert.False(await manager.VerifyChangePhoneNumberTokenAsync(user, token1, num2)); + IdentityResultAssert.VerifyUserManagerFailureLog(manager.Logger, "VerifyChangePhoneNumberTokenAsync", user.Id.ToString(), IdentityErrorDescriber.Default.InvalidToken()); } [Fact] @@ -1328,6 +1409,7 @@ namespace Microsoft.AspNet.Identity.Test string newEmail = user.UserName + "@en.vec"; var token1 = await manager.GenerateChangeEmailTokenAsync(user, newEmail); IdentityResultAssert.IsSuccess(await manager.ChangeEmailAsync(user, newEmail, token1)); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "ChangeEmailAsync", user.Id.ToString()); Assert.True(await manager.IsEmailConfirmedAsync(user)); Assert.Equal(await manager.GetEmailAsync(user), newEmail); Assert.NotEqual(stamp, user.SecurityStamp); @@ -1345,6 +1427,7 @@ namespace Microsoft.AspNet.Identity.Test var stamp = await manager.GetSecurityStampAsync(user); IdentityResultAssert.IsFailure(await manager.ChangeEmailAsync(user, "whatevah@foo.barf", "bogus"), "Invalid token."); + IdentityResultAssert.VerifyUserManagerFailureLog(manager.Logger, "ChangeEmailAsync", user.Id.ToString(), IdentityErrorDescriber.Default.InvalidToken()); Assert.False(await manager.IsEmailConfirmedAsync(user)); Assert.Equal(await manager.GetEmailAsync(user), oldEmail); Assert.Equal(stamp, user.SecurityStamp); @@ -1386,8 +1469,9 @@ namespace Microsoft.AspNet.Identity.Test Assert.Null(messageService.Message); IdentityResultAssert.IsSuccess(await manager.NotifyTwoFactorTokenAsync(user, factorId, token)); Assert.NotNull(messageService.Message); - Assert.Equal("Your security code is: "+token, messageService.Message.Body); + Assert.Equal("Your security code is: " + token, messageService.Message.Body); Assert.True(await manager.VerifyTwoFactorTokenAsync(user, factorId, token)); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "VerifyTwoFactorTokenAsync", user.Id.ToString()); } [Fact] @@ -1427,11 +1511,13 @@ namespace Microsoft.AspNet.Identity.Test Assert.NotNull(stamp); var token = await manager.GenerateTwoFactorTokenAsync(user, factorId); Assert.NotNull(token); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "GenerateTwoFactorTokenAsync", user.Id.ToString()); Assert.Null(messageService.Message); IdentityResultAssert.IsSuccess(await manager.NotifyTwoFactorTokenAsync(user, factorId, token)); Assert.NotNull(messageService.Message); Assert.Equal(subject, messageService.Message.Subject); Assert.Equal(string.Format(body, token), messageService.Message.Body); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "NotifyTwoFactorTokenAsync", user.Id.ToString()); Assert.True(await manager.VerifyTwoFactorTokenAsync(user, factorId, token)); } @@ -1461,6 +1547,7 @@ namespace Microsoft.AspNet.Identity.Test var stamp = user.SecurityStamp; Assert.NotNull(stamp); IdentityResultAssert.IsSuccess(await manager.SetTwoFactorEnabledAsync(user, true)); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "SetTwoFactorEnabledAsync", user.Id.ToString()); Assert.NotEqual(stamp, await manager.GetSecurityStampAsync(user)); Assert.True(await manager.GetTwoFactorEnabledAsync(user)); } @@ -1497,7 +1584,7 @@ namespace Microsoft.AspNet.Identity.Test Assert.Null(messageService.Message); IdentityResultAssert.IsSuccess(await manager.NotifyTwoFactorTokenAsync(user, factorId, token)); Assert.NotNull(messageService.Message); - Assert.Equal("Your security code is: "+token, messageService.Message.Body); + Assert.Equal("Your security code is: " + token, messageService.Message.Body); Assert.True(await manager.VerifyTwoFactorTokenAsync(user, factorId, token)); } @@ -1563,6 +1650,7 @@ namespace Microsoft.AspNet.Identity.Test Assert.NotNull(factors); Assert.False(factors.Any()); IdentityResultAssert.IsSuccess(await manager.SetPhoneNumberAsync(user, "111-111-1111")); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "SetPhoneNumberAsync", user.Id.ToString()); user.PhoneNumberConfirmed = true; await manager.UpdateAsync(user); factors = await manager.GetValidTwoFactorProvidersAsync(user); @@ -1570,6 +1658,7 @@ namespace Microsoft.AspNet.Identity.Test Assert.Equal(1, factors.Count()); Assert.Equal("Phone", factors[0]); IdentityResultAssert.IsSuccess(await manager.SetEmailAsync(user, "test@test.com")); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "SetEmailAsync", user.Id.ToString()); user.EmailConfirmed = true; await manager.UpdateAsync(user); factors = await manager.GetValidTwoFactorProvidersAsync(user); @@ -1618,6 +1707,7 @@ namespace Microsoft.AspNet.Identity.Test user.PhoneNumber = "4251234567"; IdentityResultAssert.IsSuccess(await manager.CreateAsync(user)); Assert.False(await manager.VerifyTwoFactorTokenAsync(user, "Phone", "bogus")); + IdentityResultAssert.VerifyUserManagerFailureLog(manager.Logger, "VerifyTwoFactorTokenAsync", user.Id.ToString(), IdentityErrorDescriber.Default.InvalidToken()); } [Fact] @@ -1637,9 +1727,9 @@ namespace Microsoft.AspNet.Identity.Test // set to a valid value await userMgr.SetLockoutEndDateAsync(user, DateTimeOffset.Parse("01/01/2014")); Assert.Equal(DateTimeOffset.Parse("01/01/2014"), await userMgr.GetLockoutEndDateAsync(user)); - } + } - [Fact] + [Fact] public async Task CanGetUsersWithClaims() { var manager = CreateManager(); @@ -1651,7 +1741,7 @@ namespace Microsoft.AspNet.Identity.Test if ((i % 2) == 0) { - IdentityResultAssert.IsSuccess(await manager.AddClaimAsync(user, new Claim("foo", "bar"))); + IdentityResultAssert.IsSuccess(await manager.AddClaimAsync(user, new Claim("foo", "bar"))); } } @@ -1680,7 +1770,8 @@ namespace Microsoft.AspNet.Identity.Test if ((i % 2) == 0) { - IdentityResultAssert.IsSuccess(await manager.AddToRolesAsync(user, roles.Select(x=>x.Name).AsEnumerable())); + IdentityResultAssert.IsSuccess(await manager.AddToRolesAsync(user, roles.Select(x => x.Name).AsEnumerable())); + IdentityResultAssert.VerifyUserManagerSuccessLog(manager.Logger, "AddToRolesAsync", user.Id.ToString()); } } @@ -1711,6 +1802,5 @@ namespace Microsoft.AspNet.Identity.Test } return roles; } - } }