StartupHelpers.cs 19.2 KB
Newer Older
1
2
using System;
using System.Globalization;
3
using System.Reflection;
janskoruba's avatar
janskoruba committed
4
using IdentityServer4.EntityFramework.Interfaces;
5
using Microsoft.AspNetCore.Authentication;
6
7
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
8
using Microsoft.AspNetCore.HttpOverrides;
9
using Microsoft.AspNetCore.Identity;
10
using Microsoft.AspNetCore.Identity.UI.Services;
11
12
13
14
15
16
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
17
using Microsoft.Extensions.DependencyInjection.Extensions;
18
using Microsoft.Extensions.Logging;
19
using Microsoft.Extensions.Options;
Michał Drzał's avatar
Michał Drzał committed
20
using SendGrid;
21
using Serilog;
22
using Skoruba.IdentityServer4.STS.Identity.Configuration;
23
using Skoruba.IdentityServer4.STS.Identity.Configuration.ApplicationParts;
24
using Skoruba.IdentityServer4.STS.Identity.Configuration.Constants;
25
using Skoruba.IdentityServer4.STS.Identity.Configuration.Intefaces;
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[]
                    {
65
                        new CultureInfo("en"),
saeed rahmani's avatar
saeed rahmani committed
66
                        new CultureInfo("fa"),
Alevtina Brown's avatar
Alevtina Brown committed
67
                        new CultureInfo("ru"),
68
                        new CultureInfo("sv"),
janskoruba's avatar
janskoruba committed
69
70
                        new CultureInfo("zh")
                    };
71
72
73
74
75
76
77

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

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

Michał Drzał's avatar
Michał Drzał committed
89
            app.UseHsts(options => options.MaxAge(days: 365));
janskoruba's avatar
janskoruba committed
90
            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);
141
            var registrationConfiguration = GetRegistrationConfiguration(configuration);
Michał Drzał's avatar
Michał Drzał committed
142
143

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

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

160
161
162
163
            var authenticationBuilder = services.AddAuthentication();

            AddExternalProviders(authenticationBuilder, configuration);

164
            AddIdentityServer<TConfigurationDbContext, TPersistedGrantDbContext, TUserIdentity>(services, configuration, logger, hostingEnvironment);
165
166
        }

167
168
169
170
171
172
173
174
        /// <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>();
175

176
177
178
179
180
181
182
183
184
            // Cannot load configuration - use default configuration values
            if (loginConfiguration == null)
            {
                return new LoginConfiguration();
            }

            return loginConfiguration;
        }

185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
        /// <summary>
        /// Get configuration for registration
        /// </summary>
        /// <param name="configuration"></param>
        /// <returns></returns>
        private static RegisterConfiguration GetRegistrationConfiguration(IConfiguration configuration)
        {
            var registerConfiguration = configuration.GetSection(nameof(RegisterConfiguration)).Get<RegisterConfiguration>();

            // Cannot load configuration - use default configuration values
            if (registerConfiguration == null)
            {
                return new RegisterConfiguration();
            }

            return registerConfiguration;
        }

203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
        /// <summary>
        /// Configuration root configuration
        /// </summary>
        /// <param name="services"></param>
        /// <param name="configuration"></param>
        /// <returns></returns>
        public static IServiceCollection ConfigureRootConfiguration(this IServiceCollection services, IConfiguration configuration)
        {
            services.AddOptions();

            services.Configure<AdminConfiguration>(configuration.GetSection(ConfigurationConsts.AdminConfigurationKey));
            services.Configure<RegisterConfiguration>(configuration.GetSection(ConfigurationConsts.RegisterConfiguration));

            services.TryAddSingleton<IRootConfiguration, RootConfiguration>();

            return services;
        }

janskoruba's avatar
janskoruba committed
221
222
223
224
        /// <summary>
        /// Add configuration for IdentityServer4
        /// </summary>
        /// <typeparam name="TUserIdentity"></typeparam>
225
226
        /// <typeparam name="TConfigurationDbContext"></typeparam>
        /// <typeparam name="TPersistedGrantDbContext"></typeparam>
janskoruba's avatar
janskoruba committed
227
228
229
        /// <param name="services"></param>
        /// <param name="configuration"></param>
        /// <param name="logger"></param>
230
231
232
233
        /// <param name="hostingEnvironment"></param>
        private static void AddIdentityServer<TConfigurationDbContext, TPersistedGrantDbContext, TUserIdentity>(
            IServiceCollection services,
            IConfiguration configuration, ILogger logger, IHostingEnvironment hostingEnvironment)
