StartupHelpers.cs 14.1 KB
Newer Older
1
using System;
2
using System.Collections.Generic;
3
using System.Globalization;
4
using System.Reflection;
janskoruba's avatar
janskoruba committed
5
using IdentityServer4.EntityFramework.Interfaces;
6
using Microsoft.AspNetCore.Authentication;
7
8
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
9
using Microsoft.AspNetCore.HttpOverrides;
10
using Microsoft.AspNetCore.Identity;
11
using Microsoft.AspNetCore.Identity.UI.Services;
12
13
14
15
16
17
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
18
using Microsoft.Extensions.DependencyInjection.Extensions;
19
using Microsoft.Extensions.Logging;
20
using Microsoft.Extensions.Options;
Michał Drzał's avatar
Michał Drzał committed
21
using SendGrid;
22
using Serilog;
23
using Skoruba.IdentityServer4.STS.Identity.Configuration;
24
using Skoruba.IdentityServer4.STS.Identity.Configuration.ApplicationParts;
25
using Skoruba.IdentityServer4.STS.Identity.Configuration.Constants;
26
using Skoruba.IdentityServer4.STS.Identity.Helpers.Localization;
27
using Skoruba.IdentityServer4.STS.Identity.Services;
28
using ILogger = Microsoft.Extensions.Logging.ILogger;
29
30
31
32
33

