private async Task UpdateUserAsync(ApplicationUser user, BuildAssetRegistryContext dbContext, UserManager <ApplicationUser> userManager, SignInManager <ApplicationUser> signInManager, GitHubClaimResolver gitHubClaimResolver) { using (IDbContextTransaction txn = await dbContext.Database.BeginTransactionAsync()) { string token = await userManager.GetAuthenticationTokenAsync(user, GitHubScheme, "access_token"); var newClaims = (await gitHubClaimResolver.GetUserInformationClaims(token)).Concat( await gitHubClaimResolver.GetMembershipClaims(token) ).Where(AccountController.ShouldAddClaimToUser); var currentClaims = (await userManager.GetClaimsAsync(user)).ToList(); // remove old claims await userManager.RemoveClaimsAsync(user, currentClaims); // add new claims await userManager.AddClaimsAsync(user, newClaims); user.LastUpdated = DateTimeOffset.UtcNow; await dbContext.SaveChangesAsync(); txn.Commit(); } }
private async Task UpdateUserIfNeededAsync(ApplicationUser user, BuildAssetRegistryContext dbContext, UserManager <ApplicationUser> userManager, SignInManager <ApplicationUser> signInManager, GitHubClaimResolver gitHubClaimResolver) { while (true) { try { if (ShouldUpdateUser(user)) { await UpdateUserAsync(user, dbContext, userManager, signInManager, gitHubClaimResolver); } break; } catch (DbUpdateConcurrencyException) { // If we have a concurrent modification exception reload the data from the DB and try again foreach (EntityEntry entry in dbContext.ChangeTracker.Entries()) { await entry.ReloadAsync(); } } } }
public TokenModel( ITokenStore tokens, ITokenRevocationProvider revocation, GitHubUserTokenHandler handler, GitHubClaimResolver resolver) { _tokens = tokens; _revocation = revocation; _handler = handler; _resolver = resolver; }
private void AddServices(IServiceCollection services) { services.AddMvc().WithRazorPagesRoot("/Pages").AddRazorPagesOptions(o => o.Conventions.AuthorizeFolder("/", MsftAuthorizationPolicyName).AllowAnonymousToPage("/Index")); services.AddApplicationInsightsTelemetry(Configuration.GetSection("ApplicationInsights").Bind); services.AddAuthentication() .AddGitHubOAuth(Configuration.GetSection("GitHubAuthentication"), GitHubScheme) .AddScheme <UserTokenOptions, GitHubUserTokenHandler>("github-token", o => { }) .AddCookie(IdentityConstants.ApplicationScheme, o => { o.ExpireTimeSpan = TimeSpan.FromDays(7); o.SlidingExpiration = true; o.Cookie.IsEssential = true; o.LoginPath = "/signin"; o.LogoutPath = "/signout"; o.ReturnUrlParameter = "r"; o.Events = new CookieAuthenticationEvents { OnValidatePrincipal = async ctx => { GitHubClaimResolver resolver = ctx.HttpContext.RequestServices.GetRequiredService <GitHubClaimResolver>(); ClaimsIdentity identity = ctx.Principal.Identities.FirstOrDefault(); identity?.AddClaims(await resolver.GetMembershipClaims(resolver.GetAccessToken(ctx.Principal))); } }; }) ; services.AddAzureTableTokenStore(o => Configuration.GetSection("AzureTableTokenStore").Bind(o)); services.AddAuthorization( options => { options.AddPolicy( MsftAuthorizationPolicyName, policy => { policy.RequireAuthenticatedUser(); if (!Env.IsDevelopment()) { policy.RequireRole("github:team:dotnet/dnceng", "github:team:dotnet/bots-high"); } }); }); services.AddScoped <SimpleSigninMiddleware>(); services.AddGitHubTokenProvider(); services.AddSingleton <IInstallationLookup, InMemoryCacheInstallationLookup>(); services.AddContextAwareAuthenticationScheme(o => { o.SelectScheme = p => p.StartsWithSegments("/api") ? "github-token" : IdentityConstants.ApplicationScheme; }); services.AddSingleton <GitHubJwtFactory>(); }
public GitHubUserTokenHandler( GitHubClaimResolver resolver, IOptionsMonitor <UserTokenOptions> options, IDataProtectionProvider dataProtector, ITokenRevocationProvider revocation, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock) { _resolver = resolver; _dataProtector = dataProtector.CreateProtector("github-token"); _revocation = revocation; }
private void AddServices(IServiceCollection services) { services.AddRazorPages(o => { o.Conventions .AuthorizeFolder("/", MsftAuthorizationPolicyName) .AllowAnonymousToPage("/Index") .AllowAnonymousToPage("/Status") .AllowAnonymousToPage("/Error"); o.RootDirectory = "/Pages"; }); services.AddControllers() .AddGitHubWebHooks(); services.AddApplicationInsightsTelemetry(Configuration.GetSection("ApplicationInsights").Bind); services.Configure <LoggerFilterOptions>(o => { // This handler is added by 'AddApplicationInsightsTelemetry' above and hard limits // and reporting below "warning", which basically kills all logging // Remove it, we already configured the filters in Program.cs o.Rules.Remove(o.Rules.FirstOrDefault(r => r.ProviderName == "Microsoft.Extensions.Logging.ApplicationInsights.ApplicationInsightsLoggerProvider")); }); services.AddAuthentication("contextual") .AddPolicyScheme("contextual", "Contextual Scheme", o => { o.ForwardDefaultSelector = context => { if (context.Request.Path.StartsWithSegments("/api")) { return("github-token"); } return(IdentityConstants.ApplicationScheme); }; }) .AddGitHubOAuth(Configuration.GetSection("GitHubAuthentication"), GitHubScheme) .AddScheme <UserTokenOptions, GitHubUserTokenHandler>("github-token", o => { }) .AddCookie(IdentityConstants.ApplicationScheme, o => { o.ExpireTimeSpan = TimeSpan.FromDays(7); o.SlidingExpiration = true; o.Cookie.IsEssential = true; o.LoginPath = "/signin"; o.LogoutPath = "/signout"; o.ReturnUrlParameter = "r"; o.Events = new CookieAuthenticationEvents { OnValidatePrincipal = async ctx => { GitHubClaimResolver resolver = ctx.HttpContext.RequestServices.GetRequiredService <GitHubClaimResolver>(); ClaimsIdentity identity = ctx.Principal.Identities.FirstOrDefault(); identity?.AddClaims(await resolver.GetMembershipClaims(resolver.GetAccessToken(ctx.Principal))); }, }; }) .AddExternalCookie() ; services.AddAzureTableTokenStore(o => Configuration.GetSection("AzureTableTokenStore").Bind(o)); services.AddAuthorization( options => { options.AddPolicy( MsftAuthorizationPolicyName, policy => { policy.RequireAuthenticatedUser(); if (!Env.IsDevelopment()) { policy.RequireRole(GitHubClaimResolver.GetTeamRole("dotnet", "dnceng"), GitHubClaimResolver.GetTeamRole("dotnet", "bots-high")); } }); }); services.AddKustoIngest(options => Configuration.GetSection("Kusto").Bind(options)); services.AddScoped <SimpleSigninMiddleware>(); services.AddGitHubTokenProvider(); services.AddSingleton <IInstallationLookup, InMemoryCacheInstallationLookup>(); services.AddSingleton <ZenHubClient>(); services.AddSingleton <IGitHubApplicationClientFactory, GitHubApplicationClientFactory>(); services.AddSingleton <IGitHubClientFactory, GitHubClientFactory>(); }
private void AddServices(IServiceCollection services) { services.AddRazorPages(o => { o.Conventions .AuthorizeFolder("/", MsftAuthorizationPolicyName) .AllowAnonymousToPage("/Index") .AllowAnonymousToPage("/Status") .AllowAnonymousToPage("/Routes") .AllowAnonymousToPage("/Error"); o.RootDirectory = "/Pages"; }); services.AddControllers() .AddGitHubWebHooks(); services.AddApplicationInsightsTelemetry(Configuration.GetSection("ApplicationInsights").Bind); services.Configure <LoggerFilterOptions>(o => { // This handler is added by 'AddApplicationInsightsTelemetry' above and hard limits // and reporting below "warning", which basically kills all logging // Remove it, we already configured the filters in Program.cs o.Rules.Remove(o.Rules.FirstOrDefault(r => r.ProviderName == "Microsoft.Extensions.Logging.ApplicationInsights.ApplicationInsightsLoggerProvider")); // These two categories log a lot of noise at "Information", let's raise them to warning o.Rules.Add(new LoggerFilterRule(null, "Microsoft.AspNetCore.Mvc.ViewFeatures.Filters.ValidateAntiforgeryTokenAuthorizationFilter", LogLevel.Warning, null)); o.Rules.Add(new LoggerFilterRule(null, "Microsoft.AspNetCore.Mvc.ViewFeatures.Filters.AutoValidateAntiforgeryTokenAuthorizationFilter", LogLevel.Warning, null)); }); services.AddAuthentication("contextual") .AddPolicyScheme("contextual", "Contextual Scheme", o => { o.ForwardDefaultSelector = context => { if (context.Request.Path.StartsWithSegments("/api/webhooks")) { return("nothing"); } if (context.Request.Path.StartsWithSegments("/api")) { return("github-token"); } return(IdentityConstants.ApplicationScheme); }; }) .AddGitHubOAuth(Configuration.GetSection("GitHubAuthentication"), GitHubScheme) .AddScheme <NothingOptions, NothingHandler>("nothing", o => { }) .AddScheme <UserTokenOptions, GitHubUserTokenHandler>("github-token", o => { }) .AddCookie(IdentityConstants.ApplicationScheme, o => { o.ExpireTimeSpan = TimeSpan.FromMinutes(30); o.SlidingExpiration = true; o.Cookie.IsEssential = true; o.LoginPath = "/signin"; o.LogoutPath = "/signout"; o.ReturnUrlParameter = "r"; o.Events = new CookieAuthenticationEvents { OnValidatePrincipal = async ctx => { GitHubClaimResolver resolver = ctx.HttpContext.RequestServices.GetRequiredService <GitHubClaimResolver>(); ClaimsIdentity identity = ctx.Principal.Identities.FirstOrDefault(); identity?.AddClaims(await resolver.GetMembershipClaims(resolver.GetAccessToken(ctx.Principal))); }, }; }) .AddExternalCookie() ; services.AddAzureTableTokenStore(o => Configuration.GetSection("AzureTableTokenStore").Bind(o)); services.AddAuthorization( options => { options.AddPolicy( MsftAuthorizationPolicyName, policy => { policy.RequireAuthenticatedUser(); if (!Env.IsDevelopment()) { policy.RequireRole(GitHubClaimResolver.GetTeamRole("dotnet", "dnceng"), GitHubClaimResolver.GetTeamRole("dotnet", "bots-high")); } }); }); services.AddKustoIngest(options => Configuration.GetSection("Kusto").Bind(options)); services.AddScoped <SimpleSigninMiddleware>(); services.AddGitHubTokenProvider(); services.AddSingleton <IInstallationLookup, InMemoryCacheInstallationLookup>(); services.AddSingleton <ZenHubClient>(); services.AddSingleton <IGitHubApplicationClientFactory, GitHubApplicationClientFactory>(); services.AddSingleton <IGitHubClientFactory, GitHubClientFactory>(); services.AddSingleton <ITimelineIssueTriage, TimelineIssueTriage>(); services.AddSingleton <ExponentialRetry>(); services.AddSingleton <ISystemClock, SystemClock>(); services.AddSingleton <Microsoft.Extensions.Internal.ISystemClock, Microsoft.Extensions.Internal.SystemClock>(); services.AddHttpClient(); services.AddHealthReporting( b => { b.AddLogging(); b.AddAzureTable((o, p) => o.WriteSasUri = p.GetRequiredService <IConfiguration>()["HealthTableUri"]); }); services.AddScoped <ITeamMentionForwarder, TeamMentionForwarder>(); }
private void ConfigureAuthServices(IServiceCollection services) { services.AddIdentity <ApplicationUser, IdentityRole <int> >( options => { options.Lockout.AllowedForNewUsers = false; }) .AddEntityFrameworkStores <BuildAssetRegistryContext>(); services.AddAuthentication(options => { options.DefaultAuthenticateScheme = options.DefaultChallengeScheme = options.DefaultScheme = "Contextual"; options.DefaultSignInScheme = IdentityConstants.ExternalScheme; }) .AddPolicyScheme("Contextual", "Contextual", policyOptions => { policyOptions.ForwardDefaultSelector = ctx => ctx.Request.Path.StartsWithSegments("/api") ? PersonalAccessTokenDefaults.AuthenticationScheme : IdentityConstants.ApplicationScheme; }) .AddGitHubOAuth(Configuration.GetSection("GitHubAuthentication"), GitHubScheme) .AddPersonalAccessToken <ApplicationUser>( options => { options.Events = new PersonalAccessTokenEvents <ApplicationUser> { OnSetTokenHash = async context => { var dbContext = context.HttpContext.RequestServices .GetRequiredService <BuildAssetRegistryContext>(); int userId = context.User.Id; var token = new ApplicationUserPersonalAccessToken { ApplicationUserId = userId, Name = context.Name, Hash = context.Hash, Created = DateTimeOffset.UtcNow }; await dbContext.Set <ApplicationUserPersonalAccessToken>().AddAsync(token); await dbContext.SaveChangesAsync(); return(token.Id); }, OnGetTokenHash = async context => { var dbContext = context.HttpContext.RequestServices .GetRequiredService <BuildAssetRegistryContext>(); ApplicationUserPersonalAccessToken token = await dbContext .Set <ApplicationUserPersonalAccessToken>() .Where(t => t.Id == context.TokenId) .Include(t => t.ApplicationUser) .FirstOrDefaultAsync(); if (token != null) { context.Success(token.Hash, token.ApplicationUser); } }, OnValidatePrincipal = async context => { ApplicationUser user = context.User; var dbContext = context.HttpContext.RequestServices .GetRequiredService <BuildAssetRegistryContext>(); var userManager = context.HttpContext.RequestServices .GetRequiredService <UserManager <ApplicationUser> >(); var signInManager = context.HttpContext.RequestServices .GetRequiredService <SignInManager <ApplicationUser> >(); var gitHubClaimResolver = context.HttpContext.RequestServices .GetRequiredService <GitHubClaimResolver>(); await UpdateUserIfNeededAsync(user, dbContext, userManager, signInManager, gitHubClaimResolver); ClaimsPrincipal principal = await signInManager.CreateUserPrincipalAsync(user); context.ReplacePrincipal(principal); } }; }); services.ConfigureExternalCookie( options => { options.ExpireTimeSpan = TimeSpan.FromMinutes(30); options.ReturnUrlParameter = "returnUrl"; options.LoginPath = "/Account/SignIn"; options.Events = new CookieAuthenticationEvents { OnRedirectToLogin = ctx => { if (ctx.Request.Path.StartsWithSegments("/api")) { ctx.Response.StatusCode = 401; return(Task.CompletedTask); } ctx.Response.Redirect(ctx.RedirectUri); return(Task.CompletedTask); }, OnRedirectToAccessDenied = ctx => { ctx.Response.StatusCode = 403; return(Task.CompletedTask); }, }; }); services.ConfigureApplicationCookie( options => { options.ExpireTimeSpan = LoginCookieLifetime; options.SlidingExpiration = true; options.ReturnUrlParameter = "returnUrl"; options.LoginPath = "/Account/SignIn"; options.Events = new CookieAuthenticationEvents { OnSigningIn = async ctx => { var dbContext = ctx.HttpContext.RequestServices .GetRequiredService <BuildAssetRegistryContext>(); var signInManager = ctx.HttpContext.RequestServices .GetRequiredService <SignInManager <ApplicationUser> >(); var userManager = ctx.HttpContext.RequestServices .GetRequiredService <UserManager <ApplicationUser> >(); ExternalLoginInfo info = await signInManager.GetExternalLoginInfoAsync(); var user = await userManager.GetUserAsync(ctx.Principal); await UpdateUserTokenAsync(dbContext, userManager, user, info); IdentityOptions identityOptions = ctx.HttpContext.RequestServices .GetRequiredService <IOptions <IdentityOptions> >() .Value; // replace the ClaimsPrincipal we are about to serialize to the cookie with a reference Claim claim = ctx.Principal.Claims.First( c => c.Type == identityOptions.ClaimsIdentity.UserIdClaimType); Claim[] claims = { claim }; var identity = new ClaimsIdentity(claims, IdentityConstants.ApplicationScheme); ctx.Principal = new ClaimsPrincipal(identity); }, OnValidatePrincipal = async ctx => { var dbContext = ctx.HttpContext.RequestServices .GetRequiredService <BuildAssetRegistryContext>(); var userManager = ctx.HttpContext.RequestServices .GetRequiredService <UserManager <ApplicationUser> >(); var signInManager = ctx.HttpContext.RequestServices .GetRequiredService <SignInManager <ApplicationUser> >(); var gitHubClaimResolver = ctx.HttpContext.RequestServices .GetRequiredService <GitHubClaimResolver>(); // extract the userId from the ClaimsPrincipal and read the user from the Db ApplicationUser user = await userManager.GetUserAsync(ctx.Principal); if (user == null) { ctx.RejectPrincipal(); } else { await UpdateUserIfNeededAsync(user, dbContext, userManager, signInManager, gitHubClaimResolver); ClaimsPrincipal principal = await signInManager.CreateUserPrincipalAsync(user); ctx.ReplacePrincipal(principal); } } }; }); services.AddAuthorization( options => { options.AddPolicy( MsftAuthorizationPolicyName, policy => { policy.RequireAuthenticatedUser(); if (!HostingEnvironment.IsDevelopment()) { policy.RequireRole(GitHubClaimResolver.GetTeamRole("dotnet", "dnceng"), GitHubClaimResolver.GetTeamRole("dotnet", "arcade-contrib")); } }); }); services.Configure <MvcOptions>( options => { options.Conventions.Add(new DefaultAuthorizeActionModelConvention(MsftAuthorizationPolicyName)); }); }