diff --git a/samples/IdentitySample.Mvc/Controllers/AccountController.cs b/samples/IdentitySample.Mvc/Controllers/AccountController.cs
index 1dcfa4b2e8..cdc6b95407 100644
--- a/samples/IdentitySample.Mvc/Controllers/AccountController.cs
+++ b/samples/IdentitySample.Mvc/Controllers/AccountController.cs
@@ -87,7 +87,13 @@ namespace IdentitySample.Models
{
var code = await UserManager.GenerateEmailConfirmationTokenAsync(user);
var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: Context.Request.Scheme);
- await UserManager.SendEmailAsync(user, "Confirm your account", "Please confirm your account by clicking this link: link");
+ var email = new IdentityMessage
+ {
+ Destination = model.Email,
+ Subject = "Confirm your account",
+ Body = "Please confirm your account by clicking this link: link"
+ };
+ await UserManager.SendMessageAsync("Email", email);
ViewBag.Link = callbackUrl;
return View("DisplayEmail");
}
@@ -240,7 +246,13 @@ namespace IdentitySample.Models
var code = await UserManager.GeneratePasswordResetTokenAsync(user);
var callbackUrl = Url.Action("ResetPassword", "Account", new { userId = user.Id, code = code }, protocol: Context.Request.Scheme);
- await UserManager.SendEmailAsync(user, "Reset Password", "Please reset your password by clicking here: link");
+ var email = new IdentityMessage
+ {
+ Destination = model.Email,
+ Subject = "Reset Password",
+ Body = "Please reset your password by clicking here: link"
+ };
+ await UserManager.SendMessageAsync("Email", email);
ViewBag.Link = callbackUrl;
return View("ForgotPasswordConfirmation");
}
diff --git a/samples/IdentitySample.Mvc/Controllers/ManageController.cs b/samples/IdentitySample.Mvc/Controllers/ManageController.cs
index 04f75e19cb..e67342fb0a 100644
--- a/samples/IdentitySample.Mvc/Controllers/ManageController.cs
+++ b/samples/IdentitySample.Mvc/Controllers/ManageController.cs
@@ -95,16 +95,14 @@ namespace IdentitySample
return View(model);
}
// Generate the token and send it
- var code = await UserManager.GenerateChangePhoneNumberTokenAsync(await GetCurrentUserAsync(), model.Number);
- if (UserManager.SmsService != null)
+ var user = await GetCurrentUserAsync();
+ var code = await UserManager.GenerateChangePhoneNumberTokenAsync(user, model.Number);
+ var message = new IdentityMessage
{
- var message = new IdentityMessage
- {
- Destination = model.Number,
- Body = "Your security code is: " + code
- };
- await UserManager.SmsService.SendAsync(message);
- }
+ Destination = model.Number,
+ Body = "Your security code is: " + code
+ };
+ await UserManager.SendMessageAsync("SMS", message);
return RedirectToAction("VerifyPhoneNumber", new { PhoneNumber = model.Number });
}
diff --git a/samples/IdentitySample.Mvc/IdentitySample.Mvc.kproj b/samples/IdentitySample.Mvc/IdentitySample.Mvc.kproj
index 24114c27a2..8b033a6cb1 100644
--- a/samples/IdentitySample.Mvc/IdentitySample.Mvc.kproj
+++ b/samples/IdentitySample.Mvc/IdentitySample.Mvc.kproj
@@ -1,8 +1,9 @@
-
+
14.0
$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
+ 63045
@@ -11,4 +12,4 @@
..\..\artifacts\bin\$(MSBuildProjectName)\
-
+
\ No newline at end of file
diff --git a/samples/IdentitySample.Mvc/LocalConfig.json b/samples/IdentitySample.Mvc/LocalConfig.json
index a57a3e9c9a..0e65b617f5 100644
--- a/samples/IdentitySample.Mvc/LocalConfig.json
+++ b/samples/IdentitySample.Mvc/LocalConfig.json
@@ -2,9 +2,6 @@
"DefaultAdminUsername": "Administrator",
"DefaultAdminPassword": "YouShouldChangeThisPassword1!",
"Data": {
- "DefaultConnection": {
- "Connectionstring": "Server=(localdb)\\mssqllocaldb;Database=IdentitySample-8-12-14;Trusted_Connection=True;MultipleActiveResultSets=true"
- },
"IdentityConnection": {
"Connectionstring": "Server=(localdb)\\mssqllocaldb;Database=IdentityMvc-8-12-14;Trusted_Connection=True;MultipleActiveResultSets=true"
}
diff --git a/samples/IdentitySample.Mvc/Startup.cs b/samples/IdentitySample.Mvc/Startup.cs
index 80be907d58..39ebd7b7b0 100644
--- a/samples/IdentitySample.Mvc/Startup.cs
+++ b/samples/IdentitySample.Mvc/Startup.cs
@@ -7,6 +7,8 @@ using Microsoft.AspNet.Routing;
using Microsoft.Data.Entity;
using Microsoft.Framework.ConfigurationModel;
using Microsoft.Framework.DependencyInjection;
+using System.Threading.Tasks;
+using System.Threading;
namespace IdentitySamples
{
@@ -36,12 +38,11 @@ namespace IdentitySamples
options.DefaultAdminPassword = Configuration.Get("DefaultAdminPassword");
});
- services.AddIdentity(Configuration, options =>
- {
- options.SecurityStampValidationInterval = TimeSpan.FromMinutes(20);
- })
+ services.AddIdentity(Configuration)
.AddEntityFrameworkStores()
- .AddDefaultTokenProviders();
+ .AddDefaultTokenProviders()
+ .AddMessageProvider()
+ .AddMessageProvider();
services.ConfigureFacebookAuthentication(options =>
{
@@ -80,5 +81,40 @@ namespace IdentitySamples
//Populates the Admin user and role
SampleData.InitializeIdentityDatabaseAsync(app.ApplicationServices).Wait();
}
+
+ public class EmailMessageProvider : IIdentityMessageProvider
+ {
+ public string Name
+ {
+ get
+ {
+ return "Email";
+ }
+ }
+
+ public Task SendAsync(IdentityMessage message, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ // Plug in your service
+ return Task.FromResult(0);
+ }
+ }
+
+ public class SmsMessageProvider : IIdentityMessageProvider
+ {
+ public string Name
+ {
+ get
+ {
+ return "SMS";
+ }
+ }
+
+ public Task SendAsync(IdentityMessage message, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ // Plug in your service
+ return Task.FromResult(0);
+ }
+ }
+
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/EmailTokenProvider.cs b/src/Microsoft.AspNet.Identity/EmailTokenProvider.cs
index 6220037c94..6f80f9fbc0 100644
--- a/src/Microsoft.AspNet.Identity/EmailTokenProvider.cs
+++ b/src/Microsoft.AspNet.Identity/EmailTokenProvider.cs
@@ -10,6 +10,8 @@ namespace Microsoft.AspNet.Identity
{
public string Name { get; set; } = Resources.DefaultEmailTokenProviderName;
+ public string MessageProvider { get; set; } = "Email";
+
public string Subject { get; set; } = "Security Code";
///
@@ -48,7 +50,7 @@ namespace Microsoft.AspNet.Identity
CancellationToken cancellationToken = default(CancellationToken))
{
var email = await manager.GetEmailAsync(user, cancellationToken);
- return !String.IsNullOrWhiteSpace(email) && await manager.IsEmailConfirmedAsync(user, cancellationToken);
+ return !string.IsNullOrWhiteSpace(email) && await manager.IsEmailConfirmedAsync(user, cancellationToken);
}
///
@@ -72,14 +74,20 @@ namespace Microsoft.AspNet.Identity
///
///
///
- public override Task NotifyAsync(string token, UserManager manager, TUser user,
+ public override async Task NotifyAsync(string token, UserManager manager, TUser user,
CancellationToken cancellationToken = default(CancellationToken))
{
if (manager == null)
{
- throw new ArgumentNullException("manager");
+ throw new ArgumentNullException(nameof(manager));
}
- return manager.SendEmailAsync(user, Options.Subject, String.Format(CultureInfo.CurrentCulture, Options.BodyFormat, token), cancellationToken);
+ var msg = new IdentityMessage
+ {
+ Destination = await manager.GetEmailAsync(user, cancellationToken),
+ Subject = Options.Subject,
+ Body = string.Format(CultureInfo.CurrentCulture, Options.BodyFormat, token)
+ };
+ await manager.SendMessageAsync(Options.MessageProvider, msg, cancellationToken);
}
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/IIdentityMessageService.cs b/src/Microsoft.AspNet.Identity/IIdentityMessageProvider.cs
similarity index 52%
rename from src/Microsoft.AspNet.Identity/IIdentityMessageService.cs
rename to src/Microsoft.AspNet.Identity/IIdentityMessageProvider.cs
index 9ae9e30463..0c0ea30bbd 100644
--- a/src/Microsoft.AspNet.Identity/IIdentityMessageService.cs
+++ b/src/Microsoft.AspNet.Identity/IIdentityMessageProvider.cs
@@ -6,17 +6,10 @@ using System.Threading.Tasks;
namespace Microsoft.AspNet.Identity
{
- ///
- /// Expose a way to send messages (email/txt)
- ///
- public interface IIdentityMessageService
+ public interface IIdentityMessageProvider
{
- ///
- /// This method should send the message
- ///
- ///
- ///
- ///
+ string Name { get; }
+
Task SendAsync(IdentityMessage message, CancellationToken cancellationToken = default(CancellationToken));
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/IdentityBuilder.cs b/src/Microsoft.AspNet.Identity/IdentityBuilder.cs
index 750550ada0..8c9d51332a 100644
--- a/src/Microsoft.AspNet.Identity/IdentityBuilder.cs
+++ b/src/Microsoft.AspNet.Identity/IdentityBuilder.cs
@@ -19,10 +19,50 @@ namespace Microsoft.AspNet.Identity
public Type RoleType { get; private set; }
public IServiceCollection Services { get; private set; }
+ private IdentityBuilder AddScoped(Type serviceType, Type concreteType)
+ {
+ Services.AddScoped(serviceType, concreteType);
+ return this;
+ }
+
+ public IdentityBuilder AddUserValidator() where T : class
+ {
+ return AddScoped(typeof(IUserValidator<>).MakeGenericType(UserType), typeof(T));
+ }
+
+ public IdentityBuilder AddRoleValidator() where T : class
+ {
+ return AddScoped(typeof(IRoleValidator<>).MakeGenericType(RoleType), typeof(T));
+ }
+
+ public IdentityBuilder AddPasswordValidator() where T : class
+ {
+ return AddScoped(typeof(IPasswordValidator<>).MakeGenericType(UserType), typeof(T));
+ }
+
+ public IdentityBuilder AddUserStore() where T : class
+ {
+ return AddScoped(typeof(IUserStore<>).MakeGenericType(UserType), typeof(T));
+ }
+
+ public IdentityBuilder AddRoleStore() where T : class
+ {
+ return AddScoped(typeof(IRoleStore<>).MakeGenericType(RoleType), typeof(T));
+ }
+
+ public IdentityBuilder AddTokenProvider() where TProvider : class
+ {
+ return AddTokenProvider(typeof(TProvider));
+ }
+
public IdentityBuilder AddTokenProvider(Type provider)
{
- Services.AddScoped(typeof(IUserTokenProvider<>).MakeGenericType(UserType), provider);
- return this;
+ return AddScoped(typeof(IUserTokenProvider<>).MakeGenericType(UserType), provider);
+ }
+
+ public IdentityBuilder AddMessageProvider() where TProvider : IIdentityMessageProvider
+ {
+ return AddScoped(typeof(IIdentityMessageProvider), typeof(TProvider));
}
public IdentityBuilder AddDefaultTokenProviders()
@@ -36,62 +76,5 @@ namespace Microsoft.AspNet.Identity
.AddTokenProvider(typeof(PhoneNumberTokenProvider<>).MakeGenericType(UserType))
.AddTokenProvider(typeof(EmailTokenProvider<>).MakeGenericType(UserType));
}
-
- }
-
- public class IdentityBuilder : IdentityBuilder where TUser : class where TRole : class
- {
- public IdentityBuilder(IServiceCollection services) : base(typeof(TUser), typeof(TRole), services) { }
-
- public IdentityBuilder AddInstance(TService instance)
- where TService : class
- {
- Services.AddInstance(instance);
- return this;
- }
-
- public IdentityBuilder AddUserStore(IUserStore store)
- {
- return AddInstance(store);
- }
-
- public IdentityBuilder AddRoleStore(IRoleStore store)
- {
- return AddInstance(store);
- }
-
- public IdentityBuilder AddPasswordValidator(IPasswordValidator validator)
- {
- return AddInstance(validator);
- }
-
- public IdentityBuilder AddUserValidator(IUserValidator validator)
- {
- return AddInstance(validator);
- }
-
- public IdentityBuilder AddTokenProvider() where TTokenProvider : IUserTokenProvider
- {
- Services.AddScoped, TTokenProvider>();
- return this;
- }
-
- public IdentityBuilder ConfigureIdentity(Action action, int order = 0)
- {
- Services.Configure(action, order);
- return this;
- }
-
- public IdentityBuilder AddUserManager() where TManager : UserManager
- {
- Services.AddScoped();
- return this;
- }
-
- public IdentityBuilder AddRoleManager() where TManager : RoleManager
- {
- Services.AddScoped();
- return this;
- }
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/IdentityMessage.cs b/src/Microsoft.AspNet.Identity/IdentityMessage.cs
index 655bd63d8b..44c9b3e16f 100644
--- a/src/Microsoft.AspNet.Identity/IdentityMessage.cs
+++ b/src/Microsoft.AspNet.Identity/IdentityMessage.cs
@@ -9,7 +9,7 @@ namespace Microsoft.AspNet.Identity
public class IdentityMessage
{
///
- /// Destination, i.e. To email, or SMS phone number
+ /// Target for the message, i.e. email or phone number
///
public virtual string Destination { get; set; }
diff --git a/src/Microsoft.AspNet.Identity/IdentityServiceCollectionExtensions.cs b/src/Microsoft.AspNet.Identity/IdentityServiceCollectionExtensions.cs
index 3be053cec8..86fd009b9e 100644
--- a/src/Microsoft.AspNet.Identity/IdentityServiceCollectionExtensions.cs
+++ b/src/Microsoft.AspNet.Identity/IdentityServiceCollectionExtensions.cs
@@ -17,12 +17,12 @@ namespace Microsoft.Framework.DependencyInjection
return services.Configure(configure);
}
- public static IdentityBuilder AddIdentity(this IServiceCollection services)
+ public static IdentityBuilder AddIdentity(this IServiceCollection services)
{
return services.AddIdentity();
}
- public static IdentityBuilder AddIdentity(
+ public static IdentityBuilder AddIdentity(
this IServiceCollection services,
IConfiguration identityConfig = null,
Action configureOptions = null,
@@ -31,7 +31,7 @@ namespace Microsoft.Framework.DependencyInjection
return services.AddIdentity(identityConfig, configureOptions, useDefaultSubKey);
}
- public static IdentityBuilder AddIdentity(
+ public static IdentityBuilder AddIdentity(
this IServiceCollection services,
IConfiguration identityConfig = null,
Action configureOptions = null,
@@ -39,8 +39,6 @@ namespace Microsoft.Framework.DependencyInjection
where TUser : class
where TRole : class
{
- services.Add(IdentityServices.GetDefaultServices());
-
if (identityConfig != null)
{
if (useDefaultSubKey)
@@ -49,16 +47,34 @@ namespace Microsoft.Framework.DependencyInjection
}
services.Configure(identityConfig);
}
+ var describe = new ServiceDescriber(identityConfig);
+
+ // Services used by identity
+ services.AddOptions(identityConfig);
+ services.AddDataProtection(identityConfig);
+
+ // Identity services
+ services.TryAdd(describe.Transient, UserValidator>());
+ services.TryAdd(describe.Transient, PasswordValidator>());
+ services.TryAdd(describe.Transient, PasswordHasher>());
+ services.TryAdd(describe.Transient());
+ services.TryAdd(describe.Transient, RoleValidator>());
+ services.TryAdd(describe.Scoped>());
+ services.TryAdd(describe.Scoped, ClaimsIdentityFactory>());
+ services.TryAdd(describe.Scoped, UserManager>());
+ services.TryAdd(describe.Scoped, SignInManager>());
+ services.TryAdd(describe.Scoped, RoleManager>());
+
if (configureOptions != null)
{
services.ConfigureIdentity(configureOptions);
}
-
services.Configure(options =>
{
options.SignInAsAuthenticationType = IdentityOptions.ExternalCookieAuthenticationType;
});
+ // Configure all of the cookie middlewares
services.Configure(options =>
{
options.AuthenticationType = IdentityOptions.ApplicationCookieAuthenticationType;
@@ -68,7 +84,6 @@ namespace Microsoft.Framework.DependencyInjection
OnValidateIdentity = SecurityStampValidator.ValidateIdentityAsync
};
}, IdentityOptions.ApplicationCookieAuthenticationType);
-
services.Configure(options =>
{
options.AuthenticationType = IdentityOptions.ExternalCookieAuthenticationType;
@@ -76,14 +91,12 @@ namespace Microsoft.Framework.DependencyInjection
options.CookieName = IdentityOptions.ExternalCookieAuthenticationType;
options.ExpireTimeSpan = TimeSpan.FromMinutes(5);
}, IdentityOptions.ExternalCookieAuthenticationType);
-
services.Configure(options =>
{
options.AuthenticationType = IdentityOptions.TwoFactorRememberMeCookieAuthenticationType;
options.AuthenticationMode = AuthenticationMode.Passive;
options.CookieName = IdentityOptions.TwoFactorRememberMeCookieAuthenticationType;
}, IdentityOptions.TwoFactorRememberMeCookieAuthenticationType);
-
services.Configure(options =>
{
options.AuthenticationType = IdentityOptions.TwoFactorUserIdCookieAuthenticationType;
@@ -92,7 +105,7 @@ namespace Microsoft.Framework.DependencyInjection
options.ExpireTimeSpan = TimeSpan.FromMinutes(5);
}, IdentityOptions.TwoFactorUserIdCookieAuthenticationType);
- return new IdentityBuilder(services);
+ return new IdentityBuilder(typeof(TUser), typeof(TRole), services);
}
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/IdentityServices.cs b/src/Microsoft.AspNet.Identity/IdentityServices.cs
deleted file mode 100644
index 850c14693d..0000000000
--- a/src/Microsoft.AspNet.Identity/IdentityServices.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-// 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.Collections.Generic;
-using Microsoft.Framework.ConfigurationModel;
-using Microsoft.Framework.DependencyInjection;
-
-namespace Microsoft.AspNet.Identity
-{
- ///
- /// Default services used by UserManager and RoleManager
- ///
- public class IdentityServices
- {
- public static IEnumerable GetDefaultServices(IConfiguration config = null)
- where TUser : class where TRole : class
- {
- ServiceDescriber describe;
- if (config == null)
- {
- describe = new ServiceDescriber();
- }
- else
- {
- describe = new ServiceDescriber(config);
- }
- yield return describe.Transient, UserValidator>();
- yield return describe.Transient, PasswordValidator>();
- yield return describe.Transient, PasswordHasher>();
- yield return describe.Transient();
- yield return describe.Transient, RoleValidator>();
- yield return describe.Scoped>();
- yield return describe.Scoped, ClaimsIdentityFactory>();
- yield return describe.Scoped, UserManager>();
- yield return describe.Scoped, SignInManager>();
- yield return describe.Scoped, RoleManager>();
- }
- }
-}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/PhoneNumberTokenProvider.cs b/src/Microsoft.AspNet.Identity/PhoneNumberTokenProvider.cs
index 2f0ded3145..3116bc5dd3 100644
--- a/src/Microsoft.AspNet.Identity/PhoneNumberTokenProvider.cs
+++ b/src/Microsoft.AspNet.Identity/PhoneNumberTokenProvider.cs
@@ -10,6 +10,8 @@ namespace Microsoft.AspNet.Identity
{
public string Name { get; set; } = Resources.DefaultPhoneNumberTokenProviderName;
+ public string MessageProvider { get; set; } = "SMS";
+
///
/// Message contents which should contain a format string which the token will be the only argument
///
@@ -50,7 +52,7 @@ namespace Microsoft.AspNet.Identity
throw new ArgumentNullException("manager");
}
var phoneNumber = await manager.GetPhoneNumberAsync(user, cancellationToken);
- return !String.IsNullOrWhiteSpace(phoneNumber) && await manager.IsPhoneNumberConfirmedAsync(user, cancellationToken);
+ return !string.IsNullOrWhiteSpace(phoneNumber) && await manager.IsPhoneNumberConfirmedAsync(user, cancellationToken);
}
///
@@ -78,14 +80,19 @@ namespace Microsoft.AspNet.Identity
///
///
///
- public override Task NotifyAsync(string token, UserManager manager, TUser user,
+ public override async Task NotifyAsync(string token, UserManager manager, TUser user,
CancellationToken cancellationToken = default(CancellationToken))
{
if (manager == null)
{
throw new ArgumentNullException("manager");
}
- return manager.SendSmsAsync(user, String.Format(CultureInfo.CurrentCulture, Options.MessageFormat, token), cancellationToken);
+ var msg = new IdentityMessage
+ {
+ Destination = await manager.GetPhoneNumberAsync(user, cancellationToken),
+ Body = string.Format(CultureInfo.CurrentCulture, Options.MessageFormat, token)
+ };
+ await manager.SendMessageAsync(Options.MessageProvider, msg, cancellationToken);
}
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Identity/Properties/Resources.Designer.cs
index 240e80680c..9c91fd4bd5 100644
--- a/src/Microsoft.AspNet.Identity/Properties/Resources.Designer.cs
+++ b/src/Microsoft.AspNet.Identity/Properties/Resources.Designer.cs
@@ -266,6 +266,22 @@ namespace Microsoft.AspNet.Identity
return GetString("LockoutNotEnabled");
}
+ ///
+ /// No IUserMessageProvider named '{0}' is registered.
+ ///
+ internal static string NoMessageProvider
+ {
+ get { return GetString("NoMessageProvider"); }
+ }
+
+ ///
+ /// No IUserMessageProvider named '{0}' is registered.
+ ///
+ internal static string FormatNoMessageProvider(object p0)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("NoMessageProvider"), p0);
+ }
+
///
/// No IUserTokenProvider named '{0}' is registered.
///
diff --git a/src/Microsoft.AspNet.Identity/Resources.resx b/src/Microsoft.AspNet.Identity/Resources.resx
index b51f0157d8..674ae53538 100644
--- a/src/Microsoft.AspNet.Identity/Resources.resx
+++ b/src/Microsoft.AspNet.Identity/Resources.resx
@@ -181,6 +181,10 @@
Lockout is not enabled for this user.
error when lockout is not enabled
+
+ No IUserMessageProvider named '{0}' is registered.
+ Error when there is no IUserMessageProvider
+
No IUserTokenProvider named '{0}' is registered.
Error when there is no IUserTokenProvider
diff --git a/src/Microsoft.AspNet.Identity/UserManager.cs b/src/Microsoft.AspNet.Identity/UserManager.cs
index 2b82d41333..bd52e159c0 100644
--- a/src/Microsoft.AspNet.Identity/UserManager.cs
+++ b/src/Microsoft.AspNet.Identity/UserManager.cs
@@ -21,6 +21,8 @@ namespace Microsoft.AspNet.Identity
{
private readonly Dictionary> _tokenProviders =
new Dictionary>();
+ private readonly Dictionary _msgProviders =
+ new Dictionary();
private TimeSpan _defaultLockout = TimeSpan.Zero;
private bool _disposed;
@@ -36,10 +38,14 @@ namespace Microsoft.AspNet.Identity
///
///
///
- public UserManager(IUserStore store, IOptions optionsAccessor,
- IPasswordHasher passwordHasher, IUserValidator userValidator,
- IPasswordValidator passwordValidator, IUserNameNormalizer userNameNormalizer,
- IEnumerable> tokenProviders)
+ public UserManager(IUserStore store,
+ IOptions optionsAccessor,
+ IPasswordHasher passwordHasher,
+ IUserValidator userValidator,
+ IPasswordValidator passwordValidator,
+ IUserNameNormalizer userNameNormalizer,
+ IEnumerable> tokenProviders,
+ IEnumerable msgProviders)
{
if (store == null)
{
@@ -68,6 +74,15 @@ namespace Microsoft.AspNet.Identity
RegisterTokenProvider(tokenProvider);
}
}
+
+ if (msgProviders != null)
+ {
+ foreach (var msgProvider in msgProviders)
+ {
+ RegisterMessageProvider(msgProvider);
+ }
+ }
+
}
///
@@ -111,16 +126,6 @@ namespace Microsoft.AspNet.Identity
///
public IUserNameNormalizer UserNameNormalizer { get; set; }
- ///
- /// Used to send email
- ///
- public IIdentityMessageService EmailService { get; set; }
-
- ///
- /// Used to send a sms message
- ///
- public IIdentityMessageService SmsService { get; set; }
-
public IdentityOptions Options
{
get
@@ -1500,7 +1505,6 @@ namespace Microsoft.AspNet.Identity
///
/// Register a user token provider
///
- ///
///
public virtual void RegisterTokenProvider(IUserTokenProvider provider)
{
@@ -1512,6 +1516,20 @@ namespace Microsoft.AspNet.Identity
_tokenProviders[provider.Name] = provider;
}
+ ///
+ /// Register a user message provider
+ ///
+ ///
+ public virtual void RegisterMessageProvider(IIdentityMessageProvider provider)
+ {
+ ThrowIfDisposed();
+ if (provider == null)
+ {
+ throw new ArgumentNullException("provider");
+ }
+ _msgProviders[provider.Name] = provider;
+ }
+
///
/// Returns a list of valid two factor providers for a user
///
@@ -1664,60 +1682,30 @@ namespace Microsoft.AspNet.Identity
return await UpdateAsync(user, cancellationToken);
}
- // SMS/Email methods
+ // Messaging methods
///
- /// Send an email to the user
+ /// Send a message to the user using the specified provider
///
- ///
- ///
- ///
- ///
- ///
- public virtual async Task SendEmailAsync(TUser user, string subject, string body,
- CancellationToken cancellationToken = default(CancellationToken))
- {
- ThrowIfDisposed();
- if (user == null)
- {
- throw new ArgumentNullException("user");
- }
- if (EmailService != null)
- {
- var msg = new IdentityMessage
- {
- Destination = await GetEmailAsync(user, cancellationToken),
- Subject = subject,
- Body = body,
- };
- await EmailService.SendAsync(msg, cancellationToken);
- }
- }
-
- ///
- /// Send a user a sms message
- ///
- ///
+ ///
///
///
///
- public virtual async Task SendSmsAsync(TUser user, string message,
+ public virtual async Task SendMessageAsync(string messageProvider, IdentityMessage message,
CancellationToken cancellationToken = default(CancellationToken))
{
ThrowIfDisposed();
- if (user == null)
+ if (message == null)
{
- throw new ArgumentNullException("user");
+ throw new ArgumentNullException(nameof(message));
}
- if (SmsService != null)
+ if (!_msgProviders.ContainsKey(messageProvider))
{
- var msg = new IdentityMessage
- {
- Destination = await GetPhoneNumberAsync(user, cancellationToken),
- Body = message
- };
- await SmsService.SendAsync(msg, cancellationToken);
+ throw new NotSupportedException(String.Format(CultureInfo.CurrentCulture,
+ Resources.NoMessageProvider, messageProvider));
}
+ await _msgProviders[messageProvider].SendAsync(message, cancellationToken);
+ return IdentityResult.Success;
}
// IUserLockoutStore methods
diff --git a/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/EntityInMemoryTestServiceCollectionExtensions.cs b/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/EntityInMemoryTestServiceCollectionExtensions.cs
index 6d21889873..98c6e6506c 100644
--- a/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/EntityInMemoryTestServiceCollectionExtensions.cs
+++ b/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/EntityInMemoryTestServiceCollectionExtensions.cs
@@ -10,12 +10,12 @@ namespace Microsoft.AspNet.Identity
{
public static class EntityInMemoryTestServiceCollectionExtensions
{
- public static IdentityBuilder AddIdentityInMemory(this ServiceCollection services, InMemoryContext context)
+ public static IdentityBuilder AddIdentityInMemory(this ServiceCollection services, InMemoryContext context)
{
return services.AddIdentityInMemory(context);
}
- public static IdentityBuilder AddIdentityInMemory(this ServiceCollection services, TDbContext context)
+ public static IdentityBuilder AddIdentityInMemory(this ServiceCollection services, TDbContext context)
where TUser : IdentityUser
where TRole : IdentityRole
where TDbContext : DbContext
diff --git a/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/RoleStoreTest.cs b/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/RoleStoreTest.cs
index 4c9175b14e..2efc785d71 100644
--- a/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/RoleStoreTest.cs
+++ b/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/RoleStoreTest.cs
@@ -19,7 +19,8 @@ namespace Microsoft.AspNet.Identity.EntityFramework.InMemory.Test
var services = new ServiceCollection();
services.AddEntityFramework().AddInMemoryStore();
var store = new RoleStore(new InMemoryContext());
- services.AddIdentity().AddRoleStore(store);
+ services.AddIdentity();
+ services.AddInstance>(store);
var provider = services.BuildServiceProvider();
var manager = provider.GetRequiredService>();
Assert.NotNull(manager);
diff --git a/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/TestIdentityFactory.cs b/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/TestIdentityFactory.cs
index 50e7d085b4..3112c72c58 100644
--- a/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/TestIdentityFactory.cs
+++ b/test/Microsoft.AspNet.Identity.EntityFramework.InMemory.Test/TestIdentityFactory.cs
@@ -1,10 +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.AspNet.Identity.Test;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.DependencyInjection.Fallback;
-using Microsoft.Framework.Logging;
namespace Microsoft.AspNet.Identity.EntityFramework.InMemory.Test
{
@@ -25,7 +23,8 @@ namespace Microsoft.AspNet.Identity.EntityFramework.InMemory.Test
public static RoleManager CreateRoleManager(InMemoryContext context)
{
var services = new ServiceCollection();
- services.AddIdentity().AddRoleStore(new RoleStore(context));
+ services.AddIdentity();
+ services.AddInstance>(new RoleStore(context));
return services.BuildServiceProvider().GetRequiredService>();
}
diff --git a/test/Microsoft.AspNet.Identity.Test/IdentityBuilderTest.cs b/test/Microsoft.AspNet.Identity.Test/IdentityBuilderTest.cs
index 1d29a5cc14..c56a000d74 100644
--- a/test/Microsoft.AspNet.Identity.Test/IdentityBuilderTest.cs
+++ b/test/Microsoft.AspNet.Identity.Test/IdentityBuilderTest.cs
@@ -4,34 +4,62 @@
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.DependencyInjection.Fallback;
using Microsoft.Framework.OptionsModel;
+using System.Collections.Generic;
using Xunit;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Linq;
+using Microsoft.AspNet.Security.DataProtection;
namespace Microsoft.AspNet.Identity.Test
{
public class IdentityBuilderTest
{
+
[Fact]
- public void CanSpecifyUserValidatorInstance()
+ public void CanOverrideUserStore()
{
var services = new ServiceCollection();
- var validator = new UserValidator();
- services.AddIdentity().AddUserValidator(validator);
- Assert.Equal(validator, services.BuildServiceProvider().GetRequiredService>());
+ services.AddIdentity().AddUserStore();
+ var thingy = services.BuildServiceProvider().GetRequiredService>() as MyUberThingy;
+ Assert.NotNull(thingy);
}
[Fact]
- public void CanSpecifyPasswordValidatorInstance()
+ public void CanOverrideRoleStore()
{
var services = new ServiceCollection();
- var validator = new PasswordValidator();
- services.AddIdentity().AddPasswordValidator(validator);
- Assert.Equal(validator, services.BuildServiceProvider().GetRequiredService>());
+ services.AddIdentity().AddRoleStore();
+ var thingy = services.BuildServiceProvider().GetRequiredService>() as MyUberThingy;
+ Assert.NotNull(thingy);
}
[Fact]
- public void CanSpecifyPasswordHasherInstance()
+ public void CanOverrideRoleValidator()
{
- CanOverride>(new PasswordHasher(new PasswordHasherOptionsAccessor()));
+ var services = new ServiceCollection();
+ services.AddIdentity().AddRoleValidator();
+ var thingy = services.BuildServiceProvider().GetRequiredService>() as MyUberThingy;
+ Assert.NotNull(thingy);
+ }
+
+ [Fact]
+ public void CanOverrideUserValidator()
+ {
+ var services = new ServiceCollection();
+ services.AddIdentity().AddUserValidator();
+ var thingy = services.BuildServiceProvider().GetRequiredService>() as MyUberThingy;
+ Assert.NotNull(thingy);
+ }
+
+ [Fact]
+ public void CanOverridePasswordValidator()
+ {
+ var services = new ServiceCollection();
+ services.AddIdentity().AddPasswordValidator();
+ var thingy = services.BuildServiceProvider().GetRequiredService>() as MyUberThingy;
+ Assert.NotNull(thingy);
}
[Fact]
@@ -52,12 +80,130 @@ namespace Microsoft.AspNet.Identity.Test
Assert.NotNull(hasher);
}
- private static void CanOverride(TService instance)
- where TService : class
+ [Fact]
+ public void EnsureDefaultTokenProviders()
{
var services = new ServiceCollection();
- services.AddIdentity().AddInstance(instance);
- Assert.Equal(instance, services.BuildServiceProvider().GetRequiredService());
+ services.AddIdentity().AddDefaultTokenProviders();
+ services.Add(DataProtectionServices.GetDefaultServices());
+ services.Add(OptionsServices.GetDefaultServices());
+
+ var provider = services.BuildServiceProvider();
+ var tokenProviders = provider.GetRequiredService>>();
+ Assert.Equal(3, tokenProviders.Count());
+ }
+
+ private class MyUberThingy : IUserValidator, IPasswordValidator, IRoleValidator, IUserStore, IRoleStore
+ {
+ public Task CreateAsync(IdentityRole role, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task CreateAsync(IdentityUser user, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task DeleteAsync(IdentityRole role, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task DeleteAsync(IdentityUser user, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Dispose()
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task FindByIdAsync(string userId, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task GetNormalizedUserNameAsync(IdentityUser user, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task GetRoleIdAsync(IdentityRole role, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task GetRoleNameAsync(IdentityRole role, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task GetUserIdAsync(IdentityUser user, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task GetUserNameAsync(IdentityUser user, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task SetNormalizedUserNameAsync(IdentityUser user, string normalizedName, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task SetRoleNameAsync(IdentityRole role, string roleName, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task SetUserNameAsync(IdentityUser user, string userName, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task UpdateAsync(IdentityRole role, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task UpdateAsync(IdentityUser user, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task ValidateAsync(RoleManager manager, IdentityRole role, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task ValidateAsync(UserManager manager, IdentityUser user, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task ValidateAsync(IdentityUser user, string password, UserManager manager, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ throw new NotImplementedException();
+ }
+
+ Task IRoleStore.FindByIdAsync(string roleId, CancellationToken cancellationToken)
+ {
+ throw new NotImplementedException();
+ }
+
+ Task IRoleStore.FindByNameAsync(string roleName, CancellationToken cancellationToken)
+ {
+ throw new NotImplementedException();
+ }
}
}
diff --git a/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs b/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs
index 386520db42..daafaf8b19 100644
--- a/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs
+++ b/test/Microsoft.AspNet.Identity.Test/UserManagerTest.cs
@@ -24,7 +24,7 @@ namespace Microsoft.AspNet.Identity.Test
public TestManager(IUserStore store, IOptions optionsAccessor,
IPasswordHasher passwordHasher, IUserValidator userValidator,
IPasswordValidator passwordValidator)
- : base(store, optionsAccessor, passwordHasher, userValidator, passwordValidator, null, null) { }
+ : base(store, optionsAccessor, passwordHasher, userValidator, passwordValidator, null, null, null) { }
}
[Fact]
@@ -538,13 +538,13 @@ namespace Microsoft.AspNet.Identity.Test
var passwordValidator = new PasswordValidator();
Assert.Throws("store",
- () => new UserManager(null, null, null, null, null, null, null));
+ () => new UserManager(null, null, null, null, null, null, null, null));
Assert.Throws("optionsAccessor",
- () => new UserManager(store, null, null, null, null, null, null));
+ () => new UserManager(store, null, null, null, null, null, null, null));
Assert.Throws("passwordHasher",
- () => new UserManager(store, optionsAccessor, null, null, null, null, null));
+ () => new UserManager(store, optionsAccessor, null, null, null, null, null, null));
- var manager = new UserManager(store, optionsAccessor, passwordHasher, userValidator, passwordValidator, null, null);
+ var manager = new UserManager(store, optionsAccessor, passwordHasher, userValidator, passwordValidator, null, null, null);
Assert.Throws("value", () => manager.PasswordHasher = null);
Assert.Throws("value", () => manager.Options = null);
@@ -671,10 +671,6 @@ namespace Microsoft.AspNet.Identity.Test
async () => await manager.GetLockoutEndDateAsync(null));
await Assert.ThrowsAsync("user",
async () => await manager.IsLockedOutAsync(null));
- await Assert.ThrowsAsync("user",
- async () => await manager.SendEmailAsync(null, null, null));
- await Assert.ThrowsAsync("user",
- async () => await manager.SendSmsAsync(null, null));
}
[Fact]
@@ -714,6 +710,7 @@ namespace Microsoft.AspNet.Identity.Test
await Assert.ThrowsAsync(() => manager.GenerateEmailConfirmationTokenAsync(null));
await Assert.ThrowsAsync(() => manager.IsEmailConfirmedAsync(null));
await Assert.ThrowsAsync(() => manager.ConfirmEmailAsync(null, null));
+ await Assert.ThrowsAsync(() => manager.SendMessageAsync(null, null));
}
private class BadPasswordValidator : IPasswordValidator where TUser : class
diff --git a/test/Shared/MockHelpers.cs b/test/Shared/MockHelpers.cs
index 0eb2418d2c..72790950fb 100644
--- a/test/Shared/MockHelpers.cs
+++ b/test/Shared/MockHelpers.cs
@@ -42,7 +42,8 @@ namespace Microsoft.AspNet.Identity.Test
new UserValidator(),
new PasswordValidator(),
new UpperInvariantUserNameNormalizer(),
- new List>());
+ new List>(),
+ new List());
}
public static Mock> MockRoleManager() where TRole : class
@@ -61,7 +62,7 @@ namespace Microsoft.AspNet.Identity.Test
var options = new OptionsManager(null);
var validator = new Mock>();
var userManager = new UserManager(store, options, new PasswordHasher(new PasswordHasherOptionsAccessor()),
- validator.Object, new PasswordValidator(), new UpperInvariantUserNameNormalizer(), null);
+ validator.Object, new PasswordValidator(), new UpperInvariantUserNameNormalizer(), null, null);
validator.Setup(v => v.ValidateAsync(userManager, It.IsAny(), CancellationToken.None))
.Returns(Task.FromResult(IdentityResult.Success)).Verifiable();
return userManager;
diff --git a/test/Shared/TestMessageService.cs b/test/Shared/TestMessageService.cs
index b57e606e46..52040cf594 100644
--- a/test/Shared/TestMessageService.cs
+++ b/test/Shared/TestMessageService.cs
@@ -1,15 +1,18 @@
// 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.Threading;
using System.Threading.Tasks;
namespace Microsoft.AspNet.Identity.Test
{
- public class TestMessageService : IIdentityMessageService
+ public class TestMessageService : IIdentityMessageProvider
{
public IdentityMessage Message { get; set; }
+ public string Name { get; set; } = "Test";
+
public Task SendAsync(IdentityMessage message, CancellationToken cancellationToken = default(CancellationToken))
{
Message = message;
diff --git a/test/Shared/UserManagerTestBase.cs b/test/Shared/UserManagerTestBase.cs
index 6d646ec0c8..a0918c92d7 100644
--- a/test/Shared/UserManagerTestBase.cs
+++ b/test/Shared/UserManagerTestBase.cs
@@ -11,8 +11,6 @@ using Microsoft.AspNet.Testing;
using Xunit;
using Microsoft.AspNet.Security.DataProtection;
using Microsoft.Framework.DependencyInjection;
-using Microsoft.Framework.OptionsModel;
-using Microsoft.AspNet.Hosting;
using Microsoft.Framework.DependencyInjection.Fallback;
namespace Microsoft.AspNet.Identity.Test
@@ -30,8 +28,8 @@ namespace Microsoft.AspNet.Identity.Test
{
protected virtual void SetupIdentityServices(IServiceCollection services, object context = null)
{
- services.Add(OptionsServices.GetDefaultServices());
- services.Add(HostingServices.GetDefaultServices());
+ services.AddOptions();
+ services.AddHosting();
services.Add(DataProtectionServices.GetDefaultServices());
services.AddIdentity().AddDefaultTokenProviders();
AddUserStore(services, context);
@@ -1206,8 +1204,8 @@ namespace Microsoft.AspNet.Identity.Test
public async Task CanEmailTwoFactorToken()
{
var manager = CreateManager();
- var messageService = new TestMessageService();
- manager.EmailService = messageService;
+ var messageService = new TestMessageService { Name = "Email" };
+ manager.RegisterMessageProvider(messageService);
const string factorId = "Email"; // default
var user = new TUser() { UserName = "EmailCodeTest", Email = "foo@foo.com" };
user.EmailConfirmed = true;
@@ -1251,8 +1249,8 @@ namespace Microsoft.AspNet.Identity.Test
o.BodyFormat = body;
});
var manager = CreateManager(null, services);
- var messageService = new TestMessageService();
- manager.EmailService = messageService;
+ var messageService = new TestMessageService { Name = "Email" };
+ manager.RegisterMessageProvider(messageService);
var user = CreateTestUser();
user.Email = user.UserName + "@foo.com";
const string password = "password";
@@ -1300,30 +1298,16 @@ namespace Microsoft.AspNet.Identity.Test
}
[Fact]
- public async Task CanSendSms()
+ public async Task CanSendMessage()
{
var manager = CreateManager();
- var messageService = new TestMessageService();
- manager.SmsService = messageService;
+ var messageService = new TestMessageService();
+ manager.RegisterMessageProvider(messageService);
var user = CreateTestUser();
- user.PhoneNumber = "4251234567";
IdentityResultAssert.IsSuccess(await manager.CreateAsync(user));
- await manager.SendSmsAsync(user, "Hi");
- Assert.NotNull(messageService.Message);
- Assert.Equal("Hi", messageService.Message.Body);
- }
-
- [Fact]
- public async Task CanSendEmail()
- {
- var manager = CreateManager();
- var messageService = new TestMessageService();
- manager.EmailService = messageService;
- var user = CreateTestUser();
- user.Email = user.UserName + "@foo.com";
- IdentityResultAssert.IsSuccess(await manager.CreateAsync(user));
- await manager.SendEmailAsync(user, "Hi", "Body");
+ await manager.SendMessageAsync(messageService.Name, new IdentityMessage { Destination = "foo", Subject = "Hi", Body = "Body" });
Assert.NotNull(messageService.Message);
+ Assert.Equal("foo", messageService.Message.Destination);
Assert.Equal("Hi", messageService.Message.Subject);
Assert.Equal("Body", messageService.Message.Body);
}
@@ -1332,8 +1316,8 @@ namespace Microsoft.AspNet.Identity.Test
public async Task CanSmsTwoFactorToken()
{
var manager = CreateManager();
- var messageService = new TestMessageService();
- manager.SmsService = messageService;
+ var messageService = new TestMessageService { Name = "SMS" };
+ manager.RegisterMessageProvider(messageService);
const string factorId = "Phone"; // default
var user = CreateTestUser();
user.PhoneNumber = "4251234567";
@@ -1360,8 +1344,8 @@ namespace Microsoft.AspNet.Identity.Test
o.MessageFormat = "Your code is: {0}";
});
var manager = CreateManager(null, services);
- var messageService = new TestMessageService();
- manager.SmsService = messageService;
+ var messageService = new TestMessageService { Name = "SMS" };
+ manager.RegisterMessageProvider(messageService);
var user = CreateTestUser();
user.PhoneNumber = "4251234567";
IdentityResultAssert.IsSuccess(await manager.CreateAsync(user));