From d40a835701f7e18f687bf98e5b9335173697c708 Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 27 Mar 2018 10:51:41 +0200 Subject: [PATCH] Port v7@2aa0dfb2c5 - WIP --- src/Umbraco.Core/Scoping/IScopeContext.cs | 8 + .../Editors/EditorModelEventManager.cs | 64 ++--- .../PublishedContentCache.cs | 5 + .../XmlPublishedCache/PublishedMediaCache.cs | 38 ++- .../XmlPublishedCache/SafeXmlReaderWriter.cs | 15 +- .../XmlPublishedCache/XmlStore.cs | 5 +- src/Umbraco.Web/Search/ExamineComponent.cs | 239 ++++++++++++++---- src/Umbraco.Web/Search/UmbracoTreeSearcher.cs | 4 + .../Identity/BackOfficeCookieManager.cs | 1 + .../Identity/ExternalSignInAutoLinkOptions.cs | 6 + ...ForceRenewalCookieAuthenticationHandler.cs | 2 +- .../Identity/GetUserSecondsMiddleWare.cs | 16 ++ src/Umbraco.Web/Security/WebAuthExtensions.cs | 22 +- src/Umbraco.Web/Security/WebSecurity.cs | 30 +-- src/Umbraco.Web/Umbraco.Web.csproj | 9 + .../developer/Macros/editMacro.aspx.cs | 4 +- .../developer/Packages/editPackage.aspx.cs | 7 + .../umbraco/developer/Xslt/editXslt.aspx.cs | 2 +- .../dialogs/importDocumenttype.aspx.cs | 2 + .../umbraco/dialogs/notifications.aspx.cs | 2 +- .../umbraco/dialogs/rollBack.aspx.cs | 35 +-- .../umbraco/dialogs/viewAuditTrail.aspx | 1 + .../umbraco/translation/default.aspx.cs | 1 - .../umbraco/translation/details.aspx.cs | 1 - .../umbraco/webservices/nodeSorter.asmx.cs | 15 +- 25 files changed, 363 insertions(+), 171 deletions(-) diff --git a/src/Umbraco.Core/Scoping/IScopeContext.cs b/src/Umbraco.Core/Scoping/IScopeContext.cs index f4fb652bc7..093ebef4f7 100644 --- a/src/Umbraco.Core/Scoping/IScopeContext.cs +++ b/src/Umbraco.Core/Scoping/IScopeContext.cs @@ -38,5 +38,13 @@ namespace Umbraco.Core.Scoping /// The action boolean parameter indicates whether the scope completed or not. /// T Enlist(string key, Func creator, Action action = null, int priority = 100); + + /// + /// Gets an enlisted object. + /// + /// The type of the object. + /// The object unique identifier. + /// The enlisted object, if any, else the default value. + T GetEnlisted(string key); } } diff --git a/src/Umbraco.Web/Editors/EditorModelEventManager.cs b/src/Umbraco.Web/Editors/EditorModelEventManager.cs index 0dea726142..e2a248cb88 100644 --- a/src/Umbraco.Web/Editors/EditorModelEventManager.cs +++ b/src/Umbraco.Web/Editors/EditorModelEventManager.cs @@ -1,39 +1,9 @@ -using System; -using System.Web.Http.Filters; +using System.Web.Http.Filters; using Umbraco.Core.Events; using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.Editors { - public class EditorModelEventArgs : EventArgs - { - public EditorModelEventArgs(object model, UmbracoContext umbracoContext) - { - Model = model; - UmbracoContext = umbracoContext; - } - - public object Model { get; private set; } - public UmbracoContext UmbracoContext { get; private set; } - } - - public sealed class EditorModelEventArgs : EditorModelEventArgs - { - public EditorModelEventArgs(EditorModelEventArgs baseArgs) - : base(baseArgs.Model, baseArgs.UmbracoContext) - { - Model = (T)baseArgs.Model; - } - - public EditorModelEventArgs(T model, UmbracoContext umbracoContext) - : base(model, umbracoContext) - { - Model = model; - } - - public new T Model { get; private set; } - } - /// /// Used to emit events for editor models in the back office /// @@ -42,23 +12,30 @@ namespace Umbraco.Web.Editors public static event TypedEventHandler> SendingContentModel; public static event TypedEventHandler> SendingMediaModel; public static event TypedEventHandler> SendingMemberModel; + public static event TypedEventHandler> SendingUserModel; + + private static void OnSendingUserModel(HttpActionExecutedContext sender, EditorModelEventArgs e) + { + var handler = SendingUserModel; + handler?.Invoke(sender, e); + } private static void OnSendingContentModel(HttpActionExecutedContext sender, EditorModelEventArgs e) { var handler = SendingContentModel; - if (handler != null) handler(sender, e); + handler?.Invoke(sender, e); } private static void OnSendingMediaModel(HttpActionExecutedContext sender, EditorModelEventArgs e) { var handler = SendingMediaModel; - if (handler != null) handler(sender, e); + handler?.Invoke(sender, e); } private static void OnSendingMemberModel(HttpActionExecutedContext sender, EditorModelEventArgs e) { var handler = SendingMemberModel; - if (handler != null) handler(sender, e); + handler?.Invoke(sender, e); } /// @@ -68,24 +45,17 @@ namespace Umbraco.Web.Editors /// internal static void EmitEvent(HttpActionExecutedContext sender, EditorModelEventArgs e) { - var contentItemDisplay = e.Model as ContentItemDisplay; - if (contentItemDisplay != null) - { + if (e.Model is ContentItemDisplay) OnSendingContentModel(sender, new EditorModelEventArgs(e)); - } - var mediaItemDisplay = e.Model as MediaItemDisplay; - if (mediaItemDisplay != null) - { + if (e.Model is MediaItemDisplay) OnSendingMediaModel(sender, new EditorModelEventArgs(e)); - } - var memberItemDisplay = e.Model as MemberDisplay; - if (memberItemDisplay != null) - { + if (e.Model is MemberDisplay) OnSendingMemberModel(sender, new EditorModelEventArgs(e)); - } - } + if (e.Model is UserDisplay) + OnSendingUserModel(sender, new EditorModelEventArgs(e)); + } } } diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs index f3829c35c0..58d856ad8d 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs @@ -486,7 +486,12 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache // not trying to be thread-safe here, that's not the point if (preview == false) + { + // if there's a current enlisted reader/writer, use its xml + var tempXml = _xmlStore.TempXml; + if (tempXml != null) return tempXml; return _xml; + } // Xml cache does not support retrieving preview content when not previewing if (_previewContent == null) diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs index 690ae7e5b7..df4b7990ad 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs @@ -108,7 +108,43 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache public override IEnumerable GetAtRoot(bool preview) { - //TODO: We should be able to look these ids first in Examine! + var searchProvider = GetSearchProviderSafe(); + + if (searchProvider != null) + { + try + { + // first check in Examine for the cache values + // +(+parentID:-1) +__IndexType:media + + var criteria = searchProvider.CreateSearchCriteria("media"); + var filter = criteria.ParentId(-1).Not().Field(UmbracoContentIndexer.IndexPathFieldName, "-1,-21,".MultipleCharacterWildcard()); + + var result = searchProvider.Search(filter.Compile()); + if (result != null) + return result.Select(x => CreateFromCacheValues(ConvertFromSearchResult(x))); + } + catch (Exception ex) + { + if (ex is FileNotFoundException) + { + //Currently examine is throwing FileNotFound exceptions when we have a loadbalanced filestore and a node is published in umbraco + //See this thread: http://examine.cdodeplex.com/discussions/264341 + //Catch the exception here for the time being, and just fallback to GetMedia + //TODO: Need to fix examine in LB scenarios! + Current.Logger.Error("Could not load data from Examine index for media", ex); + } + else if (ex is AlreadyClosedException) + { + //If the app domain is shutting down and the site is under heavy load the index reader will be closed and it really cannot + //be re-opened since the app domain is shutting down. In this case we have no option but to try to load the data from the db. + Current.Logger.Error("Could not load data from Examine index for media, the app domain is most likely in a shutdown state", ex); + } + else throw; + } + } + + //something went wrong, fetch from the db var rootMedia = _mediaService.GetRootMedia(); return rootMedia.Select(m => GetUmbracoMedia(m.Id)); diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/SafeXmlReaderWriter.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/SafeXmlReaderWriter.cs index a84d443397..7f6a6fd704 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/SafeXmlReaderWriter.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/SafeXmlReaderWriter.cs @@ -17,6 +17,11 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache private bool _using; private bool _registerXmlChange; + // the default enlist priority is 100 + // enlist with a lower priority to ensure that anything "default" has a clean xml + private const int EnlistPriority = 60; + private const string EnlistKey = "safeXmlReaderWriter"; + private SafeXmlReaderWriter(IDisposable releaser, XmlDocument xml, Action refresh, Action apply, bool isWriter, bool scoped) { _releaser = releaser; @@ -29,6 +34,12 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache _xml = IsWriter ? Clone(xml) : xml; } + public static SafeXmlReaderWriter Get(IScopeProvider scopeProvider) + { + var scopeContext = scopeProvider.Context; + return scopeContext?.GetEnlisted(EnlistKey); + } + public static SafeXmlReaderWriter Get(IScopeProvider scopeProvider, AsyncLock xmlLock, XmlDocument xml, Action refresh, Action apply, bool writer) { var scopeContext = scopeProvider.Context; @@ -42,7 +53,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache } // get or create an enlisted reader/writer - var rw = scopeContext.Enlist("safeXmlReaderWriter", + var rw = scopeContext.Enlist(EnlistKey, () => // creator { // obtain exclusive access to xml and create reader/writer @@ -52,7 +63,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache (completed, item) => // action { item.DisposeForReal(completed); - }); + }, EnlistPriority); // ensure it's not already in-use - should never happen, just being super safe if (rw._using) diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlStore.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlStore.cs index 8c56ffd87f..a0e7f6370d 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlStore.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlStore.cs @@ -38,7 +38,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache /// then passed to all instances that are created (one per request). /// This class should *not* be public. /// - class XmlStore : IDisposable + internal class XmlStore : IDisposable { private readonly IDocumentRepository _documentRepository; private readonly IMediaRepository _mediaRepository; @@ -329,6 +329,9 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache } } + // Gets the temp. Xml managed by SafeXmlReaderWrite, if any + public XmlDocument TempXml => SafeXmlReaderWriter.Get(_scopeProvider)?.Xml; + // assumes xml lock private void SetXmlLocked(XmlDocument xml, bool registerXmlChange) { diff --git a/src/Umbraco.Web/Search/ExamineComponent.cs b/src/Umbraco.Web/Search/ExamineComponent.cs index 411f77fb3f..50940ecf4c 100644 --- a/src/Umbraco.Web/Search/ExamineComponent.cs +++ b/src/Umbraco.Web/Search/ExamineComponent.cs @@ -14,6 +14,7 @@ using Umbraco.Core.Components; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Scoping; using Umbraco.Core.Services; using Umbraco.Core.Services.Changes; using Umbraco.Core.Services.Implement; @@ -31,8 +32,17 @@ namespace Umbraco.Web.Search [RuntimeLevel(MinLevel = RuntimeLevel.Run)] public sealed class ExamineComponent : UmbracoComponentBase, IUmbracoCoreComponent { - public void Initialize(IRuntimeState runtime, PropertyEditorCollection propertyEditors, IExamineIndexCollectionAccessor indexCollection, ILogger logger) + private IScopeProvider _scopeProvider; + + // the default enlist priority is 100 + // enlist with a lower priority to ensure that anything "default" runs after us + // but greater that SafeXmlReaderWriter priority which is 60 + private const int EnlistPriority = 80; + + public void Initialize(IRuntimeState runtime, PropertyEditorCollection propertyEditors, IExamineIndexCollectionAccessor indexCollection, IScopeProvider scopeProvider, ILogger logger) { + _scopeProvider = scopeProvider; + logger.Info("Starting initialize async background thread."); // make it async in order not to slow down the boot @@ -119,7 +129,7 @@ namespace Umbraco.Web.Search i.DocumentWriting += grid.DocumentWriting; } - static void MemberCacheRefresherUpdated(MemberCacheRefresher sender, CacheRefresherEventArgs args) + void MemberCacheRefresherUpdated(MemberCacheRefresher sender, CacheRefresherEventArgs args) { if (Suspendable.ExamineEvents.CanIndex == false) return; @@ -164,7 +174,7 @@ namespace Umbraco.Web.Search } } - static void MediaCacheRefresherUpdated(MediaCacheRefresher sender, CacheRefresherEventArgs args) + void MediaCacheRefresherUpdated(MediaCacheRefresher sender, CacheRefresherEventArgs args) { if (Suspendable.ExamineEvents.CanIndex == false) return; @@ -213,7 +223,7 @@ namespace Umbraco.Web.Search } } - static void ContentCacheRefresherUpdated(ContentCacheRefresher sender, CacheRefresherEventArgs args) + void ContentCacheRefresherUpdated(ContentCacheRefresher sender, CacheRefresherEventArgs args) { if (Suspendable.ExamineEvents.CanIndex == false) return; @@ -289,7 +299,7 @@ namespace Umbraco.Web.Search } } - private static void ReIndexForContent(IContent content, IContent published) + private void ReIndexForContent(IContent content, IContent published) { if (published != null && content.VersionId == published.VersionId) { @@ -311,42 +321,30 @@ namespace Umbraco.Web.Search } } - private static void ReIndexForContent(IContent sender, bool? supportUnpublished = null) + private void ReIndexForContent(IContent sender, bool? supportUnpublished = null) { - var xml = sender.ToXml(); - //add an icon attribute to get indexed - xml.Add(new XAttribute("icon", sender.ContentType.Icon)); - - ExamineManager.Instance.ReIndexNode( - xml, IndexTypes.Content, - ExamineManager.Instance.IndexProviderCollection.OfType() - // only for the specified indexers - .Where(x => supportUnpublished.HasValue == false || supportUnpublished.Value == x.SupportUnpublishedContent) - .Where(x => x.EnableDefaultEventHandler)); + var actions = DeferedActions.Get(_scopeProvider); + if (actions != null) + actions.Add(new DeferedReIndexForContent(sender, supportUnpublished)); + else + DeferedReIndexForContent.Execute(sender, supportUnpublished); } - private static void ReIndexForMember(IMember member) + private void ReIndexForMember(IMember member) { - ExamineManager.Instance.ReIndexNode( - member.ToXml(), IndexTypes.Member, - ExamineManager.Instance.IndexProviderCollection.OfType() - //ensure that only the providers are flagged to listen execute - .Where(x => x.EnableDefaultEventHandler)); - } + var actions = DeferedActions.Get(_scopeProvider); + if (actions != null) + actions.Add(new DeferedReIndexForMember(member)); + else + DeferedReIndexForMember.Execute(member); } - private static void ReIndexForMedia(IMedia sender, bool isMediaPublished) + private void ReIndexForMedia(IMedia sender, bool isMediaPublished) { - var xml = sender.ToXml(); - //add an icon attribute to get indexed - xml.Add(new XAttribute("icon", sender.ContentType.Icon)); - - ExamineManager.Instance.ReIndexNode( - xml, IndexTypes.Media, - ExamineManager.Instance.IndexProviderCollection.OfType() - // index this item for all indexers if the media is not trashed, otherwise if the item is trashed - // then only index this for indexers supporting unpublished media - .Where(x => isMediaPublished || (x.SupportUnpublishedContent)) - .Where(x => x.EnableDefaultEventHandler)); + var actions = DeferedActions.Get(_scopeProvider); + if (actions != null) + actions.Add(new DeferedReIndexForMedia(sender, isMediaPublished)); + else + DeferedReIndexForMedia.Execute(sender, isMediaPublished); } /// @@ -357,15 +355,13 @@ namespace Umbraco.Web.Search /// If true, indicates that we will only delete this item from indexes that don't support unpublished content. /// If false it will delete this from all indexes regardless. /// - private static void DeleteIndexForEntity(int entityId, bool keepIfUnpublished) + private void DeleteIndexForEntity(int entityId, bool keepIfUnpublished) { - ExamineManager.Instance.DeleteFromIndex( - entityId.ToString(CultureInfo.InvariantCulture), - ExamineManager.Instance.IndexProviderCollection.OfType() - // if keepIfUnpublished == true then only delete this item from indexes not supporting unpublished content, - // otherwise if keepIfUnpublished == false then remove from all indexes - .Where(x => keepIfUnpublished == false || (x is UmbracoContentIndexer && ((UmbracoContentIndexer)x).SupportUnpublishedContent == false)) - .Where(x => x.EnableDefaultEventHandler)); + var actions = DeferedActions.Get(_scopeProvider); + if (actions != null) + actions.Add(new DeferedDeleteIndex(entityId, keepIfUnpublished)); + else + DeferedDeleteIndex.Execute(entityId, keepIfUnpublished); } /// @@ -390,5 +386,162 @@ namespace Umbraco.Web.Search )); } } + + private class DeferedActions + { + private readonly List _actions = new List(); + + public static DeferedActions Get(IScopeProvider scopeProvider) + { + var scopeContext = scopeProvider.Context; + if (scopeContext == null) return null; + + return scopeContext.Enlist("examineEvents", + () => new DeferedActions(), // creator + (completed, actions) => // action + { + if (completed) actions.Execute(); + }, EnlistPriority); + } + + public void Add(DeferedAction action) + { + _actions.Add(action); + } + + private void Execute() + { + foreach (var action in _actions) + action.Execute(); + } + } + + private abstract class DeferedAction + { + public virtual void Execute() + { } + } + + private class DeferedReIndexForContent : DeferedAction + { + private readonly IContent _content; + private readonly bool? _supportUnpublished; + + public DeferedReIndexForContent(IContent content, bool? supportUnpublished) + { + _content = content; + _supportUnpublished = supportUnpublished; + } + + public override void Execute() + { + Execute(_content, _supportUnpublished); + } + + public static void Execute(IContent content, bool? supportUnpublished) + { + var xml = content.ToXml(); + //add an icon attribute to get indexed + xml.Add(new XAttribute("icon", content.ContentType.Icon)); + + ExamineManager.Instance.ReIndexNode( + xml, IndexTypes.Content, + ExamineManager.Instance.IndexProviderCollection.OfType() + + //Index this item for all indexers if the content is published, otherwise if the item is not published + // then only index this for indexers supporting unpublished content + + .Where(x => supportUnpublished.HasValue == false || supportUnpublished.Value == x.SupportUnpublishedContent) + .Where(x => x.EnableDefaultEventHandler)); + } + } + + private class DeferedReIndexForMedia : DeferedAction + { + private readonly IMedia _media; + private readonly bool _isPublished; + + public DeferedReIndexForMedia(IMedia media, bool isPublished) + { + _media = media; + _isPublished = isPublished; + } + + public override void Execute() + { + Execute(_media, _isPublished); + } + + public static void Execute(IMedia media, bool isPublished) + { + var xml = media.ToXml(); + //add an icon attribute to get indexed + xml.Add(new XAttribute("icon", media.ContentType.Icon)); + + ExamineManager.Instance.ReIndexNode( + xml, IndexTypes.Media, + ExamineManager.Instance.IndexProviderCollection.OfType() + + //Index this item for all indexers if the media is not trashed, otherwise if the item is trashed + // then only index this for indexers supporting unpublished media + + .Where(x => isPublished || (x.SupportUnpublishedContent)) + .Where(x => x.EnableDefaultEventHandler)); + } + } + + private class DeferedReIndexForMember : DeferedAction + { + private readonly IMember _member; + + public DeferedReIndexForMember(IMember member) + { + _member = member; + } + + public override void Execute() + { + Execute(_member); + } + + public static void Execute(IMember member) + { + ExamineManager.Instance.ReIndexNode( + member.ToXml(), IndexTypes.Member, + ExamineManager.Instance.IndexProviderCollection.OfType() + //ensure that only the providers are flagged to listen execute + .Where(x => x.EnableDefaultEventHandler)); + } + } + + private class DeferedDeleteIndex : DeferedAction + { + private readonly int _id; + private readonly bool _keepIfUnpublished; + + public DeferedDeleteIndex(int id, bool keepIfUnpublished) + { + _id = id; + _keepIfUnpublished = keepIfUnpublished; + } + + public override void Execute() + { + Execute(_id, _keepIfUnpublished); + } + + public static void Execute(int id, bool keepIfUnpublished) + { + ExamineManager.Instance.DeleteFromIndex( + id.ToString(CultureInfo.InvariantCulture), + ExamineManager.Instance.IndexProviderCollection.OfType() + + //if keepIfUnpublished == true then only delete this item from indexes not supporting unpublished content, + // otherwise if keepIfUnpublished == false then remove from all indexes + + .Where(x => keepIfUnpublished == false || x.SupportUnpublishedContent == false) + .Where(x => x.EnableDefaultEventHandler)); + } + } } } diff --git a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs index 917df00c4c..eb565b127e 100644 --- a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs +++ b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs @@ -208,6 +208,10 @@ namespace Umbraco.Web.Search if (sb == null) throw new ArgumentNullException("sb"); if (entityService == null) throw new ArgumentNullException("entityService"); + Udi udi; + Udi.TryParse(searchFrom, true, out udi); + searchFrom = udi == null ? searchFrom : entityService.GetIdForUdi(udi).Result.ToString(); + int searchFromId; var entityPath = int.TryParse(searchFrom, out searchFromId) && searchFromId > 0 ? entityService.GetAllPaths(objectType, searchFromId).FirstOrDefault() diff --git a/src/Umbraco.Web/Security/Identity/BackOfficeCookieManager.cs b/src/Umbraco.Web/Security/Identity/BackOfficeCookieManager.cs index c5ad3d121c..98493db1c7 100644 --- a/src/Umbraco.Web/Security/Identity/BackOfficeCookieManager.cs +++ b/src/Umbraco.Web/Security/Identity/BackOfficeCookieManager.cs @@ -6,6 +6,7 @@ using Microsoft.Owin; using Microsoft.Owin.Infrastructure; using Umbraco.Core; using Umbraco.Core.Configuration; +using Umbraco.Core.IO; namespace Umbraco.Web.Security.Identity { diff --git a/src/Umbraco.Web/Security/Identity/ExternalSignInAutoLinkOptions.cs b/src/Umbraco.Web/Security/Identity/ExternalSignInAutoLinkOptions.cs index a808f7eb62..43738aec17 100644 --- a/src/Umbraco.Web/Security/Identity/ExternalSignInAutoLinkOptions.cs +++ b/src/Umbraco.Web/Security/Identity/ExternalSignInAutoLinkOptions.cs @@ -52,6 +52,12 @@ namespace Umbraco.Web.Security.Identity /// public Action OnAutoLinking { get; set; } + /// + /// A callback executed during every time a user authenticates using an external login. + /// returns a boolean indicating if sign in should continue or not. + /// + public Func OnExternalLogin { get; set; } + [EditorBrowsable(EditorBrowsableState.Never)] [Obsolete("Use the overload specifying user groups instead")] public string GetDefaultUserType(UmbracoContext umbracoContext, ExternalLoginInfo loginInfo) diff --git a/src/Umbraco.Web/Security/Identity/ForceRenewalCookieAuthenticationHandler.cs b/src/Umbraco.Web/Security/Identity/ForceRenewalCookieAuthenticationHandler.cs index 86b1a0522c..f3c7013701 100644 --- a/src/Umbraco.Web/Security/Identity/ForceRenewalCookieAuthenticationHandler.cs +++ b/src/Umbraco.Web/Security/Identity/ForceRenewalCookieAuthenticationHandler.cs @@ -1,7 +1,7 @@ using System; using Umbraco.Core; using System.Threading.Tasks; -using Microsoft.Owin; +using Umbraco.Core.Security; using Microsoft.Owin.Security; using Microsoft.Owin.Security.Cookies; using Microsoft.Owin.Security.Infrastructure; diff --git a/src/Umbraco.Web/Security/Identity/GetUserSecondsMiddleWare.cs b/src/Umbraco.Web/Security/Identity/GetUserSecondsMiddleWare.cs index 68a356fa82..0efb0b66a6 100644 --- a/src/Umbraco.Web/Security/Identity/GetUserSecondsMiddleWare.cs +++ b/src/Umbraco.Web/Security/Identity/GetUserSecondsMiddleWare.cs @@ -2,12 +2,15 @@ using System.Diagnostics; using System.Globalization; using System.Threading.Tasks; +using System.Web; +using System.Web.Security; using Microsoft.Owin; using Microsoft.Owin.Logging; using Microsoft.Owin.Security.Cookies; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Security; namespace Umbraco.Web.Security.Identity { @@ -82,6 +85,9 @@ namespace Umbraco.Web.Security.Identity //if it's time to renew, then do it if (timeRemaining < timeElapsed) { + //TODO: This would probably be simpler just to do: context.OwinContext.Authentication.SignIn(context.Properties, identity); + // this will invoke the default Cookie middleware to basically perform this logic for us. + ticket.Properties.IssuedUtc = currentUtc; var timeSpan = expiresUtc.Value.Subtract(issuedUtc.Value); ticket.Properties.ExpiresUtc = currentUtc.Add(timeSpan); @@ -99,6 +105,9 @@ namespace Umbraco.Web.Security.Identity remainingSeconds = (ticket.Properties.ExpiresUtc.Value - currentUtc).TotalSeconds; } } + + //We also need to re-validate the user's session if we are relying on this ping to keep their session alive + await SessionIdValidator.ValidateSessionAsync(TimeSpan.FromMinutes(1), context, _authOptions.CookieManager, _authOptions.SystemClock, issuedUtc, ticket.Identity); } else if (remainingSeconds <= 30) { @@ -114,6 +123,13 @@ namespace Umbraco.Web.Security.Identity return; } } + + //Hack! we need to suppress the stupid forms authentcation module but we can only do that by using non owin stuff + if (HttpContext.Current != null && HttpContext.Current.Response != null) + { + HttpContext.Current.Response.SuppressFormsAuthenticationRedirect = true; + } + response.StatusCode = 401; } else if (Next != null) diff --git a/src/Umbraco.Web/Security/WebAuthExtensions.cs b/src/Umbraco.Web/Security/WebAuthExtensions.cs index 138dd36b67..d38345e48c 100644 --- a/src/Umbraco.Web/Security/WebAuthExtensions.cs +++ b/src/Umbraco.Web/Security/WebAuthExtensions.cs @@ -14,18 +14,13 @@ namespace Umbraco.Web.Security internal static class WebAuthExtensions { /// - /// This will set a an authenticated IPrincipal to the current request given the IUser object + /// This will set a an authenticated IPrincipal to the current request for webforms & webapi /// /// - /// + /// /// - internal static IPrincipal SetPrincipalForRequest(this HttpRequestMessage request, IUser user) + internal static IPrincipal SetPrincipalForRequest(this HttpRequestMessage request, IPrincipal principal) { - var principal = new ClaimsPrincipal( - new UmbracoBackOfficeIdentity( - new ClaimsIdentity(), - Mapper.Map(user))); - //It is actually not good enough to set this on the current app Context and the thread, it also needs // to be set explicitly on the HttpContext.Current !! This is a strange web api thing that is actually // an underlying fault of asp.net not propogating the User correctly. @@ -50,15 +45,10 @@ namespace Umbraco.Web.Security /// This will set a an authenticated IPrincipal to the current request given the IUser object /// /// - /// + /// /// - internal static IPrincipal SetPrincipalForRequest(this HttpContextBase httpContext, UserData userData) - { - var principal = new ClaimsPrincipal( - new UmbracoBackOfficeIdentity( - new ClaimsIdentity(), - userData)); - + internal static IPrincipal SetPrincipalForRequest(this HttpContextBase httpContext, IPrincipal principal) + { //It is actually not good enough to set this on the current app Context and the thread, it also needs // to be set explicitly on the HttpContext.Current !! This is a strange web api thing that is actually // an underlying fault of asp.net not propogating the User correctly. diff --git a/src/Umbraco.Web/Security/WebSecurity.cs b/src/Umbraco.Web/Security/WebSecurity.cs index d8d001bab9..547b2d3595 100644 --- a/src/Umbraco.Web/Security/WebSecurity.cs +++ b/src/Umbraco.Web/Security/WebSecurity.cs @@ -23,7 +23,7 @@ namespace Umbraco.Web.Security /// /// A utility class used for dealing with USER security in Umbraco /// - public class WebSecurity : DisposableObject + public class WebSecurity : DisposableObjectSlim { private HttpContextBase _httpContext; private readonly IUserService _userService; @@ -112,34 +112,14 @@ namespace Umbraco.Web.Security owinCtx.Authentication.SignOut(Constants.Security.BackOfficeExternalAuthenticationType); var user = UserManager.FindByIdAsync(userId).Result; - var userData = Mapper.Map(user); - _httpContext.SetPrincipalForRequest(userData); SignInManager.SignInAsync(user, isPersistent: true, rememberBrowser: false).Wait(); + + _httpContext.SetPrincipalForRequest(owinCtx.Request.User); + return TimeSpan.FromMinutes(GlobalSettings.TimeOutInMinutes).TotalSeconds; } - - [Obsolete("This method should not be used, login is performed by the OWIN pipeline, use the overload that returns double and accepts a UserId instead")] - public virtual FormsAuthenticationTicket PerformLogin(IUser user) - { - //clear the external cookie - we do this first without owin context because we're writing cookies directly to httpcontext - // and cookie handling is different with httpcontext vs webapi and owin, normally we'd just do: - //_httpContext.GetOwinContext().Authentication.SignOut(Constants.Security.BackOfficeExternalAuthenticationType); - - var externalLoginCookie = _httpContext.Request.Cookies.Get(Constants.Security.BackOfficeExternalCookieName); - if (externalLoginCookie != null) - { - externalLoginCookie.Expires = DateTime.Now.AddYears(-1); - _httpContext.Response.Cookies.Set(externalLoginCookie); - } - - //ensure it's done for owin too - _httpContext.GetOwinContext().Authentication.SignOut(Constants.Security.BackOfficeExternalAuthenticationType); - - var ticket = _httpContext.CreateUmbracoAuthTicket(Mapper.Map(user)); - return ticket; - } - + /// /// Clears the current login for the currently logged in user /// diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index e3753ed994..0f44de7c14 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -131,14 +131,17 @@ + + + @@ -199,6 +202,10 @@ + + + + @@ -251,6 +258,7 @@ + @@ -386,6 +394,7 @@ + diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Macros/editMacro.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Macros/editMacro.aspx.cs index a3ff49e545..420002f9b3 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Macros/editMacro.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Macros/editMacro.aspx.cs @@ -46,7 +46,7 @@ namespace umbraco.cms.presentation.developer { ClientTools .SetActiveTreeType(Constants.Trees.Macros) - .SyncTree("-1,init," + _macro.Id, false); + .SyncTree("-1," + _macro.Id, false); string tempMacroAssembly = _macro.ControlAssembly ?? ""; string tempMacroType = _macro.ControlType ?? ""; @@ -291,7 +291,7 @@ namespace umbraco.cms.presentation.developer ClientTools .SetActiveTreeType(Constants.Trees.Macros) - .SyncTree("-1,init," + _macro.Id.ToInvariantString(), true); //true forces the reload + .SyncTree("-1," + _macro.Id.ToInvariantString(), true); //true forces the reload var tempMacroAssembly = macroAssembly.Text; var tempMacroType = macroType.Text; diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/editPackage.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/editPackage.aspx.cs index 35f36f9733..56830f8230 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/editPackage.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/editPackage.aspx.cs @@ -167,6 +167,13 @@ namespace umbraco.presentation.developer.packages ///*Data types */ //cms.businesslogic.datatype.DataTypeDefinition[] umbDataType = cms.businesslogic.datatype.DataTypeDefinition.GetAll(); + + // sort array by name + Array.Sort(umbDataType, delegate(cms.businesslogic.datatype.DataTypeDefinition umbDataType1, cms.businesslogic.datatype.DataTypeDefinition umbDataType2) + { + return umbDataType1.Text.CompareTo(umbDataType2.Text); + }); + //foreach (cms.businesslogic.datatype.DataTypeDefinition umbDtd in umbDataType) //{ diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Xslt/editXslt.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Xslt/editXslt.aspx.cs index 0768475133..392a914eb3 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Xslt/editXslt.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Xslt/editXslt.aspx.cs @@ -28,7 +28,7 @@ namespace umbraco.cms.presentation.developer if (!IsPostBack) { string file = Request.QueryString["file"]; - string path = BaseTree.GetTreePathFromFilePath(file); + string path = BaseTree.GetTreePathFromFilePath(file, false, tree); ClientTools .SetActiveTreeType(Constants.Trees.Xslt) .SyncTree(path, false); diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/importDocumenttype.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/importDocumenttype.aspx.cs index d934ea725b..59720e8e63 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/importDocumenttype.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/importDocumenttype.aspx.cs @@ -73,6 +73,7 @@ namespace umbraco.presentation.umbraco.dialogs private void import_Click(object sender, EventArgs e) { var xd = new XmlDocument(); + xd.XmlResolver = null; xd.Load(tempFile.Value); var userId = Security.GetUserId(); @@ -107,6 +108,7 @@ namespace umbraco.presentation.umbraco.dialogs documentTypeFile.PostedFile.SaveAs(fileName); var xd = new XmlDocument(); + xd.XmlResolver = null; xd.Load(fileName); dtName.Text = xd.DocumentElement.SelectSingleNode("//DocumentType/Info/Name").FirstChild.Value; dtAlias.Text = xd.DocumentElement.SelectSingleNode("//DocumentType/Info/Alias").FirstChild.Value; diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/notifications.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/notifications.aspx.cs index 74fc5af5e0..268e604547 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/notifications.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/notifications.aspx.cs @@ -30,7 +30,7 @@ namespace umbraco.dialogs protected void Page_Load(object sender, EventArgs e) { Button1.Text = Services.TextService.Localize("update"); - pane_form.Text = Services.TextService.Localize("notifications/editNotifications", new[] { node.Name}); + pane_form.Text = Services.TextService.Localize("notifications/editNotifications", new[] { Server.HtmlEncode(node.Name) }); } #region Web Form Designer generated code diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/rollBack.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/rollBack.aspx.cs index 03558ad847..1bf5e1b158 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/rollBack.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/rollBack.aspx.cs @@ -5,6 +5,7 @@ //using System.ComponentModel; //using System.Data; //using System.Drawing; +//using System.Linq; //using System.Web; //using System.Web.SessionState; //using System.Web.UI; @@ -119,36 +120,22 @@ // if (!IsPostBack) { // allVersions.Items.Add(new ListItem(Services.TextService.Localize("rollback/selectVersion")+ "...", "")); -// foreach (DocumentVersionList dl in currentDoc.GetVersions()) { + +// foreach (DocumentVersionList dl in currentDoc.GetVersions()) +// { +// //we don't need to show the current version +// if (dl.Version == currentDoc.Version) +// continue; +// // allVersions.Items.Add(new ListItem(dl.Text + " (" + Services.TextService.Localize("content/createDate") + ": " + dl.Date.ToShortDateString() + " " + dl.Date.ToShortTimeString() + ")", dl.Version.ToString())); // } // Button1.Text = Services.TextService.Localize("actions/rollback"); // } // } - -// #region Web Form Designer generated code -// override protected void OnInit(EventArgs e) -// { -// // -// // CODEGEN: This call is required by the ASP.NET Web Form Designer. -// // -// InitializeComponent(); -// base.OnInit(e); -// } - -// /// -// /// Required method for Designer support - do not modify -// /// the contents of this method with the code editor. -// /// -// private void InitializeComponent() -// { - -// } -// #endregion - // protected void doRollback_Click(object sender, System.EventArgs e) // { -// if (allVersions.SelectedValue.Trim() != "") { +// if (allVersions.SelectedValue.Trim() != "") +// { // Document d = new Document(int.Parse(helper.Request("nodeId"))); // d.RollBack(new Guid(allVersions.SelectedValue), Security.CurrentUser); @@ -161,6 +148,8 @@ // feedBackMsg.Text = ui.Text("rollback", "documentRolledBack", vars, new global::umbraco.BusinessLogic.User(0)) + "

