1 // Copyright (c) .NET Foundation. All rights reserved.
2 // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 
4 using System;
5 using System.Reflection;
6 using Microsoft.Extensions.DependencyInjection;
7 using Microsoft.Extensions.DependencyInjection.Extensions;
8 using Microsoft.Extensions.Identity.Core;
9 
10 namespace Microsoft.AspNetCore.Identity
11 {
12     /// <summary>
13     /// Helper functions for configuring identity services.
14     /// </summary>
15     public class IdentityBuilder
16     {
17         /// <summary>
18         /// Creates a new instance of <see cref="IdentityBuilder"/>.
19         /// </summary>
20         /// <param name="user">The <see cref="Type"/> to use for the users.</param>
21         /// <param name="services">The <see cref="IServiceCollection"/> to attach to.</param>
IdentityBuilder(Type user, IServiceCollection services)22         public IdentityBuilder(Type user, IServiceCollection services)
23         {
24             UserType = user;
25             Services = services;
26         }
27 
28         /// <summary>
29         /// Creates a new instance of <see cref="IdentityBuilder"/>.
30         /// </summary>
31         /// <param name="user">The <see cref="Type"/> to use for the users.</param>
32         /// <param name="role">The <see cref="Type"/> to use for the roles.</param>
33         /// <param name="services">The <see cref="IServiceCollection"/> to attach to.</param>
IdentityBuilder(Type user, Type role, IServiceCollection services)34         public IdentityBuilder(Type user, Type role, IServiceCollection services) : this(user, services)
35             => RoleType = role;
36 
37         /// <summary>
38         /// Gets the <see cref="Type"/> used for users.
39         /// </summary>
40         /// <value>
41         /// The <see cref="Type"/> used for users.
42         /// </value>
43         public Type UserType { get; private set; }
44 
45 
46         /// <summary>
47         /// Gets the <see cref="Type"/> used for roles.
48         /// </summary>
49         /// <value>
50         /// The <see cref="Type"/> used for roles.
51         /// </value>
52         public Type RoleType { get; private set; }
53 
54         /// <summary>
55         /// Gets the <see cref="IServiceCollection"/> services are attached to.
56         /// </summary>
57         /// <value>
58         /// The <see cref="IServiceCollection"/> services are attached to.
59         /// </value>
60         public IServiceCollection Services { get; private set; }
61 
AddScoped(Type serviceType, Type concreteType)62         private IdentityBuilder AddScoped(Type serviceType, Type concreteType)
63         {
64             Services.AddScoped(serviceType, concreteType);
65             return this;
66         }
67 
68         /// <summary>
69         /// Adds an <see cref="IUserValidator{TUser}"/> for the <seealso cref="UserType"/>.
70         /// </summary>
71         /// <typeparam name="TValidator">The user validator type.</typeparam>
72         /// <returns>The current <see cref="IdentityBuilder"/> instance.</returns>
73         public virtual IdentityBuilder AddUserValidator<TValidator>() where TValidator : class
74             => AddScoped(typeof(IUserValidator<>).MakeGenericType(UserType), typeof(TValidator));
75 
76         /// <summary>
77         /// Adds an <see cref="IUserClaimsPrincipalFactory{TUser}"/> for the <seealso cref="UserType"/>.
78         /// </summary>
79         /// <typeparam name="TFactory">The type of the claims principal factory.</typeparam>
80         /// <returns>The current <see cref="IdentityBuilder"/> instance.</returns>
81         public virtual IdentityBuilder AddClaimsPrincipalFactory<TFactory>() where TFactory : class
82             => AddScoped(typeof(IUserClaimsPrincipalFactory<>).MakeGenericType(UserType), typeof(TFactory));
83 
84         /// <summary>
85         /// Adds an <see cref="IdentityErrorDescriber"/>.
86         /// </summary>
87         /// <typeparam name="TDescriber">The type of the error describer.</typeparam>
88         /// <returns>The current <see cref="IdentityBuilder"/> instance.</returns>
89         public virtual IdentityBuilder AddErrorDescriber<TDescriber>() where TDescriber : IdentityErrorDescriber
90         {
91             Services.AddScoped<IdentityErrorDescriber, TDescriber>();
92             return this;
93         }
94 
95         /// <summary>
96         /// Adds an <see cref="IPasswordValidator{TUser}"/> for the <seealso cref="UserType"/>.
97         /// </summary>
98         /// <typeparam name="TValidator">The validator type used to validate passwords.</typeparam>
99         /// <returns>The current <see cref="IdentityBuilder"/> instance.</returns>
100         public virtual IdentityBuilder AddPasswordValidator<TValidator>() where TValidator : class
101             => AddScoped(typeof(IPasswordValidator<>).MakeGenericType(UserType), typeof(TValidator));
102 
103         /// <summary>
104         /// Adds an <see cref="IUserStore{TUser}"/> for the <seealso cref="UserType"/>.
105         /// </summary>
106         /// <typeparam name="TStore">The user store type.</typeparam>
107         /// <returns>The current <see cref="IdentityBuilder"/> instance.</returns>
108         public virtual IdentityBuilder AddUserStore<TStore>() where TStore : class
109             => AddScoped(typeof(IUserStore<>).MakeGenericType(UserType), typeof(TStore));
110 
111         /// <summary>
112         /// Adds a token provider.
113         /// </summary>
114         /// <typeparam name="TProvider">The type of the token provider to add.</typeparam>
115         /// <param name="providerName">The name of the provider to add.</param>
116         /// <returns>The current <see cref="IdentityBuilder"/> instance.</returns>
117         public virtual IdentityBuilder AddTokenProvider<TProvider>(string providerName) where TProvider : class
118             => AddTokenProvider(providerName, typeof(TProvider));
119 
120         /// <summary>
121         /// Adds a token provider for the <seealso cref="UserType"/>.
122         /// </summary>
123         /// <param name="providerName">The name of the provider to add.</param>
124         /// <param name="provider">The type of the <see cref="IUserTwoFactorTokenProvider{TUser}"/> to add.</param>
125         /// <returns>The current <see cref="IdentityBuilder"/> instance.</returns>
AddTokenProvider(string providerName, Type provider)126         public virtual IdentityBuilder AddTokenProvider(string providerName, Type provider)
127         {
128             if (!typeof(IUserTwoFactorTokenProvider<>).MakeGenericType(UserType).GetTypeInfo().IsAssignableFrom(provider.GetTypeInfo()))
129             {
130                 throw new InvalidOperationException(Resources.FormatInvalidManagerType(provider.Name, "IUserTwoFactorTokenProvider", UserType.Name));
131             }
132             Services.Configure<IdentityOptions>(options =>
133             {
134                 options.Tokens.ProviderMap[providerName] = new TokenProviderDescriptor(provider);
135             });
136             Services.AddTransient(provider);
137             return this;
138         }
139 
140         /// <summary>
141         /// Adds a <see cref="UserManager{TUser}"/> for the <seealso cref="UserType"/>.
142         /// </summary>
143         /// <typeparam name="TUserManager">The type of the user manager to add.</typeparam>
144         /// <returns>The current <see cref="IdentityBuilder"/> instance.</returns>
145         public virtual IdentityBuilder AddUserManager<TUserManager>() where TUserManager : class
146         {
147             var userManagerType = typeof(UserManager<>).MakeGenericType(UserType);
148             var customType = typeof(TUserManager);
149             if (!userManagerType.GetTypeInfo().IsAssignableFrom(customType.GetTypeInfo()))
150             {
151                 throw new InvalidOperationException(Resources.FormatInvalidManagerType(customType.Name, "UserManager", UserType.Name));
152             }
153             if (userManagerType != customType)
154             {
155                 Services.AddScoped(customType, services => services.GetRequiredService(userManagerType));
156             }
157             return AddScoped(userManagerType, customType);
158         }
159 
160         /// <summary>
161         /// Adds Role related services for TRole, including IRoleStore, IRoleValidator, and RoleManager.
162         /// </summary>
163         /// <typeparam name="TRole">The role type.</typeparam>
164         /// <returns>The current <see cref="IdentityBuilder"/> instance.</returns>
165         public virtual IdentityBuilder AddRoles<TRole>() where TRole : class
166         {
167             RoleType = typeof(TRole);
AddRoleValidator()168             AddRoleValidator<RoleValidator<TRole>>();
Services.TryAddScoped()169             Services.TryAddScoped<RoleManager<TRole>>();
MakeGenericType(UserType)170             Services.AddScoped(typeof(IUserClaimsPrincipalFactory<>).MakeGenericType(UserType), typeof(UserClaimsPrincipalFactory<,>).MakeGenericType(UserType, RoleType));
171             return this;
172         }
173 
174         /// <summary>
175         /// Adds an <see cref="IRoleValidator{TRole}"/> for the <seealso cref="RoleType"/>.
176         /// </summary>
177         /// <typeparam name="TRole">The role validator type.</typeparam>
178         /// <returns>The current <see cref="IdentityBuilder"/> instance.</returns>
179         public virtual IdentityBuilder AddRoleValidator<TRole>() where TRole : class
180         {
181             if (RoleType == null)
182             {
183                 throw new InvalidOperationException(Resources.NoRoleType);
184             }
185             return AddScoped(typeof(IRoleValidator<>).MakeGenericType(RoleType), typeof(TRole));
186         }
187 
188         /// <summary>
189         /// Adds an <see cref="ILookupProtector"/> and <see cref="ILookupProtectorKeyRing"/>.
190         /// </summary>
191         /// <typeparam name="TProtector">The personal data protector type.</typeparam>
192         /// <typeparam name="TKeyRing">The personal data protector key ring type.</typeparam>
193         /// <returns>The current <see cref="IdentityBuilder"/> instance.</returns>
194         public virtual IdentityBuilder AddPersonalDataProtection<TProtector, TKeyRing>()
195             where TProtector : class,ILookupProtector
196             where TKeyRing : class, ILookupProtectorKeyRing
197         {
Services.AddSingleton()198             Services.AddSingleton<IPersonalDataProtector, DefaultPersonalDataProtector>();
Services.AddSingleton()199             Services.AddSingleton<ILookupProtector, TProtector>();
Services.AddSingleton()200             Services.AddSingleton<ILookupProtectorKeyRing, TKeyRing>();
201             return this;
202         }
203 
204         /// <summary>
205         /// Adds a <see cref="IRoleStore{TRole}"/> for the <seealso cref="RoleType"/>.
206         /// </summary>
207         /// <typeparam name="TStore">The role store.</typeparam>
208         /// <returns>The current <see cref="IdentityBuilder"/> instance.</returns>
209         public virtual IdentityBuilder AddRoleStore<TStore>() where TStore : class
210         {
211             if (RoleType == null)
212             {
213                 throw new InvalidOperationException(Resources.NoRoleType);
214             }
215             return AddScoped(typeof(IRoleStore<>).MakeGenericType(RoleType), typeof(TStore));
216         }
217 
218         /// <summary>
219         /// Adds a <see cref="RoleManager{TRole}"/> for the <seealso cref="RoleType"/>.
220         /// </summary>
221         /// <typeparam name="TRoleManager">The type of the role manager to add.</typeparam>
222         /// <returns>The current <see cref="IdentityBuilder"/> instance.</returns>
223         public virtual IdentityBuilder AddRoleManager<TRoleManager>() where TRoleManager : class
224         {
225             if (RoleType == null)
226             {
227                 throw new InvalidOperationException(Resources.NoRoleType);
228             }
229             var managerType = typeof(RoleManager<>).MakeGenericType(RoleType);
230             var customType = typeof(TRoleManager);
231             if (!managerType.GetTypeInfo().IsAssignableFrom(customType.GetTypeInfo()))
232             {
233                 throw new InvalidOperationException(Resources.FormatInvalidManagerType(customType.Name, "RoleManager", RoleType.Name));
234             }
235             if (managerType != customType)
236             {
237                 Services.AddScoped(typeof(TRoleManager), services => services.GetRequiredService(managerType));
238             }
239             return AddScoped(managerType, typeof(TRoleManager));
240         }
241     }
242 }
243