using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Extensions.Logging; using Raven.Client.Documents; using Raven.Client.Documents.Session; using System.Security.Claims; namespace zero.Applications; 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; } IList _apps; /// /// The system application is bound to the context in case no application was resolved /// SystemApplication _systemApplication; public ApplicationResolver(IZeroStore store, IZeroOptions options, ILogger logger, IHandlerHolder handler = null) { Store = store; Options = options; Logger = logger; Handler = handler; _systemApplication = new() { Id = "system", Name = "zero system", Database = Options.For().Database, Alias = "system" }; } /// public async Task Resolve(HttpContext context, ClaimsPrincipal user) { if (context?.Request == null) { Logger.LogWarning("Could not resolve application as HTTP request is null"); return _systemApplication; } Application app; if (context.IsBackofficeRequest(Options.ZeroPath)) { ZeroUser userEntity = await GetBackofficeUser(user); app = await ResolveFromBackofficeHandlers(context, userEntity) ?? _systemApplication; } else { app = await ResolveFromHandlers(context) ?? await ResolveFromRequest(context); } if (app == null) { Logger.LogWarning("Could not resolve application for host {host}", context.Request.Host); return _systemApplication; } return app; } /// public async Task ResolveFromHandlers(HttpContext context) { IList apps = await GetApplications(); IEnumerable handlers = Handler.GetAll(); foreach (IApplicationResolverHandler handler in handlers) { if (handler.TryResolve(context, apps, out Application resolved)) { return resolved; } } return null; } /// public async Task ResolveFromBackofficeHandlers(HttpContext context, ZeroUser user) { IList apps = await GetApplications(); IEnumerable handlers = Handler.GetAll(); foreach (IBackofficeApplicationResolverHandler handler in handlers) { if (handler.TryResolve(context, apps, user, out Application resolved)) { return resolved; } } return null; } /// public async Task ResolveFromUser(ClaimsPrincipal user) { ZeroUser userEntity = await GetBackofficeUser(user); return await ResolveFromUser(userEntity); } /// public async Task ResolveFromUser(ZeroUser 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"); } IAsyncDocumentSession session = Store.Session(global: true); return await session.LoadAsync(appId); } /// public Task ResolveFromRequest(HttpContext context) => ResolveFromUri(context.Request.GetEncodedUrl()); /// public async Task ResolveFromUri(string uriString) => ResolveFromUriInternal(new Uri(uriString, UriKind.Absolute), await GetApplications()); /// public async Task ResolveFromUri(Uri uri) => ResolveFromUriInternal(uri, await GetApplications()); /// /// Get matching application from an URI /// Application ResolveFromUriInternal(Uri uri, IList apps) { foreach (Application 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; } IAsyncDocumentSession session = Store.Session(global: true); _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); IAsyncDocumentSession session = Store.Session(global: true); 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(ZeroUser user); }