" + Services.TextService.Localize("closeThisWindow") + ""; // diffPanel.Visible = false; // pl_buttons.Visible = false; +// +// ClientTools.ReloadLocationIfMatched(string.Format("/content/content/edit/{0}", d.Id)); // } // } // } diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/viewAuditTrail.aspx b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/viewAuditTrail.aspx index 1c703d25a0..159b55421f 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/viewAuditTrail.aspx +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/viewAuditTrail.aspx @@ -7,6 +7,7 @@ diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/translation/default.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/translation/default.aspx.cs index 1995cdb580..4d7e033635 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/translation/default.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/translation/default.aspx.cs @@ -3,7 +3,6 @@ using System.Data; using System.IO; using System.Text; using System.Xml; -using Umbraco.Core; using Umbraco.Core.Services; using Umbraco.Core.IO; using System.Collections.Generic; diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/translation/details.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/translation/details.aspx.cs index 8736baf6e8..79c22c3c08 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/translation/details.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/translation/details.aspx.cs @@ -5,7 +5,6 @@ using Umbraco.Core; using Umbraco.Core.Services; using Umbraco.Web.Composing; using Umbraco.Web._Legacy.BusinessLogic; - namespace umbraco.presentation.umbraco.translation { public partial class details : Umbraco.Web.UI.Pages.UmbracoEnsuredPage { diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/nodeSorter.asmx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/nodeSorter.asmx.cs index 672aa151cf..9574d4e3bf 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/nodeSorter.asmx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/nodeSorter.asmx.cs @@ -168,13 +168,16 @@ namespace umbraco.presentation.webservices { var contentService = Services.ContentService; try - { - var intIds = ids.Select(int.Parse).ToArray(); - var allContent = contentService.GetByIds(intIds).ToDictionary(x => x.Id, x => x); - var sortedContent = intIds.Select(x => allContent[x]); - + { // Save content with new sort order and update db+cache accordingly - var sorted = contentService.Sort(sortedContent); + var intIds = new List(); + foreach (var stringId in ids) + { + int intId; + if (int.TryParse(stringId, out intId)) + intIds.Add(intId); + } + var sorted = contentService.Sort(intIds.ToArray()); // refresh sort order on cached xml // but no... this is not distributed - solely relying on content service & events should be enough