using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; 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.Entities; using zero.Core.Extensions; using zero.Core.Identity; using zero.Core.Options; namespace zero.Core.Api { public class ApplicationContext : IApplicationContext { /// public IApplication App { get; protected set; } /// public string AppId { get; protected set; } protected IDocumentStore Raven { get; private set; } protected IZeroOptions Options { get; private set; } protected UserManager UserManager { get; private set; } public ApplicationContext(IDocumentStore raven, IZeroOptions options, UserManager userManager) { Raven = raven; Options = options; UserManager = userManager; } /// public async Task Resolve(HttpContext context) { if (context?.Request == null) { return null; } IApplication app; if (IsBackofficeRequest(context)) { app = await ResolveFromUser(context.User); } else { app = await ResolveFromRequest(context); } App = app; AppId = app?.Id; return app; } /// public async Task TrySwitchForUser(User user, string appId) { if (user == null || appId.IsNullOrEmpty()) { return false; } string[] allowedAppIds = GetAllowedAppIdsForUser(user); bool isMainId = appId.Equals(user.AppId, StringComparison.InvariantCultureIgnoreCase); bool isAllowedId = allowedAppIds.Contains(appId, StringComparer.InvariantCultureIgnoreCase); if (user.IsSuper || isMainId || isAllowedId) { user.CurrentAppId = appId; IdentityResult updateResult = await UserManager.UpdateAsync(user); return updateResult.Succeeded; } return false; } /// public async Task ResolveFromUser(ClaimsPrincipal user) { User userEntity = await UserManager.GetUserAsync(user); return await ResolveFromUser(userEntity); } /// public async Task ResolveFromUser(User user) { if (user == null) { return null; } string appId = null; string[] allowedAppIds = GetAllowedAppIdsForUser(user); if (!user.CurrentAppId.IsNullOrEmpty()) { if (user.IsSuper || allowedAppIds.Contains(user.CurrentAppId, StringComparer.InvariantCultureIgnoreCase)) { appId = user.CurrentAppId; } else { appId = user.AppId; } } else { appId = user.AppId; } if (appId.IsNullOrEmpty()) { throw new Exception($"User entity ${user.Id} needs a valid AppId"); } using (IAsyncDocumentSession session = Raven.OpenAsyncSession()) { return await session.LoadAsync(appId); } } /// public async Task ResolveFromRequest(HttpContext context) { return await ResolveFromUri(context.Request.GetEncodedUrl()); } /// public async Task ResolveFromUri(string uriString) { return ResolveFromUriInternal(new Uri(uriString, UriKind.Absolute), await GetApplications()); } /// public async Task ResolveFromUri(Uri uri) { return ResolveFromUriInternal(uri, await GetApplications()); } /// /// Get matching application from an URI /// IApplication ResolveFromUriInternal(Uri uri, IList apps) { string[] protocols = new string[3] { "https://", "http://", "//" }; IApplication currentApp = null; foreach (Application app in apps) { if (app.Domains?.Length < 1) { continue; } foreach (Uri domain in app.Domains) { //string normalizedDomain = domain; //if (!protocols.Any(protocol => domain.StartsWith(protocol, StringComparison.OrdinalIgnoreCase))) //{ // normalizedDomain = "http://" + domain; //} //UriBuilder uriBuilder = new UriBuilder(normalizedDomain); //if (!uriBuilder.Uri.IsAbsoluteUri) //{ // continue; //} int compareResult = Uri.Compare(uri, domain, UriComponents.HostAndPort, UriFormat.SafeUnescaped, StringComparison.OrdinalIgnoreCase); if (compareResult == 0) { currentApp = app; break; } } } return currentApp; } /// /// Get all applications to choose from /// async Task> GetApplications() { using (IAsyncDocumentSession session = Raven.OpenAsyncSession()) { return await session.Query().ToListAsync(); } } /// /// Get applications the user has access to /// string[] GetAllowedAppIdsForUser(User user) { IEnumerable permissions = user.Claims .Where(claim => claim.Type == Constants.Auth.Claims.Permission && claim.Value.StartsWith(Permissions.Applications)) .Select(x => Permission.FromClaim(x.ToClaim(), Permissions.Applications)); string[] appIds = permissions.Where(x => x.IsTrue).Select(x => x.NormalizedKey).ToArray(); return appIds; } /// /// Whether the current request is a backoffice request /// bool IsBackofficeRequest(HttpContext context) { string path = Options.BackofficePath.EnsureStartsWith('/').TrimEnd('/'); return context.Request.Path.ToString().StartsWith(path); } } public interface IApplicationContext { /// /// Currently loaded application /// IApplication App { get; } /// /// Current loaded application Id /// string AppId { get; } /// /// Resolves the current application from either the backoffice user (in case it is backoffice request) /// or the domain (in case it is frontend request). /// The resolved data is stored in the App + AppId properties. /// Task Resolve(HttpContext context); /// /// Try to switch the current application for a user /// Task TrySwitchForUser(User user, string appId); /// /// Resolves the current application from the request path /// Task ResolveFromRequest(HttpContext context); /// /// Get matching application from an URI string /// Task ResolveFromUri(string uriString); /// /// Get matching application from an URI /// Task ResolveFromUri(Uri uri); /// /// Resolves the current application from the logged-in backoffice user. /// This method won't return apps the user has no access to. /// Task ResolveFromUser(ClaimsPrincipal user); /// /// Resolves the current application from a user. /// This method won't return apps the user has no access to. /// Task ResolveFromUser(User user); } }