using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Security.Claims; using System.Threading.Tasks; using zero.Core.Caches.Internal; using zero.Core.Database; using zero.Core.Entities; using zero.Core.Extensions; using zero.Core.Handlers; using zero.Core.Options; namespace zero.Core { public class ApplicationResolver : IApplicationResolver { ApplicationCache _applicationCache; BackofficeUserCache _backofficeUserCache; protected IZeroOptions Options { get; private set; } protected ILogger Logger { get; private set; } protected IHandlerHolder Handler { get; private set; } public ApplicationResolver(IZeroStore store, IZeroOptions options, ILogger logger, IHandlerHolder handler = null) { _applicationCache = new ApplicationCache(store); _backofficeUserCache = new BackofficeUserCache(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; } Application 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); app = _applicationCache.All.FirstOrDefault(); } return app; } /// public async Task ResolveFromUser(ClaimsPrincipal user) { BackofficeUserCache.Result userEntity = GetBackofficeUser(user); return userEntity != null ? await ResolveFromUser(new BackofficeUser() { Id = userEntity.Id, AppId = userEntity.AppId, CurrentAppId = userEntity.CurrentAppId, Claims = userEntity.Claims, IsSuper = userEntity.IsSuper }) : null; } /// public Task ResolveFromUser(BackofficeUser 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"); } return Task.FromResult(_applicationCache.ById(appId)); } /// public async Task ResolveFromRequest(HttpContext context) { return Handler.Get()?.Resolve(context.Request, _applicationCache.All) ?? await ResolveFromUri(context.Request.GetEncodedUrl()); } /// public Task ResolveFromUri(string uriString) { return Task.FromResult(ResolveFromUriInternal(new Uri(uriString, UriKind.Absolute), _applicationCache.All)); } /// public Task ResolveFromUri(Uri uri) { return Task.FromResult(ResolveFromUriInternal(uri, _applicationCache.All)); } /// /// Get matching application from an URI /// Application ResolveFromUriInternal(Uri uri, IEnumerable 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 backoffice user from claims principal /// BackofficeUserCache.Result GetBackofficeUser(ClaimsPrincipal user) { string userId = user.FindFirstValue(Constants.Auth.Claims.UserId); return _backofficeUserCache.ById(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(BackofficeUser user); } }