using Microsoft.AspNetCore.Identity; using Raven.Client.Documents; using Raven.Client.Documents.Session; using System; using System.Collections.Generic; using System.Linq; using System.Security.Claims; using System.Threading.Tasks; using zero.Core.Database; using zero.Core.Entities; using zero.Core.Extensions; using zero.Core.Identity; namespace zero.Core.Api { public class AuthenticationApi : IAuthenticationApi { protected IZeroContext Context { get; set; } protected SignInManager SignInManager { get; private set; } protected IZeroStore Store { get; set; } public AuthenticationApi(IZeroContext context, SignInManager signInManager, IZeroStore store) { Context = context; SignInManager = signInManager; Store = store; } /// public bool IsLoggedIn() { return SignInManager.IsSignedIn(Context.BackofficeUser); } /// public bool IsSuper() { return Context.BackofficeUser.HasClaim(Constants.Auth.Claims.IsSuper, PermissionsValue.True); } /// public bool IsAdmin() { return Context.BackofficeUser.HasClaim(Constants.Auth.Claims.Role, "administrator"); // TODO use constant (in setup as well) } /// public async Task GetUser() { return await SignInManager.UserManager.GetUserAsync(Context.BackofficeUser); } /// public IList GetPermissions(string prefix = null) { return Context.BackofficeUser.Claims .Where(claim => claim.Type == Constants.Auth.Claims.Permission && (prefix == null || claim.Value.StartsWith(prefix))) .Select(claim => Permission.FromClaim(claim, prefix)) .ToList(); } /// public Permission GetPermission(string key = null) { Claim claim = Context.BackofficeUser.Claims.FirstOrDefault(claim => claim.Type == Constants.Auth.Claims.Permission && claim.Value.StartsWith(key + ":")); return Permission.FromClaim(claim); } /// public async Task Login(string email, string password, bool isPersistent) { EntityResult result = new EntityResult(); BackofficeUser user = await SignInManager.UserManager.FindByNameAsync(email); if (user == null) { result.AddError("@login.errors.wrongcredentials"); // TODO we don't need translations here, but return an enum, so the app itself can translate the error return result; } // TODO probably move this logic into a custom SignInManager which overrides CanSignInAsync() // see https://stackoverflow.com/a/35484758/670860 else if (!user.IsActive) { result.AddError("@login.errors.disabled"); return result; } SignInResult signInResult = await SignInManager.PasswordSignInAsync(user, password, isPersistent, true); if (!signInResult.Succeeded) { if (signInResult.IsLockedOut) { result.AddError("@login.errors.lockedout"); } else if (signInResult.IsNotAllowed) { result.AddError("@login.errors.notallowed"); } else if (signInResult.RequiresTwoFactor) { result.AddError("@login.errors.requirestwofactor"); } else { result.AddError("@login.errors.wrongcredentials"); } return result; } return EntityResult.Success(); } /// public async Task Logout() { await SignInManager.SignOutAsync(); } /// public string GetUserId() { return SignInManager.UserManager.GetUserId(Context.BackofficeUser); } /// public async Task TrySwitchApp(string appId) { IZeroDocumentSession session = Store.Session(global: true); BackofficeUser user = await GetUser(); if (user == null || appId.IsNullOrEmpty()) { return false; } string[] allowedAppIds = user.GetAllowedAppIds(); bool isMainId = appId.Equals(user.AppId, StringComparison.InvariantCultureIgnoreCase); bool isAllowedId = allowedAppIds.Contains(appId, StringComparer.InvariantCultureIgnoreCase); if (user.IsSuper || isMainId || isAllowedId) { user.CurrentAppId = appId; //byte[] bytes = new byte[20]; //RandomNumberGenerator.Fill(bytes); //user.SecurityStamp = Base32.ToBase32(bytes); // TODO update security stamp but Base32 is .net core internal await session.StoreAsync(user); await session.SaveChangesAsync(); return true; } return false; } } public interface IAuthenticationApi { /// /// Get currently logged-in user /// Task GetUser(); /// /// Whether a user is currently logged-in /// bool IsLoggedIn(); /// /// Whether the current user is the super user who created the zero instance /// bool IsSuper(); /// /// Whether the current user belongs to the administrator role (will always return false if this role gets deleted) /// bool IsAdmin(); /// /// Logs a zero-user in and sets cookie /// Task Login(string email, string password, bool isPersistent); /// /// Logs out the current user /// Task Logout(); /// /// Get the ID of the currently logged in user /// string GetUserId(); /// /// Get all permissions for the current user with an optional prefix /// IList GetPermissions(string prefix = null); /// /// Get a single permissions by key /// Permission GetPermission(string key = null); /// /// Tries to switch the currently loaded backoffice application for the current user /// Task TrySwitchApp(string appId); } }