// This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddApplicationInsightsTelemetry(Configuration); services.AddSingleton(Configuration); _logger.LogCritical("Logging from ConfigureServices."); services.Configure <CookiePolicyOptions>(options => { // This lambda determines whether user consent for non-essential cookies is needed for a given request. options.CheckConsentNeeded = context => true; options.MinimumSameSitePolicy = SameSiteMode.None; }); services.AddSingleton <IConfiguration>(Configuration); services.AddTransient <IUkrlpWcfClientFactory, UkrlpWcfClientFactory>(); services.Configure <CourseForComponentSettings>(Configuration.GetSection("AppUISettings:CourseForComponentSettings")); services.Configure <EntryRequirementsComponentSettings>(Configuration.GetSection("AppUISettings:EntryRequirementsComponentSettings")); services.Configure <WhatWillLearnComponentSettings>(Configuration.GetSection("AppUISettings:WhatWillLearnComponentSettings")); services.Configure <HowYouWillLearnComponentSettings>(Configuration.GetSection("AppUISettings:HowYouWillLearnComponentSettings")); services.Configure <WhatYouNeedComponentSettings>(Configuration.GetSection("AppUISettings:WhatYouNeedComponentSettings")); services.Configure <HowAssessedComponentSettings>(Configuration.GetSection("AppUISettings:HowAssessedComponentSettings")); services.Configure <WhereNextComponentSettings>(Configuration.GetSection("AppUISettings:WhereNextComponentSettings")); services.AddOptions(); services.Configure <ProviderServiceSettings>(Configuration.GetSection(nameof(ProviderServiceSettings))); services.AddScoped <IProviderService, ProviderService>(); services.AddTransient((provider) => new HttpClient()); services.Configure <ApprenticeshipSettings>(Configuration.GetSection(nameof(ApprenticeshipSettings))); services.Configure <LarsSearchSettings>(Configuration.GetSection(nameof(LarsSearchSettings))); services.AddScoped <IPaginationHelper, PaginationHelper>(); services.AddScoped <IVenueSearchHelper, VenueSearchHelper>(); services.Configure <VenueServiceSettings>(Configuration.GetSection(nameof(VenueServiceSettings))); services.AddScoped <IVenueService, VenueService>(); services.Configure <CourseServiceSettings>(Configuration.GetSection(nameof(CourseServiceSettings))); services.Configure <FindACourseServiceSettings>(Configuration.GetSection(nameof(FindACourseServiceSettings))); services.AddScoped <ICourseService, CourseService>(); services.Configure <CourseTextServiceSettings>(Configuration.GetSection(nameof(CourseTextServiceSettings))); services.AddScoped <ICourseTextService, CourseTextService>(); services.AddScoped <IUserHelper, UserHelper>(); services.AddScoped <ICSVHelper, CSVHelper>(); services.AddScoped <ICourseProvisionHelper, CourseProvisionHelper>(); services.Configure <ApprenticeshipServiceSettings>(Configuration.GetSection(nameof(ApprenticeshipServiceSettings))); services.AddScoped <IApprenticeshipService, ApprenticeshipService>(); services.AddScoped <IBulkUploadService, BulkUploadService>(); services.AddScoped <IApprenticeshipBulkUploadService, ApprenticeshipBulkUploadService>(); services.Configure <BlobStorageSettings>(Configuration.GetSection(nameof(BlobStorageSettings))); services.AddScoped <IBlobStorageService, BlobStorageService>(); services.Configure <EnvironmentSettings>(Configuration.GetSection(nameof(EnvironmentSettings))); services.AddScoped <IEnvironmentHelper, EnvironmentHelper>(); services.AddScoped <IApprenticeshipProvisionHelper, ApprenticeshipProvisionHelper>(); services.AddSingleton <IBinaryStorageProvider, BlobStorageBinaryStorageProvider>(); services.Configure <BlobStorageBinaryStorageProviderSettings>(Configuration.GetSection(nameof(BlobStorageBinaryStorageProviderSettings))); services.AddSingleton <QueueBackgroundWorkScheduler>(); services.AddHostedService(sp => sp.GetRequiredService <QueueBackgroundWorkScheduler>()); services.AddSingleton <IBackgroundWorkScheduler>(sp => sp.GetRequiredService <QueueBackgroundWorkScheduler>()); services.AddCourseDirectory(_env, Configuration); var mvcBuilder = services .AddMvc(options => { options.Filters.Add(new RedirectOnMissingUKPRNActionFilter()); }) .SetCompatibilityVersion(CompatibilityVersion.Version_3_0) .AddSessionStateTempDataProvider(); #if DEBUG mvcBuilder.AddRazorRuntimeCompilation(options => { // Fix auto reload on IIS when views in V2 project are changed // (see https://github.com/aspnet/Razor/issues/2426#issuecomment-420750249) var v2ProjectPath = Path.Combine(Directory.GetParent(Environment.CurrentDirectory).FullName, "Dfc.CourseDirectory.WebV2"); options.FileProviders.Add(new PhysicalFileProvider(v2ProjectPath)); }); #endif services.AddAuthorization(options => { options.AddPolicy("Admin", policy => policy.RequireRole("Developer")); options.AddPolicy("ElevatedUserRole", policy => policy.RequireRole("Developer", "Helpdesk")); options.AddPolicy("SuperUser", policy => policy.RequireRole("Developer", "Helpdesk", "Provider Superuser")); options.AddPolicy("Helpdesk", policy => policy.RequireRole("Helpdesk")); options.AddPolicy("ProviderSuperUser", policy => policy.RequireRole("Provider Superuser")); options.AddPolicy("Provider", policy => policy.RequireRole("Provider User", "Provider Superuser")); options.AddPolicy( "Apprenticeship", policy => policy.AddRequirements(new ProviderTypeRequirement(Core.Models.ProviderType.Apprenticeships))); options.AddPolicy( "Fe", policy => policy.AddRequirements(new ProviderTypeRequirement(Core.Models.ProviderType.FE))); }); if (_env.IsProduction()) { services.AddStackExchangeRedisCache(options => { options.Configuration = Configuration.GetConnectionString("Redis"); }); } else { #if DEBUG // Adds FileDistributedCache for persistent cached state, including session state, during development ONLY. services.AddSingleton <IDistributedCache>(_ => new FileDistributedCache(() => DateTimeOffset.UtcNow)); #else services.AddDistributedMemoryCache(); #endif } services.Configure <FormOptions>(x => x.ValueCountLimit = 2048); services.AddResponseCaching(); services.AddSession(options => { options.IdleTimeout = TimeSpan.FromMinutes(40); options.Cookie.HttpOnly = true; options.Cookie.SecurePolicy = CookieSecurePolicy.Always; }); var dataProtectionBuilder = services.AddDataProtection(); if (_env.IsProduction()) { dataProtectionBuilder.PersistKeysToAzureBlobStorage(GetDataProtectionBlobToken()); } //TODO //services.Configure<GoogleAnalyticsOptions>(options => Configuration.GetSection("GoogleAnalytics").Bind(options)); // Register the background worker helper services.AddHostedService <QueuedHostedService>(); services.AddSingleton <IBackgroundTaskQueue, BackgroundTaskQueue>(); services.Configure <FormOptions>(x => x.ValueCountLimit = 10000); var dfeSettings = new DfeSignInSettings(); Configuration.GetSection("DFESignInSettings").Bind(dfeSettings); services.AddDfeSignIn(dfeSettings); Uri GetDataProtectionBlobToken() { var cloudStorageAccount = new CloudStorageAccount( new Microsoft.Azure.Storage.Auth.StorageCredentials( Configuration["BlobStorageSettings:AccountName"], Configuration["BlobStorageSettings:AccountKey"]), useHttps: true); var blobClient = cloudStorageAccount.CreateCloudBlobClient(); var container = blobClient.GetContainerReference(Configuration["DataProtection:ContainerName"]); var blob = container.GetBlockBlobReference(Configuration["DataProtection:BlobName"]); var sharedAccessPolicy = new SharedAccessBlobPolicy() { SharedAccessExpiryTime = DateTime.UtcNow.AddYears(1), Permissions = SharedAccessBlobPermissions.Read | SharedAccessBlobPermissions.Write | SharedAccessBlobPermissions.Create }; var sasToken = blob.GetSharedAccessSignature(sharedAccessPolicy); return(new Uri(blob.Uri + sasToken)); } }
public static void AddDfeSignIn(this IServiceCollection services, DfeSignInSettings settings) { var overallSessionTimeout = TimeSpan.FromMinutes(90); services.AddSingleton <DfeUserInfoHelper>(); // N.B. We can't use a typed client here because the legacy bits override the HttpClient registration :-/ services.AddHttpClient("DfeSignIn", client => { client.BaseAddress = new Uri(settings.ApiBaseUri); client.DefaultRequestHeaders.Add("Authorization", $"Bearer {DfeUserInfoHelper.CreateApiToken(settings)}"); }); services.TryAddSingleton(settings); services .AddAuthentication(options => { options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; }) .AddCookie(options => { options.ExpireTimeSpan = TimeSpan.FromMinutes(40); options.SlidingExpiration = true; options.LogoutPath = "/auth/logout"; options.Events.OnRedirectToAccessDenied = ctx => { ctx.Response.StatusCode = 403; return(Task.CompletedTask); }; }) .AddOpenIdConnect(options => { options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.MetadataAddress = settings.MetadataAddress; options.RequireHttpsMetadata = false; options.ClientId = settings.ClientId; options.ClientSecret = settings.ClientSecret; options.ResponseType = OpenIdConnectResponseType.Code; options.GetClaimsFromUserInfoEndpoint = true; options.Scope.Clear(); options.Scope.Add("openid"); options.Scope.Add("email"); options.Scope.Add("profile"); options.Scope.Add("organisationid"); options.Scope.Add("offline_access"); // When we expire the session, ensure user is prompted to sign in again at DfE Sign In options.MaxAge = overallSessionTimeout; options.SaveTokens = true; options.CallbackPath = settings.CallbackPath; options.SignedOutCallbackPath = settings.SignedOutCallbackPath; options.SecurityTokenValidator = new JwtSecurityTokenHandler() { InboundClaimTypeMap = new Dictionary <string, string>(), TokenLifetimeInMinutes = 90, SetDefaultTimesOnTokenCreation = true }; options.ProtocolValidator = new OpenIdConnectProtocolValidator { RequireSub = true, RequireStateValidation = false, NonceLifetime = TimeSpan.FromMinutes(60) }; options.DisableTelemetry = true; options.Events = new OpenIdConnectEvents { // Sometimes, problems in the OIDC provider (such as session timeouts) // Redirect the user to the /auth/cb endpoint. ASP.NET Core middleware interprets this by default // as a successful authentication and throws in surprise when it doesn't find an authorization code. // This override ensures that these cases redirect to the root. OnMessageReceived = context => { var isSpuriousAuthCbRequest = context.Request.Path == options.CallbackPath && context.Request.Method == "GET" && !context.Request.Query.ContainsKey("code"); if (isSpuriousAuthCbRequest) { context.HandleResponse(); context.Response.Redirect("/"); } return(Task.CompletedTask); }, // Sometimes the auth flow fails. The most commonly observed causes for this are // Cookie correlation failures, caused by obscure load balancing stuff. // In these cases, rather than send user to a 500 page, prompt them to re-authenticate. // This is derived from the recommended approach: https://github.com/aspnet/Security/issues/1165 OnRemoteFailure = ctx => { ctx.HandleResponse(); return(Task.FromException(ctx.Failure)); }, OnTokenValidated = async ctx => { ctx.Properties.IsPersistent = true; ctx.Properties.ExpiresUtc = DateTime.UtcNow.Add(overallSessionTimeout); var userInfo = ClaimsPrincipalCurrentUserProvider.MapUserInfoFromPrincipal(ctx.Principal); var signInContext = new SignInContext(ctx.Principal) { UserInfo = userInfo }; var signInActions = ctx.HttpContext.RequestServices.GetServices <ISignInAction>(); foreach (var a in signInActions) { await a.OnUserSignedIn(signInContext); } ctx.Principal = ClaimsPrincipalCurrentUserProvider.GetPrincipalFromSignInContext(signInContext); if (signInContext.Provider != null) { // For driving legacy views ctx.HttpContext.Session.SetInt32("UKPRN", signInContext.Provider.Ukprn); } } }; }); }