zero authorize

This commit is contained in:
2020-04-15 14:09:52 +02:00
parent 9f5d9bb920
commit ccc63b9602
23 changed files with 389 additions and 97 deletions
+41 -18
View File
@@ -1,6 +1,8 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Raven.Client.Documents;
using Raven.Client.Documents.Session;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using zero.Core.Entities;
@@ -12,34 +14,50 @@ namespace zero.Core.Api
protected IHttpContextAccessor HttpContextAccessor { get; set; }
protected SignInManager<User> SignInManager { get; private set; }
public AuthenticationApi(IDocumentStore raven, IHttpContextAccessor httpContextAccessor)
public AuthenticationApi(IDocumentStore raven, IHttpContextAccessor httpContextAccessor, SignInManager<User> signInManager)
{
Raven = raven;
HttpContextAccessor = httpContextAccessor;
SignInManager = signInManager;
}
/// <inheritdoc />
public bool IsLoggedIn()
{
return HttpContextAccessor.HttpContext.User != null;
ClaimsPrincipal principal = HttpContextAccessor.HttpContext.User;
bool isAuthenticated = principal.Identity.IsAuthenticated;
bool isZeroUser = principal.HasClaim(Constants.Auth.Claims.IsZero, Constants.Auth.Claims.IsZero);
return isAuthenticated && isZeroUser;
}
/// <inheritdoc />
public async Task<bool> Login(string email, string password, bool isPersistent)
{
SignInResult result = await SignInManager.PasswordSignInAsync(email, password, isPersistent, true);
return result.Succeeded;
}
/// <inheritdoc />
public async Task Logout()
{
await SignInManager.SignOutAsync();
}
/// <inheritdoc />
public string GetUserId()
{
return null;
//ResolveIdFromClaimsPrincipal(HttpContextAccessor.HttpContext.User);
}
/// <inheritdoc />
public async Task<User> GetUser()
{
await Task.Delay(0);
return null;
ClaimsPrincipal principal = HttpContextAccessor.HttpContext.User;
return principal.Claims.FirstOrDefault(x => x.Type == Constants.Auth.Claims.UserId)?.Value;
}
}
@@ -51,14 +69,19 @@ namespace zero.Core.Api
/// </summary>
bool IsLoggedIn();
/// <summary>
/// Logs a zero-user in and sets cookie
/// </summary>
Task<bool> Login(string email, string password, bool isPersistent);
/// <summary>
/// Logs out the current user
/// </summary>
Task Logout();
/// <summary>
/// Get the ID of the currently logged in user
/// </summary>
string GetUserId();
/// <summary>
/// Get the currently logged-in user (or null if not logged in)
/// </summary>
Task<User> GetUser();
}
}
+67
View File
@@ -0,0 +1,67 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Raven.Client.Documents;
using System.Threading.Tasks;
using zero.Core.Entities;
namespace zero.Core.Api
{
public class UserApi : IUserApi
{
protected IDocumentStore Raven { get; private set; }
protected IHttpContextAccessor HttpContextAccessor { get; set; }
protected UserManager<User> UserManager { get; private set; }
public UserApi(IDocumentStore raven, IHttpContextAccessor httpContextAccessor, UserManager<User> userManager)
{
Raven = raven;
HttpContextAccessor = httpContextAccessor;
UserManager = userManager;
}
/// <inheritdoc />
public async Task<User> GetUser()
{
User user = await UserManager.GetUserAsync(HttpContextAccessor.HttpContext.User);
return user;
}
/// <inheritdoc />
public async Task<User> GetUserById(string id)
{
User user = await UserManager.FindByIdAsync(id);
return user;
}
/// <inheritdoc />
public async Task<User> GetUserByEmail(string email)
{
User user = await UserManager.FindByEmailAsync(email);
return user;
}
}
public interface IUserApi
{
/// <summary>
/// Get currently logged-in user
/// </summary>
Task<User> GetUser();
/// <summary>
/// Find user by id
/// </summary>
Task<User> GetUserById(string id);
/// <summary>
/// Find user by email
/// </summary>
Task<User> GetUserByEmail(string email);
}
}
+34 -18
View File
@@ -1,48 +1,64 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
namespace zero.Core.Auth
{
public class ZeroAuthorizeAttribute : TypeFilterAttribute
{
public ZeroAuthorizeAttribute(string name) : base(typeof(ZeroAuthorizeFilter))
public ZeroAuthorizeAttribute() : this(String.Empty, true) { }
public ZeroAuthorizeAttribute(bool enabled) : this(String.Empty, enabled) { }
public ZeroAuthorizeAttribute(string name, bool enabled) : base(typeof(ZeroAuthorizeFilter))
{
Arguments = new object[] { name };
Arguments = new object[] { name, enabled };
}
}
public class ZeroAuthorizeFilter : AuthorizeFilter
public class ZeroAuthorizeFilter : IAuthorizationFilter
{
string Name { get; set; }
bool Enabled { get; set; }
public ZeroAuthorizeFilter(string name)
public ZeroAuthorizeFilter(string name, bool enabled) : base()
{
Name = name;
Enabled = enabled;
}
//public ZeroAuthorizeFilter(IAuthorizationPolicyProvider provider)
// : base(provider, new[] { new AuthorizeData(Constants.AzureAdPolicy) }) { }
public override async Task OnAuthorizationAsync(AuthorizationFilterContext context)
public void OnAuthorization(AuthorizationFilterContext context)
{
await Task.Delay(0);
//await base.OnAuthorizationAsync(context);
Console.WriteLine("zeroauthorize: " + (Name ?? "[empty]"));
Console.WriteLine("zeroauthorize: " + Name);
// allow anonymous skips all authorization
if (context.Filters.Any(item => item is IAllowAnonymousFilter) || !Enabled)
{
return;
}
context.Result = new AcceptedResult();
//context.Result = new ForbidResult();
//await base.OnAuthorizationAsync(context);
var httpContext = context.HttpContext;
var authService = httpContext.RequestServices.GetRequiredService<IAuthorizationService>();
//var username = context.HttpContext.User.Identity.Name;
ClaimsPrincipal user = context.HttpContext.User;
//Console.WriteLine($"{username} just logged in!");
bool isAuthenticated = user.Identity.IsAuthenticated;
bool isZeroUser = user.HasClaim(Constants.Auth.Claims.IsZero, Constants.Auth.Claims.IsZero);
if (!isAuthenticated || !isZeroUser)
{
context.Result = new StatusCodeResult(401);
}
}
}
}
+13
View File
@@ -7,6 +7,19 @@
public const string Scheme = "zeroCookies";
public const string CookieName = "zero.session";
public static class Claims
{
public const string IsZero = "zero.claim.iszero";
public const string UserId = "zero.claim.userid";
public const string UserName = "zero.claim.username";
public const string RoleId = "zero.claim.roleid";
public const string SecurityStamp = "zero.claim.securitystamp";
}
}
public static class Database
+6
View File
@@ -30,6 +30,12 @@ namespace zero.Core.Extensions
}
public static string EnsureSurroundedWith(this string input, char toSurroundWith)
{
return input.EnsureStartsWith(toSurroundWith).EnsureEndsWith(toSurroundWith);
}
public static string TrimEnd(this string value, string forRemoving)
{
if (String.IsNullOrEmpty(value)) return value;
+2 -2
View File
@@ -43,7 +43,7 @@ namespace zero.Core.Identity
/// <inheritdoc />
public Task SetEmailAsync(TUser user, string email, CancellationToken cancellationToken)
{
user.Email = email;
user.Email = email.ToLowerInvariant();
return Task.CompletedTask;
}
@@ -59,7 +59,7 @@ namespace zero.Core.Identity
/// <inheritdoc />
public Task SetNormalizedEmailAsync(TUser user, string normalizedEmail, CancellationToken cancellationToken)
{
user.Email = normalizedEmail;
user.Email = normalizedEmail.ToLowerInvariant();
return Task.CompletedTask;
}
}
+2 -2
View File
@@ -93,11 +93,11 @@ namespace zero.Core.Identity
/// <inheritdoc />
public Task<TUser> FindByIdAsync(string userId, CancellationToken cancellationToken)
public async Task<TUser> FindByIdAsync(string userId, CancellationToken cancellationToken)
{
using (IAsyncDocumentSession session = Raven.OpenAsyncSession())
{
return session.LoadAsync<TUser>(userId);
return await session.LoadAsync<TUser>(userId);
}
}
@@ -6,7 +6,6 @@ using zero.Core.Api;
namespace zero.Web.Controllers
{
[AllowAnonymous]
public class ApplicationsController : BackofficeController
{
private IApplicationsApi Api { get; set; }
@@ -4,11 +4,11 @@ using System;
using System.Threading.Tasks;
using zero.Core;
using zero.Core.Api;
using zero.Core.Entities;
using zero.Core.Auth;
namespace zero.Web.Controllers
{
[AllowAnonymous]
[ZeroAuthorize]
public class AuthenticationController : BackofficeController
{
private IAuthenticationApi Api { get; set; }
@@ -22,8 +22,6 @@ namespace zero.Web.Controllers
/// <summary>
/// Get the currently logged in user
/// </summary>
[AllowAnonymous]
//[Authorize()]
public async Task<IActionResult> GetUser()
{
return Json(await Api.GetUser());
@@ -1,8 +1,10 @@
using Microsoft.AspNetCore.Mvc;
using zero.Core;
using zero.Core.Auth;
namespace zero.Web.Controllers
{
[ZeroAuthorize]
public abstract class BackofficeController : Controller
{
protected IZeroConfiguration Configuration { get; set; }
@@ -6,7 +6,6 @@ using zero.Core.Api;
namespace zero.Web.Controllers
{
[AllowAnonymous]
public class CountriesController : BackofficeController
{
private ICountriesApi Api { get; set; }
+2 -1
View File
@@ -2,11 +2,12 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using zero.Core;
using zero.Core.Auth;
using zero.Core.Extensions;
namespace zero.Web.Controllers
{
[AllowAnonymous]
[ZeroAuthorize(false)]
public class IndexController : BackofficeController
{
private ZeroOptions Options { get; set; }
@@ -7,7 +7,6 @@ using zero.Core.Api;
namespace zero.Web.Controllers
{
[AllowAnonymous]
public class PageTreeController : BackofficeController
{
private IPageTreeApi Api { get; set; }
@@ -5,7 +5,6 @@ using zero.Core.Api;
namespace zero.Web.Controllers
{
[AllowAnonymous]
public class SectionsController : BackofficeController
{
private ISectionsApi Api { get; set; }
@@ -5,7 +5,6 @@ using zero.Core.Api;
namespace zero.Web.Controllers
{
[AllowAnonymous]
public class SettingsController : BackofficeController
{
private ISettingsApi Api { get; set; }
+5 -4
View File
@@ -1,6 +1,4 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using System;
@@ -8,6 +6,7 @@ using System.Linq;
using System.Threading.Tasks;
using zero.Core;
using zero.Core.Api;
using zero.Core.Auth;
using zero.Core.Entities;
using zero.Core.Entities.Setup;
using zero.Core.Extensions;
@@ -15,7 +14,7 @@ using zero.Web.Controllers;
namespace zero.Web.Setup
{
[AllowAnonymous]
[ZeroAuthorize(false)]
public class SetupController : BackofficeController
{
protected ISetupApi Api { get; private set; }
@@ -32,6 +31,7 @@ namespace zero.Web.Setup
Options = options.CurrentValue;
}
public IActionResult Index()
{
if (!Configuration.ZeroVersion.IsNullOrEmpty())
@@ -42,6 +42,7 @@ namespace zero.Web.Setup
return View("/Views/Setup.cshtml");
}
[HttpPost]
public async Task<IActionResult> Install([FromBody] SetupModel model)
{
+25 -11
View File
@@ -1,5 +1,4 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
using zero.Core;
@@ -23,15 +22,6 @@ namespace zero.Web.Controllers
}
[HttpGet]
public IActionResult GetUser()
{
return Json(new
{
user = HttpContext.User.Identity.IsAuthenticated
});
}
[HttpPost]
public async Task<IActionResult> Login([FromQuery] string username, [FromQuery] string password)
{
@@ -44,5 +34,29 @@ namespace zero.Web.Controllers
result
});
}
[ZeroAuthorize]
public async Task<IActionResult> GetUser()
{
User user = await SignInManager.UserManager.GetUserAsync(HttpContext.User);
return Json(new
{
user
});
}
[HttpPost]
public async Task<IActionResult> Logout()
{
await SignInManager.SignOutAsync();
return Json(new
{
success = true
});
}
}
}
+11
View File
@@ -0,0 +1,11 @@
namespace zero.Web.Models
{
public class LoginModel
{
public string Email { get; set; }
public string Password { get; set; }
public bool IsPersistent { get; set; } = true;
}
}
+5 -30
View File
@@ -1,4 +1,3 @@
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
@@ -13,10 +12,8 @@ using Newtonsoft.Json.Serialization;
using Raven.Client.Documents;
using Raven.Client.Documents.Conventions;
using System;
using System.Threading.Tasks;
using zero.Core;
using zero.Core.Api;
using zero.Core.Entities;
using zero.Core.Extensions;
namespace zero.Web
@@ -123,6 +120,7 @@ namespace zero.Web
services.AddTransient<ISettingsApi, SettingsApi>();
services.AddTransient<IAuthenticationApi, AuthenticationApi>();
services.AddTransient<ICountriesApi, CountriesApi>();
services.AddTransient<IUserApi, UserApi>();
}
/// <summary>
@@ -153,34 +151,11 @@ namespace zero.Web
//app.UseCors();
app.UseWhen(ctx => ctx.Request.Path.ToString().StartsWith(zeroPath.TrimEnd('/')), zeroApp =>
app.UseZero();
app.Run(async (context) =>
{
zeroApp.UseRouting();
zeroApp.UseAuthentication();
zeroApp.UseAuthorization();
zeroApp.UseEndpoints(endpoints =>
{
// setup route
endpoints.MapControllerRoute(
name: "setup",
pattern: zeroPath + "setup",
defaults: new
{
controller = "Setup",
action = "Index"
}
);
// routes for API
endpoints.MapControllerRoute(
name: "api",
pattern: zeroPath + "api/{controller=Index}/{action=Index}/{id?}"
);
// fallbacks for SPA
endpoints.MapFallbackToController(zeroPath + "{**path}", "Index", "Index");
});
await context.Response.WriteAsync("from app");
});
}
}
@@ -0,0 +1,63 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using System;
using zero.Core;
using zero.Core.Extensions;
namespace zero.Web
{
public static class ZeroApplicationBuilderExtensions
{
public static IApplicationBuilder UseZero(this IApplicationBuilder app)
{
ZeroOptions options = app.ApplicationServices.GetService<IOptionsMonitor<ZeroOptions>>().CurrentValue;
string path = options.BackofficePath.EnsureStartsWith('/').TrimEnd('/');
// map backoffice
//app.UseWhen()
app.UseWhen(ctx => ctx.Request.Path.ToString().StartsWith(path), builder =>
{
builder.UseRouting();
builder.UseAuthentication();
builder.UseAuthorization();
//builder.UseMiddleware<ZeroMiddleware>(options);
builder.UseEndpoints(endpoints =>
{
// setup route
endpoints.MapControllerRoute(
name: "setup",
pattern: path + "/setup",
defaults: new
{
controller = "Setup",
action = "Index"
}
);
// routes for API
endpoints.MapControllerRoute(
name: "api",
pattern: path + "/api/{controller}/{action}/{id?}"
);
// fallbacks for SPA
endpoints.MapFallbackToController(path + "/{**path}", "Index", "Index");
});
});
return app;
}
//public static ZeroBuilder AddZero(this IApplicationBuilder app, Action<ZeroOptions> setupAction)
//{
// return services.AddZero().WithOptions(setupAction);
//}
}
}
+9 -2
View File
@@ -25,18 +25,25 @@ namespace zero.Web
Services.AddIdentity<User, UserRole>(opts =>
{
opts.ClaimsIdentity.UserIdClaimType = Constants.Auth.Claims.UserId;
opts.ClaimsIdentity.UserNameClaimType = Constants.Auth.Claims.UserName;
opts.ClaimsIdentity.RoleClaimType = Constants.Auth.Claims.RoleId;
opts.ClaimsIdentity.SecurityStampClaimType = Constants.Auth.Claims.SecurityStamp;
opts.Password.RequireDigit = false;
opts.Password.RequireLowercase = false;
opts.Password.RequireUppercase = false;
opts.Password.RequireNonAlphanumeric = false;
opts.Password.RequiredLength = 12;
opts.Password.RequiredLength = 8;
opts.Password.RequiredUniqueChars = 1;
opts.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
opts.Lockout.MaxFailedAccessAttempts = 5;
opts.Lockout.AllowedForNewUsers = true;
}).AddDefaultTokenProviders();
})
.AddClaimsPrincipalFactory<ZeroClaimsPrinicipalFactory>()
.AddDefaultTokenProviders();
Services.ConfigureApplicationCookie(opts =>
{
+26
View File
@@ -0,0 +1,26 @@
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using System.Security.Claims;
using System.Threading.Tasks;
using zero.Core;
using zero.Core.Entities;
namespace zero.Web
{
public class ZeroClaimsPrinicipalFactory : UserClaimsPrincipalFactory<User, UserRole>
{
public ZeroClaimsPrinicipalFactory(UserManager<User> userManager, RoleManager<UserRole> roleManager, IOptions<IdentityOptions> optionsAccessor) : base(userManager, roleManager, optionsAccessor)
{
}
public async override Task<ClaimsPrincipal> CreateAsync(User user)
{
ClaimsPrincipal principal = await base.CreateAsync(user);
((ClaimsIdentity)principal.Identity).AddClaim(new Claim(Constants.Auth.Claims.IsZero, Constants.Auth.Claims.IsZero));
return principal;
}
}
}
+74
View File
@@ -0,0 +1,74 @@
using System;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using zero.Core;
namespace zero.Web
{
public class ZeroMiddleware
{
private RequestDelegate Next { get; set; }
private ZeroOptions Options { get; set; }
public ZeroMiddleware(RequestDelegate next, ZeroOptions options)
{
Next = next;
Options = options;
}
public async Task Invoke(HttpContext httpContext)
{
await httpContext.Response.WriteAsync("from zero");
//var context = null; // new AspNetCoreDashboardContext(_storage, _options, httpContext);
//var findResult = Routes.FindDispatcher(httpContext.Request.Path.Value);
//if (findResult == null)
//{
// await Next.Invoke(httpContext);
// return;
//}
// ReSharper disable once LoopCanBeConvertedToQuery
//foreach (var filter in Options.Authorization)
//{
// if (!filter.Authorize(context))
// {
// var isAuthenticated = httpContext.User?.Identity?.IsAuthenticated;
// httpContext.Response.StatusCode = isAuthenticated == true
// ? (int)HttpStatusCode.Forbidden
// : (int)HttpStatusCode.Unauthorized;
// return;
// }
//}
//if (!_options.IgnoreAntiforgeryToken)
//{
// var antiforgery = httpContext.RequestServices.GetService<IAntiforgery>();
// if (antiforgery != null)
// {
// var requestValid = await antiforgery.IsRequestValidAsync(httpContext);
// if (!requestValid)
// {
// // Invalid or missing CSRF token
// httpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
// return;
// }
// }
//}
//context.UriMatch = findResult.Item2;
//await findResult.Item1.Dispatch(context);
}
}
}