update identity integration

This commit is contained in:
2022-12-12 00:15:21 +01:00
parent 313bd59b98
commit 30388b74a7
6 changed files with 196 additions and 180 deletions
-80
View File
@@ -4,11 +4,6 @@ public static partial class Constants
{
public const string ErrorFieldNone = "__zero_no_field";
public static partial class Tabs
{
public const string General = "general";
}
public static partial class Auth
{
public const string SystemUser = "system";
@@ -17,86 +12,11 @@ public static partial class Constants
public const string BackofficeScheme = "zeroBackoffice";
public const string BackofficeCookieName = "zero.be.session";
public const string DefaultCookieName = "zero.session";
public static partial class Claims
{
public const string IsZero = "zero.claim.iszero";
public const string IsSuper = "zero.claim.issuper";
public const string UserId = "zero.claim.userid";
public const string UserName = "zero.claim.username";
public const string Role = "zero.claim.rolealias";
public const string SecurityStamp = "zero.claim.securitystamp";
public const string Email = "zero.claim.email";
public const string Permission = "zero.claim.permission";
public const string DefaultAppId = "zero.claim.defaultAppId";
public const string AppId = "zero.claim.appId";
public const string TicketExpires = "zero.claim.ticketExpires";
}
}
public static partial class Permissions
{
public static partial class Groups
{
public const string Sections = "zero.permissions.groups.sections";
public const string Spaces = "zero.permissions.groups.spaces";
public const string Settings = "zero.permissions.groups.settings";
public const string Pages = "zero.permissions.groups.pages";
public const string Modules = "zero.permissions.groups.modules";
}
}
public static partial class Database
{
public const string ReservationPrefix = "zero.";
public const string Expires = Raven.Client.Constants.Documents.Metadata.Expires;
}
public static partial class Sections
{
public const string Dashboard = "dashboard";
public const string Pages = "pages";
public const string Spaces = "spaces";
public const string Media = "media";
public const string Settings = "settings";
}
public static partial class Settings
{
public const string Updates = "updates";
public const string Applications = "applications";
public const string Users = "users";
public const string Languages = "languages";
public const string Translations = "translations";
public const string Countries = "countries";
public const string Mails = "mailTemplates";
public const string Integrations = "integrations";
public const string Logging = "logs";
public const string Plugins = "plugins";
public const string CreatePlugin = "createplugin";
}
public static partial class PermissionCollections
{
public const string Sections = "permissionCollectionSections";
public const string Spaces = "permissionCollectionSpaces";
public const string Settings = "permisssionCollectionSettings";
public const string Modules = "permissionCollectionModules";
}
public static partial class Pages
{
public const string FolderAlias = "zero.folder";
public const string DefaultRootPageTypeAlias = "root";
public const string PageRouteProviderAlias = "zero.pages";
}
public static partial class Routing
{
public const string InternalRoutePrefix = "/__zero/";
public const string ErrorRoute = InternalRoutePrefix + "error";
}
}
+1 -3
View File
@@ -9,12 +9,10 @@ public static class IdentityBuilderExtensions
/// <summary>
/// Adds an implementation of identity information stores.
/// </summary>
public static IdentityBuilder AddZeroIdentityStores<T>(this IdentityBuilder builder) where T : class, IZeroIdentityStoreDbProvider
public static IdentityBuilder AddZeroIdentityStores(this IdentityBuilder builder)
{
Type userStoreType = typeof(ZeroUserStore<,>).MakeGenericType(builder.UserType, builder.RoleType);
Type roleStoreType = typeof(ZeroRoleStore<>).MakeGenericType(builder.RoleType);
builder.Services.AddScoped<IZeroIdentityStoreDbProvider, T>();
builder.Services.TryAddScoped(typeof(IUserStore<>).MakeGenericType(builder.UserType), userStoreType);
builder.Services.TryAddScoped(typeof(IRoleStore<>).MakeGenericType(builder.RoleType), roleStoreType);
@@ -1,97 +0,0 @@
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using zero.Identity;
namespace zero.Identity;
public static class ServiceCollectionExtensions
{
/// <summary>
/// Adds the default identity system configuration for the specified User and Role types
/// </summary>
// public static IdentityBuilder AddZeroIdentity<TUser, TRole, TStore>(
// this IServiceCollection services)
// where TUser : ZeroIdentityUser, new()
// where TRole : ZeroIdentityRole, new()
// where TStore : class, IZeroIdentityStoreDbProvider
// => services.AddIdentity<TUser, TRole>(setupAction: null!);
/// <summary>
/// Adds and configures the identity system for the specified User and Role types
/// </summary>
public static IdentityBuilder AddZeroIdentity<TUser, TRole, TStore>(
this IServiceCollection services,
Action<IdentityOptions> setupAction
)
where TUser : ZeroIdentityUser, new()
where TRole : ZeroIdentityRole, new()
where TStore : class, IZeroIdentityStoreDbProvider
{
// Services used by identity
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme;
options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme;
options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
})
.AddCookie(IdentityConstants.ApplicationScheme, o =>
{
o.LoginPath = new PathString("/Account/Login");
o.Events = new CookieAuthenticationEvents
{
OnValidatePrincipal = SecurityStampValidator.ValidatePrincipalAsync
};
})
.AddCookie(IdentityConstants.ExternalScheme, o =>
{
o.Cookie.Name = IdentityConstants.ExternalScheme;
o.ExpireTimeSpan = TimeSpan.FromMinutes(5);
})
.AddCookie(IdentityConstants.TwoFactorRememberMeScheme, o =>
{
o.Cookie.Name = IdentityConstants.TwoFactorRememberMeScheme;
o.Events = new CookieAuthenticationEvents
{
OnValidatePrincipal = SecurityStampValidator.ValidateAsync<ITwoFactorSecurityStampValidator>
};
})
.AddCookie(IdentityConstants.TwoFactorUserIdScheme, o =>
{
o.Cookie.Name = IdentityConstants.TwoFactorUserIdScheme;
o.ExpireTimeSpan = TimeSpan.FromMinutes(5);
});
// Hosting doesn't add IHttpContextAccessor by default
services.AddHttpContextAccessor();
// Data stores
services.AddScoped<IZeroIdentityStoreDbProvider, TStore>();
services.TryAddScoped<IUserStore<TUser>, ZeroUserStore<TUser>>();
services.TryAddScoped<IRoleStore<TRole>, ZeroRoleStore<TRole>>();
// Identity services
services.TryAddScoped<IUserValidator<TUser>, UserValidator<TUser>>();
services.TryAddScoped<IPasswordValidator<TUser>, PasswordValidator<TUser>>();
services.TryAddScoped<IPasswordHasher<TUser>, PasswordHasher<TUser>>();
services.TryAddScoped<ILookupNormalizer, UpperInvariantLookupNormalizer>();
services.TryAddScoped<IRoleValidator<TRole>, RoleValidator<TRole>>();
services.TryAddScoped<IdentityErrorDescriber>();
services.TryAddScoped<ISecurityStampValidator, SecurityStampValidator<TUser>>();
services.TryAddScoped<ITwoFactorSecurityStampValidator, TwoFactorSecurityStampValidator<TUser>>();
services.TryAddScoped<IUserClaimsPrincipalFactory<TUser>, UserClaimsPrincipalFactory<TUser, TRole>>();
services.TryAddScoped<IUserConfirmation<TUser>, DefaultUserConfirmation<TUser>>();
services.TryAddScoped<UserManager<TUser>>();
services.TryAddScoped<SignInManager<TUser>>();
services.TryAddScoped<RoleManager<TRole>>();
if (setupAction != null)
{
services.Configure(setupAction);
}
return new IdentityBuilder(typeof(TUser), typeof(TRole), services);
}
}
+31
View File
@@ -0,0 +1,31 @@
namespace zero.Identity;
/// <summary>
/// Represents all the options you can use to configure the cookies middleware used by the identity system.
/// </summary>
public class ZeroIdentityConstants
{
public static class CookieNames
{
private const string CookiePrefix = "zero.id";
public static readonly string Application = CookiePrefix + ".app";
public static readonly string External = CookiePrefix + ".ext";
public static readonly string TwoFactorRememberMe = CookiePrefix + ".2fa_rem";
public static readonly string TwoFactorUserId = CookiePrefix + ".2fa_id";
}
public static partial class Claims
{
private const string ClaimPrefix = "zero.claim";
public static readonly string IsZero = ClaimPrefix + ".iszero";
public static readonly string UserId = ClaimPrefix + ".userid";
public static readonly string UserName = ClaimPrefix + ".username";
public static readonly string Role = ClaimPrefix + ".rolealias";
public static readonly string SecurityStamp = ClaimPrefix + ".securitystamp";
public static readonly string Email = ClaimPrefix + ".email";
public static readonly string Permission = ClaimPrefix + ".permission";
public static readonly string TicketExpires = ClaimPrefix + ".ticketExpires";
}
}
+161
View File
@@ -0,0 +1,161 @@
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace zero.Identity;
public static class ZeroIdentityExtensions
{
// <summary>
/// Adds the default identity system configuration for the specified User and Role types.
/// </summary>
/// <typeparam name="TUser">The type representing a User in the system.</typeparam>
/// <typeparam name="TRole">The type representing a Role in the system.</typeparam>
/// <param name="services">The services available in the application.</param>
/// <returns>An <see cref="IdentityBuilder"/> for creating and configuring the identity system.</returns>
public static IdentityBuilder AddZeroIdentity<TUser, TRole>(this IServiceCollection services)
where TUser : ZeroIdentityUser, new()
where TRole : ZeroIdentityRole, new() =>
AddZeroIdentity<TUser, TRole>(services, null);
/// <summary>
/// Adds and configures the identity system for the specified User and Role types.
/// </summary>
/// <typeparam name="TUser">The type representing a User in the system.</typeparam>
/// <typeparam name="TRole">The type representing a Role in the system.</typeparam>
/// <param name="services">The services available in the application.</param>
/// <param name="setupAction">An action to configure the <see cref="IdentityOptions"/>.</param>
/// <returns>An <see cref="IdentityBuilder"/> for creating and configuring the identity system.</returns>
public static IdentityBuilder AddZeroIdentity<TUser, TRole>(this IServiceCollection services,
Action<IdentityOptions> setupAction)
where TUser : ZeroIdentityUser, new()
where TRole : ZeroIdentityRole, new()
{
// Services identity depends on
services
.AddOptions()
.AddLogging();
// Services used by identity
services.AddAuthentication(options =>
{
options.DefaultScheme = IdentityConstants.ApplicationScheme;
options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme;
options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme;
options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
})
.AddCookie(IdentityConstants.ApplicationScheme, o =>
{
o.LoginPath = new PathString("/Account/Login");
o.SlidingExpiration = true;
o.ExpireTimeSpan = TimeSpan.FromDays(90);
o.Cookie.Name = ZeroIdentityConstants.CookieNames.Application;
o.Cookie.HttpOnly = true;
o.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
o.Cookie.SameSite = SameSiteMode.Lax;
o.Cookie.Path = "/";
o.Events = new CookieAuthenticationEvents
{
OnValidatePrincipal = SecurityStampValidator.ValidatePrincipalAsync
};
})
.AddCookie(IdentityConstants.ExternalScheme, o =>
{
o.Cookie.Name = ZeroIdentityConstants.CookieNames.External;
o.ExpireTimeSpan = TimeSpan.FromMinutes(5);
})
.AddCookie(IdentityConstants.TwoFactorRememberMeScheme, o =>
{
o.Cookie.Name = ZeroIdentityConstants.CookieNames.TwoFactorRememberMe;
o.Events = new CookieAuthenticationEvents
{
OnValidatePrincipal = SecurityStampValidator.ValidateAsync<ITwoFactorSecurityStampValidator>
};
})
.AddCookie(IdentityConstants.TwoFactorUserIdScheme, o =>
{
o.Cookie.Name = ZeroIdentityConstants.CookieNames.TwoFactorUserId;
o.ExpireTimeSpan = TimeSpan.FromMinutes(5);
});
// Hosting doesn't add IHttpContextAccessor by default
services.AddHttpContextAccessor();
// Identity services
services.TryAddScoped<IUserValidator<TUser>, UserValidator<TUser>>();
services.TryAddScoped<IPasswordValidator<TUser>, PasswordValidator<TUser>>();
services.TryAddScoped<IPasswordHasher<TUser>, PasswordHasher<TUser>>();
services.TryAddScoped<ILookupNormalizer, UpperInvariantLookupNormalizer>();
services.TryAddScoped<IRoleValidator<TRole>, RoleValidator<TRole>>();
// No interface for the error describer so we can add errors without rev'ing the interface
services.TryAddScoped<IdentityErrorDescriber>();
services.TryAddScoped<ISecurityStampValidator, SecurityStampValidator<TUser>>();
services.TryAddScoped<ITwoFactorSecurityStampValidator, TwoFactorSecurityStampValidator<TUser>>();
services.TryAddScoped<IUserClaimsPrincipalFactory<TUser>, UserClaimsPrincipalFactory<TUser, TRole>>();
services.TryAddScoped<IUserConfirmation<TUser>, DefaultUserConfirmation<TUser>>();
services.TryAddScoped<UserManager<TUser>>();
services.TryAddScoped<SignInManager<TUser>>();
services.TryAddScoped<RoleManager<TRole>>();
services.Configure<IdentityOptions>(opts =>
{
opts.ClaimsIdentity.UserIdClaimType = ZeroIdentityConstants.Claims.UserId;
opts.ClaimsIdentity.UserNameClaimType = ZeroIdentityConstants.Claims.UserName;
opts.ClaimsIdentity.RoleClaimType = ZeroIdentityConstants.Claims.Role;
opts.ClaimsIdentity.SecurityStampClaimType = ZeroIdentityConstants.Claims.SecurityStamp;
opts.ClaimsIdentity.EmailClaimType = ZeroIdentityConstants.Claims.Email;
opts.Password.RequireDigit = false;
opts.Password.RequireLowercase = false;
opts.Password.RequireUppercase = false;
opts.Password.RequireNonAlphanumeric = false;
opts.Password.RequiredLength = 8;
opts.Password.RequiredUniqueChars = 1;
opts.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(60);
opts.Lockout.MaxFailedAccessAttempts = 5;
opts.Lockout.AllowedForNewUsers = true;
opts.User.RequireUniqueEmail = true;
});
services.Configure<SecurityStampValidatorOptions>(opts => { opts.ValidationInterval = TimeSpan.FromMinutes(90); });
if (setupAction != null)
{
services.Configure(setupAction);
}
IdentityBuilder builder = new(typeof(TUser), typeof(TRole), services);
builder.AddDefaultTokenProviders();
builder.AddZeroIdentityStores();
return builder;
}
/// <summary>
/// Configures the application cookie.
/// </summary>
/// <param name="services">The services available in the application.</param>
/// <param name="configure">An action to configure the <see cref="CookieAuthenticationOptions"/>.</param>
/// <returns>The services.</returns>
public static IServiceCollection ConfigureZeroApplicationCookie(this IServiceCollection services, Action<CookieAuthenticationOptions> configure)
=> services.Configure(IdentityConstants.ApplicationScheme, configure);
/// <summary>
/// Configure the external cookie.
/// </summary>
/// <param name="services">The services available in the application.</param>
/// <param name="configure">An action to configure the <see cref="CookieAuthenticationOptions"/>.</param>
/// <returns>The services.</returns>
public static IServiceCollection ConfigureZeroExternalCookie(this IServiceCollection services, Action<CookieAuthenticationOptions> configure)
=> services.Configure(IdentityConstants.ExternalScheme, configure);
}
+3
View File
@@ -1,4 +1,5 @@
using FluentValidation;
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@@ -33,6 +34,8 @@ public class ZeroBuilder
services.AddControllers();
services.AddRazorPages();
services.Configure<AntiforgeryOptions>(opts => opts.Cookie.Name = "zero.antiforgery");
// adds and discovers additional and built-in assemblies
new AssemblyDiscovery(Mvc).Execute(_startupOptions.AssemblyDiscoveryRules);