using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Extensions.Logging; 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.Handlers; using zero.Core.Identity; using zero.Core.Options; namespace zero.Core { public class ApplicationResolver : IApplicationResolver { protected IZeroStore Store { get; private set; } protected IZeroOptions Options { get; private set; } protected ILogger Logger { get; private set; } protected IHandlerHolder Handler { get; private set; } private IList Apps { get; set; } public ApplicationResolver(IZeroStore store, IZeroOptions options, ILogger logger, IHandlerHolder handler = null) { Store = store; Options = options; Logger = logger; Handler = handler; } /// public async Task Resolve(HttpContext context, ClaimsPrincipal user) { if (context?.Request == null) { Logger.LogWarning("Could not resolve application as HTTP request is null"); return null; } IApplication app; if (context.IsBackofficeRequest(Options.BackofficePath)) { app = await ResolveFromUser(user); } else { app = await ResolveFromRequest(context); } if (app == null) { //Logger.LogWarning("Could not resolve application for host {host}", context.Request.Host); IList apps = await GetApplications(); app = apps.FirstOrDefault(); } return app; } /// public async Task ResolveFromUser(ClaimsPrincipal user) { IBackofficeUser userEntity = await GetBackofficeUser(user); return await ResolveFromUser(userEntity); } /// public async Task ResolveFromUser(IBackofficeUser user) { if (user == null) { return null; } string appId; string[] allowedAppIds = user.GetAllowedAppIds(); 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 = Store.OpenCoreSession(); return await session.LoadAsync(appId); } /// public async Task ResolveFromRequest(HttpContext context) { return Handler.Get()?.Resolve(context.Request, await GetApplications()) ?? 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) { foreach (IApplication app in apps) { if (app.Domains?.Length < 1) { Logger.LogWarning("No domains specified for app {app}", app.Id); continue; } foreach (Uri domain in app.Domains) { int compareResult = Uri.Compare(uri, domain, UriComponents.HostAndPort, UriFormat.SafeUnescaped, StringComparison.OrdinalIgnoreCase); if (compareResult == 0) { return app; } } } return null; } /// /// Get all applications to choose from /// async Task> GetApplications() { if (Apps != null) { return Apps; } using IAsyncDocumentSession session = Store.OpenCoreSession(); Apps = await session.Query().ToListAsync(); return Apps; } /// /// Get backoffice user from claims principal /// async Task GetBackofficeUser(ClaimsPrincipal user) { string userId = user.FindFirstValue(Constants.Auth.Claims.UserId); using IAsyncDocumentSession session = Store.OpenCoreSession(); return await session.LoadAsync(userId); } } public interface IApplicationResolver { /// /// Resolves the current application from either the backoffice user (in case it is backoffice request) /// or the domain (in case it is frontend request). /// Task Resolve(HttpContext context, ClaimsPrincipal user); /// /// 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(IBackofficeUser user); } }