public ManageApi(ManageCoursesApiClient apiClient) { _apiClient = apiClient; _jsonSerializerSettings = new JsonSerializerSettings { MissingMemberHandling = MissingMemberHandling.Ignore, NullValueHandling = NullValueHandling.Ignore }; }
public ManageApi(ILogger logger, string apiLocation, string bearerToken) { _logger = logger; var patientHttpClient = new System.Net.Http.HttpClient() { Timeout = TimeSpan.FromMinutes(240) }; _client = new ManageCoursesApiClient(new ApiConf(bearerToken), patientHttpClient) { BaseUrl = apiLocation }; }
// This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddLogging(loggingBuilder => loggingBuilder.AddSerilog(dispose: true)); var sharedAssembly = typeof(CourseDetailsViewComponent).GetTypeInfo().Assembly; var cookieSecurePolicy = _env.IsDevelopment() ? CookieSecurePolicy.SameAsRequest : CookieSecurePolicy.Always; services.AddMvc(options => options.Filters.Add(typeof(McExceptionFilter)) ) .ConfigureApplicationPartManager(apm => { var apiClientAssembly = typeof(GovUk.Education.ManageCourses.ApiClient.ManageCoursesApiClient).GetTypeInfo().Assembly.GetName().Name; var api = typeof(GovUk.Education.ManageCourses.Api.Startup).GetTypeInfo().Assembly.GetName().Name; var dependentLibraries = apm.ApplicationParts.Where(x => x.Name == api || x.Name == apiClientAssembly).ToList(); foreach (var item in dependentLibraries) { apm.ApplicationParts.Remove(item); } }).AddCookieTempDataProvider(options => { options.Cookie.SecurePolicy = cookieSecurePolicy; }) .AddApplicationPart(sharedAssembly); services.Configure <RazorViewEngineOptions>(o => o.FileProviders.Add(new EmbeddedFileProvider(sharedAssembly, "GovUk.Education.SearchAndCompare.UI.Shared"))); services.AddRouting(options => options.LowercaseUrls = true); services.AddAntiforgery(options => { options.Cookie.SecurePolicy = cookieSecurePolicy; }); services.AddAuthentication(options => { options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; }).AddCookie(options => { options.ExpireTimeSpan = TimeSpan.FromHours(6); options.Events = new CookieAuthenticationEvents { // refer to // https://github.com/mderriey/TokenRenewal // https://stackoverflow.com/questions/40032851/how-to-handle-expired-access-token-in-asp-net-core-using-refresh-token-with-open // for more details // this event is fired everytime the cookie has been validated by the cookie middleware, // so basically during every authenticated request // the decryption of the cookie has already happened so we have access to the user claims // and cookie properties - expiration, etc.. OnValidatePrincipal = async x => { // since our cookie lifetime is based on the access token one, // check if we're more than halfway of the cookie lifetime // assume a timeout of 20 minutes. var timeElapsed = DateTimeOffset.UtcNow.Subtract(x.Properties.IssuedUtc.Value); if (timeElapsed > TimeSpan.FromMinutes(19.5)) { var identity = (ClaimsIdentity)x.Principal.Identity; var accessTokenClaim = identity.FindFirst("access_token"); var refreshTokenClaim = identity.FindFirst("refresh_token"); // if we have to refresh, grab the refresh token from the claims, and request // new access token and refresh token var refreshToken = refreshTokenClaim.Value; var clientId = Configuration["auth:oidc:clientId"]; const string envKeyClientSecret = "DFE_SIGNIN_CLIENT_SECRET"; var clientSecret = Configuration[envKeyClientSecret]; if (string.IsNullOrWhiteSpace(clientSecret)) { throw new Exception("Missing environment variable " + envKeyClientSecret + " - get this from the DfE Sign-in team."); } var tokenEndpoint = Configuration["auth:oidc:tokenEndpoint"]; var client = new TokenClient(tokenEndpoint, clientId, clientSecret); var response = await client.RequestRefreshTokenAsync(refreshToken, new { client_secret = clientSecret }); if (!response.IsError) { // everything went right, remove old tokens and add new ones identity.RemoveClaim(accessTokenClaim); identity.RemoveClaim(refreshTokenClaim); identity.AddClaims(new[] { new Claim("access_token", response.AccessToken), new Claim("refresh_token", response.RefreshToken) }); // indicate to the cookie middleware to renew the session cookie // the new lifetime will be the same as the old one, so the alignment // between cookie and access token is preserved x.ShouldRenew = true; } else { // could not refresh - log the user out _logger.LogWarning("Token refresh failed with message: " + response.ErrorDescription); x.RejectPrincipal(); } } } }; }).AddOpenIdConnect(options => { options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.MetadataAddress = Configuration["auth:oidc:metadataAddress"]; options.ClientId = Configuration["auth:oidc:clientId"]; const string envKeyClientSecret = "DFE_SIGNIN_CLIENT_SECRET"; var clientSecret = Configuration[envKeyClientSecret]; if (string.IsNullOrWhiteSpace(clientSecret)) { throw new Exception("Missing environment variable " + envKeyClientSecret + " - get this from the DfE Sign-in team."); } options.ClientSecret = clientSecret; options.ResponseType = OpenIdConnectResponseType.Code; options.GetClaimsFromUserInfoEndpoint = true; // using this property would align the expiration of the cookie // with the expiration of the identity token // UseTokenLifetime = true; options.Scope.Clear(); options.Scope.Add("openid"); options.Scope.Add("email"); options.Scope.Add("profile"); options.Scope.Add("offline_access"); options.SaveTokens = true; options.CallbackPath = new PathString(Configuration["auth:oidc:callbackPath"]); options.SignedOutCallbackPath = new PathString(Configuration["auth:oidc:signedOutCallbackPath"]); options.SecurityTokenValidator = new JwtSecurityTokenHandler { InboundClaimTypeMap = new Dictionary <string, string>(), TokenLifetimeInMinutes = 20, SetDefaultTimesOnTokenCreation = true, }; options.ProtocolValidator = new OpenIdConnectProtocolValidator { RequireSub = true, RequireStateValidation = false, NonceLifetime = TimeSpan.FromMinutes(15) }; 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.StatusCode = 302; context.Response.Headers["Location"] = "/"; } 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.Response.Redirect("/"); ctx.HandleResponse(); return(Task.FromResult(0)); }, OnRedirectToIdentityProvider = context => { context.ProtocolMessage.Prompt = "consent"; return(Task.CompletedTask); }, // that event is called after the OIDC middleware received the auhorisation code, // redeemed it for an access token and a refresh token, // and validated the identity token OnTokenValidated = x => { // store both access and refresh token in the claims - hence in the cookie var identity = (ClaimsIdentity)x.Principal.Identity; identity.AddClaims(new[] { new Claim("access_token", x.TokenEndpointResponse.AccessToken), new Claim("refresh_token", x.TokenEndpointResponse.RefreshToken) }); // so that we don't issue a session cookie but one with a fixed expiration x.Properties.IsPersistent = true; return(Task.CompletedTask); } }; }); services.AddScoped <SearchAndCompare.UI.Shared.Features.IFeatureFlags, SearchAndCompare.UI.Shared.Features.FeatureFlags>(); services.AddSingleton <ISearchAndCompareUrlService>(x => new SearchAndCompareUrlService(Configuration.GetValue("SearchAndCompare:UiBaseUrl", ""))); services.AddSingleton <IFrontendUrlService, FrontendUrlService>(); services.AddSingleton <IHttpClient>(serviceProvider => { var httpContextAccessor = serviceProvider.GetService <IHttpContextAccessor>(); return(new ManageCoursesApiHttpClientWrapper(httpContextAccessor, new HttpClient())); }); services.AddScoped(provider => AnalyticsPolicy.FromEnv()); services.AddScoped <AnalyticsAttribute>(); services.AddSingleton <IHttpContextAccessor, HttpContextAccessor>(); services.AddSingleton <ManageCoursesConfig, ManageCoursesConfig>(); services.AddSingleton <IManageApi, ManageApi>(); services.AddSingleton <ITelemetryInitializer, SubjectTelemetryInitialiser>(); services.AddApplicationInsightsTelemetry(); services.AddSingleton(serviceProvider => { var clientWrapper = serviceProvider.GetService <IHttpClient>(); var config = serviceProvider.GetService <ManageCoursesConfig>(); var manageCoursesApiClient = new ManageCoursesApiClient(config.ApiUrl, clientWrapper); return(manageCoursesApiClient); }); }
public void SetUp() { mockHttp = new Mock <HttpClientWrapper>(MockBehavior.Strict, new object[1]); manageCoursesApiClient = new ManageCoursesApiClient(baseurl, mockHttp.Object); }