namespace Skoruba.IdentityServer4.STS.Identity.Helpers
{
    public static class StartupHelpers
    {
janskoruba's avatar
janskoruba committed
34
35
36
37
        /// <summary>
        /// Register services for MVC and localization including available languages
        /// </summary>
        /// <param name="services"></param>
38
39
40
        public static void AddMvcWithLocalization<TUser, TKey>(this IServiceCollection services)
            where TUser : IdentityUser<TKey>
            where TKey : IEquatable<TKey>
41
42
43
        {
            services.AddLocalization(opts => { opts.ResourcesPath = ConfigurationConsts.ResourcesPath; });

44
45
            services.TryAddTransient(typeof(IGenericControllerLocalizer<>), typeof(GenericControllerLocalizer<>));

46
47
48
49
            services.AddMvc(o =>
                {
                    o.Conventions.Add(new GenericControllerRouteConvention());
                })
50
51
52
53
                .SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
                .AddViewLocalization(
                    LanguageViewLocationExpanderFormat.Suffix,
                    opts => { opts.ResourcesPath = ConfigurationConsts.ResourcesPath; })
54
55
56
57
58
                .AddDataAnnotationsLocalization()
                .ConfigureApplicationPartManager(m =>
                {
                    m.FeatureProviders.Add(new GenericTypeControllerFeatureProvider<TUser, TKey>());
                });
59
60
61
62
63
64

            services.Configure<RequestLocalizationOptions>(
                opts =>
                {
                    var supportedCultures = new[]
                    {
saeed rahmani's avatar
saeed rahmani committed
65
                        new CultureInfo("fa"),
Alevtina Brown's avatar
Alevtina Brown committed
66
                        new CultureInfo("ru"),
67
                        new CultureInfo("en"),
janskoruba's avatar
janskoruba committed
68
69
                        new CultureInfo("zh")
                    };
70
71
72
73
74
75
76

                    opts.DefaultRequestCulture = new RequestCulture("en");
                    opts.SupportedCultures = supportedCultures;
                    opts.SupportedUICultures = supportedCultures;
                });
        }

janskoruba's avatar
janskoruba committed
77
78
79
80
        /// <summary>
        /// Using of Forwarded Headers and Referrer Policy
        /// </summary>
        /// <param name="app"></param>
81
82
83
84
85
86
        public static void UseSecurityHeaders(this IApplicationBuilder app)
        {
            app.UseForwardedHeaders(new ForwardedHeadersOptions()
            {
                ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
            });
janskoruba's avatar
janskoruba committed
87

Michał Drzał's avatar
Michał Drzał committed
88
            app.UseHsts(options => options.MaxAge(days: 365));
janskoruba's avatar
janskoruba committed
89
90
            app.UseXfo(options => options.SameOrigin());
            app.UseReferrerPolicy(options => options.NoReferrer());
91
92
        }

janskoruba's avatar
janskoruba committed
93
94
95
96
97
        /// <summary>
        /// Add email senders - configuration of sendgrid, smtp senders
        /// </summary>
        /// <param name="services"></param>
        /// <param name="configuration"></param>
98
        public static void AddEmailSenders(this IServiceCollection services, IConfiguration configuration)
Michał Drzał's avatar
Michał Drzał committed
99
100
101
102
103
104
105
106
107
108
109
        {
            var sendgridConnectionString = configuration.GetConnectionString(ConfigurationConsts.SendgridConnectionStringKey);
            var smtpConfiguration = configuration.GetSection(nameof(SmtpConfiguration)).Get<SmtpConfiguration>();
            var sendgridConfiguration = configuration.GetSection(nameof(SendgridConfiguration)).Get<SendgridConfiguration>();

            if (!string.IsNullOrWhiteSpace(sendgridConnectionString))
            {
                services.AddSingleton<ISendGridClient>(_ => new SendGridClient(sendgridConnectionString));
                services.AddSingleton(sendgridConfiguration);
                services.AddTransient<IEmailSender, SendgridEmailSender>();
            }
110
            else if (smtpConfiguration != null && !string.IsNullOrWhiteSpace(smtpConfiguration.Host))
Michał Drzał's avatar
Michał Drzał committed
111
112
113
            {
                services.AddSingleton(smtpConfiguration);
                services.AddTransient<IEmailSender, SmtpEmailSender>();
114
115
            }
            else
Michał Drzał's avatar
Michał Drzał committed
116
117
118
119
120
            {
                services.AddSingleton<IEmailSender, EmailSender>();
            }
        }

janskoruba's avatar
janskoruba committed
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
        /// <summary>
        /// Add services for authentication, including Identity model, IdentityServer4 and external providers
        /// </summary>
        /// <typeparam name="TIdentityDbContext">DbContext for Identity</typeparam>
        /// <typeparam name="TUserIdentity">User Identity class</typeparam>
        /// <typeparam name="TUserIdentityRole">User Identity Role class</typeparam>
        /// <typeparam name="TConfigurationDbContext"></typeparam>
        /// <typeparam name="TPersistedGrantDbContext"></typeparam>
        /// <param name="services"></param>
        /// <param name="hostingEnvironment"></param>
        /// <param name="configuration"></param>
        /// <param name="logger"></param>
        public static void AddAuthenticationServices<TConfigurationDbContext, TPersistedGrantDbContext, TIdentityDbContext, TUserIdentity, TUserIdentityRole>(this IServiceCollection services, IHostingEnvironment hostingEnvironment, IConfiguration configuration, ILogger logger)
            where TPersistedGrantDbContext : DbContext, IPersistedGrantDbContext
            where TConfigurationDbContext : DbContext, IConfigurationDbContext
            where TIdentityDbContext : DbContext
137
138
            where TUserIdentity : class
            where TUserIdentityRole : class
139
        {
140
            var loginConfiguration = GetLoginConfiguration(configuration);
Michał Drzał's avatar
Michał Drzał committed
141
142
143
144
145

            services
                .AddSingleton(loginConfiguration)
                .AddScoped<UserResolver<TUserIdentity>>()
                .AddIdentity<TUserIdentity, TUserIdentityRole>(options =>
146
147
148
                {
                    options.User.RequireUniqueEmail = true;
                })
janskoruba's avatar
janskoruba committed
149
                .AddEntityFrameworkStores<TIdentityDbContext>()
150
                .AddDefaultTokenProviders();
151

152
153
154
155
156
157
            services.Configure<IISOptions>(iis =>
            {
                iis.AuthenticationDisplayName = "Windows";
                iis.AutomaticAuthentication = false;
            });

158
159
160
161
            var authenticationBuilder = services.AddAuthentication();

            AddExternalProviders(authenticationBuilder, configuration);

janskoruba's avatar
janskoruba committed
162
            AddIdentityServer<TConfigurationDbContext, TPersistedGrantDbContext, TUserIdentity>(services, configuration, logger);
163
164
        }

165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
        /// <summary>
        /// Get configuration for login
        /// </summary>
        /// <param name="configuration"></param>
        /// <returns></returns>
        private static LoginConfiguration GetLoginConfiguration(IConfiguration configuration)
        {
            var loginConfiguration = configuration.GetSection(nameof(LoginConfiguration)).Get<LoginConfiguration>();
            
            // Cannot load configuration - use default configuration values
            if (loginConfiguration == null)
            {
                return new LoginConfiguration();
            }

            return loginConfiguration;
        }

janskoruba's avatar
janskoruba committed
183
184
185
186
187
188
189
190
191
192
193
194
        /// <summary>
        /// Add configuration for IdentityServer4
        /// </summary>
        /// <typeparam name="TUserIdentity"></typeparam>
        /// <param name="services"></param>
        /// <param name="configuration"></param>
        /// <param name="logger"></param>
        private static void AddIdentityServer<TConfigurationDbContext, TPersistedGrantDbContext, TUserIdentity>(IServiceCollection services,
            IConfiguration configuration, ILogger logger)
            where TUserIdentity : class
            where TPersistedGrantDbContext : DbContext, IPersistedGrantDbContext
            where TConfigurationDbContext : DbContext, IConfigurationDbContext
195
        {
196
            var builder = services.AddIdentityServer(options =>
197
198
199
200
201
202
                {
                    options.Events.RaiseErrorEvents = true;
                    options.Events.RaiseInformationEvents = true;
                    options.Events.RaiseFailureEvents = true;
                    options.Events.RaiseSuccessEvents = true;
                })
203
                .AddAspNetIdentity<TUserIdentity>()
janskoruba's avatar
janskoruba committed
204
                .AddIdentityServerStoresWithDbContexts<TConfigurationDbContext, TPersistedGrantDbContext>(configuration);
205

206
207
            builder.AddCustomSigningCredential(configuration, logger);
            builder.AddCustomValidationKey(configuration, logger);
208
209
        }

janskoruba's avatar
janskoruba committed
210
211
212
213
214
        /// <summary>
        /// Add external providers
        /// </summary>
        /// <param name="authenticationBuilder"></param>
        /// <param name="configuration"></param>
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
        private static void AddExternalProviders(AuthenticationBuilder authenticationBuilder,
            IConfiguration configuration)
        {
            var externalProviderConfiguration = configuration.GetSection(nameof(ExternalProvidersConfiguration)).Get<ExternalProvidersConfiguration>();

            if (externalProviderConfiguration.UseGitHubProvider)
            {
                authenticationBuilder.AddGitHub(options =>
                {
                    options.ClientId = externalProviderConfiguration.GitHubClientId;
                    options.ClientSecret = externalProviderConfiguration.GitHubClientSecret;
                    options.Scope.Add("user:email");
                });
            }
        }

231
232
233
234
235
236
237
238
239
240
241
242
243
        /// <summary>
        /// Add DbContext for Identity
        /// </summary>
        /// <typeparam name="TContext"></typeparam>
        /// <param name="services"></param>
        /// <param name="configuration"></param>
        public static void AddIdentityDbContext<TContext>(this IServiceCollection services, IConfiguration configuration)
            where TContext : DbContext
        {
            var connectionString = configuration.GetConnectionString(ConfigurationConsts.IdentityDbConnectionStringKey);
            services.AddDbContext<TContext>(options => options.UseSqlServer(connectionString));
        }

janskoruba's avatar
janskoruba committed
244
245
246
247
248
249
        /// <summary>
        /// Add shared DbContext for Identity and IdentityServer4 stores
        /// </summary>
        /// <typeparam name="TContext"></typeparam>
        /// <param name="services"></param>
        /// <param name="configuration"></param>
250
        public static void AddDbContexts<TContext>(this IServiceCollection services, IConfiguration configuration)
janskoruba's avatar
janskoruba committed
251
            where TContext : DbContext
252
253
254
255
256
        {
            var connectionString = configuration.GetConnectionString(ConfigurationConsts.AdminConnectionStringKey);
            services.AddDbContext<TContext>(options => options.UseSqlServer(connectionString));
        }

janskoruba's avatar
janskoruba committed
257
258
259
260
261
262
263
264
265
266
267
        /// <summary>
        /// Register DbContexts and configure stores for IdentityServer4
        /// </summary>
        /// <typeparam name="TConfigurationDbContext"></typeparam>
        /// <typeparam name="TPersistedGrantDbContext"></typeparam>        
        /// <param name="builder"></param>
        /// <param name="configuration"></param>
        public static IIdentityServerBuilder AddIdentityServerStoresWithDbContexts<TConfigurationDbContext, TPersistedGrantDbContext>(this IIdentityServerBuilder builder, IConfiguration configuration)
            where TPersistedGrantDbContext : DbContext, IPersistedGrantDbContext
            where TConfigurationDbContext : DbContext, IConfigurationDbContext
        {
268
269
            var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;

janskoruba's avatar
janskoruba committed
270
271
272
273
274
            // Config DB from existing connection
            builder.AddConfigurationStore<TConfigurationDbContext>(options =>
            {
                options.ConfigureDbContext = b =>
                    b.UseSqlServer(configuration.GetConnectionString(ConfigurationConsts.ConfigurationDbConnectionStringKey),
275
                        sql => sql.MigrationsAssembly(migrationsAssembly));
janskoruba's avatar
janskoruba committed
276
277
278
279
280
281
282
283
284
285
286
            });

            // Operational DB from existing connection
            builder.AddOperationalStore<TPersistedGrantDbContext>(options =>
            {
                options.EnableTokenCleanup = true;
#if DEBUG
                options.TokenCleanupInterval = 15;
#endif
                options.ConfigureDbContext = b =>
                    b.UseSqlServer(configuration.GetConnectionString(ConfigurationConsts.PersistedGrantDbConnectionStringKey),
287
                        sql => sql.MigrationsAssembly(migrationsAssembly));
janskoruba's avatar
janskoruba committed
288
289
290
291
292
293
294
295
296
            });

            return builder;
        }

        /// <summary>
        /// Register middleware for localization
        /// </summary>
        /// <param name="app"></param>
297
298
299
300
301
        public static void UseMvcLocalizationServices(this IApplicationBuilder app)
        {
            var options = app.ApplicationServices.GetService<IOptions<RequestLocalizationOptions>>();
            app.UseRequestLocalization(options.Value);
        }
302

janskoruba's avatar
janskoruba committed
303
304
305
306
307
308
        /// <summary>
        /// Add configuration for logging
        /// </summary>
        /// <param name="app"></param>
        /// <param name="loggerFactory"></param>
        /// <param name="configuration"></param>
janskoruba's avatar
janskoruba committed
309
310
311
312
313
314
315
        public static void AddLogging(this IApplicationBuilder app, ILoggerFactory loggerFactory, IConfiguration configuration)
        {
            Log.Logger = new LoggerConfiguration()
                .ReadFrom.Configuration(configuration)
                .CreateLogger();
        }
    }
316
}