diff --git a/src/Umbraco.Core/Cookie/ICookieManager.cs b/src/Umbraco.Core/Cookie/ICookieManager.cs index af0ee7b1f6..0eced07b37 100644 --- a/src/Umbraco.Core/Cookie/ICookieManager.cs +++ b/src/Umbraco.Core/Cookie/ICookieManager.cs @@ -7,4 +7,5 @@ namespace Umbraco.Core.Cookie void SetCookieValue(string cookieName, string value); bool HasCookie(string cookieName); } + } diff --git a/src/Umbraco.Core/Request/IRequestAccessor.cs b/src/Umbraco.Core/Request/IRequestAccessor.cs new file mode 100644 index 0000000000..52d532de26 --- /dev/null +++ b/src/Umbraco.Core/Request/IRequestAccessor.cs @@ -0,0 +1,8 @@ +namespace Umbraco.Core.Request +{ + public interface IRequestAccessor + { + string GetRequestValue(string name); + string GetQueryStringValue(string culture); + } +} diff --git a/src/Umbraco.Web/Routing/ContentFinderByIdPath.cs b/src/Umbraco.Core/Routing/ContentFinderByIdPath.cs similarity index 88% rename from src/Umbraco.Web/Routing/ContentFinderByIdPath.cs rename to src/Umbraco.Core/Routing/ContentFinderByIdPath.cs index bf7d5ef7c4..c4bfd5a697 100644 --- a/src/Umbraco.Web/Routing/ContentFinderByIdPath.cs +++ b/src/Umbraco.Core/Routing/ContentFinderByIdPath.cs @@ -4,6 +4,7 @@ using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Models.PublishedContent; using System.Globalization; +using Umbraco.Core.Request; namespace Umbraco.Web.Routing { @@ -16,14 +17,14 @@ namespace Umbraco.Web.Routing public class ContentFinderByIdPath : IContentFinder { private readonly ILogger _logger; - private readonly IHttpContextAccessor _httpContextAccessor; + private readonly IRequestAccessor _requestAccessor; private readonly IWebRoutingSection _webRoutingSection; - public ContentFinderByIdPath(IWebRoutingSection webRoutingSection, ILogger logger, IHttpContextAccessor httpContextAccessor) + public ContentFinderByIdPath(IWebRoutingSection webRoutingSection, ILogger logger, IRequestAccessor requestAccessor) { _webRoutingSection = webRoutingSection ?? throw new System.ArgumentNullException(nameof(webRoutingSection)); _logger = logger ?? throw new System.ArgumentNullException(nameof(logger)); - _httpContextAccessor = httpContextAccessor; + _requestAccessor = requestAccessor; } /// @@ -56,12 +57,14 @@ namespace Umbraco.Web.Routing if (node != null) { - var httpContext = _httpContextAccessor.GetRequiredHttpContext(); + + var cultureFromQuerystring = _requestAccessor.GetQueryStringValue("culture"); + //if we have a node, check if we have a culture in the query string - if (httpContext.Request.QueryString.ContainsKey("culture")) + if (!string.IsNullOrEmpty(cultureFromQuerystring)) { //we're assuming it will match a culture, if an invalid one is passed in, an exception will throw (there is no TryGetCultureInfo method), i think this is ok though - frequest.Culture = CultureInfo.GetCultureInfo(httpContext.Request.QueryString["culture"]); + frequest.Culture = CultureInfo.GetCultureInfo(cultureFromQuerystring); } frequest.PublishedContent = node; diff --git a/src/Umbraco.Web/Routing/ContentFinderByPageIdQuery.cs b/src/Umbraco.Core/Routing/ContentFinderByPageIdQuery.cs similarity index 69% rename from src/Umbraco.Web/Routing/ContentFinderByPageIdQuery.cs rename to src/Umbraco.Core/Routing/ContentFinderByPageIdQuery.cs index fb79e13dbc..6a9adda5f8 100644 --- a/src/Umbraco.Web/Routing/ContentFinderByPageIdQuery.cs +++ b/src/Umbraco.Core/Routing/ContentFinderByPageIdQuery.cs @@ -1,4 +1,6 @@ -namespace Umbraco.Web.Routing +using Umbraco.Core.Request; + +namespace Umbraco.Web.Routing { /// /// This looks up a document by checking for the umbPageId of a request/query string @@ -9,17 +11,17 @@ /// public class ContentFinderByPageIdQuery : IContentFinder { - private readonly IHttpContextAccessor _httpContextAccessor; + private readonly IRequestAccessor _requestAccessor; - public ContentFinderByPageIdQuery(IHttpContextAccessor httpContextAccessor) + public ContentFinderByPageIdQuery(IRequestAccessor requestAccessor) { - _httpContextAccessor = httpContextAccessor; + _requestAccessor = requestAccessor; } public bool TryFindContent(IPublishedRequest frequest) { int pageId; - if (int.TryParse(_httpContextAccessor.GetRequiredHttpContext().Request["umbPageID"], out pageId)) + if (int.TryParse(_requestAccessor.GetRequestValue("umbPageID"), out pageId)) { var doc = frequest.UmbracoContext.Content.GetById(pageId); diff --git a/src/Umbraco.Web/Routing/PublishedRouter.cs b/src/Umbraco.Core/Routing/PublishedRouter.cs similarity index 84% rename from src/Umbraco.Web/Routing/PublishedRouter.cs rename to src/Umbraco.Core/Routing/PublishedRouter.cs index 1a6048e4ec..90d0d8876c 100644 --- a/src/Umbraco.Web/Routing/PublishedRouter.cs +++ b/src/Umbraco.Core/Routing/PublishedRouter.cs @@ -4,13 +4,12 @@ using System.Threading; using System.Globalization; using System.IO; using Umbraco.Core; -using Umbraco.Web.Composing; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Request; using Umbraco.Core.Services; -using Umbraco.Web.Macros; using Umbraco.Web.Security; namespace Umbraco.Web.Routing @@ -23,13 +22,17 @@ namespace Umbraco.Web.Routing private readonly IWebRoutingSection _webRoutingSection; private readonly ContentFinderCollection _contentFinders; private readonly IContentLastChanceFinder _contentLastChanceFinder; - private readonly ServiceContext _services; private readonly IProfilingLogger _profilingLogger; private readonly IVariationContextAccessor _variationContextAccessor; private readonly ILogger _logger; private readonly IUmbracoSettingsSection _umbracoSettingsSection; - private readonly IHttpContextAccessor _httpContextAccessor; private readonly IPublishedUrlProvider _publishedUrlProvider; + private readonly IRequestAccessor _requestAccessor; + private readonly IPublishedValueFallback _publishedValueFallback; + private readonly IPublicAccessChecker _publicAccessChecker; + private readonly IFileService _fileService; + private readonly IContentTypeService _contentTypeService; + private readonly IPublicAccessService _publicAccessService; /// /// Initializes a new instance of the class. @@ -39,22 +42,30 @@ namespace Umbraco.Web.Routing ContentFinderCollection contentFinders, IContentLastChanceFinder contentLastChanceFinder, IVariationContextAccessor variationContextAccessor, - ServiceContext services, IProfilingLogger proflog, IUmbracoSettingsSection umbracoSettingsSection, - IHttpContextAccessor httpContextAccessor, - IPublishedUrlProvider publishedUrlProvider) + IPublishedUrlProvider publishedUrlProvider, + IRequestAccessor requestAccessor, + IPublishedValueFallback publishedValueFallback, + IPublicAccessChecker publicAccessChecker, + IFileService fileService, + IContentTypeService contentTypeService, + IPublicAccessService publicAccessService) { _webRoutingSection = webRoutingSection ?? throw new ArgumentNullException(nameof(webRoutingSection)); _contentFinders = contentFinders ?? throw new ArgumentNullException(nameof(contentFinders)); _contentLastChanceFinder = contentLastChanceFinder ?? throw new ArgumentNullException(nameof(contentLastChanceFinder)); - _services = services ?? throw new ArgumentNullException(nameof(services)); _profilingLogger = proflog ?? throw new ArgumentNullException(nameof(proflog)); _variationContextAccessor = variationContextAccessor ?? throw new ArgumentNullException(nameof(variationContextAccessor)); _logger = proflog; _umbracoSettingsSection = umbracoSettingsSection ?? throw new ArgumentNullException(nameof(umbracoSettingsSection)); - _httpContextAccessor = httpContextAccessor; _publishedUrlProvider = publishedUrlProvider; + _requestAccessor = requestAccessor; + _publishedValueFallback = publishedValueFallback; + _publicAccessChecker = publicAccessChecker; + _fileService = fileService; + _contentTypeService = contentTypeService; + _publicAccessService = publicAccessService; } /// @@ -358,7 +369,7 @@ namespace Umbraco.Web.Routing /// public ITemplate GetTemplate(string alias) { - return _services.FileService.GetTemplate(alias); + return _fileService.GetTemplate(alias); } /// @@ -498,7 +509,7 @@ namespace Umbraco.Web.Routing var redirect = false; var valid = false; IPublishedContent internalRedirectNode = null; - var internalRedirectId = request.PublishedContent.Value(Constants.Conventions.Content.InternalRedirectId, defaultValue: -1); + var internalRedirectId = request.PublishedContent.Value(_publishedValueFallback, Constants.Conventions.Content.InternalRedirectId, defaultValue: -1); if (internalRedirectId > 0) { @@ -508,7 +519,7 @@ namespace Umbraco.Web.Routing } else { - var udiInternalRedirectId = request.PublishedContent.Value(Constants.Conventions.Content.InternalRedirectId); + var udiInternalRedirectId = request.PublishedContent.Value(_publishedValueFallback, Constants.Conventions.Content.InternalRedirectId); if (udiInternalRedirectId != null) { // try and get the redirect node from a UDI Guid @@ -555,59 +566,66 @@ namespace Umbraco.Web.Routing var path = request.PublishedContent.Path; - var publicAccessAttempt = _services.PublicAccessService.IsProtected(path); + var publicAccessAttempt = _publicAccessService.IsProtected(path); if (publicAccessAttempt) { _logger.Debug("EnsurePublishedContentAccess: Page is protected, check for access"); - var membershipHelper = Current.Factory.GetInstance(); - - if (membershipHelper.IsLoggedIn() == false) + var status = _publicAccessChecker.HasMemberAccessToContent(request.PublishedContent.Id); + switch (status) { - _logger.Debug("EnsurePublishedContentAccess: Not logged in, redirect to login page"); - - var loginPageId = publicAccessAttempt.Result.LoginNodeId; - - if (loginPageId != request.PublishedContent.Id) - request.PublishedContent = request.UmbracoContext.PublishedSnapshot.Content.GetById(loginPageId); - } - else if (_services.PublicAccessService.HasAccess(request.PublishedContent.Id, _services.ContentService, membershipHelper.CurrentUserName, membershipHelper.GetCurrentUserRoles()) == false) - { - _logger.Debug("EnsurePublishedContentAccess: Current member has not access, redirect to error page"); - var errorPageId = publicAccessAttempt.Result.NoAccessNodeId; - if (errorPageId != request.PublishedContent.Id) - request.PublishedContent = request.UmbracoContext.PublishedSnapshot.Content.GetById(errorPageId); - } - else - { - // grab the current member - var member = membershipHelper.GetCurrentMember(); - // if the member has the "approved" and/or "locked out" properties, make sure they're correctly set before allowing access - var memberIsActive = true; - if (member != null) - { - if (member.HasProperty(Constants.Conventions.Member.IsApproved) == false) - memberIsActive = member.Value(Constants.Conventions.Member.IsApproved); - - if (member.HasProperty(Constants.Conventions.Member.IsLockedOut) == false) - memberIsActive = member.Value(Constants.Conventions.Member.IsLockedOut) == false; - } - - if (memberIsActive == false) - { - _logger.Debug( - "Current member is either unapproved or locked out, redirect to error page"); - var errorPageId = publicAccessAttempt.Result.NoAccessNodeId; - if (errorPageId != request.PublishedContent.Id) - request.PublishedContent = - request.UmbracoContext.PublishedSnapshot.Content.GetById(errorPageId); - } - else - { + case PublicAccessStatus.NotLoggedIn: + _logger.Debug("EnsurePublishedContentAccess: Not logged in, redirect to login page"); + SetPublishedContentAsOtherPage(request, publicAccessAttempt.Result.LoginNodeId); + break; + case PublicAccessStatus.AccessDenied: + _logger.Debug("EnsurePublishedContentAccess: Current member has not access, redirect to error page"); + SetPublishedContentAsOtherPage(request, publicAccessAttempt.Result.NoAccessNodeId); + break; + case PublicAccessStatus.LockedOut: + _logger.Debug("Current member is locked out, redirect to error page"); + SetPublishedContentAsOtherPage(request, publicAccessAttempt.Result.NoAccessNodeId); + break; + case PublicAccessStatus.NotApproved: + _logger.Debug("Current member is unapproved, redirect to error page"); + SetPublishedContentAsOtherPage(request, publicAccessAttempt.Result.NoAccessNodeId); + break; + case PublicAccessStatus.AccessAccepted: _logger.Debug("Current member has access"); - } + break; } + + // + // else + // { + // // grab the current member + // var member = _membershipHelper.GetCurrentMember(); + // // if the member has the "approved" and/or "locked out" properties, make sure they're correctly set before allowing access + // var memberIsActive = true; + // if (member != null) + // { + // if (member.HasProperty(Constants.Conventions.Member.IsApproved) == false) + // memberIsActive = member.Value(Constants.Conventions.Member.IsApproved); + // + // if (member.HasProperty(Constants.Conventions.Member.IsLockedOut) == false) + // memberIsActive = member.Value(Constants.Conventions.Member.IsLockedOut) == false; + // } + // + // if (memberIsActive == false) + // { + // _logger.Debug( + // "Current member is either unapproved or locked out, redirect to error page"); + // var errorPageId = publicAccessAttempt.Result.NoAccessNodeId; + // if (errorPageId != request.PublishedContent.Id) + // request.PublishedContent = + // request.UmbracoContext.PublishedSnapshot.Content.GetById(errorPageId); + // } + // else + // { + // _logger.Debug("Current member has access"); + // } + // } } else { @@ -615,6 +633,12 @@ namespace Umbraco.Web.Routing } } + private static void SetPublishedContentAsOtherPage(IPublishedRequest request, int errorPageId) + { + if (errorPageId != request.PublishedContent.Id) + request.PublishedContent = request.UmbracoContext.PublishedSnapshot.Content.GetById(errorPageId); + } + /// /// Finds a template for the current node, if any. /// @@ -637,7 +661,7 @@ namespace Umbraco.Web.Routing var useAltTemplate = request.IsInitialPublishedContent || (_webRoutingSection.InternalRedirectPreservesTemplate && request.IsInternalRedirectPublishedContent); var altTemplate = useAltTemplate - ? _httpContextAccessor.GetRequiredHttpContext().Request[Constants.Conventions.Url.AltTemplate] + ? _requestAccessor.GetRequestValue(Constants.Conventions.Url.AltTemplate) : null; if (string.IsNullOrWhiteSpace(altTemplate)) @@ -674,10 +698,15 @@ namespace Umbraco.Web.Routing _logger.Debug("FindTemplate: Look for alternative template alias={AltTemplate}", altTemplate); // IsAllowedTemplate deals both with DisableAlternativeTemplates and ValidateAlternativeTemplates settings - if (request.PublishedContent.IsAllowedTemplate(altTemplate)) + if (request.PublishedContent.IsAllowedTemplate( + _fileService, + _contentTypeService, + _umbracoSettingsSection.WebRouting.DisableAlternativeTemplates, + _umbracoSettingsSection.WebRouting.ValidateAlternativeTemplates, + altTemplate)) { // allowed, use - var template = _services.FileService.GetTemplate(altTemplate); + var template = _fileService.GetTemplate(altTemplate); if (template != null) { @@ -731,7 +760,7 @@ namespace Umbraco.Web.Routing if (templateId == null) throw new InvalidOperationException("The template is not set, the page cannot render."); - var template = _services.FileService.GetTemplate(templateId.Value); + var template = _fileService.GetTemplate(templateId.Value); if (template == null) throw new InvalidOperationException("The template with Id " + templateId + " does not exist, the page cannot render."); _logger.Debug("GetTemplateModel: Got template id={TemplateId} alias={TemplateAlias}", template.Id, template.Alias); @@ -750,7 +779,7 @@ namespace Umbraco.Web.Routing if (request.PublishedContent.HasProperty(Constants.Conventions.Content.Redirect) == false) return; - var redirectId = request.PublishedContent.Value(Constants.Conventions.Content.Redirect, defaultValue: -1); + var redirectId = request.PublishedContent.Value(_publishedValueFallback, Constants.Conventions.Content.Redirect, defaultValue: -1); var redirectUrl = "#"; if (redirectId > 0) { @@ -759,7 +788,7 @@ namespace Umbraco.Web.Routing else { // might be a UDI instead of an int Id - var redirectUdi = request.PublishedContent.Value(Constants.Conventions.Content.Redirect); + var redirectUdi = request.PublishedContent.Value(_publishedValueFallback, Constants.Conventions.Content.Redirect); if (redirectUdi != null) redirectUrl = _publishedUrlProvider.GetUrl(redirectUdi.Guid); } diff --git a/src/Umbraco.Core/Security/IMemberUserKeyProvider.cs b/src/Umbraco.Core/Security/IMemberUserKeyProvider.cs new file mode 100644 index 0000000000..439e7a82b8 --- /dev/null +++ b/src/Umbraco.Core/Security/IMemberUserKeyProvider.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Core.Security +{ + public interface IMemberUserKeyProvider + { + object GetMemberProviderUserKey(); + } +} diff --git a/src/Umbraco.Core/Security/IPublicAccessChecker.cs b/src/Umbraco.Core/Security/IPublicAccessChecker.cs new file mode 100644 index 0000000000..a47186394e --- /dev/null +++ b/src/Umbraco.Core/Security/IPublicAccessChecker.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Web.Security +{ + public interface IPublicAccessChecker + { + PublicAccessStatus HasMemberAccessToContent(int publishedContentId); + } +} diff --git a/src/Umbraco.Core/Security/PublicAccessStatus.cs b/src/Umbraco.Core/Security/PublicAccessStatus.cs new file mode 100644 index 0000000000..57df423749 --- /dev/null +++ b/src/Umbraco.Core/Security/PublicAccessStatus.cs @@ -0,0 +1,11 @@ +namespace Umbraco.Web.Security +{ + public enum PublicAccessStatus + { + NotLoggedIn, + AccessDenied, + NotApproved, + LockedOut, + AccessAccepted + } +} diff --git a/src/Umbraco.Core/Session/ISessionManager.cs b/src/Umbraco.Core/Session/ISessionManager.cs new file mode 100644 index 0000000000..f3a47202ee --- /dev/null +++ b/src/Umbraco.Core/Session/ISessionManager.cs @@ -0,0 +1,8 @@ +namespace Umbraco.Core.Session +{ + public interface ISessionManager + { + object GetSessionValue(string sessionName); + void SetSessionValue(string sessionName, object value); + } +} diff --git a/src/Umbraco.Infrastructure/Migrations/PostMigrations/ClearCsrfCookies.cs b/src/Umbraco.Infrastructure/Migrations/PostMigrations/ClearCsrfCookies.cs index ab9c946e05..4f176c797f 100644 --- a/src/Umbraco.Infrastructure/Migrations/PostMigrations/ClearCsrfCookies.cs +++ b/src/Umbraco.Infrastructure/Migrations/PostMigrations/ClearCsrfCookies.cs @@ -2,7 +2,6 @@ using Umbraco.Core.Cookie; using Umbraco.Core.Migrations; - namespace Umbraco.Web.Migrations.PostMigrations { /// diff --git a/src/Umbraco.Web/Routing/RedirectTrackingComponent.cs b/src/Umbraco.Infrastructure/Routing/RedirectTrackingComponent.cs similarity index 93% rename from src/Umbraco.Web/Routing/RedirectTrackingComponent.cs rename to src/Umbraco.Infrastructure/Routing/RedirectTrackingComponent.cs index dffb956b1a..0eae54bf7d 100644 --- a/src/Umbraco.Web/Routing/RedirectTrackingComponent.cs +++ b/src/Umbraco.Infrastructure/Routing/RedirectTrackingComponent.cs @@ -6,6 +6,7 @@ using Umbraco.Core.Composing; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Events; using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; using Umbraco.Web.PublishedCache; @@ -26,12 +27,14 @@ namespace Umbraco.Web.Routing private readonly IUmbracoSettingsSection _umbracoSettings; private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; private readonly IRedirectUrlService _redirectUrlService; + private readonly IVariationContextAccessor _variationContextAccessor; - public RedirectTrackingComponent(IUmbracoSettingsSection umbracoSettings, IPublishedSnapshotAccessor publishedSnapshotAccessor, IRedirectUrlService redirectUrlService) + public RedirectTrackingComponent(IUmbracoSettingsSection umbracoSettings, IPublishedSnapshotAccessor publishedSnapshotAccessor, IRedirectUrlService redirectUrlService, IVariationContextAccessor variationContextAccessor) { _umbracoSettings = umbracoSettings; _publishedSnapshotAccessor = publishedSnapshotAccessor; _redirectUrlService = redirectUrlService; + _variationContextAccessor = variationContextAccessor; } public void Initialize() @@ -99,12 +102,12 @@ namespace Umbraco.Web.Routing { var contentCache = _publishedSnapshotAccessor.PublishedSnapshot.Content; var entityContent = contentCache.GetById(entity.Id); - if (entityContent == null) return; + if (entityContent == null) return; - // get the default affected cultures by going up the tree until we find the first culture variant entity (default to no cultures) + // get the default affected cultures by going up the tree until we find the first culture variant entity (default to no cultures) var defaultCultures = entityContent.AncestorsOrSelf()?.FirstOrDefault(a => a.Cultures.Any())?.Cultures.Keys.ToArray() ?? new[] { (string)null }; - foreach (var x in entityContent.DescendantsOrSelf()) + foreach (var x in entityContent.DescendantsOrSelf(_variationContextAccessor)) { // if this entity defines specific cultures, use those instead of the default ones var cultures = x.Cultures.Any() ? x.Cultures.Keys : defaultCultures; diff --git a/src/Umbraco.Web/Routing/RedirectTrackingComposer.cs b/src/Umbraco.Infrastructure/Routing/RedirectTrackingComposer.cs similarity index 100% rename from src/Umbraco.Web/Routing/RedirectTrackingComposer.cs rename to src/Umbraco.Infrastructure/Routing/RedirectTrackingComposer.cs diff --git a/src/Umbraco.Tests/Routing/ContentFinderByIdTests.cs b/src/Umbraco.Tests/Routing/ContentFinderByIdTests.cs index 875117afbc..227a7c26e3 100644 --- a/src/Umbraco.Tests/Routing/ContentFinderByIdTests.cs +++ b/src/Umbraco.Tests/Routing/ContentFinderByIdTests.cs @@ -1,10 +1,8 @@ -using Moq; -using NUnit.Framework; +using NUnit.Framework; using Umbraco.Core; -using Umbraco.Core.Composing; using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Request; using Umbraco.Tests.TestHelpers; -using Umbraco.Web; using Umbraco.Web.Routing; namespace Umbraco.Tests.Routing @@ -20,7 +18,7 @@ namespace Umbraco.Tests.Routing var umbracoContext = GetUmbracoContext(urlAsString); var publishedRouter = CreatePublishedRouter(); var frequest = publishedRouter.CreateRequest(umbracoContext); - var lookup = new ContentFinderByIdPath(Factory.GetInstance().WebRouting, Logger, HttpContextAccessor); + var lookup = new ContentFinderByIdPath(Factory.GetInstance().WebRouting, Logger, Factory.GetInstance()); var result = lookup.TryFindContent(frequest); diff --git a/src/Umbraco.Tests/Routing/ContentFinderByPageIdQueryTests.cs b/src/Umbraco.Tests/Routing/ContentFinderByPageIdQueryTests.cs index 20bbeb92d4..d18353eb87 100644 --- a/src/Umbraco.Tests/Routing/ContentFinderByPageIdQueryTests.cs +++ b/src/Umbraco.Tests/Routing/ContentFinderByPageIdQueryTests.cs @@ -1,5 +1,6 @@ using Moq; using NUnit.Framework; +using Umbraco.Core.Request; using Umbraco.Tests.TestHelpers; using Umbraco.Web; using Umbraco.Web.Routing; @@ -20,15 +21,10 @@ namespace Umbraco.Tests.Routing var httpContext = GetHttpContextFactory(urlAsString).HttpContext; var publishedRouter = CreatePublishedRouter(); var frequest = publishedRouter.CreateRequest(umbracoContext); - var mockHttpContextAccessor = new Mock(); - mockHttpContextAccessor.Setup(x => x.HttpContext).Returns(httpContext); - var lookup = new ContentFinderByPageIdQuery(mockHttpContextAccessor.Object); + var mockRequestAccessor = new Mock(); + mockRequestAccessor.Setup(x => x.GetRequestValue("umbPageID")).Returns(httpContext.Request.QueryString["umbPageID"]); - //we need to manually stub the return output of HttpContext.Request["umbPageId"] - var requestMock = Mock.Get(httpContext.Request); - - requestMock.Setup(x => x["umbPageID"]) - .Returns(httpContext.Request.QueryString["umbPageID"]); + var lookup = new ContentFinderByPageIdQuery(mockRequestAccessor.Object); var result = lookup.TryFindContent(frequest); diff --git a/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs b/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs index e8b4a993c4..b09620103b 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs @@ -10,7 +10,9 @@ using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Request; using Umbraco.Core.Services; +using Umbraco.Core.Services.Implement; using Umbraco.Core.Strings; using Umbraco.Tests.PublishedContent; using Umbraco.Tests.TestHelpers.Stubs; @@ -19,6 +21,7 @@ using Umbraco.Web; using Umbraco.Web.Composing; using Umbraco.Web.Models.PublishedContent; using Umbraco.Web.Routing; +using Umbraco.Web.Security; namespace Umbraco.Tests.TestHelpers { @@ -97,12 +100,16 @@ namespace Umbraco.Tests.TestHelpers contentFinders ?? new ContentFinderCollection(Enumerable.Empty()), new TestLastChanceFinder(), new TestVariationContextAccessor(), - container?.TryGetInstance() ?? ServiceContext.CreatePartial(), new ProfilingLogger(Mock.Of(), Mock.Of()), container?.TryGetInstance() ?? Current.Factory.GetInstance(), - Mock.Of(), - Mock.Of() - ); + Mock.Of(), + Mock.Of(), + container?.GetInstance() ?? Current.Factory.GetInstance(), + container?.GetInstance()?? Current.Factory.GetInstance(), + container?.GetInstance()?? Current.Factory.GetInstance(), + container?.GetInstance() ?? Current.Factory.GetInstance(), + container?.GetInstance() ?? Current.Factory.GetInstance() + ); } } } diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index c87b92f1c9..de0db554f3 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using System.Reflection; using System.Web.Routing; +using System.Web.Security; using System.Xml.Linq; using Examine; using Moq; @@ -51,12 +52,15 @@ using Umbraco.Web.Templates; using Umbraco.Web.PropertyEditors; using Umbraco.Core.Dictionary; using Umbraco.Core.Models; -using Umbraco.Core.Models.Identity; +using Umbraco.Core.Request; using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Net; +using Umbraco.Tests.LegacyXmlPublishedCache; +using Umbraco.Web.AspNet; using Umbraco.Web.Install; using Umbraco.Web.Security; +using Umbraco.Web.Security.Providers; using Umbraco.Web.Trees; using Current = Umbraco.Web.Composing.Current; namespace Umbraco.Tests.Testing @@ -203,6 +207,18 @@ namespace Umbraco.Tests.Testing Composition.RegisterUnique(ipResolver); Composition.RegisterUnique(); Composition.RegisterUnique(TestHelper.ShortStringHelper); + Composition.RegisterUnique(); + Composition.RegisterUnique(); + + + var memberService = Mock.Of(); + var memberTypeService = Mock.Of(); + var membershipProvider = new MembersMembershipProvider(memberService, memberTypeService, Mock.Of(), TestHelper.GetHostingEnvironment(), TestHelper.GetIpResolver()); + var membershipHelper = new MembershipHelper(Mock.Of(), Mock.Of(), membershipProvider, Mock.Of(), memberService, memberTypeService, Mock.Of(), AppCaches.Disabled, logger, ShortStringHelper, Mock.Of()); + + Composition.RegisterUnique(membershipHelper); + + TestObjects = new TestObjects(register); diff --git a/src/Umbraco.Web.UI/config/imageprocessor/processing.config b/src/Umbraco.Web.UI/config/imageprocessor/processing.config index 34c9fd96c4..dddcddb0bd 100644 --- a/src/Umbraco.Web.UI/config/imageprocessor/processing.config +++ b/src/Umbraco.Web.UI/config/imageprocessor/processing.config @@ -1,38 +1,10 @@  - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + @@ -63,4 +35,4 @@ - \ No newline at end of file + diff --git a/src/Umbraco.Web/AspNet/AspNetRequestAccessor.cs b/src/Umbraco.Web/AspNet/AspNetRequestAccessor.cs new file mode 100644 index 0000000000..d2675b0dc1 --- /dev/null +++ b/src/Umbraco.Web/AspNet/AspNetRequestAccessor.cs @@ -0,0 +1,24 @@ +using Umbraco.Core.Request; + +namespace Umbraco.Web.AspNet +{ + public class AspNetRequestAccessor : IRequestAccessor + { + private readonly IHttpContextAccessor _httpContextAccessor; + + public AspNetRequestAccessor(IHttpContextAccessor httpContextAccessor) + { + _httpContextAccessor = httpContextAccessor; + } + + public string GetRequestValue(string name) + { + return _httpContextAccessor.GetRequiredHttpContext().Request[name]; + } + + public string GetQueryStringValue(string name) + { + return _httpContextAccessor.GetRequiredHttpContext().Request.QueryString[name]; + } + } +} diff --git a/src/Umbraco.Web/AspNet/AspNetSessionIdResolver.cs b/src/Umbraco.Web/AspNet/AspNetSessionIdResolver.cs deleted file mode 100644 index 1b22ebaa60..0000000000 --- a/src/Umbraco.Web/AspNet/AspNetSessionIdResolver.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Web; -using Umbraco.Core; -using Umbraco.Net; - -namespace Umbraco.Web -{ - internal class AspNetSessionIdResolver : ISessionIdResolver - { - public string SessionId => HttpContext.Current?.Session?.SessionID; - } -} diff --git a/src/Umbraco.Web/AspNet/AspNetSessionManager.cs b/src/Umbraco.Web/AspNet/AspNetSessionManager.cs new file mode 100644 index 0000000000..bf7b1c05c3 --- /dev/null +++ b/src/Umbraco.Web/AspNet/AspNetSessionManager.cs @@ -0,0 +1,26 @@ +using System.Web; +using Umbraco.Core.Session; +using Umbraco.Net; + +namespace Umbraco.Web.AspNet +{ + public class AspNetSessionManager: ISessionManager, ISessionIdResolver + { + + public AspNetSessionManager() + { + } + + public object GetSessionValue(string sessionName) + { + return HttpContext.Current.Session[sessionName]; + } + + public void SetSessionValue(string sessionName, object value) + { + HttpContext.Current.Session[sessionName] = value; + } + + public string SessionId => HttpContext.Current?.Session?.SessionID; + } +} diff --git a/src/Umbraco.Web/Macros/MacroRenderer.cs b/src/Umbraco.Web/Macros/MacroRenderer.cs old mode 100755 new mode 100644 index 85861a1496..8518293241 --- a/src/Umbraco.Web/Macros/MacroRenderer.cs +++ b/src/Umbraco.Web/Macros/MacroRenderer.cs @@ -1,5 +1,4 @@ using System; -using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; @@ -12,10 +11,11 @@ using Umbraco.Core.Events; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Macros; -using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Request; +using Umbraco.Core.Security; using Umbraco.Core.Services; -using Umbraco.Web.Security; +using Umbraco.Core.Session; namespace Umbraco.Web.Macros { @@ -29,10 +29,23 @@ namespace Umbraco.Web.Macros private readonly IMacroService _macroService; private readonly IIOHelper _ioHelper; private readonly ICookieManager _cookieManager; - private readonly IUserService _userService; - private readonly IHttpContextAccessor _httpContextAccessor; + private readonly IMemberUserKeyProvider _memberUserKeyProvider; + private readonly ISessionManager _sessionManager; + private readonly IRequestAccessor _requestAccessor; - public MacroRenderer(IProfilingLogger plogger, IUmbracoContextAccessor umbracoContextAccessor, IContentSection contentSection, ILocalizedTextService textService, AppCaches appCaches, IMacroService macroService, IUserService userService, IHttpContextAccessor httpContextAccessor, IIOHelper ioHelper, ICookieManager cookieManager) + + public MacroRenderer( + IProfilingLogger plogger, + IUmbracoContextAccessor umbracoContextAccessor, + IContentSection contentSection, + ILocalizedTextService textService, + AppCaches appCaches, + IMacroService macroService, + IIOHelper ioHelper, + ICookieManager cookieManager, + IMemberUserKeyProvider memberUserKeyProvider, + ISessionManager sessionManager, + IRequestAccessor requestAccessor) { _plogger = plogger ?? throw new ArgumentNullException(nameof(plogger)); _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); @@ -42,8 +55,9 @@ namespace Umbraco.Web.Macros _macroService = macroService ?? throw new ArgumentNullException(nameof(macroService)); _ioHelper = ioHelper ?? throw new ArgumentNullException(nameof(ioHelper)); _cookieManager = cookieManager; - _userService = userService ?? throw new ArgumentNullException(nameof(userService)); - _httpContextAccessor = httpContextAccessor; + _memberUserKeyProvider = memberUserKeyProvider; + _sessionManager = sessionManager; + _requestAccessor = requestAccessor; } #region MacroContent cache @@ -63,12 +77,9 @@ namespace Umbraco.Web.Macros { object key = 0; - if (_httpContextAccessor.HttpContext?.User?.Identity?.IsAuthenticated ?? false) + if (_umbracoContextAccessor.UmbracoContext.Security.IsAuthenticated()) { - //ugh, membershipproviders :( - var provider = MembershipProviderExtensions.GetMembersMembershipProvider(); - var member = MembershipProviderExtensions.GetCurrentUser(provider); - key = member?.ProviderUserKey ?? 0; + key = _memberUserKeyProvider.GetMemberProviderUserKey() ?? 0; } id.AppendFormat("m{0}-", key); @@ -127,10 +138,8 @@ namespace Umbraco.Web.Macros // do not cache if it should cache by member and there's not member if (model.CacheByMember) { - var provider = MembershipProviderExtensions.GetMembersMembershipProvider(); - var member = MembershipProviderExtensions.GetCurrentUser(provider); - var key = member?.ProviderUserKey; - if (key == null) return; + var key = _memberUserKeyProvider.GetMemberProviderUserKey(); + if (key is null) return; } // remember when we cache the content @@ -364,8 +373,6 @@ namespace Umbraco.Web.Macros return attributeValue; } - var context = _httpContextAccessor.HttpContext; - foreach (var token in tokens) { var isToken = token.Length > 4 && token[0] == '[' && token[token.Length - 1] == ']' && validTypes.Contains(token[1]); @@ -383,10 +390,10 @@ namespace Umbraco.Web.Macros switch (type) { case '@': - attributeValue = context?.Request[name]; + attributeValue = _requestAccessor.GetRequestValue(name); break; case '%': - attributeValue = context?.Session[name]?.ToString(); + attributeValue = _sessionManager.GetSessionValue(name)?.ToString(); if (string.IsNullOrEmpty(attributeValue)) attributeValue = _cookieManager.GetCookieValue(name); break; diff --git a/src/Umbraco.Web/Macros/MemberUserKeyProvider.cs b/src/Umbraco.Web/Macros/MemberUserKeyProvider.cs new file mode 100644 index 0000000000..cb57943bad --- /dev/null +++ b/src/Umbraco.Web/Macros/MemberUserKeyProvider.cs @@ -0,0 +1,17 @@ +using Umbraco.Core.Security; +using Umbraco.Web.Security; + +namespace Umbraco.Web.Macros +{ + internal class MemberUserKeyProvider : IMemberUserKeyProvider + { + public object GetMemberProviderUserKey() + { + //ugh, membershipproviders :( + var provider = MembershipProviderExtensions.GetMembersMembershipProvider(); + var member = MembershipProviderExtensions.GetCurrentUser(provider); + + return member?.ProviderUserKey; + } + } +} diff --git a/src/Umbraco.Web/Runtime/WebInitialComposer.cs b/src/Umbraco.Web/Runtime/WebInitialComposer.cs index 35aae084de..420e4b5daa 100644 --- a/src/Umbraco.Web/Runtime/WebInitialComposer.cs +++ b/src/Umbraco.Web/Runtime/WebInitialComposer.cs @@ -1,5 +1,4 @@ using System.Linq; -using System.Web; using System.Web.Security; using Examine; using Microsoft.AspNet.SignalR; @@ -7,13 +6,11 @@ using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Cookie; -using Umbraco.Core.Dashboards; using Umbraco.Core.Dictionary; using Umbraco.Core.Events; using Umbraco.Core.Hosting; using Umbraco.Core.Install; using Umbraco.Core.Migrations.PostMigrations; -using Umbraco.Core.Models.Identity; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors.ValueConverters; using Umbraco.Core.Runtime; @@ -49,6 +46,9 @@ using Current = Umbraco.Web.Composing.Current; using Umbraco.Web.PropertyEditors; using Umbraco.Examine; using Umbraco.Core.Models; +using Umbraco.Core.Request; +using Umbraco.Core.Session; +using Umbraco.Web.AspNet; using Umbraco.Web.Models; namespace Umbraco.Web.Runtime @@ -64,7 +64,14 @@ namespace Umbraco.Web.Runtime composition.Register(); composition.Register(); - composition.Register(); + + + composition.Register(Lifetime.Singleton); + composition.Register(factory => factory.GetInstance(), Lifetime.Singleton); + composition.Register(factory => factory.GetInstance(), Lifetime.Singleton); + + composition.Register(); + composition.Register(); composition.Register(); composition.Register(); @@ -73,6 +80,7 @@ namespace Umbraco.Web.Runtime composition.RegisterUnique(); // required for hybrid accessors composition.RegisterUnique(); + composition.ComposeWebMappingProfiles(); //register the install components @@ -85,6 +93,8 @@ namespace Umbraco.Web.Runtime composition.Register(factory => Roles.Enabled ? Roles.Provider : new MembersRoleProvider(factory.GetInstance())); composition.Register(Lifetime.Request); composition.Register(factory => factory.GetInstance().PublishedSnapshot.Members); + composition.RegisterUnique(); + composition.RegisterUnique(); // register accessors for cultures composition.RegisterUnique(); @@ -115,6 +125,7 @@ namespace Umbraco.Web.Runtime composition.RegisterUnique(); composition.RegisterUnique(); + composition.RegisterUnique(); composition.RegisterUnique(); @@ -125,6 +136,7 @@ namespace Umbraco.Web.Runtime // register the umbraco helper - this is Transient! very important! // also, if not level.Run, we cannot really use the helper (during upgrade...) // so inject a "void" helper (not exactly pretty but...) + if (composition.RuntimeState.Level == RuntimeLevel.Run) if (composition.RuntimeState.Level == RuntimeLevel.Run) composition.Register(factory => { diff --git a/src/Umbraco.Web/Security/PublicAccessChecker.cs b/src/Umbraco.Web/Security/PublicAccessChecker.cs new file mode 100644 index 0000000000..1ea22586e5 --- /dev/null +++ b/src/Umbraco.Web/Security/PublicAccessChecker.cs @@ -0,0 +1,54 @@ +using System; +using Umbraco.Core; +using Umbraco.Core.Services; + +namespace Umbraco.Web.Security +{ + public class PublicAccessChecker : IPublicAccessChecker + { + //TODO: This is lazy to avoid circular dependency. We don't care right now because all membership is going to be changed. + private readonly Lazy _membershipHelper; + private readonly IPublicAccessService _publicAccessService; + private readonly IContentService _contentService; + + public PublicAccessChecker(Lazy membershipHelper, IPublicAccessService publicAccessService, IContentService contentService) + { + _membershipHelper = membershipHelper; + _publicAccessService = publicAccessService; + _contentService = contentService; + } + + public PublicAccessStatus HasMemberAccessToContent(int publishedContentId) + { + var membershipHelper = _membershipHelper.Value; + + if (membershipHelper.IsLoggedIn() == false) + { + return PublicAccessStatus.NotLoggedIn; + } + + var username = membershipHelper.CurrentUserName; + var userRoles = membershipHelper.GetCurrentUserRoles(); + + if (_publicAccessService.HasAccess(publishedContentId, _contentService, username, userRoles) == false) + { + return PublicAccessStatus.AccessDenied; + } + + var member = membershipHelper.GetCurrentMember(); + + if (member.HasProperty(Constants.Conventions.Member.IsApproved) == false) + { + return PublicAccessStatus.NotApproved; + } + + if (member.HasProperty(Constants.Conventions.Member.IsLockedOut) && + member.Value(Constants.Conventions.Member.IsApproved)) + { + return PublicAccessStatus.LockedOut; + } + + return PublicAccessStatus.AccessAccepted; + } + } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 27f372753a..8c06da4894 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -134,6 +134,8 @@ + + @@ -166,6 +168,8 @@ + + @@ -185,7 +189,6 @@ - @@ -200,6 +203,7 @@ + @@ -214,7 +218,6 @@ - @@ -274,7 +277,6 @@ - @@ -323,7 +325,6 @@ - @@ -529,8 +530,6 @@ - - @@ -556,7 +555,6 @@ - Code diff --git a/src/Umbraco.Web/UmbracoApplicationBase.cs b/src/Umbraco.Web/UmbracoApplicationBase.cs index 12925604db..1111d028f8 100644 --- a/src/Umbraco.Web/UmbracoApplicationBase.cs +++ b/src/Umbraco.Web/UmbracoApplicationBase.cs @@ -11,6 +11,7 @@ using Umbraco.Core.Hosting; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Logging.Serilog; +using Umbraco.Web.AspNet; using Umbraco.Web.Hosting; using Current = Umbraco.Web.Composing.Current; @@ -39,7 +40,7 @@ namespace Umbraco.Web var ioHelper = new IOHelper(hostingEnvironment); var configs = configFactory.Create(ioHelper); - var logger = SerilogLogger.CreateWithDefaultConfiguration(hostingEnvironment, new AspNetSessionIdResolver(), () => _factory?.GetInstance(), coreDebug, ioHelper, new FrameworkMarchal()); + var logger = SerilogLogger.CreateWithDefaultConfiguration(hostingEnvironment, new AspNetSessionManager(), () => _factory?.GetInstance(), coreDebug, ioHelper, new FrameworkMarchal()); var backOfficeInfo = new AspNetBackOfficeInfo(configs.Global(), ioHelper, configs.Settings(), logger); var profiler = new LogProfiler(logger); Umbraco.Composing.Current.Initialize(logger, configs, ioHelper, hostingEnvironment, backOfficeInfo, profiler);