janskoruba's avatar
janskoruba committed
234
235
236
            where TUserIdentity : class
            where TPersistedGrantDbContext : DbContext, IPersistedGrantDbContext
            where TConfigurationDbContext : DbContext, IConfigurationDbContext
237
        {
238
            var builder = services.AddIdentityServer(options =>
239
240
241
242
243
244
                {
                    options.Events.RaiseErrorEvents = true;
                    options.Events.RaiseInformationEvents = true;
                    options.Events.RaiseFailureEvents = true;
                    options.Events.RaiseSuccessEvents = true;
                })
245
                .AddAspNetIdentity<TUserIdentity>()
246
                .AddIdentityServerStoresWithDbContexts<TConfigurationDbContext, TPersistedGrantDbContext>(configuration, hostingEnvironment);
247

248
249
            builder.AddCustomSigningCredential(configuration, logger);
            builder.AddCustomValidationKey(configuration, logger);
250
251
        }

janskoruba's avatar
janskoruba committed
252
253
254
255
256
        /// <summary>
        /// Add external providers
        /// </summary>
        /// <param name="authenticationBuilder"></param>
        /// <param name="configuration"></param>
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
        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");
                });
            }
        }

273
274
275
276
277
278
        /// <summary>
        /// Add DbContext for Identity
        /// </summary>
        /// <typeparam name="TContext"></typeparam>
        /// <param name="services"></param>
        /// <param name="configuration"></param>
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
        /// <param name="hostingEnvironment"></param>
        public static void AddIdentityDbContext<TContext>(this IServiceCollection services,
            IConfiguration configuration, IHostingEnvironment hostingEnvironment)
            where TContext : DbContext
        {
            if (hostingEnvironment.IsStaging())
            {
                RegisterIdentityDbContextStaging<TContext>(services);
            }
            else
            {
                RegisterIdentityDbContext<TContext>(services, configuration);
            }
        }

        private static void RegisterIdentityDbContextStaging<TContext>(IServiceCollection services) where TContext : DbContext
        {
            var identityDatabaseName = Guid.NewGuid().ToString();

            services.AddDbContext<TContext>(optionsBuilder => optionsBuilder.UseInMemoryDatabase(identityDatabaseName));
        }

        private static void RegisterIdentityDbContext<TContext>(IServiceCollection services, IConfiguration configuration)
302
303
304
305
306
307
            where TContext : DbContext
        {
            var connectionString = configuration.GetConnectionString(ConfigurationConsts.IdentityDbConnectionStringKey);
            services.AddDbContext<TContext>(options => options.UseSqlServer(connectionString));
        }

janskoruba's avatar
janskoruba committed
308
309
310
311
312
313
        /// <summary>
        /// Add shared DbContext for Identity and IdentityServer4 stores
        /// </summary>
        /// <typeparam name="TContext"></typeparam>
        /// <param name="services"></param>
        /// <param name="configuration"></param>
314
        public static void AddDbContexts<TContext>(this IServiceCollection services, IConfiguration configuration)
janskoruba's avatar
janskoruba committed
315
            where TContext : DbContext
316
317
318
319
320
        {
            var connectionString = configuration.GetConnectionString(ConfigurationConsts.AdminConnectionStringKey);
            services.AddDbContext<TContext>(options => options.UseSqlServer(connectionString));
        }

janskoruba's avatar
janskoruba committed
321
322
323
324
        /// <summary>
        /// Register DbContexts and configure stores for IdentityServer4
        /// </summary>
        /// <typeparam name="TConfigurationDbContext"></typeparam>
325
        /// <typeparam name="TPersistedGrantDbContext"></typeparam>
janskoruba's avatar
janskoruba committed
326
327
        /// <param name="builder"></param>
        /// <param name="configuration"></param>
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
        /// <param name="hostingEnvironment"></param>
        public static IIdentityServerBuilder AddIdentityServerStoresWithDbContexts<TConfigurationDbContext,
            TPersistedGrantDbContext>(this IIdentityServerBuilder builder, IConfiguration configuration,
            IHostingEnvironment hostingEnvironment)
            where TPersistedGrantDbContext : DbContext, IPersistedGrantDbContext
            where TConfigurationDbContext : DbContext, IConfigurationDbContext
        {
            if (hostingEnvironment.IsStaging())
            {
                return RegisterIdentityServerStoresWithDbContextsStaging<TConfigurationDbContext, TPersistedGrantDbContext>(builder, configuration);
            }
            else
            {
                return RegisterIdentityServerStoresWithDbContexts<TConfigurationDbContext, TPersistedGrantDbContext>(builder, configuration);
            }
        }

        private static IIdentityServerBuilder
            RegisterIdentityServerStoresWithDbContextsStaging<TConfigurationDbContext, TPersistedGrantDbContext>(
                IIdentityServerBuilder builder, IConfiguration configuration)
            where TPersistedGrantDbContext : DbContext, IPersistedGrantDbContext
            where TConfigurationDbContext : DbContext, IConfigurationDbContext
        {
            var configurationDatabaseName = Guid.NewGuid().ToString();
            var operationalDatabaseName = Guid.NewGuid().ToString();

            builder.AddConfigurationStore<TConfigurationDbContext>(options =>
            {
                options.ConfigureDbContext = b => b.UseInMemoryDatabase(configurationDatabaseName);
            });

            builder.AddOperationalStore<TPersistedGrantDbContext>(options =>
            {
                options.ConfigureDbContext = b => b.UseInMemoryDatabase(operationalDatabaseName);
            });

            return builder;
        }

        private static IIdentityServerBuilder
            RegisterIdentityServerStoresWithDbContexts<TConfigurationDbContext, TPersistedGrantDbContext>(
                IIdentityServerBuilder builder, IConfiguration configuration)
janskoruba's avatar
janskoruba committed
370
371
372
            where TPersistedGrantDbContext : DbContext, IPersistedGrantDbContext
            where TConfigurationDbContext : DbContext, IConfigurationDbContext
        {
373
374
            var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;

janskoruba's avatar
janskoruba committed
375
376
377
378
            // Config DB from existing connection
            builder.AddConfigurationStore<TConfigurationDbContext>(options =>
            {
                options.ConfigureDbContext = b =>
379
380
                    b.UseSqlServer(
                        configuration.GetConnectionString(ConfigurationConsts.ConfigurationDbConnectionStringKey),
381
                        sql => sql.MigrationsAssembly(migrationsAssembly));
janskoruba's avatar
janskoruba committed
382
383
384
385
386
387
388
389
390
391
            });

            // Operational DB from existing connection
            builder.AddOperationalStore<TPersistedGrantDbContext>(options =>
            {
                options.EnableTokenCleanup = true;
#if DEBUG
                options.TokenCleanupInterval = 15;
#endif
                options.ConfigureDbContext = b =>
392
393
                    b.UseSqlServer(
                        configuration.GetConnectionString(ConfigurationConsts.PersistedGrantDbConnectionStringKey),
394
                        sql => sql.MigrationsAssembly(migrationsAssembly));
janskoruba's avatar
janskoruba committed
395
396
397
398
399
400
401
402
403
            });

            return builder;
        }

        /// <summary>
        /// Register middleware for localization
        /// </summary>
        /// <param name="app"></param>
404
405
406
407
408
        public static void UseMvcLocalizationServices(this IApplicationBuilder app)
        {
            var options = app.ApplicationServices.GetService<IOptions<RequestLocalizationOptions>>();
            app.UseRequestLocalization(options.Value);
        }
409

janskoruba's avatar
janskoruba committed
410
411
412
413
414
415
        /// <summary>
        /// Add configuration for logging
        /// </summary>
        /// <param name="app"></param>
        /// <param name="loggerFactory"></param>
        /// <param name="configuration"></param>
janskoruba's avatar
janskoruba committed
416
417
418
419
420
421
        public static void AddLogging(this IApplicationBuilder app, ILoggerFactory loggerFactory, IConfiguration configuration)
        {
            Log.Logger = new LoggerConfiguration()
                .ReadFrom.Configuration(configuration)
                .CreateLogger();
        }
422
423
424
425
426
427
428
429
430
431
432
433
434

        /// <summary>
        /// Add authorization policies
        /// </summary>
        /// <param name="services"></param>
        public static void AddAuthorizationPolicies(this IServiceCollection services)
        {
            services.AddAuthorization(options =>
            {
                options.AddPolicy(AuthorizationConsts.AdministrationPolicy,
                    policy => policy.RequireRole(AuthorizationConsts.AdministrationRole));
            });
        }
janskoruba's avatar
janskoruba committed
435
    }
436
}