Port 7.7 - WIP
This commit is contained in:
@@ -82,7 +82,7 @@ namespace Umbraco.Core.Configuration
|
||||
catch (Exception ex)
|
||||
{
|
||||
//invalid path format or something... try/catch to be safe
|
||||
LogHelper.Error<ClientDependencyConfiguration>("Could not get path from ClientDependency.config", ex);
|
||||
_logger.Error<ClientDependencyConfiguration>("Could not get path from ClientDependency.config", ex);
|
||||
}
|
||||
|
||||
var success = true;
|
||||
@@ -99,7 +99,7 @@ namespace Umbraco.Core.Configuration
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Something could be locking the directory or the was another error, making sure we don't break the upgrade installer
|
||||
LogHelper.Error<ClientDependencyConfiguration>("Could not clear temp files", ex);
|
||||
_logger.Error<ClientDependencyConfiguration>("Could not clear temp files", ex);
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -419,7 +419,7 @@ namespace Umbraco.Core.Models.Membership
|
||||
if (customUserGroup != null)
|
||||
{
|
||||
//if the group isn't IUserGroup we'll need to look it up
|
||||
var realGroup = customUserGroup as IUserGroup ?? ApplicationContext.Current.Services.UserService.GetUserGroupById(customUserGroup.Id);
|
||||
var realGroup = customUserGroup as IUserGroup ?? Current.Services.UserService.GetUserGroupById(customUserGroup.Id);
|
||||
realGroup.AddAllowedSection(sectionAlias);
|
||||
//now we need to flag this for saving (hack!)
|
||||
GroupsToSave.Add(realGroup);
|
||||
|
||||
@@ -252,7 +252,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
public Dictionary<string, Guid> GetDictionaryItemKeyMap()
|
||||
{
|
||||
var columns = new[] { "key", "id" }.Select(x => (object) SqlSyntax.GetQuotedColumnName(x)).ToArray();
|
||||
var sql = new Sql().Select(columns).From<DictionaryDto>(SqlSyntax);
|
||||
var sql = UnitOfWork.Sql().Select(columns).From<DictionaryDto>();
|
||||
return Database.Fetch<DictionaryItemKeyIdDto>(sql).ToDictionary(x => x.Key, x => x.Id);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,29 +1,10 @@
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
public interface IMediaRepository : IRepositoryVersionable<int, IMedia>, IRecycleBinRepository<IMedia>, IReadRepository<Guid, IMedia>
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to add/update published xml for the media item
|
||||
/// </summary>
|
||||
/// <param name="content"></param>
|
||||
/// <param name="xml"></param>
|
||||
void AddOrUpdateContentXml(IMedia content, Func<IMedia, XElement> xml);
|
||||
|
||||
/// <summary>
|
||||
/// Used to remove the content xml for a content item
|
||||
/// </summary>
|
||||
/// <param name="content"></param>
|
||||
void DeleteContentXml(IMedia content);
|
||||
|
||||
/// <summary>
|
||||
/// Used to add/update preview xml for the content item
|
||||
/// </summary>
|
||||
/// <param name="content"></param>
|
||||
/// <param name="xml"></param>
|
||||
void AddOrUpdatePreviewXml(IMedia content, Func<IMedia, XElement> xml);
|
||||
IMedia GetMediaByPath(string mediaPath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -600,7 +600,7 @@ ORDER BY colName";
|
||||
/// </remarks>
|
||||
public IEnumerable<IUser> GetPagedResultsByQuery(IQuery<IUser> query, long pageIndex, int pageSize, out long totalRecords,
|
||||
Expression<Func<IUser, object>> orderBy, Direction orderDirection = Direction.Ascending,
|
||||
string[] userGroups = null, UserState[] userState = null, IQuery<IUser> filter = null)
|
||||
string[] includeUserGroups = null, string[] excludeUserGroups = null, UserState[] userState = null, IQuery<IUser> filter = null)
|
||||
{
|
||||
if (orderBy == null) throw new ArgumentNullException(nameof(orderBy));
|
||||
|
||||
|
||||
@@ -129,7 +129,7 @@ namespace Umbraco.Core.Persistence
|
||||
/// </summary>
|
||||
internal bool EnableSqlCount
|
||||
{
|
||||
get { return _enableCount; }
|
||||
get => _enableCount;
|
||||
set
|
||||
{
|
||||
_enableCount = value;
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Web.Security;
|
||||
using Microsoft.AspNet.Identity;
|
||||
using Microsoft.AspNet.Identity.Owin;
|
||||
using Microsoft.Owin.Security.DataProtection;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.Models.Identity;
|
||||
@@ -49,20 +50,6 @@ namespace Umbraco.Core.Security
|
||||
|
||||
#region Static Create methods
|
||||
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[Obsolete("Use the overload specifying all dependencies instead")]
|
||||
public static BackOfficeUserManager Create(
|
||||
IdentityFactoryOptions<BackOfficeUserManager> options,
|
||||
IUserService userService,
|
||||
IExternalLoginService externalLoginService,
|
||||
MembershipProviderBase membershipProvider)
|
||||
{
|
||||
return Create(options, userService,
|
||||
ApplicationContext.Current.Services.EntityService,
|
||||
externalLoginService, membershipProvider,
|
||||
UmbracoConfig.For.UmbracoSettings().Content);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a BackOfficeUserManager instance with all default options and the default BackOfficeUserManager
|
||||
/// </summary>
|
||||
|
||||
@@ -350,6 +350,7 @@ namespace Umbraco.Core.Security
|
||||
//Special cases to allow changing password without validating existing credentials
|
||||
// * the member is new and doesn't have a password set
|
||||
// * during installation to set the admin password
|
||||
var installing = Current.RuntimeState.Level == RuntimeLevel.Install;
|
||||
if (AllowManuallyChangingPassword == false
|
||||
&& (rawPasswordValue.StartsWith(Constants.Security.EmptyPasswordPrefix)
|
||||
|| (installing && oldPassword == "default")))
|
||||
|
||||
@@ -411,9 +411,9 @@ namespace Umbraco.Core.Services
|
||||
|
||||
public Dictionary<string, Guid> GetDictionaryItemKeyMap()
|
||||
{
|
||||
using (var uow = UowProvider.GetUnitOfWork(readOnly: true))
|
||||
using (var uow = UowProvider.CreateUnitOfWork(readOnly: true))
|
||||
{
|
||||
var repository = RepositoryFactory.CreateDictionaryRepository(uow);
|
||||
var repository = uow.CreateRepository<IDictionaryRepository>();
|
||||
return repository.GetDictionaryItemKeyMap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ namespace Umbraco.Core.Services
|
||||
var parent = GetById(parentId);
|
||||
return CreateMedia(name, parent, mediaTypeAlias, userId);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="IMedia"/> object of a specified media type.
|
||||
/// </summary>
|
||||
@@ -378,7 +378,7 @@ namespace Umbraco.Core.Services
|
||||
return repository.GetAll(idsA);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection of <see cref="IMedia"/> objects by the Id of the <see cref="IMediaType"/>
|
||||
/// </summary>
|
||||
@@ -632,7 +632,7 @@ namespace Umbraco.Core.Services
|
||||
var query = uow.Query<IMedia>();
|
||||
//if the id is System Root, then just get all
|
||||
if (id != Constants.System.Root)
|
||||
{
|
||||
{
|
||||
var entityRepository = uow.CreateRepository<IEntityRepository>();
|
||||
var mediaPath = entityRepository.GetAllPaths(Constants.ObjectTypes.MediaGuid, id).ToArray();
|
||||
if (mediaPath.Length == 0)
|
||||
@@ -797,7 +797,7 @@ namespace Umbraco.Core.Services
|
||||
|
||||
using (var uow = UowProvider.CreateUnitOfWork())
|
||||
{
|
||||
var saveEventArgs = new SaveEventArgs<IMedia>(media, evtMsgs);
|
||||
var saveEventArgs = new SaveEventArgs<IMedia>(media, evtMsgs);
|
||||
if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, saveEventArgs))
|
||||
{
|
||||
uow.Complete();
|
||||
@@ -1170,7 +1170,7 @@ namespace Umbraco.Core.Services
|
||||
.ToArray();
|
||||
|
||||
moveEventArgs.MoveInfoCollection = moveInfo;
|
||||
moveEventArgs.CanCancel = false;
|
||||
moveEventArgs.CanCancel = false;
|
||||
uow.Events.Dispatch(Moved, this, moveEventArgs);
|
||||
Audit(uow, AuditType.Move, "Move Media performed by user", userId, media.Id);
|
||||
uow.Complete();
|
||||
|
||||
@@ -132,17 +132,18 @@ namespace Umbraco.Core.Services
|
||||
var saveEventArgs = new SaveEventArgs<PublicAccessEntry>(entry, evtMsgs);
|
||||
if (uow.Events.DispatchCancelable(Saving, this, saveEventArgs))
|
||||
{
|
||||
uow.Commit();
|
||||
uow.Complete();
|
||||
return OperationStatus.Attempt.Cancel(evtMsgs, entry);
|
||||
}
|
||||
|
||||
repo.AddOrUpdate(entry);
|
||||
|
||||
uow.Complete();
|
||||
|
||||
saveEventArgs.CanCancel = false;
|
||||
uow.Events.Dispatch(Saved, this, saveEventArgs);
|
||||
}
|
||||
|
||||
saveEventArgs.CanCancel = false;
|
||||
uow.Events.Dispatch(Saved, this, saveEventArgs);
|
||||
return OperationStatus.Attempt.Succeed(evtMsgs, entry);
|
||||
}
|
||||
|
||||
@@ -171,16 +172,17 @@ namespace Umbraco.Core.Services
|
||||
var saveEventArgs = new SaveEventArgs<PublicAccessEntry>(entry, evtMsgs);
|
||||
if (uow.Events.DispatchCancelable(Saving, this, saveEventArgs))
|
||||
{
|
||||
uow.Commit();
|
||||
uow.Complete();
|
||||
return OperationStatus.Attempt.Cancel(evtMsgs);
|
||||
}
|
||||
|
||||
repo.AddOrUpdate(entry);
|
||||
uow.Complete();
|
||||
|
||||
saveEventArgs.CanCancel = false;
|
||||
uow.Events.Dispatch(Saved, this, saveEventArgs);
|
||||
}
|
||||
|
||||
saveEventArgs.CanCancel = false;
|
||||
uow.Events.Dispatch(Saved, this, saveEventArgs);
|
||||
return OperationStatus.Attempt.Succeed(evtMsgs);
|
||||
}
|
||||
|
||||
@@ -197,17 +199,18 @@ namespace Umbraco.Core.Services
|
||||
var saveEventArgs = new SaveEventArgs<PublicAccessEntry>(entry, evtMsgs);
|
||||
if (uow.Events.DispatchCancelable(Saving, this, saveEventArgs))
|
||||
{
|
||||
uow.Commit();
|
||||
uow.Complete();
|
||||
return OperationStatus.Attempt.Cancel(evtMsgs);
|
||||
}
|
||||
|
||||
var repo = uow.CreateRepository<IPublicAccessRepository>();
|
||||
repo.AddOrUpdate(entry);
|
||||
uow.Complete();
|
||||
|
||||
saveEventArgs.CanCancel = false;
|
||||
uow.Events.Dispatch(Saved, this, saveEventArgs);
|
||||
}
|
||||
|
||||
saveEventArgs.CanCancel = false;
|
||||
uow.Events.Dispatch(Saved, this, saveEventArgs);
|
||||
return OperationStatus.Attempt.Succeed(evtMsgs);
|
||||
}
|
||||
|
||||
@@ -224,17 +227,18 @@ namespace Umbraco.Core.Services
|
||||
var deleteEventArgs = new DeleteEventArgs<PublicAccessEntry>(entry, evtMsgs);
|
||||
if (uow.Events.DispatchCancelable(Deleting, this, deleteEventArgs))
|
||||
{
|
||||
uow.Commit();
|
||||
return OperationStatus.Cancelled(evtMsgs);
|
||||
uow.Complete();
|
||||
return OperationStatus.Attempt.Cancel(evtMsgs);
|
||||
}
|
||||
|
||||
var repo = uow.CreateRepository<IPublicAccessRepository>();
|
||||
repo.Delete(entry);
|
||||
uow.Complete();
|
||||
|
||||
deleteEventArgs.CanCancel = false;
|
||||
uow.Events.Dispatch(Deleted, this, deleteEventArgs);
|
||||
}
|
||||
|
||||
deleteEventArgs.CanCancel = false;
|
||||
uow.Events.Dispatch(Deleted, this, deleteEventArgs);
|
||||
return OperationStatus.Attempt.Succeed(evtMsgs);
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,6 @@ namespace Umbraco.Core
|
||||
{ PartialView, UdiType.StringUdi},
|
||||
{ PartialViewMacro, UdiType.StringUdi},
|
||||
{ Stylesheet, UdiType.StringUdi},
|
||||
{ UserControl, UdiType.StringUdi},
|
||||
{ Xslt, UdiType.StringUdi},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -54,6 +54,9 @@
|
||||
<ItemGroup>
|
||||
<!-- note: NuGet deals with transitive references now -->
|
||||
<PackageReference Include="AutoMapper" Version="6.1.1" />
|
||||
<PackageReference Include="ClientDependency">
|
||||
<Version>1.9.2</Version>
|
||||
</PackageReference>
|
||||
<!-- do not update -->
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.5.1" />
|
||||
<PackageReference Include="ImageProcessor" Version="2.5.4" />
|
||||
@@ -1311,6 +1314,7 @@
|
||||
<Compile Include="Security\MembershipProviderPasswordValidator.cs" />
|
||||
<Compile Include="Security\OwinExtensions.cs" />
|
||||
<Compile Include="Security\UmbracoBackOfficeIdentity.cs" />
|
||||
<Compile Include="Security\UmbracoEmailMessage.cs" />
|
||||
<Compile Include="Security\UmbracoMembershipProviderBase.cs" />
|
||||
<Compile Include="Security\UserAwareMembershipProviderPasswordHasher.cs" />
|
||||
<Compile Include="Security\UserData.cs" />
|
||||
|
||||
@@ -7,10 +7,10 @@ namespace Umbraco.Tests.Cache
|
||||
public class CacheRefresherTests
|
||||
{
|
||||
[TestCase("", "123456", "testmachine", true)] //empty hash will continue
|
||||
[TestCase("fffffff28449cf33", "123456", "testmachine", false)] //match, don't continue
|
||||
[TestCase("fffffff28449cf33", "12345", "testmachine", true)] // no match, continue
|
||||
[TestCase("fffffff28449cf33", "123456", "testmachin", true)] // same
|
||||
[TestCase("fffffff28449cf3", "123456", "testmachine", true)] // same
|
||||
[TestCase("2e6deefea4444a69dbd15a01b4c2749d", "123456", "testmachine", false)] //match, don't continue
|
||||
[TestCase("2e6deefea4444a69dbd15a01b4c2749d", "12345", "testmachine", true)] // no match, continue
|
||||
[TestCase("2e6deefea4444a69dbd15a01b4c2749d", "123456", "testmachin", true)] // same
|
||||
[TestCase("2e6deefea4444a69dbd15a01b4c2749", "123456", "testmachine", true)] // same
|
||||
public void Continue_Refreshing_For_Request(string hash, string appDomainAppId, string machineName, bool expected)
|
||||
{
|
||||
if (expected)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using LightInject;
|
||||
using Moq;
|
||||
using Newtonsoft.Json;
|
||||
@@ -7,6 +9,7 @@ using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Deploy;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Serialization;
|
||||
|
||||
@@ -31,7 +34,7 @@ namespace Umbraco.Tests.CoreThings
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void StringEntityCtorTest()
|
||||
public void StringUdiCtorTest()
|
||||
{
|
||||
var udi = new StringUdi(Constants.UdiEntityType.AnyString, "test-id");
|
||||
Assert.AreEqual(Constants.UdiEntityType.AnyString, udi.EntityType);
|
||||
@@ -40,7 +43,7 @@ namespace Umbraco.Tests.CoreThings
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void StringEntityParseTest()
|
||||
public void StringUdiParseTest()
|
||||
{
|
||||
var udi = Udi.Parse("umb://" + Constants.UdiEntityType.AnyString + "/test-id");
|
||||
Assert.AreEqual(Constants.UdiEntityType.AnyString, udi.EntityType);
|
||||
@@ -49,6 +52,9 @@ namespace Umbraco.Tests.CoreThings
|
||||
Assert.IsNotNull(stringEntityId);
|
||||
Assert.AreEqual("test-id", stringEntityId.Id);
|
||||
Assert.AreEqual("umb://" + Constants.UdiEntityType.AnyString + "/test-id", udi.ToString());
|
||||
|
||||
udi = Udi.Parse("umb://" + Constants.UdiEntityType.AnyString + "/DA845952BE474EE9BD6F6194272AC750");
|
||||
Assert.IsInstanceOf<StringUdi>(udi);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -103,7 +109,7 @@ namespace Umbraco.Tests.CoreThings
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GuidEntityCtorTest()
|
||||
public void GuidUdiCtorTest()
|
||||
{
|
||||
var guid = Guid.NewGuid();
|
||||
var udi = new GuidUdi(Constants.UdiEntityType.AnyGuid, guid);
|
||||
@@ -113,7 +119,7 @@ namespace Umbraco.Tests.CoreThings
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GuidEntityParseTest()
|
||||
public void GuidUdiParseTest()
|
||||
{
|
||||
var guid = Guid.NewGuid();
|
||||
var s = "umb://" + Constants.UdiEntityType.AnyGuid + "/" + guid.ToString("N");
|
||||
@@ -168,9 +174,33 @@ namespace Umbraco.Tests.CoreThings
|
||||
Assert.AreEqual(Constants.UdiEntityType.AnyGuid, udi.EntityType);
|
||||
Assert.AreEqual(guid, ((GuidUdi)udi).Guid);
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() => Udi.Create(Constants.UdiEntityType.AnyString, guid));
|
||||
Assert.Throws<InvalidOperationException>(() => Udi.Create(Constants.UdiEntityType.AnyGuid, "foo"));
|
||||
Assert.Throws<ArgumentException>(() => Udi.Create("barf", "foo"));
|
||||
|
||||
// *not* testing whether Udi.Create(type, invalidValue) throws
|
||||
// because we don't throw anymore - see U4-10409
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RootUdiTest()
|
||||
{
|
||||
var stringUdi = new StringUdi(Constants.UdiEntityType.AnyString, string.Empty);
|
||||
Assert.IsTrue(stringUdi.IsRoot);
|
||||
Assert.AreEqual("umb://any-string/", stringUdi.ToString());
|
||||
|
||||
var guidUdi = new GuidUdi(Constants.UdiEntityType.AnyGuid, Guid.Empty);
|
||||
Assert.IsTrue(guidUdi.IsRoot);
|
||||
Assert.AreEqual("umb://any-guid/00000000000000000000000000000000", guidUdi.ToString());
|
||||
|
||||
var udi = Udi.Parse("umb://any-string/");
|
||||
Assert.IsTrue(udi.IsRoot);
|
||||
Assert.IsInstanceOf<StringUdi>(udi);
|
||||
|
||||
udi = Udi.Parse("umb://any-guid/00000000000000000000000000000000");
|
||||
Assert.IsTrue(udi.IsRoot);
|
||||
Assert.IsInstanceOf<GuidUdi>(udi);
|
||||
|
||||
udi = Udi.Parse("umb://any-guid/");
|
||||
Assert.IsTrue(udi.IsRoot);
|
||||
Assert.IsInstanceOf<GuidUdi>(udi);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -222,5 +252,112 @@ namespace Umbraco.Tests.CoreThings
|
||||
Assert.AreEqual(string.Format("umb://any-guid/{0:N}", guid), drange.Udi.UriValue.ToString());
|
||||
Assert.AreEqual(Constants.DeploySelector.ChildrenOfThis, drange.Selector);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ValidateUdiEntityType()
|
||||
{
|
||||
var types = Constants.UdiEntityType.GetTypes();
|
||||
|
||||
foreach (var fi in typeof(Constants.UdiEntityType).GetFields(BindingFlags.Public | BindingFlags.Static))
|
||||
{
|
||||
// IsLiteral determines if its value is written at
|
||||
// compile time and not changeable
|
||||
// IsInitOnly determine if the field can be set
|
||||
// in the body of the constructor
|
||||
// for C# a field which is readonly keyword would have both true
|
||||
// but a const field would have only IsLiteral equal to true
|
||||
if (fi.IsLiteral && fi.IsInitOnly == false)
|
||||
{
|
||||
var value = fi.GetValue(null).ToString();
|
||||
|
||||
if (types.ContainsKey(value) == false)
|
||||
Assert.Fail("Error in class Constants.UdiEntityType, type \"{0}\" is not declared by GetTypes.", value);
|
||||
types.Remove(value);
|
||||
}
|
||||
}
|
||||
|
||||
Assert.AreEqual(0, types.Count, "Error in class Constants.UdiEntityType, GetTypes declares types that don't exist ({0}).", string.Join(",", types.Keys.Select(x => "\"" + x + "\"")));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void KnownTypes()
|
||||
{
|
||||
Udi udi;
|
||||
|
||||
// cannot parse an unknown type, udi is null
|
||||
// this will scan
|
||||
Assert.IsFalse(Udi.TryParse("umb://whatever/1234", out udi));
|
||||
Assert.IsNull(udi);
|
||||
|
||||
Udi.ResetUdiTypes();
|
||||
|
||||
// unless we want to know
|
||||
Assert.IsFalse(Udi.TryParse("umb://whatever/1234", true, out udi));
|
||||
Assert.AreEqual(Constants.UdiEntityType.Unknown, udi.EntityType);
|
||||
Assert.AreEqual("Umbraco.Core.Udi+UnknownTypeUdi", udi.GetType().FullName);
|
||||
|
||||
Udi.ResetUdiTypes();
|
||||
|
||||
// not known
|
||||
Assert.IsFalse(Udi.TryParse("umb://foo/A87F65C8D6B94E868F6949BA92C93045", true, out udi));
|
||||
Assert.AreEqual(Constants.UdiEntityType.Unknown, udi.EntityType);
|
||||
Assert.AreEqual("Umbraco.Core.Udi+UnknownTypeUdi", udi.GetType().FullName);
|
||||
|
||||
// scanned
|
||||
Assert.IsTrue(Udi.TryParse("umb://foo/A87F65C8D6B94E868F6949BA92C93045", out udi));
|
||||
Assert.IsInstanceOf<GuidUdi>(udi);
|
||||
|
||||
// known
|
||||
Assert.IsTrue(Udi.TryParse("umb://foo/A87F65C8D6B94E868F6949BA92C93045", true, out udi));
|
||||
Assert.IsInstanceOf<GuidUdi>(udi);
|
||||
|
||||
// can get method for Deploy compatibility
|
||||
var method = typeof(Udi).GetMethod("Parse", BindingFlags.Static | BindingFlags.Public, null, new[] { typeof(string), typeof(bool) }, null);
|
||||
Assert.IsNotNull(method);
|
||||
}
|
||||
|
||||
[UdiDefinition("foo", UdiType.GuidUdi)]
|
||||
public class FooConnector : IServiceConnector
|
||||
{
|
||||
public IArtifact GetArtifact(Udi udi)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public IArtifact GetArtifact(object entity)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public ArtifactDeployState ProcessInit(IArtifact art, IDeployContext context)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Process(ArtifactDeployState dart, IDeployContext context, int pass)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Explode(UdiRange range, List<Udi> udis)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public NamedUdiRange GetRange(Udi udi, string selector)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public NamedUdiRange GetRange(string entityType, string sid, string selector)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public bool Compare(IArtifact art1, IArtifact art2, ICollection<Difference> differences = null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,21 @@ namespace Umbraco.Tests.FrontEnd
|
||||
Assert.AreEqual("Hello world, this is some…", result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If a truncated string ends with a space, we should trim the space before appending the ellipsis.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void Truncate_Simple_With_Trimming()
|
||||
{
|
||||
var text = "Hello world, this is some text <a href='blah'>with a link</a>";
|
||||
|
||||
var helper = new UmbracoHelper();
|
||||
|
||||
var result = helper.Truncate(text, 26).ToString();
|
||||
|
||||
Assert.AreEqual("Hello world, this is some…", result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Truncate_Inside_Word()
|
||||
{
|
||||
@@ -78,5 +93,79 @@ namespace Umbraco.Tests.FrontEnd
|
||||
|
||||
Assert.AreEqual(expectedResult, result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Truncate_By_Words()
|
||||
{
|
||||
var text = "Hello world, this is some text <a href='blah'>with a link</a>";
|
||||
|
||||
var helper = new UmbracoHelper();
|
||||
|
||||
var result = helper.TruncateByWords(text, 4).ToString();
|
||||
|
||||
Assert.AreEqual("Hello world, this is…", result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Truncate_By_Words_With_Tag()
|
||||
{
|
||||
var text = "Hello world, <b>this</b> is some text <a href='blah'>with a link</a>";
|
||||
|
||||
var helper = new UmbracoHelper();
|
||||
|
||||
var result = helper.TruncateByWords(text, 4).ToString();
|
||||
|
||||
Assert.AreEqual("Hello world, <b>this</b> is…", result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Truncate_By_Words_Mid_Tag()
|
||||
{
|
||||
var text = "Hello world, this is some text <a href='blah'>with a link</a>";
|
||||
|
||||
var helper = new UmbracoHelper();
|
||||
|
||||
var result = helper.TruncateByWords(text, 7).ToString();
|
||||
|
||||
Assert.AreEqual("Hello world, this is some text <a href='blah'>with…</a>", result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Strip_All_Html()
|
||||
{
|
||||
var text = "Hello world, <b>this</b> is some text <a href='blah'>with a link</a>";
|
||||
|
||||
var helper = new UmbracoHelper();
|
||||
|
||||
var result = helper.StripHtml(text, null).ToString();
|
||||
|
||||
Assert.AreEqual("Hello world, this is some text with a link", result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Strip_Specific_Html()
|
||||
{
|
||||
var text = "Hello world, <b>this</b> is some text <a href='blah'>with a link</a>";
|
||||
|
||||
string [] tags = {"b"};
|
||||
|
||||
var helper = new UmbracoHelper();
|
||||
|
||||
var result = helper.StripHtml(text, tags).ToString();
|
||||
|
||||
Assert.AreEqual("Hello world, this is some text <a href='blah'>with a link</a>", result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Strip_Invalid_Html()
|
||||
{
|
||||
var text = "Hello world, <bthis</b> is some text <a href='blah'>with a link</a>";
|
||||
|
||||
var helper = new UmbracoHelper();
|
||||
|
||||
var result = helper.StripHtml(text).ToString();
|
||||
|
||||
Assert.AreEqual("Hello world, is some text with a link", result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
|
||||
namespace Umbraco.Tests
|
||||
namespace Umbraco.Tests.Misc
|
||||
{
|
||||
[TestFixture]
|
||||
public class DateTimeExtensionsTests
|
||||
@@ -0,0 +1,184 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
|
||||
namespace Umbraco.Tests.Misc
|
||||
{
|
||||
[TestFixture]
|
||||
public class HashGeneratorTests
|
||||
{
|
||||
private string Generate(bool isCaseSensitive, params string[] strs)
|
||||
{
|
||||
using (var generator = new HashGenerator())
|
||||
{
|
||||
foreach (var str in strs)
|
||||
{
|
||||
if (isCaseSensitive)
|
||||
generator.AddString(str);
|
||||
else
|
||||
generator.AddCaseInsensitiveString(str);
|
||||
}
|
||||
return generator.GenerateHash();
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Generate_Hash_Multiple_Strings_Case_Sensitive()
|
||||
{
|
||||
|
||||
var hash1 = Generate(true, "hello", "world");
|
||||
var hash2 = Generate(true, "hello", "world");
|
||||
var hashFalse1 = Generate(true, "hello", "worlD");
|
||||
var hashFalse2 = Generate(true, "hEllo", "world");
|
||||
|
||||
Assert.AreEqual(hash1, hash2);
|
||||
Assert.AreNotEqual(hash1, hashFalse1);
|
||||
Assert.AreNotEqual(hash1, hashFalse2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Generate_Hash_Multiple_Strings_Case_Insensitive()
|
||||
{
|
||||
var hash1 = Generate(false, "hello", "world");
|
||||
var hash2 = Generate(false, "hello", "world");
|
||||
var hashFalse1 = Generate(false, "hello", "worlD");
|
||||
var hashFalse2 = Generate(false, "hEllo", "world");
|
||||
|
||||
Assert.AreEqual(hash1, hash2);
|
||||
Assert.AreEqual(hash1, hashFalse1);
|
||||
Assert.AreEqual(hash1, hashFalse2);
|
||||
}
|
||||
|
||||
private DirectoryInfo PrepareFolder()
|
||||
{
|
||||
var assDir = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory;
|
||||
var dir = Directory.CreateDirectory(Path.Combine(assDir.FullName, "HashCombiner", Guid.NewGuid().ToString("N")));
|
||||
foreach (var f in dir.GetFiles())
|
||||
{
|
||||
f.Delete();
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void HashCombiner_Test_String()
|
||||
{
|
||||
using (var combiner1 = new HashGenerator())
|
||||
using (var combiner2 = new HashGenerator())
|
||||
{
|
||||
combiner1.AddCaseInsensitiveString("Hello");
|
||||
combiner2.AddCaseInsensitiveString("hello");
|
||||
Assert.AreEqual(combiner1.GenerateHash(), combiner2.GenerateHash());
|
||||
combiner2.AddCaseInsensitiveString("world");
|
||||
Assert.AreNotEqual(combiner1.GenerateHash(), combiner2.GenerateHash());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void HashCombiner_Test_Int()
|
||||
{
|
||||
using (var combiner1 = new HashGenerator())
|
||||
using (var combiner2 = new HashGenerator())
|
||||
{
|
||||
combiner1.AddInt(1234);
|
||||
combiner2.AddInt(1234);
|
||||
Assert.AreEqual(combiner1.GenerateHash(), combiner2.GenerateHash());
|
||||
combiner2.AddInt(1);
|
||||
Assert.AreNotEqual(combiner1.GenerateHash(), combiner2.GenerateHash());
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void HashCombiner_Test_DateTime()
|
||||
{
|
||||
using (var combiner1 = new HashGenerator())
|
||||
using (var combiner2 = new HashGenerator())
|
||||
{
|
||||
var dt = DateTime.Now;
|
||||
combiner1.AddDateTime(dt);
|
||||
combiner2.AddDateTime(dt);
|
||||
Assert.AreEqual(combiner1.GenerateHash(), combiner2.GenerateHash());
|
||||
combiner2.AddDateTime(DateTime.Now);
|
||||
Assert.AreNotEqual(combiner1.GenerateHash(), combiner2.GenerateHash());
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void HashCombiner_Test_File()
|
||||
{
|
||||
using (var combiner1 = new HashGenerator())
|
||||
using (var combiner2 = new HashGenerator())
|
||||
using (var combiner3 = new HashGenerator())
|
||||
{
|
||||
var dir = PrepareFolder();
|
||||
var file1Path = Path.Combine(dir.FullName, "hastest1.txt");
|
||||
File.Delete(file1Path);
|
||||
using (var file1 = File.CreateText(Path.Combine(dir.FullName, "hastest1.txt")))
|
||||
{
|
||||
file1.WriteLine("hello");
|
||||
}
|
||||
var file2Path = Path.Combine(dir.FullName, "hastest2.txt");
|
||||
File.Delete(file2Path);
|
||||
using (var file2 = File.CreateText(Path.Combine(dir.FullName, "hastest2.txt")))
|
||||
{
|
||||
//even though files are the same, the dates are different
|
||||
file2.WriteLine("hello");
|
||||
}
|
||||
|
||||
combiner1.AddFile(new FileInfo(file1Path));
|
||||
|
||||
combiner2.AddFile(new FileInfo(file1Path));
|
||||
|
||||
combiner3.AddFile(new FileInfo(file2Path));
|
||||
|
||||
Assert.AreEqual(combiner1.GenerateHash(), combiner2.GenerateHash());
|
||||
Assert.AreNotEqual(combiner1.GenerateHash(), combiner3.GenerateHash());
|
||||
|
||||
combiner2.AddFile(new FileInfo(file2Path));
|
||||
|
||||
Assert.AreNotEqual(combiner1.GenerateHash(), combiner2.GenerateHash());
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void HashCombiner_Test_Folder()
|
||||
{
|
||||
using (var combiner1 = new HashGenerator())
|
||||
using (var combiner2 = new HashGenerator())
|
||||
using (var combiner3 = new HashGenerator())
|
||||
{
|
||||
var dir = PrepareFolder();
|
||||
var file1Path = Path.Combine(dir.FullName, "hastest1.txt");
|
||||
File.Delete(file1Path);
|
||||
using (var file1 = File.CreateText(Path.Combine(dir.FullName, "hastest1.txt")))
|
||||
{
|
||||
file1.WriteLine("hello");
|
||||
}
|
||||
|
||||
//first test the whole folder
|
||||
combiner1.AddFolder(dir);
|
||||
|
||||
combiner2.AddFolder(dir);
|
||||
|
||||
Assert.AreEqual(combiner1.GenerateHash(), combiner2.GenerateHash());
|
||||
|
||||
//now add a file to the folder
|
||||
|
||||
var file2Path = Path.Combine(dir.FullName, "hastest2.txt");
|
||||
File.Delete(file2Path);
|
||||
using (var file2 = File.CreateText(Path.Combine(dir.FullName, "hastest2.txt")))
|
||||
{
|
||||
//even though files are the same, the dates are different
|
||||
file2.WriteLine("hello");
|
||||
}
|
||||
|
||||
combiner3.AddFolder(dir);
|
||||
|
||||
Assert.AreNotEqual(combiner1.GenerateHash(), combiner3.GenerateHash());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,6 +54,9 @@ namespace Umbraco.Tests.Models
|
||||
[TestCase("3,8", "2,6", "3,2")] // exclude bin
|
||||
[TestCase("", "6", "")] // exclude bin
|
||||
|
||||
[TestCase("1,-1", "1", "1")] // was an issue
|
||||
[TestCase("-1,1", "1", "1")] // was an issue
|
||||
|
||||
public void CombineStartNodes(string groupSn, string userSn, string expected)
|
||||
{
|
||||
// 1
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Models;
|
||||
@@ -41,29 +43,82 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
base.TearDown();
|
||||
}
|
||||
|
||||
private ContentRepository CreateRepository(IScopeUnitOfWork unitOfWork, out ContentTypeRepository contentTypeRepository, out DataTypeDefinitionRepository dtdRepository)
|
||||
private ContentRepository CreateRepository(IScopeUnitOfWork unitOfWork, out ContentTypeRepository contentTypeRepository, out DataTypeDefinitionRepository dtdRepository, CacheHelper cacheHelper = null)
|
||||
{
|
||||
cacheHelper = cacheHelper ?? CacheHelper;
|
||||
|
||||
TemplateRepository tr;
|
||||
var ctRepository = CreateRepository(unitOfWork, out contentTypeRepository, out tr);
|
||||
dtdRepository = new DataTypeDefinitionRepository(unitOfWork, CacheHelper, Logger, contentTypeRepository);
|
||||
dtdRepository = new DataTypeDefinitionRepository(unitOfWork, cacheHelper, Logger, contentTypeRepository);
|
||||
return ctRepository;
|
||||
}
|
||||
|
||||
private ContentRepository CreateRepository(IScopeUnitOfWork unitOfWork, out ContentTypeRepository contentTypeRepository)
|
||||
private ContentRepository CreateRepository(IScopeUnitOfWork unitOfWork, out ContentTypeRepository contentTypeRepository, CacheHelper cacheHelper = null)
|
||||
{
|
||||
TemplateRepository tr;
|
||||
return CreateRepository(unitOfWork, out contentTypeRepository, out tr);
|
||||
return CreateRepository(unitOfWork, out contentTypeRepository, out tr, cacheHelper);
|
||||
}
|
||||
|
||||
private ContentRepository CreateRepository(IScopeUnitOfWork unitOfWork, out ContentTypeRepository contentTypeRepository, out TemplateRepository templateRepository)
|
||||
private ContentRepository CreateRepository(IScopeUnitOfWork unitOfWork, out ContentTypeRepository contentTypeRepository, out TemplateRepository templateRepository, CacheHelper cacheHelper = null)
|
||||
{
|
||||
templateRepository = new TemplateRepository(unitOfWork, CacheHelper, Logger, Mock.Of<IFileSystem>(), Mock.Of<IFileSystem>(), Mock.Of<ITemplatesSection>());
|
||||
var tagRepository = new TagRepository(unitOfWork, CacheHelper, Logger);
|
||||
contentTypeRepository = new ContentTypeRepository(unitOfWork, CacheHelper, Logger, templateRepository);
|
||||
var repository = new ContentRepository(unitOfWork, CacheHelper, Logger, contentTypeRepository, templateRepository, tagRepository, Mock.Of<IContentSection>());
|
||||
cacheHelper = cacheHelper ?? CacheHelper;
|
||||
|
||||
templateRepository = new TemplateRepository(unitOfWork, cacheHelper, Logger, Mock.Of<IFileSystem>(), Mock.Of<IFileSystem>(), Mock.Of<ITemplatesSection>());
|
||||
var tagRepository = new TagRepository(unitOfWork, cacheHelper, Logger);
|
||||
contentTypeRepository = new ContentTypeRepository(unitOfWork, cacheHelper, Logger, templateRepository);
|
||||
var repository = new ContentRepository(unitOfWork, cacheHelper, Logger, contentTypeRepository, templateRepository, tagRepository, Mock.Of<IContentSection>());
|
||||
return repository;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Cache_Active_By_Int_And_Guid()
|
||||
{
|
||||
ContentTypeRepository contentTypeRepository;
|
||||
|
||||
var realCache = new CacheHelper(
|
||||
new ObjectCacheRuntimeCacheProvider(),
|
||||
new StaticCacheProvider(),
|
||||
new StaticCacheProvider(),
|
||||
new IsolatedRuntimeCache(t => new ObjectCacheRuntimeCacheProvider()));
|
||||
|
||||
var provider = TestObjects.GetScopeUnitOfWorkProvider(Logger);
|
||||
using (var unitOfWork = provider.CreateUnitOfWork())
|
||||
{
|
||||
var repository = CreateRepository(unitOfWork, out contentTypeRepository, cacheHelper: realCache);
|
||||
|
||||
var udb = (UmbracoDatabase) unitOfWork.Database;
|
||||
|
||||
udb.EnableSqlCount = false;
|
||||
|
||||
var contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage1", "Textpage");
|
||||
var content = MockedContent.CreateSimpleContent(contentType);
|
||||
contentTypeRepository.AddOrUpdate(contentType);
|
||||
repository.AddOrUpdate(content);
|
||||
unitOfWork.Complete();
|
||||
|
||||
udb.EnableSqlCount = true;
|
||||
|
||||
//go get it, this should already be cached since the default repository key is the INT
|
||||
var found = repository.Get(content.Id);
|
||||
Assert.AreEqual(0, udb.SqlCount);
|
||||
//retrieve again, this should use cache
|
||||
found = repository.Get(content.Id);
|
||||
Assert.AreEqual(0, udb.SqlCount);
|
||||
|
||||
//reset counter
|
||||
udb.EnableSqlCount = false;
|
||||
udb.EnableSqlCount = true;
|
||||
|
||||
//now get by GUID, this won't be cached yet because the default repo key is not a GUID
|
||||
found = repository.Get(content.Key);
|
||||
var sqlCount = udb.SqlCount;
|
||||
Assert.Greater(sqlCount, 0);
|
||||
//retrieve again, this should use cache now
|
||||
found = repository.Get(content.Key);
|
||||
Assert.AreEqual(sqlCount, udb.SqlCount);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Get_Always_Returns_Latest_Version()
|
||||
{
|
||||
@@ -822,6 +877,16 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
Assert.That(contents, Is.Not.Null);
|
||||
Assert.That(contents.Any(), Is.True);
|
||||
Assert.That(contents.Count(), Is.GreaterThanOrEqualTo(4));
|
||||
|
||||
contents = repository.GetAll(contents.Select(x => x.Id).ToArray());
|
||||
Assert.That(contents, Is.Not.Null);
|
||||
Assert.That(contents.Any(), Is.True);
|
||||
Assert.That(contents.Count(), Is.GreaterThanOrEqualTo(4));
|
||||
|
||||
contents = ((IReadRepository<Guid, IContent>)repository).GetAll(contents.Select(x => x.Key).ToArray());
|
||||
Assert.That(contents, Is.Not.Null);
|
||||
Assert.That(contents.Any(), Is.True);
|
||||
Assert.That(contents.Count(), Is.GreaterThanOrEqualTo(4));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core.Models;
|
||||
@@ -148,7 +149,6 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void Can_Perform_GetAll_On_DictionaryRepository()
|
||||
{
|
||||
@@ -354,6 +354,24 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Perform_GetDictionaryItemKeyMap_On_DictionaryRepository()
|
||||
{
|
||||
Dictionary<string, Guid> keyMap;
|
||||
|
||||
var provider = TestObjects.GetScopeUnitOfWorkProvider(Logger);
|
||||
using (var unitOfWork = provider.CreateUnitOfWork())
|
||||
{
|
||||
var repository = CreateRepository(unitOfWork);
|
||||
keyMap = repository.GetDictionaryItemKeyMap();
|
||||
}
|
||||
|
||||
Assert.IsNotNull(keyMap);
|
||||
Assert.IsNotEmpty(keyMap);
|
||||
foreach (var kvp in keyMap)
|
||||
Console.WriteLine("{0}: {1}", kvp.Key, kvp.Value);
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public override void TearDown()
|
||||
{
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
using System.Linq;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
using Umbraco.Core.Models.Rdbms;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.Repositories;
|
||||
using Umbraco.Core.Persistence.UnitOfWork;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
@@ -26,14 +28,65 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
CreateTestData();
|
||||
}
|
||||
|
||||
private MediaRepository CreateRepository(IScopeUnitOfWork unitOfWork, out MediaTypeRepository mediaTypeRepository)
|
||||
private MediaRepository CreateRepository(IScopeUnitOfWork unitOfWork, out MediaTypeRepository mediaTypeRepository, CacheHelper cacheHelper = null)
|
||||
{
|
||||
mediaTypeRepository = new MediaTypeRepository(unitOfWork, CacheHelper, Logger);
|
||||
var tagRepository = new TagRepository(unitOfWork, CacheHelper, Logger);
|
||||
var repository = new MediaRepository(unitOfWork, CacheHelper, Logger, mediaTypeRepository, tagRepository, Mock.Of<IContentSection>());
|
||||
cacheHelper = cacheHelper ?? CacheHelper;
|
||||
|
||||
mediaTypeRepository = new MediaTypeRepository(unitOfWork, cacheHelper, Logger);
|
||||
var tagRepository = new TagRepository(unitOfWork, cacheHelper, Logger);
|
||||
var repository = new MediaRepository(unitOfWork, cacheHelper, Logger, mediaTypeRepository, tagRepository, Mock.Of<IContentSection>());
|
||||
return repository;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Cache_Active_By_Int_And_Guid()
|
||||
{
|
||||
MediaTypeRepository mediaTypeRepository;
|
||||
|
||||
var realCache = new CacheHelper(
|
||||
new ObjectCacheRuntimeCacheProvider(),
|
||||
new StaticCacheProvider(),
|
||||
new StaticCacheProvider(),
|
||||
new IsolatedRuntimeCache(t => new ObjectCacheRuntimeCacheProvider()));
|
||||
|
||||
var provider = TestObjects.GetScopeUnitOfWorkProvider(Logger);
|
||||
using (var unitOfWork = provider.CreateUnitOfWork())
|
||||
{
|
||||
var repository = CreateRepository(unitOfWork, out mediaTypeRepository, cacheHelper: realCache);
|
||||
|
||||
var udb = (UmbracoDatabase)unitOfWork.Database;
|
||||
|
||||
udb.EnableSqlCount = false;
|
||||
|
||||
var mediaType = MockedContentTypes.CreateSimpleMediaType("umbTextpage1", "Textpage");
|
||||
var media = MockedMedia.CreateSimpleMedia(mediaType, "hello", -1);
|
||||
mediaTypeRepository.AddOrUpdate(mediaType);
|
||||
repository.AddOrUpdate(media);
|
||||
unitOfWork.Complete();
|
||||
|
||||
udb.EnableSqlCount = true;
|
||||
|
||||
//go get it, this should already be cached since the default repository key is the INT
|
||||
var found = repository.Get(media.Id);
|
||||
Assert.AreEqual(0, udb.SqlCount);
|
||||
//retrieve again, this should use cache
|
||||
found = repository.Get(media.Id);
|
||||
Assert.AreEqual(0, udb.SqlCount);
|
||||
|
||||
//reset counter
|
||||
udb.EnableSqlCount = false;
|
||||
udb.EnableSqlCount = true;
|
||||
|
||||
//now get by GUID, this won't be cached yet because the default repo key is not a GUID
|
||||
found = repository.Get(media.Key);
|
||||
var sqlCount = udb.SqlCount;
|
||||
Assert.Greater(sqlCount, 0);
|
||||
//retrieve again, this should use cache now
|
||||
found = repository.Get(media.Key);
|
||||
Assert.AreEqual(sqlCount, udb.SqlCount);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Perform_Add_On_MediaRepository()
|
||||
{
|
||||
@@ -486,6 +539,16 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
Assert.That(medias, Is.Not.Null);
|
||||
Assert.That(medias.Any(), Is.True);
|
||||
Assert.That(medias.Count(), Is.GreaterThanOrEqualTo(3));
|
||||
|
||||
medias = repository.GetAll(medias.Select(x => x.Id).ToArray());
|
||||
Assert.That(medias, Is.Not.Null);
|
||||
Assert.That(medias.Any(), Is.True);
|
||||
Assert.That(medias.Count(), Is.GreaterThanOrEqualTo(3));
|
||||
|
||||
medias = ((IReadRepository<Guid, IMedia>)repository).GetAll(medias.Select(x => x.Key).ToArray());
|
||||
Assert.That(medias, Is.Not.Null);
|
||||
Assert.That(medias.Any(), Is.True);
|
||||
Assert.That(medias.Count(), Is.GreaterThanOrEqualTo(3));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core.Persistence.Repositories;
|
||||
|
||||
namespace Umbraco.Tests.Persistence.Repositories
|
||||
{
|
||||
[TestFixture]
|
||||
public class SimilarNodeNameTests
|
||||
{
|
||||
[TestCase("Alpha", "Alpha", 0)]
|
||||
[TestCase("Alpha", "ALPHA", +1)] // case is important
|
||||
[TestCase("Alpha", "Bravo", -1)]
|
||||
[TestCase("Bravo", "Alpha", +1)]
|
||||
[TestCase("Alpha (1)", "Alpha (1)", 0)]
|
||||
[TestCase("Alpha", "Alpha (1)", -1)]
|
||||
[TestCase("Alpha (1)", "Alpha", +1)]
|
||||
[TestCase("Alpha (1)", "Alpha (2)", -1)]
|
||||
[TestCase("Alpha (2)", "Alpha (1)", +1)]
|
||||
[TestCase("Alpha (2)", "Alpha (10)", -1)] // this is the real stuff
|
||||
[TestCase("Alpha (10)", "Alpha (2)", +1)] // this is the real stuff
|
||||
[TestCase("Kilo", "Golf (2)", +1)]
|
||||
[TestCase("Kilo (1)", "Golf (2)", +1)]
|
||||
public void ComparerTest(string name1, string name2, int expected)
|
||||
{
|
||||
var comparer = new SimilarNodeName.Comparer();
|
||||
|
||||
var result = comparer.Compare(new SimilarNodeName { Name = name1 }, new SimilarNodeName { Name = name2 });
|
||||
if (expected == 0)
|
||||
Assert.AreEqual(0, result);
|
||||
else if (expected < 0)
|
||||
Assert.IsTrue(result < 0, "Expected <0 but was " + result);
|
||||
else if (expected > 0)
|
||||
Assert.IsTrue(result > 0, "Expected >0 but was " + result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OrderByTest()
|
||||
{
|
||||
var names = new[]
|
||||
{
|
||||
new SimilarNodeName { Id = 1, Name = "Alpha (2)" },
|
||||
new SimilarNodeName { Id = 2, Name = "Alpha" },
|
||||
new SimilarNodeName { Id = 3, Name = "Golf" },
|
||||
new SimilarNodeName { Id = 4, Name = "Zulu" },
|
||||
new SimilarNodeName { Id = 5, Name = "Mike" },
|
||||
new SimilarNodeName { Id = 6, Name = "Kilo (1)" },
|
||||
new SimilarNodeName { Id = 7, Name = "Yankee" },
|
||||
new SimilarNodeName { Id = 8, Name = "Kilo" },
|
||||
new SimilarNodeName { Id = 9, Name = "Golf (2)" },
|
||||
new SimilarNodeName { Id = 10, Name = "Alpha (1)" },
|
||||
};
|
||||
|
||||
var ordered = names.OrderBy(x => x, new SimilarNodeName.Comparer()).ToArray();
|
||||
|
||||
var i = 0;
|
||||
Assert.AreEqual(2, ordered[i++].Id);
|
||||
Assert.AreEqual(10, ordered[i++].Id);
|
||||
Assert.AreEqual(1, ordered[i++].Id);
|
||||
Assert.AreEqual(3, ordered[i++].Id);
|
||||
Assert.AreEqual(9, ordered[i++].Id);
|
||||
Assert.AreEqual(8, ordered[i++].Id);
|
||||
Assert.AreEqual(6, ordered[i++].Id);
|
||||
Assert.AreEqual(5, ordered[i++].Id);
|
||||
Assert.AreEqual(7, ordered[i++].Id);
|
||||
Assert.AreEqual(4, ordered[i++].Id);
|
||||
}
|
||||
|
||||
[TestCase(0, "Charlie", "Charlie")]
|
||||
[TestCase(0, "Zulu", "Zulu (1)")]
|
||||
[TestCase(0, "Golf", "Golf (1)")]
|
||||
[TestCase(0, "Kilo", "Kilo (2)")]
|
||||
[TestCase(0, "Alpha", "Alpha (3)")]
|
||||
[TestCase(0, "Kilo (1)", "Kilo (1) (1)")] // though... we might consider "Kilo (2)"
|
||||
[TestCase(6, "Kilo (1)", "Kilo (1)")] // because of the id
|
||||
public void Test(int nodeId, string nodeName, string expected)
|
||||
{
|
||||
var names = new[]
|
||||
{
|
||||
new SimilarNodeName { Id = 1, Name = "Alpha (2)" },
|
||||
new SimilarNodeName { Id = 2, Name = "Alpha" },
|
||||
new SimilarNodeName { Id = 3, Name = "Golf" },
|
||||
new SimilarNodeName { Id = 4, Name = "Zulu" },
|
||||
new SimilarNodeName { Id = 5, Name = "Mike" },
|
||||
new SimilarNodeName { Id = 6, Name = "Kilo (1)" },
|
||||
new SimilarNodeName { Id = 7, Name = "Yankee" },
|
||||
new SimilarNodeName { Id = 8, Name = "Kilo" },
|
||||
new SimilarNodeName { Id = 9, Name = "Golf (2)" },
|
||||
new SimilarNodeName { Id = 10, Name = "Alpha (1)" },
|
||||
};
|
||||
|
||||
Assert.AreEqual(expected, SimilarNodeName.GetUniqueName(names, nodeId, nodeName));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -541,6 +541,7 @@ namespace Umbraco.Tests.Persistence
|
||||
{
|
||||
var helper = new DatabaseSchemaHelper(scope.Database, Mock.Of<ILogger>());
|
||||
|
||||
helper.CreateTable<NodeDto>();
|
||||
helper.CreateTable<UserGroupDto>();
|
||||
|
||||
scope.Complete();
|
||||
|
||||
@@ -204,15 +204,7 @@ AnotherContentFinder
|
||||
//ensure they are all found
|
||||
Assert.IsTrue(plugins.Result.ContainsAll(shouldContain));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PluginHash_From_String()
|
||||
{
|
||||
var s = "hello my name is someone".GetHashCode().ToString("x", CultureInfo.InvariantCulture);
|
||||
var output = TypeLoader.ConvertHashToInt64(s);
|
||||
Assert.AreNotEqual(0, output);
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void Get_Plugins_Hash()
|
||||
{
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using Umbraco.Tests.Testing;
|
||||
using Umbraco.Web.Routing;
|
||||
@@ -65,6 +68,80 @@ namespace Umbraco.Tests.Routing
|
||||
|
||||
var result = lookup.TryFindContent(frequest);
|
||||
|
||||
Assert.IsTrue(result);
|
||||
Assert.AreEqual(expectedId, frequest.PublishedContent.Id);
|
||||
}
|
||||
/// <summary>
|
||||
/// This test handles requests with special characters in the URL.
|
||||
/// </summary>
|
||||
/// <param name="urlString"></param>
|
||||
/// <param name="expectedId"></param>
|
||||
[TestCase("/", 1046)]
|
||||
[TestCase("/home/sub1/custom-sub-3-with-accént-character", 1179)]
|
||||
[TestCase("/home/sub1/custom-sub-4-with-æøå", 1180)]
|
||||
public void Match_Document_By_Url_With_Special_Characters(string urlString, int expectedId)
|
||||
{
|
||||
var umbracoContext = GetUmbracoContext(urlString);
|
||||
var facadeRouter = CreateFacadeRouter();
|
||||
var frequest = facadeRouter.CreateRequest(umbracoContext);
|
||||
var lookup = new ContentFinderByNiceUrl(Logger);
|
||||
SettingsForTests.HideTopLevelNodeFromPath = false;
|
||||
|
||||
var result = lookup.TryFindContent(frequest);
|
||||
|
||||
Assert.IsTrue(result);
|
||||
Assert.AreEqual(expectedId, frequest.PublishedContent.Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This test handles requests with a hostname associated.
|
||||
/// The logic for handling this goes through the DomainHelper and is a bit different
|
||||
/// from what happens in a normal request - so it has a separate test with a mocked
|
||||
/// hostname added.
|
||||
/// </summary>
|
||||
/// <param name="urlString"></param>
|
||||
/// <param name="expectedId"></param>
|
||||
[TestCase("/", 1046)]
|
||||
[TestCase("/home/sub1/custom-sub-3-with-accént-character", 1179)]
|
||||
[TestCase("/home/sub1/custom-sub-4-with-æøå", 1180)]
|
||||
public void Match_Document_By_Url_With_Special_Characters_Using_Hostname(string urlString, int expectedId)
|
||||
{
|
||||
var umbracoContext = GetUmbracoContext(urlString);
|
||||
var facadeRouter = CreateFacadeRouter();
|
||||
var frequest = facadeRouter.CreateRequest(umbracoContext);
|
||||
frequest.Domain = new DomainAndUri(new Domain(1, "mysite", -1, CultureInfo.CurrentCulture, false), new Uri("http://mysite/"));
|
||||
var lookup = new ContentFinderByNiceUrl(Logger);
|
||||
SettingsForTests.HideTopLevelNodeFromPath = false;
|
||||
|
||||
var result = lookup.TryFindContent(frequest);
|
||||
|
||||
Assert.IsTrue(result);
|
||||
Assert.AreEqual(expectedId, frequest.PublishedContent.Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This test handles requests with a hostname with special characters associated.
|
||||
/// The logic for handling this goes through the DomainHelper and is a bit different
|
||||
/// from what happens in a normal request - so it has a separate test with a mocked
|
||||
/// hostname added.
|
||||
/// </summary>
|
||||
/// <param name="urlString"></param>
|
||||
/// <param name="expectedId"></param>
|
||||
[TestCase("/æøå/", 1046)]
|
||||
[TestCase("/æøå/home/sub1", 1173)]
|
||||
[TestCase("/æøå/home/sub1/custom-sub-3-with-accént-character", 1179)]
|
||||
[TestCase("/æøå/home/sub1/custom-sub-4-with-æøå", 1180)]
|
||||
public void Match_Document_By_Url_With_Special_Characters_In_Hostname(string urlString, int expectedId)
|
||||
{
|
||||
var umbracoContext = GetUmbracoContext(urlString);
|
||||
var facadeRouter = CreateFacadeRouter();
|
||||
var frequest = facadeRouter.CreateRequest(umbracoContext);
|
||||
frequest.Domain = new DomainAndUri(new Domain(1, "mysite/æøå", -1, CultureInfo.CurrentCulture, false), new Uri("http://mysite/æøå"));
|
||||
var lookup = new ContentFinderByNiceUrl(Logger);
|
||||
SettingsForTests.HideTopLevelNodeFromPath = false;
|
||||
|
||||
var result = lookup.TryFindContent(frequest);
|
||||
|
||||
Assert.IsTrue(result);
|
||||
Assert.AreEqual(expectedId, frequest.PublishedContent.Id);
|
||||
}
|
||||
|
||||
@@ -1145,6 +1145,21 @@ namespace Umbraco.Tests.Services
|
||||
Assert.That(content.Published, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IsPublishable()
|
||||
{
|
||||
// Arrange
|
||||
var contentService = ServiceContext.ContentService;
|
||||
var parent = contentService.CreateContent("parent", -1, "umbTextpage");
|
||||
contentService.SaveAndPublishWithStatus(parent);
|
||||
var content = contentService.CreateContent("child", parent, "umbTextpage");
|
||||
contentService.Save(content);
|
||||
|
||||
Assert.IsTrue(contentService.IsPublishable(content));
|
||||
contentService.UnPublish(parent);
|
||||
Assert.IsFalse(contentService.IsPublishable(content));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Publish_Content_WithEvents()
|
||||
{
|
||||
@@ -1318,6 +1333,39 @@ namespace Umbraco.Tests.Services
|
||||
Assert.That(content.Published, Is.True);
|
||||
Assert.That(published, Is.True);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to immitate a new child content item being created through the UI.
|
||||
/// This content item will have no Id, Path or Identity.
|
||||
/// It seems like this is wiped somewhere in the process when creating an item through the UI
|
||||
/// and we need to make sure we handle nullchecks for these properties when creating content.
|
||||
/// This is unfortunately not caught by the normal ContentService tests.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void Can_Save_And_Publish_Content_And_Child_Without_Identity()
|
||||
{
|
||||
// Arrange
|
||||
var contentService = ServiceContext.ContentService;
|
||||
var content = contentService.CreateContent("Home US", -1, "umbTextpage", 0);
|
||||
content.SetValue("author", "Barack Obama");
|
||||
|
||||
// Act
|
||||
var published = contentService.SaveAndPublish(content, 0);
|
||||
var childContent = contentService.CreateContent("Child", content.Id, "umbTextpage", 0);
|
||||
// Reset all identity properties
|
||||
childContent.Id = 0;
|
||||
childContent.Path = null;
|
||||
((Content)childContent).ResetIdentity();
|
||||
var childPublished = contentService.SaveAndPublish(childContent, 0);
|
||||
|
||||
// Assert
|
||||
Assert.That(content.HasIdentity, Is.True);
|
||||
Assert.That(content.Published, Is.True);
|
||||
Assert.That(childContent.HasIdentity, Is.True);
|
||||
Assert.That(childContent.Published, Is.True);
|
||||
Assert.That(published, Is.True);
|
||||
Assert.That(childPublished, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Get_Published_Descendant_Versions()
|
||||
@@ -1636,6 +1684,14 @@ namespace Umbraco.Tests.Services
|
||||
ServiceContext.ContentService.Save(content2, 0);
|
||||
Assert.IsTrue(ServiceContext.ContentService.PublishWithStatus(content2, 0).Success);
|
||||
|
||||
var editorGroup = ServiceContext.UserService.GetUserGroupByAlias("editor");
|
||||
editorGroup.StartContentId = content1.Id;
|
||||
ServiceContext.UserService.Save(editorGroup);
|
||||
|
||||
var admin = ServiceContext.UserService.GetUserById(0);
|
||||
admin.StartContentIds = new[] {content1.Id};
|
||||
ServiceContext.UserService.Save(admin);
|
||||
|
||||
ServiceContext.RelationService.Save(new RelationType(Constants.ObjectTypes.DocumentGuid, Constants.ObjectTypes.DocumentGuid, "test"));
|
||||
Assert.IsNotNull(ServiceContext.RelationService.Relate(content1, content2, "test"));
|
||||
|
||||
@@ -1661,6 +1717,7 @@ namespace Umbraco.Tests.Services
|
||||
}).Success);
|
||||
|
||||
// Act
|
||||
ServiceContext.ContentService.MoveToRecycleBin(content1);
|
||||
ServiceContext.ContentService.EmptyRecycleBin();
|
||||
var contents = ServiceContext.ContentService.GetContentInRecycleBin();
|
||||
|
||||
|
||||
@@ -536,9 +536,11 @@ namespace Umbraco.Tests.Services
|
||||
public void EntityService_Cannot_Get_Key_For_Id_With_Incorrect_Object_Type()
|
||||
{
|
||||
var service = ServiceContext.EntityService;
|
||||
var result = service.GetKeyForId(1060, UmbracoObjectTypes.MediaType);
|
||||
var result1 = service.GetKeyForId(1060, UmbracoObjectTypes.DocumentType);
|
||||
var result2 = service.GetKeyForId(1060, UmbracoObjectTypes.MediaType);
|
||||
|
||||
Assert.IsFalse(result.Success);
|
||||
Assert.IsTrue(result1.Success);
|
||||
Assert.IsFalse(result2.Success);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -555,9 +557,11 @@ namespace Umbraco.Tests.Services
|
||||
public void EntityService_Cannot_Get_Id_For_Key_With_Incorrect_Object_Type()
|
||||
{
|
||||
var service = ServiceContext.EntityService;
|
||||
var result = service.GetIdForKey(Guid.Parse("1D3A8E6E-2EA9-4CC1-B229-1AEE19821522"), UmbracoObjectTypes.MediaType);
|
||||
var result1 = service.GetIdForKey(Guid.Parse("1D3A8E6E-2EA9-4CC1-B229-1AEE19821522"), UmbracoObjectTypes.DocumentType);
|
||||
var result2 = service.GetIdForKey(Guid.Parse("1D3A8E6E-2EA9-4CC1-B229-1AEE19821522"), UmbracoObjectTypes.MediaType);
|
||||
|
||||
Assert.IsFalse(result.Success);
|
||||
Assert.IsTrue(result1.Success);
|
||||
Assert.IsFalse(result2.Success);
|
||||
}
|
||||
|
||||
private static bool _isSetup = false;
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Web.Security;
|
||||
using NPoco;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Composing;
|
||||
@@ -15,10 +17,12 @@ using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
using Umbraco.Core.Persistence.Repositories;
|
||||
using Umbraco.Core.Persistence.UnitOfWork;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using Umbraco.Tests.TestHelpers.Entities;
|
||||
using Umbraco.Tests.Testing;
|
||||
using Umbraco.Web.Security.Providers;
|
||||
|
||||
namespace Umbraco.Tests.Services
|
||||
{
|
||||
@@ -26,6 +30,52 @@ namespace Umbraco.Tests.Services
|
||||
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, FacadeServiceRepositoryEvents = true)]
|
||||
public class MemberServiceTests : TestWithSomeContentBase
|
||||
{
|
||||
public override void SetUp()
|
||||
{
|
||||
base.SetUp();
|
||||
|
||||
//hack! but we have no choice until we remove the SavePassword method from IMemberService
|
||||
var providerMock = new Mock<MembersMembershipProvider>(ServiceContext.MemberService) { CallBase = true };
|
||||
providerMock.Setup(@base => @base.AllowManuallyChangingPassword).Returns(false);
|
||||
providerMock.Setup(@base => @base.PasswordFormat).Returns(MembershipPasswordFormat.Hashed);
|
||||
var provider = providerMock.Object;
|
||||
|
||||
((MemberService)ServiceContext.MemberService).MembershipProvider = provider;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Set_Password_On_New_Member()
|
||||
{
|
||||
IMemberType memberType = MockedContentTypes.CreateSimpleMemberType();
|
||||
ServiceContext.MemberTypeService.Save(memberType);
|
||||
//this will construct a member without a password
|
||||
var member = MockedMember.CreateSimpleMember(memberType, "test", "test@test.com", "test");
|
||||
ServiceContext.MemberService.Save(member);
|
||||
|
||||
Assert.IsTrue(member.RawPasswordValue.StartsWith(Constants.Security.EmptyPasswordPrefix));
|
||||
|
||||
ServiceContext.MemberService.SavePassword(member, "hello123456$!");
|
||||
|
||||
var foundMember = ServiceContext.MemberService.GetById(member.Id);
|
||||
Assert.IsNotNull(foundMember);
|
||||
Assert.AreNotEqual("hello123456$!", foundMember.RawPasswordValue);
|
||||
Assert.IsFalse(member.RawPasswordValue.StartsWith(Constants.Security.EmptyPasswordPrefix));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Not_Set_Password_On_Existing_Member()
|
||||
{
|
||||
IMemberType memberType = MockedContentTypes.CreateSimpleMemberType();
|
||||
ServiceContext.MemberTypeService.Save(memberType);
|
||||
//this will construct a member with a password
|
||||
var member = MockedMember.CreateSimpleMember(memberType, "test", "test@test.com", "hello123456$!", "test");
|
||||
ServiceContext.MemberService.Save(member);
|
||||
|
||||
Assert.IsFalse(member.RawPasswordValue.StartsWith(Constants.Security.EmptyPasswordPrefix));
|
||||
|
||||
Assert.Throws<NotSupportedException>(() => ServiceContext.MemberService.SavePassword(member, "HELLO123456$!"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Create_Member()
|
||||
{
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Threading;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Scoping;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using Umbraco.Tests.TestHelpers.Entities;
|
||||
|
||||
@@ -193,9 +193,9 @@ namespace Umbraco.Tests.Services
|
||||
//assert
|
||||
|
||||
//there will be 3 since that is how many content items there are
|
||||
Assert.AreEqual(3, permissions.Count);
|
||||
|
||||
//test permissions contains content[0]
|
||||
Assert.AreEqual(3, permissions.Count);
|
||||
|
||||
//test permissions contains content[0]
|
||||
Assert.IsTrue(permissions.ContainsKey(content[0].Id));
|
||||
//test that this permissions set contains permissions for all groups
|
||||
Assert.IsTrue(permissions[content[0].Id].ContainsKey(userGroup1.Id));
|
||||
@@ -204,9 +204,9 @@ namespace Umbraco.Tests.Services
|
||||
//test that the correct number of permissions are returned for each group
|
||||
Assert.AreEqual(2, permissions[content[0].Id][userGroup1.Id].SelectMany(x => x.AssignedPermissions).Count());
|
||||
Assert.AreEqual(1, permissions[content[0].Id][userGroup2.Id].SelectMany(x => x.AssignedPermissions).Count());
|
||||
Assert.AreEqual(defaultPermissionCount, permissions[content[0].Id][userGroup3.Id].SelectMany(x => x.AssignedPermissions).Count());
|
||||
|
||||
//test permissions contains content[1]
|
||||
Assert.AreEqual(defaultPermissionCount, permissions[content[0].Id][userGroup3.Id].SelectMany(x => x.AssignedPermissions).Count());
|
||||
|
||||
//test permissions contains content[1]
|
||||
Assert.IsTrue(permissions.ContainsKey(content[1].Id));
|
||||
//test that this permissions set contains permissions for all groups
|
||||
Assert.IsTrue(permissions[content[1].Id].ContainsKey(userGroup1.Id));
|
||||
@@ -332,8 +332,8 @@ namespace Umbraco.Tests.Services
|
||||
Assert.AreEqual(1, result.EntityId);
|
||||
allPermissions = result.GetAllPermissions().ToArray();
|
||||
Assert.AreEqual(5, allPermissions.Length, string.Join(",", allPermissions));
|
||||
Assert.IsTrue(allPermissions.ContainsAll(new[] { "S", "D", "F", "G", "K" }));
|
||||
|
||||
Assert.IsTrue(allPermissions.ContainsAll(new[] { "S", "D", "F", "G", "K" }));
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -389,7 +389,7 @@ namespace Umbraco.Tests.Services
|
||||
Assert.IsTrue(result.IsDefaultPermissions);
|
||||
Assert.IsTrue(result.AssignedPermissions.ContainsAll(defaults));
|
||||
Assert.AreEqual(3, result.EntityId);
|
||||
Assert.AreEqual(9876, result.UserGroupId);
|
||||
Assert.AreEqual(9876, result.UserGroupId);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -406,8 +406,8 @@ namespace Umbraco.Tests.Services
|
||||
var child1 = MockedContent.CreateSimpleContent(contentType, "child1", parent);
|
||||
ServiceContext.ContentService.Save(child1);
|
||||
var child2 = MockedContent.CreateSimpleContent(contentType, "child2", child1);
|
||||
ServiceContext.ContentService.Save(child2);
|
||||
|
||||
ServiceContext.ContentService.Save(child2);
|
||||
|
||||
ServiceContext.ContentService.AssignContentPermission(parent, ActionBrowse.Instance.Letter, new int[] { userGroup.Id });
|
||||
ServiceContext.ContentService.AssignContentPermission(parent, ActionDelete.Instance.Letter, new int[] { userGroup.Id });
|
||||
ServiceContext.ContentService.AssignContentPermission(parent, ActionMove.Instance.Letter, new int[] { userGroup.Id });
|
||||
@@ -425,8 +425,8 @@ namespace Umbraco.Tests.Services
|
||||
[Test]
|
||||
public void Can_Delete_User()
|
||||
{
|
||||
var user = ServiceContext.UserService.CreateUserWithIdentity("JohnDoe", "john@umbraco.io");
|
||||
|
||||
var user = ServiceContext.UserService.CreateUserWithIdentity("JohnDoe", "john@umbraco.io");
|
||||
|
||||
ServiceContext.UserService.Delete(user, true);
|
||||
var deleted = ServiceContext.UserService.GetUserById(user.Id);
|
||||
|
||||
@@ -570,7 +570,7 @@ namespace Umbraco.Tests.Services
|
||||
[Test]
|
||||
public void Get_All_Paged_Users_With_Filter()
|
||||
{
|
||||
var users = MockedUser.CreateMulipleUsers(10).ToArray();
|
||||
var users = MockedUser.CreateMulipleUsers(10).ToArray();
|
||||
ServiceContext.UserService.Save(users);
|
||||
|
||||
var found = ServiceContext.UserService.GetAll(0, 2, out var totalRecs, "username", Direction.Ascending, filter: "test");
|
||||
@@ -596,7 +596,7 @@ namespace Umbraco.Tests.Services
|
||||
ServiceContext.UserService.Save(users);
|
||||
|
||||
long totalRecs;
|
||||
var found = ServiceContext.UserService.GetAll(0, 2, out totalRecs, "username", Direction.Ascending, userGroups: new[] { userGroup.Alias });
|
||||
var found = ServiceContext.UserService.GetAll(0, 2, out totalRecs, "username", Direction.Ascending, includeUserGroups: new[] {userGroup.Alias});
|
||||
|
||||
Assert.AreEqual(2, found.Count());
|
||||
Assert.AreEqual(5, totalRecs);
|
||||
@@ -821,15 +821,15 @@ namespace Umbraco.Tests.Services
|
||||
userGroup2.AddAllowedSection("test");
|
||||
|
||||
var userGroup3 = new UserGroup
|
||||
{
|
||||
{
|
||||
Alias = "Group3",
|
||||
Name = "Group 3"
|
||||
};
|
||||
ServiceContext.UserService.Save(userGroup1);
|
||||
ServiceContext.UserService.Save(userGroup2);
|
||||
ServiceContext.UserService.Save(userGroup3);
|
||||
|
||||
//assert
|
||||
ServiceContext.UserService.Save(userGroup3);
|
||||
|
||||
//assert
|
||||
var result1 = ServiceContext.UserService.GetUserGroupById(userGroup1.Id);
|
||||
var result2 = ServiceContext.UserService.GetUserGroupById(userGroup2.Id);
|
||||
var result3 = ServiceContext.UserService.GetUserGroupById(userGroup3.Id);
|
||||
@@ -857,9 +857,9 @@ namespace Umbraco.Tests.Services
|
||||
public void Cannot_Create_User_With_Empty_Username()
|
||||
{
|
||||
// Arrange
|
||||
var userService = ServiceContext.UserService;
|
||||
|
||||
// Act & Assert
|
||||
var userService = ServiceContext.UserService;
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentException>(() => userService.CreateUserWithIdentity(string.Empty, "john@umbraco.io"));
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace Umbraco.Tests.Strings
|
||||
var helper = Current.ShortStringHelper;
|
||||
Assert.IsInstanceOf<MockShortStringHelper>(helper);
|
||||
}
|
||||
|
||||
|
||||
[TestCase("hello", "world", false)]
|
||||
[TestCase("hello", "hello", true)]
|
||||
[TestCase("hellohellohellohellohellohellohello", "hellohellohellohellohellohellohelloo", false)]
|
||||
|
||||
@@ -53,6 +53,8 @@ namespace Umbraco.Tests.TestHelpers
|
||||
</Home>
|
||||
<CustomDocument id=""1177"" parentID=""1173"" level=""3"" writerID=""0"" creatorID=""0"" nodeType=""1234"" template=""" + templateId + @""" sortOrder=""4"" createDate=""2012-07-16T15:26:59"" updateDate=""2012-07-18T14:23:35"" nodeName=""custom sub 1"" urlName=""custom-sub-1"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1173,1177"" isDoc="""" />
|
||||
<CustomDocument id=""1178"" parentID=""1173"" level=""3"" writerID=""0"" creatorID=""0"" nodeType=""1234"" template=""" + templateId + @""" sortOrder=""4"" createDate=""2012-07-16T15:26:59"" updateDate=""2012-07-16T14:23:35"" nodeName=""custom sub 2"" urlName=""custom-sub-2"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1173,1178"" isDoc="""" />
|
||||
<CustomDocument id=""1179"" parentID=""1173"" level=""3"" writerID=""0"" creatorID=""0"" nodeType=""1234"" template=""" + templateId + @""" sortOrder=""4"" createDate=""2012-07-16T15:26:59"" updateDate=""2012-07-16T14:23:35"" nodeName=""custom sub 3 with accént character"" urlName=""custom-sub-3-with-accént-character"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1173,1179"" isDoc="""" />
|
||||
<CustomDocument id=""1180"" parentID=""1173"" level=""3"" writerID=""0"" creatorID=""0"" nodeType=""1234"" template=""" + templateId + @""" sortOrder=""4"" createDate=""2012-07-16T15:26:59"" updateDate=""2012-07-16T14:23:35"" nodeName=""custom sub 4 with æøå"" urlName=""custom-sub-4-with-æøå"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1173,1180"" isDoc="""" />
|
||||
</Home>
|
||||
<Home id=""1175"" parentID=""1046"" level=""2"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""" + templateId + @""" sortOrder=""3"" createDate=""2012-07-20T18:08:01"" updateDate=""2012-07-20T18:49:32"" nodeName=""Sub 2"" urlName=""sub-2"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1175"" isDoc=""""><content><![CDATA[]]></content>
|
||||
</Home>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Web;
|
||||
@@ -43,6 +44,9 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting
|
||||
var owinContext = request.TryGetOwinContext().Result;
|
||||
|
||||
var mockedUserService = Mock.Of<IUserService>();
|
||||
var mockedContentService = Mock.Of<IContentService>();
|
||||
var mockedMediaService = Mock.Of<IMediaService>();
|
||||
var mockedEntityService = Mock.Of<IEntityService>();
|
||||
|
||||
var mockedMigrationService = new Mock<IMigrationEntryService>();
|
||||
//set it up to return anything so that the app ctx is 'Configured'
|
||||
@@ -50,6 +54,9 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting
|
||||
|
||||
var serviceContext = new ServiceContext(
|
||||
userService: mockedUserService,
|
||||
contentService: mockedContentService,
|
||||
mediaService: mockedMediaService,
|
||||
entityService: mockedEntityService,
|
||||
migrationEntryService: mockedMigrationService.Object,
|
||||
localizedTextService:Mock.Of<ILocalizedTextService>(),
|
||||
sectionService:Mock.Of<ISectionService>());
|
||||
@@ -86,10 +93,17 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting
|
||||
var webSecurity = new Mock<WebSecurity>(null, null);
|
||||
|
||||
//mock CurrentUser
|
||||
var groups = new List<ReadOnlyUserGroup>();
|
||||
for (var index = 0; index < backofficeIdentity.Roles.Length; index++)
|
||||
{
|
||||
var role = backofficeIdentity.Roles[index];
|
||||
groups.Add(new ReadOnlyUserGroup(index + 1, role, "icon-user", null, null, role, new string[0], new string[0]));
|
||||
}
|
||||
webSecurity.Setup(x => x.CurrentUser)
|
||||
.Returns(Mock.Of<IUser>(u => u.IsApproved == true
|
||||
&& u.IsLockedOut == false
|
||||
&& u.AllowedSections == backofficeIdentity.AllowedApplications
|
||||
&& u.Groups == groups
|
||||
&& u.Email == "admin@admin.com"
|
||||
&& u.Id == (int) backofficeIdentity.Id
|
||||
&& u.Language == "en"
|
||||
@@ -101,7 +115,7 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting
|
||||
//mock Validate
|
||||
webSecurity.Setup(x => x.ValidateCurrentUser())
|
||||
.Returns(() => true);
|
||||
webSecurity.Setup(x => x.UserHasAppAccess(It.IsAny<string>(), It.IsAny<IUser>()))
|
||||
webSecurity.Setup(x => x.UserHasSectionAccess(It.IsAny<string>(), It.IsAny<IUser>()))
|
||||
.Returns(() => true);
|
||||
|
||||
var umbCtx = UmbracoContext.EnsureContext(
|
||||
|
||||
@@ -31,6 +31,29 @@ namespace Umbraco.Tests.TestHelpers.Entities
|
||||
return member;
|
||||
}
|
||||
|
||||
public static Member CreateSimpleMember(IMemberType contentType, string name, string email, string username, Guid? key = null)
|
||||
{
|
||||
var member = new Member(name, email, username, contentType)
|
||||
{
|
||||
CreatorId = 0,
|
||||
Email = email,
|
||||
Username = username
|
||||
};
|
||||
|
||||
if (key.HasValue)
|
||||
{
|
||||
member.Key = key.Value;
|
||||
}
|
||||
|
||||
member.SetValue("title", name + " member");
|
||||
member.SetValue("bodyText", "This is a subpage");
|
||||
member.SetValue("author", "John Doe");
|
||||
|
||||
member.ResetDirtyProperties(false);
|
||||
|
||||
return member;
|
||||
}
|
||||
|
||||
public static IEnumerable<IMember> CreateSimpleMember(IMemberType memberType, int amount, Action<int, IMember> onCreating = null)
|
||||
{
|
||||
var list = new List<IMember>();
|
||||
|
||||
@@ -157,12 +157,12 @@ namespace Umbraco.Tests.TestHelpers
|
||||
|
||||
var userService = GetLazyService<IUserService>(container, () => new UserService(provider, logger, eventMessagesFactory, runtimeState));
|
||||
var dataTypeService = GetLazyService<IDataTypeService>(container, () => new DataTypeService(provider, logger, eventMessagesFactory));
|
||||
var contentService = GetLazyService<IContentService>(container, () => new ContentService(provider, logger, eventMessagesFactory, mediaFileSystem, idkMap));
|
||||
var contentService = GetLazyService<IContentService>(container, () => new ContentService(provider, logger, eventMessagesFactory, mediaFileSystem));
|
||||
var notificationService = GetLazyService<INotificationService>(container, () => new NotificationService(provider, userService.Value, contentService.Value, logger));
|
||||
var serverRegistrationService = GetLazyService<IServerRegistrationService>(container, () => new ServerRegistrationService(provider, logger, eventMessagesFactory));
|
||||
var memberGroupService = GetLazyService<IMemberGroupService>(container, () => new MemberGroupService(provider, logger, eventMessagesFactory));
|
||||
var memberService = GetLazyService<IMemberService>(container, () => new MemberService(provider, logger, eventMessagesFactory, memberGroupService.Value, mediaFileSystem));
|
||||
var mediaService = GetLazyService<IMediaService>(container, () => new MediaService(provider, mediaFileSystem, logger, eventMessagesFactory, idkMap));
|
||||
var mediaService = GetLazyService<IMediaService>(container, () => new MediaService(provider, mediaFileSystem, logger, eventMessagesFactory));
|
||||
var contentTypeService = GetLazyService<IContentTypeService>(container, () => new ContentTypeService(provider, logger, eventMessagesFactory, contentService.Value));
|
||||
var mediaTypeService = GetLazyService<IMediaTypeService>(container, () => new MediaTypeService(provider, logger, eventMessagesFactory, mediaService.Value));
|
||||
var fileService = GetLazyService<IFileService>(container, () => new FileService(provider, logger, eventMessagesFactory));
|
||||
|
||||
@@ -218,7 +218,8 @@
|
||||
<Compile Include="CoreThings\CallContextTests.cs" />
|
||||
<Compile Include="Components\ComponentTests.cs" />
|
||||
<Compile Include="CoreXml\RenamedRootNavigatorTests.cs" />
|
||||
<Compile Include="DateTimeExtensionsTests.cs" />
|
||||
<Compile Include="Misc\DateTimeExtensionsTests.cs" />
|
||||
<Compile Include="Misc\HashGeneratorTests.cs" />
|
||||
<Compile Include="IO\ShadowFileSystemTests.cs" />
|
||||
<Compile Include="Issues\U9560.cs" />
|
||||
<Compile Include="Integration\ContentEventsTests.cs" />
|
||||
@@ -227,6 +228,7 @@
|
||||
<Compile Include="Models\Mapping\AutoMapper6Tests.cs" />
|
||||
<Compile Include="Models\Mapping\UserModelMapperTests.cs" />
|
||||
<Compile Include="Persistence\Repositories\EntityRepositoryTests.cs" />
|
||||
<Compile Include="Persistence\Repositories\SimilarNodeNameTests.cs" />
|
||||
<Compile Include="Persistence\Repositories\UserGroupRepositoryTest.cs" />
|
||||
<Compile Include="PublishedContent\ModelsAndConvertersTests.cs" />
|
||||
<Compile Include="Routing\NiceUrlRoutesTests.cs" />
|
||||
@@ -285,8 +287,11 @@
|
||||
<Compile Include="Collections\DeepCloneableListTests.cs" />
|
||||
<Compile Include="Web\Controllers\BackOfficeControllerUnitTests.cs" />
|
||||
<Compile Include="CoreThings\DelegateExtensionsTests.cs" />
|
||||
<Compile Include="Web\Controllers\UserEditorAuthorizationHelperTests.cs" />
|
||||
<Compile Include="Web\Controllers\UsersControllerTests.cs" />
|
||||
<Compile Include="Web\HealthChecks\HealthCheckResultsTests.cs" />
|
||||
<Compile Include="Web\HttpCookieExtensionsTests.cs" />
|
||||
<Compile Include="Web\Mvc\HtmlStringUtilitiesTests.cs" />
|
||||
<Compile Include="Web\Mvc\RenderIndexActionSelectorAttributeTests.cs" />
|
||||
<Compile Include="Persistence\PetaPocoCachesTest.cs" />
|
||||
<Compile Include="Persistence\Repositories\AuditRepositoryTest.cs" />
|
||||
|
||||
@@ -0,0 +1,427 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Editors;
|
||||
|
||||
namespace Umbraco.Tests.Web.Controllers
|
||||
{
|
||||
[TestFixture]
|
||||
public class UserEditorAuthorizationHelperTests
|
||||
{
|
||||
[Test]
|
||||
public void Admin_Is_Authorized()
|
||||
{
|
||||
var currentUser = GetAdminUser();
|
||||
var savingUser = Mock.Of<IUser>();
|
||||
|
||||
var contentService = new Mock<IContentService>();
|
||||
var mediaService = new Mock<IMediaService>();
|
||||
var userService = new Mock<IUserService>();
|
||||
var entityService = new Mock<IEntityService>();
|
||||
|
||||
var authHelper = new UserEditorAuthorizationHelper(
|
||||
contentService.Object,
|
||||
mediaService.Object,
|
||||
userService.Object,
|
||||
entityService.Object);
|
||||
|
||||
var result = authHelper.IsAuthorized(currentUser, savingUser, new int[0], new int[0], new string[0]);
|
||||
|
||||
Assert.IsTrue(result.Success);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Non_Admin_Cannot_Save_Admin()
|
||||
{
|
||||
var currentUser = Mock.Of<IUser>();
|
||||
var savingUser = GetAdminUser();
|
||||
|
||||
var contentService = new Mock<IContentService>();
|
||||
var mediaService = new Mock<IMediaService>();
|
||||
var userService = new Mock<IUserService>();
|
||||
var entityService = new Mock<IEntityService>();
|
||||
|
||||
var authHelper = new UserEditorAuthorizationHelper(
|
||||
contentService.Object,
|
||||
mediaService.Object,
|
||||
userService.Object,
|
||||
entityService.Object);
|
||||
|
||||
var result = authHelper.IsAuthorized(currentUser, savingUser, new int[0], new int[0], new string[0]);
|
||||
|
||||
Assert.IsFalse(result.Success);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Cannot_Grant_Group_Membership_Without_Being_A_Member()
|
||||
{
|
||||
var currentUser = Mock.Of<IUser>(user => user.Groups == new[]
|
||||
{
|
||||
new ReadOnlyUserGroup(1, "Test", "icon-user", null, null, "test", new string[0], new string[0])
|
||||
});
|
||||
var savingUser = Mock.Of<IUser>();
|
||||
|
||||
var contentService = new Mock<IContentService>();
|
||||
var mediaService = new Mock<IMediaService>();
|
||||
var userService = new Mock<IUserService>();
|
||||
var entityService = new Mock<IEntityService>();
|
||||
|
||||
var authHelper = new UserEditorAuthorizationHelper(
|
||||
contentService.Object,
|
||||
mediaService.Object,
|
||||
userService.Object,
|
||||
entityService.Object);
|
||||
|
||||
var result = authHelper.IsAuthorized(currentUser, savingUser, new int[0], new int[0], new[] {"FunGroup"});
|
||||
|
||||
Assert.IsFalse(result.Success);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Grant_Group_Membership_With_Being_A_Member()
|
||||
{
|
||||
var currentUser = Mock.Of<IUser>(user => user.Groups == new[]
|
||||
{
|
||||
new ReadOnlyUserGroup(1, "Test", "icon-user", null, null, "test", new string[0], new string[0])
|
||||
});
|
||||
var savingUser = Mock.Of<IUser>();
|
||||
|
||||
var contentService = new Mock<IContentService>();
|
||||
var mediaService = new Mock<IMediaService>();
|
||||
var userService = new Mock<IUserService>();
|
||||
var entityService = new Mock<IEntityService>();
|
||||
|
||||
var authHelper = new UserEditorAuthorizationHelper(
|
||||
contentService.Object,
|
||||
mediaService.Object,
|
||||
userService.Object,
|
||||
entityService.Object);
|
||||
|
||||
var result = authHelper.IsAuthorized(currentUser, savingUser, new int[0], new int[0], new[] { "test" });
|
||||
|
||||
Assert.IsTrue(result.Success);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Add_Another_Content_Start_Node_On_User_With_Access()
|
||||
{
|
||||
var nodePaths = new Dictionary<int, string>
|
||||
{
|
||||
{1234, "-1,1234"},
|
||||
{9876, "-1,9876"},
|
||||
{5555, "-1,9876,5555"},
|
||||
{4567, "-1,4567"},
|
||||
};
|
||||
|
||||
var currentUser = Mock.Of<IUser>(user => user.StartContentIds == new[] { 9876 });
|
||||
var savingUser = Mock.Of<IUser>(user => user.StartContentIds == new[] {1234});
|
||||
|
||||
var contentService = new Mock<IContentService>();
|
||||
contentService.Setup(x => x.GetById(It.IsAny<int>()))
|
||||
.Returns((int id) => Mock.Of<IContent>(content => content.Path == nodePaths[id]));
|
||||
var mediaService = new Mock<IMediaService>();
|
||||
var userService = new Mock<IUserService>();
|
||||
var entityService = new Mock<IEntityService>();
|
||||
entityService.Setup(service => service.GetAllPaths(It.IsAny<UmbracoObjectTypes>(), It.IsAny<int[]>()))
|
||||
.Returns((UmbracoObjectTypes objType, int[] ids) =>
|
||||
{
|
||||
return ids.Select(x => new EntityPath {Path = nodePaths[x], Id = x});
|
||||
});
|
||||
|
||||
var authHelper = new UserEditorAuthorizationHelper(
|
||||
contentService.Object,
|
||||
mediaService.Object,
|
||||
userService.Object,
|
||||
entityService.Object);
|
||||
|
||||
//adding 5555 which currentUser has access to since it's a child of 9876 ... adding is still ok even though currentUser doesn't have access to 1234
|
||||
var result = authHelper.IsAuthorized(currentUser, savingUser, new[] { 1234, 5555 }, new int[0], new string[0]);
|
||||
|
||||
Assert.IsTrue(result.Success);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Remove_Content_Start_Node_On_User_Without_Access()
|
||||
{
|
||||
var nodePaths = new Dictionary<int, string>
|
||||
{
|
||||
{1234, "-1,1234"},
|
||||
{9876, "-1,9876"},
|
||||
{5555, "-1,9876,5555"},
|
||||
{4567, "-1,4567"},
|
||||
};
|
||||
|
||||
var currentUser = Mock.Of<IUser>(user => user.StartContentIds == new[] { 9876 });
|
||||
var savingUser = Mock.Of<IUser>(user => user.StartContentIds == new[] { 1234, 4567 });
|
||||
|
||||
var contentService = new Mock<IContentService>();
|
||||
contentService.Setup(x => x.GetById(It.IsAny<int>()))
|
||||
.Returns((int id) => Mock.Of<IContent>(content => content.Path == nodePaths[id]));
|
||||
var mediaService = new Mock<IMediaService>();
|
||||
var userService = new Mock<IUserService>();
|
||||
var entityService = new Mock<IEntityService>();
|
||||
entityService.Setup(service => service.GetAllPaths(It.IsAny<UmbracoObjectTypes>(), It.IsAny<int[]>()))
|
||||
.Returns((UmbracoObjectTypes objType, int[] ids) =>
|
||||
{
|
||||
return ids.Select(x => new EntityPath { Path = nodePaths[x], Id = x });
|
||||
});
|
||||
|
||||
var authHelper = new UserEditorAuthorizationHelper(
|
||||
contentService.Object,
|
||||
mediaService.Object,
|
||||
userService.Object,
|
||||
entityService.Object);
|
||||
|
||||
//removing 4567 start node even though currentUser doesn't have acces to it ... removing is ok
|
||||
var result = authHelper.IsAuthorized(currentUser, savingUser, new[] { 1234 }, new int[0], new string[0]);
|
||||
|
||||
Assert.IsTrue(result.Success);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Cannot_Add_Content_Start_Node_On_User_Without_Access()
|
||||
{
|
||||
var nodePaths = new Dictionary<int, string>
|
||||
{
|
||||
{1234, "-1,1234"},
|
||||
{9876, "-1,9876"},
|
||||
{5555, "-1,9876,5555"},
|
||||
{4567, "-1,4567"},
|
||||
};
|
||||
|
||||
var currentUser = Mock.Of<IUser>(user => user.StartContentIds == new[]{9876});
|
||||
var savingUser = Mock.Of<IUser>();
|
||||
|
||||
var contentService = new Mock<IContentService>();
|
||||
contentService.Setup(x => x.GetById(It.IsAny<int>()))
|
||||
.Returns((int id) => Mock.Of<IContent>(content => content.Path == nodePaths[id]));
|
||||
var mediaService = new Mock<IMediaService>();
|
||||
var userService = new Mock<IUserService>();
|
||||
var entityService = new Mock<IEntityService>();
|
||||
entityService.Setup(service => service.GetAllPaths(It.IsAny<UmbracoObjectTypes>(), It.IsAny<int[]>()))
|
||||
.Returns((UmbracoObjectTypes objType, int[] ids) =>
|
||||
{
|
||||
return ids.Select(x => new EntityPath { Path = nodePaths[x], Id = x });
|
||||
});
|
||||
|
||||
var authHelper = new UserEditorAuthorizationHelper(
|
||||
contentService.Object,
|
||||
mediaService.Object,
|
||||
userService.Object,
|
||||
entityService.Object);
|
||||
|
||||
//adding 1234 but currentUser doesn't have access to it ... nope
|
||||
var result = authHelper.IsAuthorized(currentUser, savingUser, new []{1234}, new int[0], new string[0]);
|
||||
|
||||
Assert.IsFalse(result.Success);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Add_Content_Start_Node_On_User_With_Access()
|
||||
{
|
||||
var nodePaths = new Dictionary<int, string>
|
||||
{
|
||||
{1234, "-1,1234"},
|
||||
{9876, "-1,9876"},
|
||||
{5555, "-1,9876,5555"},
|
||||
{4567, "-1,4567"},
|
||||
};
|
||||
|
||||
var currentUser = Mock.Of<IUser>(user => user.StartContentIds == new[] { 9876 });
|
||||
var savingUser = Mock.Of<IUser>();
|
||||
|
||||
var contentService = new Mock<IContentService>();
|
||||
contentService.Setup(x => x.GetById(It.IsAny<int>()))
|
||||
.Returns((int id) => Mock.Of<IContent>(content => content.Path == nodePaths[id]));
|
||||
var mediaService = new Mock<IMediaService>();
|
||||
var userService = new Mock<IUserService>();
|
||||
var entityService = new Mock<IEntityService>();
|
||||
entityService.Setup(service => service.GetAllPaths(It.IsAny<UmbracoObjectTypes>(), It.IsAny<int[]>()))
|
||||
.Returns((UmbracoObjectTypes objType, int[] ids) =>
|
||||
{
|
||||
return ids.Select(x => new EntityPath { Path = nodePaths[x], Id = x });
|
||||
});
|
||||
|
||||
var authHelper = new UserEditorAuthorizationHelper(
|
||||
contentService.Object,
|
||||
mediaService.Object,
|
||||
userService.Object,
|
||||
entityService.Object);
|
||||
|
||||
//adding 5555 which currentUser has access to since it's a child of 9876 ... ok
|
||||
var result = authHelper.IsAuthorized(currentUser, savingUser, new[] { 5555 }, new int[0], new string[0]);
|
||||
|
||||
Assert.IsTrue(result.Success);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Cannot_Add_Media_Start_Node_On_User_Without_Access()
|
||||
{
|
||||
var nodePaths = new Dictionary<int, string>
|
||||
{
|
||||
{1234, "-1,1234"},
|
||||
{9876, "-1,9876"},
|
||||
{5555, "-1,9876,5555"},
|
||||
{4567, "-1,4567"},
|
||||
};
|
||||
|
||||
var currentUser = Mock.Of<IUser>(user => user.StartMediaIds == new[] { 9876 });
|
||||
var savingUser = Mock.Of<IUser>();
|
||||
|
||||
var contentService = new Mock<IContentService>();
|
||||
var mediaService = new Mock<IMediaService>();
|
||||
mediaService.Setup(x => x.GetById(It.IsAny<int>()))
|
||||
.Returns((int id) => Mock.Of<IMedia>(content => content.Path == nodePaths[id]));
|
||||
var userService = new Mock<IUserService>();
|
||||
var entityService = new Mock<IEntityService>();
|
||||
entityService.Setup(service => service.GetAllPaths(It.IsAny<UmbracoObjectTypes>(), It.IsAny<int[]>()))
|
||||
.Returns((UmbracoObjectTypes objType, int[] ids) =>
|
||||
{
|
||||
return ids.Select(x => new EntityPath { Path = nodePaths[x], Id = x });
|
||||
});
|
||||
|
||||
var authHelper = new UserEditorAuthorizationHelper(
|
||||
contentService.Object,
|
||||
mediaService.Object,
|
||||
userService.Object,
|
||||
entityService.Object);
|
||||
|
||||
//adding 1234 but currentUser doesn't have access to it ... nope
|
||||
var result = authHelper.IsAuthorized(currentUser, savingUser, new int[0], new[] {1234}, new string[0]);
|
||||
|
||||
Assert.IsFalse(result.Success);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Add_Media_Start_Node_On_User_With_Access()
|
||||
{
|
||||
var nodePaths = new Dictionary<int, string>
|
||||
{
|
||||
{1234, "-1,1234"},
|
||||
{9876, "-1,9876"},
|
||||
{5555, "-1,9876,5555"},
|
||||
{4567, "-1,4567"},
|
||||
};
|
||||
|
||||
var currentUser = Mock.Of<IUser>(user => user.StartMediaIds == new[] { 9876 });
|
||||
var savingUser = Mock.Of<IUser>();
|
||||
|
||||
var contentService = new Mock<IContentService>();
|
||||
var mediaService = new Mock<IMediaService>();
|
||||
mediaService.Setup(x => x.GetById(It.IsAny<int>()))
|
||||
.Returns((int id) => Mock.Of<IMedia>(content => content.Path == nodePaths[id]));
|
||||
var userService = new Mock<IUserService>();
|
||||
var entityService = new Mock<IEntityService>();
|
||||
entityService.Setup(service => service.GetAllPaths(It.IsAny<UmbracoObjectTypes>(), It.IsAny<int[]>()))
|
||||
.Returns((UmbracoObjectTypes objType, int[] ids) =>
|
||||
{
|
||||
return ids.Select(x => new EntityPath { Path = nodePaths[x], Id = x });
|
||||
});
|
||||
|
||||
var authHelper = new UserEditorAuthorizationHelper(
|
||||
contentService.Object,
|
||||
mediaService.Object,
|
||||
userService.Object,
|
||||
entityService.Object);
|
||||
|
||||
//adding 5555 which currentUser has access to since it's a child of 9876 ... ok
|
||||
var result = authHelper.IsAuthorized(currentUser, savingUser, new int[0], new[] { 5555 }, new string[0]);
|
||||
|
||||
Assert.IsTrue(result.Success);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Add_Another_Media_Start_Node_On_User_With_Access()
|
||||
{
|
||||
var nodePaths = new Dictionary<int, string>
|
||||
{
|
||||
{1234, "-1,1234"},
|
||||
{9876, "-1,9876"},
|
||||
{5555, "-1,9876,5555"},
|
||||
{4567, "-1,4567"},
|
||||
};
|
||||
|
||||
var currentUser = Mock.Of<IUser>(user => user.StartMediaIds == new[] { 9876 });
|
||||
var savingUser = Mock.Of<IUser>(user => user.StartMediaIds == new[] { 1234 });
|
||||
|
||||
var contentService = new Mock<IContentService>();
|
||||
var mediaService = new Mock<IMediaService>();
|
||||
mediaService.Setup(x => x.GetById(It.IsAny<int>()))
|
||||
.Returns((int id) => Mock.Of<IMedia>(content => content.Path == nodePaths[id]));
|
||||
var userService = new Mock<IUserService>();
|
||||
var entityService = new Mock<IEntityService>();
|
||||
entityService.Setup(service => service.GetAllPaths(It.IsAny<UmbracoObjectTypes>(), It.IsAny<int[]>()))
|
||||
.Returns((UmbracoObjectTypes objType, int[] ids) =>
|
||||
{
|
||||
return ids.Select(x => new EntityPath { Path = nodePaths[x], Id = x });
|
||||
});
|
||||
|
||||
var authHelper = new UserEditorAuthorizationHelper(
|
||||
contentService.Object,
|
||||
mediaService.Object,
|
||||
userService.Object,
|
||||
entityService.Object);
|
||||
|
||||
//adding 5555 which currentUser has access to since it's a child of 9876 ... adding is still ok even though currentUser doesn't have access to 1234
|
||||
var result = authHelper.IsAuthorized(currentUser, savingUser, new int[0], new[] { 1234, 5555 }, new string[0]);
|
||||
|
||||
Assert.IsTrue(result.Success);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Remove_Media_Start_Node_On_User_Without_Access()
|
||||
{
|
||||
var nodePaths = new Dictionary<int, string>
|
||||
{
|
||||
{1234, "-1,1234"},
|
||||
{9876, "-1,9876"},
|
||||
{5555, "-1,9876,5555"},
|
||||
{4567, "-1,4567"},
|
||||
};
|
||||
|
||||
var currentUser = Mock.Of<IUser>(user => user.StartMediaIds == new[] { 9876 });
|
||||
var savingUser = Mock.Of<IUser>(user => user.StartMediaIds == new[] { 1234, 4567 });
|
||||
|
||||
var contentService = new Mock<IContentService>();
|
||||
var mediaService = new Mock<IMediaService>();
|
||||
mediaService.Setup(x => x.GetById(It.IsAny<int>()))
|
||||
.Returns((int id) => Mock.Of<IMedia>(content => content.Path == nodePaths[id]));
|
||||
var userService = new Mock<IUserService>();
|
||||
var entityService = new Mock<IEntityService>();
|
||||
entityService.Setup(service => service.GetAllPaths(It.IsAny<UmbracoObjectTypes>(), It.IsAny<int[]>()))
|
||||
.Returns((UmbracoObjectTypes objType, int[] ids) =>
|
||||
{
|
||||
return ids.Select(x => new EntityPath { Path = nodePaths[x], Id = x });
|
||||
});
|
||||
|
||||
var authHelper = new UserEditorAuthorizationHelper(
|
||||
contentService.Object,
|
||||
mediaService.Object,
|
||||
userService.Object,
|
||||
entityService.Object);
|
||||
|
||||
//removing 4567 start node even though currentUser doesn't have acces to it ... removing is ok
|
||||
var result = authHelper.IsAuthorized(currentUser, savingUser, new int[0], new[] { 1234 }, new string[0]);
|
||||
|
||||
Assert.IsTrue(result.Success);
|
||||
}
|
||||
|
||||
private IUser GetAdminUser()
|
||||
{
|
||||
var admin = Mock.Of<IUser>(user => user.Groups == new[]
|
||||
{
|
||||
new ReadOnlyUserGroup(1, "Admin", "icon-user", null, null, Constants.Security.AdminGroupAlias, new string[0], new string[0])
|
||||
});
|
||||
return admin;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Identity;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using Umbraco.Tests.TestHelpers.ControllerTesting;
|
||||
@@ -107,7 +108,9 @@ namespace Umbraco.Tests.Web.Controllers
|
||||
var userServiceMock = Mock.Get(Current.Services.UserService);
|
||||
var users = MockedUser.CreateMulipleUsers(10);
|
||||
long outVal = 10;
|
||||
userServiceMock.Setup(service => service.GetAll(It.IsAny<long>(), It.IsAny<int>(), out outVal, It.IsAny<string>(), It.IsAny<Direction>(), It.IsAny<UserState[]>(), It.IsAny<string[]>(), It.IsAny<string>()))
|
||||
userServiceMock.Setup(service => service.GetAll(
|
||||
It.IsAny<long>(), It.IsAny<int>(), out outVal, It.IsAny<string>(), It.IsAny<Direction>(),
|
||||
It.IsAny<UserState[]>(), It.IsAny<string[]>(), It.IsAny<string[]>(), It.IsAny<IQuery<IUser>>()))
|
||||
.Returns(() => users);
|
||||
|
||||
//we need to manually apply automapper mappings with the mocked applicationcontext
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Web;
|
||||
|
||||
namespace Umbraco.Tests.Web
|
||||
{
|
||||
[TestFixture]
|
||||
public class HttpCookieExtensionsTests
|
||||
{
|
||||
[TestCase("hello=world;cookies=are fun;", "hello", "world", true)]
|
||||
[TestCase("HELlo=world;cookies=are fun", "hello", "world", true)]
|
||||
[TestCase("HELlo= world;cookies=are fun", "hello", "world", true)]
|
||||
[TestCase("HELlo =world;cookies=are fun", "hello", "world", true)]
|
||||
[TestCase("hello = world;cookies=are fun;", "hello", "world", true)]
|
||||
[TestCase("hellos=world;cookies=are fun", "hello", "world", false)]
|
||||
[TestCase("hello=world;cookies?=are fun?", "hello", "world", true)]
|
||||
[TestCase("hel?lo=world;cookies=are fun?", "hel?lo", "world", true)]
|
||||
public void Get_Cookie_Value_From_HttpRequestHeaders(string cookieHeaderVal, string cookieName, string cookieVal, bool matches)
|
||||
{
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "http://test.com");
|
||||
var requestHeaders = request.Headers;
|
||||
requestHeaders.Add("Cookie", cookieHeaderVal);
|
||||
|
||||
var valueFromHeader = requestHeaders.GetCookieValue(cookieName);
|
||||
|
||||
if (matches)
|
||||
{
|
||||
Assert.IsNotNull(valueFromHeader);
|
||||
Assert.AreEqual(cookieVal, valueFromHeader);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.IsNull(valueFromHeader);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Web;
|
||||
|
||||
namespace Umbraco.Tests.Web.Mvc
|
||||
{
|
||||
[TestFixture]
|
||||
public class HtmlStringUtilitiesTests
|
||||
{
|
||||
private HtmlStringUtilities _htmlStringUtilities;
|
||||
|
||||
[SetUp]
|
||||
public virtual void Initialize()
|
||||
{
|
||||
|
||||
_htmlStringUtilities = new HtmlStringUtilities();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ReplaceLineBreaksWithHtmlBreak()
|
||||
{
|
||||
var output = _htmlStringUtilities.ReplaceLineBreaksForHtml("<div><h1>hello world</h1><p>hello world\r\nhello world\rhello world\nhello world</p></div>");
|
||||
var expected = "<div><h1>hello world</h1><p>hello world<br />hello world<br />hello world<br />hello world</p></div>";
|
||||
Assert.AreEqual(expected, output);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,8 +62,12 @@ namespace Umbraco.Web.Cache
|
||||
// content and when the PublishedCachesService is notified of changes it does not see
|
||||
// the new content...
|
||||
|
||||
bool draftChanged, publishedChanged;
|
||||
_facadeService.Notify(payloads, out draftChanged, out publishedChanged);
|
||||
// fixme - what about this?
|
||||
// should rename it, and then, this is only for Deploy, and then, ???
|
||||
//if (Suspendable.PageCacheRefresher.CanUpdateDocumentCache)
|
||||
// ...
|
||||
|
||||
_facadeService.Notify(payloads, out _, out var publishedChanged);
|
||||
|
||||
if (payloads.Any(x => x.ChangeTypes.HasType(TreeChangeTypes.RefreshAll)) || publishedChanged)
|
||||
{
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Umbraco.Web.Cache
|
||||
{
|
||||
public sealed class TemplateCacheRefresher : CacheRefresherBase<TemplateCacheRefresher>
|
||||
{
|
||||
private readonly IdkMap _idkMap;
|
||||
private readonly IdkMap _idkMap;
|
||||
|
||||
public TemplateCacheRefresher(CacheHelper cacheHelper, IdkMap idkMap)
|
||||
: base(cacheHelper)
|
||||
|
||||
@@ -165,7 +165,7 @@ namespace Umbraco.Web.Editors
|
||||
? Security.IsAuthenticated()
|
||||
//current culture is set at the very beginning of each request
|
||||
? Thread.CurrentThread.CurrentCulture
|
||||
: CultureInfo.GetCultureInfo("en")
|
||||
: CultureInfo.GetCultureInfo(GlobalSettings.DefaultUILanguage)
|
||||
: CultureInfo.GetCultureInfo(culture);
|
||||
|
||||
var textForCulture = Services.TextService.GetAllStoredValues(cultureInfo)
|
||||
|
||||
@@ -295,10 +295,11 @@ namespace Umbraco.Web.Editors
|
||||
GetMaxRequestLength()
|
||||
},
|
||||
{"keepUserLoggedIn", UmbracoConfig.For.UmbracoSettings().Security.KeepUserLoggedIn},
|
||||
{"usernameIsEmail", UmbracoConfig.For.UmbracoSettings().Security.UsernameIsEmail},
|
||||
{"cssPath", IOHelper.ResolveUrl(SystemDirectories.Css).TrimEnd('/')},
|
||||
{"allowPasswordReset", UmbracoConfig.For.UmbracoSettings().Security.AllowPasswordReset},
|
||||
{"loginBackgroundImage", UmbracoConfig.For.UmbracoSettings().Content.LoginBackgroundImage},
|
||||
{"emailServerConfigured", GlobalSettings.HasSmtpServerConfigured(_httpContext.Request.ApplicationPath)},
|
||||
{"showUserInvite", EmailSender.CanSendRequiredEmail},
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -85,6 +85,8 @@ namespace Umbraco.Web.Editors
|
||||
{
|
||||
if (saveModel.ContentId <= 0) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
|
||||
|
||||
//TODO: Should non-admins be alowed to set granular permissions?
|
||||
|
||||
var content = Services.ContentService.GetById(saveModel.ContentId);
|
||||
if (content == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
|
||||
|
||||
@@ -146,7 +148,9 @@ namespace Umbraco.Web.Editors
|
||||
if (contentId <= 0) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
|
||||
var content = Services.ContentService.GetById(contentId);
|
||||
if (content == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
|
||||
|
||||
|
||||
//TODO: Should non-admins be able to see detailed permissions?
|
||||
|
||||
var allUserGroups = Services.UserService.GetAllUserGroups();
|
||||
|
||||
return GetDetailedPermissions(content, allUserGroups);
|
||||
@@ -232,6 +236,7 @@ namespace Umbraco.Web.Editors
|
||||
content.Path = string.Format("-1,{0},{1}", persistedContent.ContentTypeId, content.Id);
|
||||
|
||||
content.AllowedActions = new[] {"A"};
|
||||
content.IsBlueprint = true;
|
||||
|
||||
var excludeProps = new[] {"_umb_urls", "_umb_releasedate", "_umb_expiredate", "_umb_template"};
|
||||
var propsTab = content.Tabs.Last();
|
||||
@@ -299,7 +304,7 @@ namespace Umbraco.Web.Editors
|
||||
}
|
||||
|
||||
[OutgoingEditorModelEvent]
|
||||
public ContentItemDisplay GetEmpty(int blueprintId)
|
||||
public ContentItemDisplay GetEmpty(int blueprintId, int parentId)
|
||||
{
|
||||
var blueprint = Services.ContentService.GetBlueprintById(blueprintId);
|
||||
if (blueprint == null)
|
||||
@@ -309,6 +314,7 @@ namespace Umbraco.Web.Editors
|
||||
|
||||
blueprint.Id = 0;
|
||||
blueprint.Name = string.Empty;
|
||||
blueprint.ParentId = parentId;
|
||||
|
||||
var mapped = Mapper.Map<ContentItemDisplay>(blueprint);
|
||||
|
||||
|
||||
@@ -83,10 +83,10 @@ namespace Umbraco.Web.Editors
|
||||
contentIdToCheck = contentToCheck.Id;
|
||||
break;
|
||||
case ContentSaveAction.SaveNew:
|
||||
//Save new requires both ActionNew AND ActionUpdate
|
||||
//Save new requires ActionNew
|
||||
|
||||
permissionToCheck.Add(ActionNew.Instance.Letter);
|
||||
|
||||
permissionToCheck.Add(ActionNew.Instance.Letter);
|
||||
permissionToCheck.Add(ActionUpdate.Instance.Letter);
|
||||
if (contentItem.ParentId != Constants.System.Root)
|
||||
{
|
||||
contentToCheck = ContentService.GetById(contentItem.ParentId);
|
||||
@@ -98,7 +98,7 @@ namespace Umbraco.Web.Editors
|
||||
}
|
||||
break;
|
||||
case ContentSaveAction.SendPublishNew:
|
||||
//Send new requires both ActionToPublish AND ActionUpdate
|
||||
//Send new requires both ActionToPublish AND ActionNew
|
||||
|
||||
permissionToCheck.Add(ActionNew.Instance.Letter);
|
||||
permissionToCheck.Add(ActionToPublish.Instance.Letter);
|
||||
@@ -114,6 +114,7 @@ namespace Umbraco.Web.Editors
|
||||
break;
|
||||
case ContentSaveAction.PublishNew:
|
||||
//Publish new requires both ActionNew AND ActionPublish
|
||||
//TODO: Shoudn't publish also require ActionUpdate since it will definitely perform an update to publish but maybe that's just implied
|
||||
|
||||
permissionToCheck.Add(ActionNew.Instance.Letter);
|
||||
permissionToCheck.Add(ActionPublish.Instance.Letter);
|
||||
|
||||
@@ -159,6 +159,15 @@ namespace Umbraco.Web.Editors
|
||||
: Request.CreateNotificationValidationErrorResponse(result.Exception.Message);
|
||||
}
|
||||
|
||||
public HttpResponseMessage PostRenameContainer(int id, string name)
|
||||
{
|
||||
var result = Services.ContentTypeService.RenameContainer(id, name, Security.CurrentUser.Id);
|
||||
|
||||
return result
|
||||
? Request.CreateResponse(HttpStatusCode.OK, result.Result) //return the id
|
||||
: Request.CreateNotificationValidationErrorResponse(result.Exception.Message);
|
||||
}
|
||||
|
||||
public DocumentTypeDisplay PostSave(DocumentTypeSave contentTypeSave)
|
||||
{
|
||||
var savedCt = PerformPostSave<DocumentTypeDisplay, DocumentTypeSave, PropertyTypeBasic>(
|
||||
@@ -279,8 +288,8 @@ namespace Umbraco.Web.Editors
|
||||
{
|
||||
basic.Name = localizedTextService.UmbracoDictionaryTranslate(basic.Name);
|
||||
basic.Description = localizedTextService.UmbracoDictionaryTranslate(basic.Description);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//map the blueprints
|
||||
var blueprints = Services.ContentService.GetBlueprintsForContentTypes(types.Select(x => x.Id).ToArray()).ToArray();
|
||||
foreach (var basic in basics)
|
||||
@@ -290,7 +299,7 @@ namespace Umbraco.Web.Editors
|
||||
{
|
||||
basic.Blueprints[blueprint.Id] = blueprint.Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return basics;
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ namespace Umbraco.Web.Editors
|
||||
public async Task<ModelWithNotifications<string>> PostChangePassword(ChangingPasswordModel data)
|
||||
{
|
||||
var passwordChanger = new PasswordChanger(Logger, Services.UserService);
|
||||
var passwordChangeResult = await passwordChanger.ChangePasswordWithIdentityAsync(Security.CurrentUser, data, ModelState, UserManager);
|
||||
var passwordChangeResult = await passwordChanger.ChangePasswordWithIdentityAsync(Security.CurrentUser, Security.CurrentUser, data, UserManager);
|
||||
|
||||
if (passwordChangeResult.Success)
|
||||
{
|
||||
@@ -90,6 +90,11 @@ namespace Umbraco.Web.Editors
|
||||
return result;
|
||||
}
|
||||
|
||||
foreach (var memberName in passwordChangeResult.Result.ChangeError.MemberNames)
|
||||
{
|
||||
ModelState.AddModelError(memberName, passwordChangeResult.Result.ChangeError.ErrorMessage);
|
||||
}
|
||||
|
||||
throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState));
|
||||
}
|
||||
|
||||
|
||||
@@ -123,54 +123,8 @@ namespace Umbraco.Web.Editors
|
||||
[ValidateAngularAntiForgeryToken]
|
||||
public IEnumerable<Tab<DashboardControl>> GetDashboard(string section)
|
||||
{
|
||||
var tabs = new List<Tab<DashboardControl>>();
|
||||
var i = 1;
|
||||
|
||||
// The dashboard config can contain more than one area inserted by a package.
|
||||
foreach( var dashboardSection in UmbracoConfig.For.DashboardSettings().Sections.Where(x => x.Areas.Contains(section)))
|
||||
{
|
||||
//we need to validate access to this section
|
||||
if (DashboardSecurity.AuthorizeAccess(dashboardSection, Security.CurrentUser, Services.SectionService) == false)
|
||||
continue;
|
||||
|
||||
//User is authorized
|
||||
foreach (var tab in dashboardSection.Tabs)
|
||||
{
|
||||
//we need to validate access to this tab
|
||||
if (DashboardSecurity.AuthorizeAccess(tab, Security.CurrentUser, Services.SectionService) == false)
|
||||
continue;
|
||||
|
||||
var dashboardControls = new List<DashboardControl>();
|
||||
|
||||
foreach (var control in tab.Controls)
|
||||
{
|
||||
if (DashboardSecurity.AuthorizeAccess(control, Security.CurrentUser, Services.SectionService) == false)
|
||||
continue;
|
||||
|
||||
var dashboardControl = new DashboardControl();
|
||||
var controlPath = control.ControlPath.Trim();
|
||||
dashboardControl.Path = IOHelper.FindFile(controlPath);
|
||||
if (controlPath.ToLowerInvariant().EndsWith(".ascx".ToLowerInvariant()))
|
||||
dashboardControl.ServerSide = true;
|
||||
|
||||
dashboardControls.Add(dashboardControl);
|
||||
}
|
||||
|
||||
tabs.Add(new Tab<DashboardControl>
|
||||
{
|
||||
Id = i,
|
||||
Alias = tab.Caption.ToSafeAlias(),
|
||||
IsActive = i == 1,
|
||||
Label = tab.Caption,
|
||||
Properties = dashboardControls
|
||||
});
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
//In case there are no tabs or a user doesn't have access the empty tabs list is returned
|
||||
return tabs;
|
||||
var dashboardHelper = new DashboardHelper(Services.SectionService);
|
||||
return dashboardHelper.GetDashboard(section, Security.CurrentUser);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.Editors
|
||||
{
|
||||
internal class DashboardHelper
|
||||
{
|
||||
private readonly ISectionService _sectionService;
|
||||
|
||||
public DashboardHelper(ISectionService sectionService)
|
||||
{
|
||||
if (sectionService == null) throw new ArgumentNullException("sectionService");
|
||||
_sectionService = sectionService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the dashboard models per section for the current user and it's access
|
||||
/// </summary>
|
||||
/// <param name="currentUser"></param>
|
||||
/// <returns></returns>
|
||||
public IDictionary<string, IEnumerable<Tab<DashboardControl>>> GetDashboards(IUser currentUser)
|
||||
{
|
||||
var result = new Dictionary<string, IEnumerable<Tab<DashboardControl>>>();
|
||||
foreach (var section in _sectionService.GetSections())
|
||||
{
|
||||
result[section.Alias] = GetDashboard(section.Alias, currentUser);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the dashboard model for the given section based on the current user and it's access
|
||||
/// </summary>
|
||||
/// <param name="section"></param>
|
||||
/// <param name="currentUser"></param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<Tab<DashboardControl>> GetDashboard(string section, IUser currentUser)
|
||||
{
|
||||
var tabs = new List<Tab<DashboardControl>>();
|
||||
var i = 1;
|
||||
|
||||
// The dashboard config can contain more than one area inserted by a package.
|
||||
foreach (var dashboardSection in UmbracoConfig.For.DashboardSettings().Sections.Where(x => x.Areas.Contains(section)))
|
||||
{
|
||||
//we need to validate access to this section
|
||||
if (DashboardSecurity.AuthorizeAccess(dashboardSection, currentUser, _sectionService) == false)
|
||||
continue;
|
||||
|
||||
//User is authorized
|
||||
foreach (var tab in dashboardSection.Tabs)
|
||||
{
|
||||
//we need to validate access to this tab
|
||||
if (DashboardSecurity.AuthorizeAccess(tab, currentUser, _sectionService) == false)
|
||||
continue;
|
||||
|
||||
var dashboardControls = new List<DashboardControl>();
|
||||
|
||||
foreach (var control in tab.Controls)
|
||||
{
|
||||
if (DashboardSecurity.AuthorizeAccess(control, currentUser, _sectionService) == false)
|
||||
continue;
|
||||
|
||||
var dashboardControl = new DashboardControl();
|
||||
var controlPath = control.ControlPath.Trim();
|
||||
dashboardControl.Path = IOHelper.FindFile(controlPath);
|
||||
if (controlPath.ToLowerInvariant().EndsWith(".ascx".ToLowerInvariant()))
|
||||
dashboardControl.ServerSide = true;
|
||||
|
||||
dashboardControls.Add(dashboardControl);
|
||||
}
|
||||
|
||||
tabs.Add(new Tab<DashboardControl>
|
||||
{
|
||||
Id = i,
|
||||
Alias = tab.Caption.ToSafeAlias(),
|
||||
IsActive = i == 1,
|
||||
Label = tab.Caption,
|
||||
Properties = dashboardControls
|
||||
});
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
//In case there are no tabs or a user doesn't have access the empty tabs list is returned
|
||||
return tabs;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ using Umbraco.Web.Composing;
|
||||
|
||||
namespace Umbraco.Web.Editors
|
||||
{
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The API controller used for editing data types
|
||||
/// </summary>
|
||||
@@ -113,7 +113,7 @@ namespace Umbraco.Web.Editors
|
||||
//if it doesnt exist yet, we will create it.
|
||||
if (dt == null)
|
||||
{
|
||||
dt = new DataTypeDefinition( Constants.PropertyEditors.ListViewAlias );
|
||||
dt = new DataTypeDefinition(Constants.PropertyEditors.ListViewAlias);
|
||||
dt.Name = Constants.Conventions.DataTypes.ListViewPrefix + contentTypeAlias;
|
||||
Services.DataTypeService.Save(dt);
|
||||
}
|
||||
@@ -265,6 +265,15 @@ namespace Umbraco.Web.Editors
|
||||
}
|
||||
}
|
||||
|
||||
public HttpResponseMessage PostRenameContainer(int id, string name)
|
||||
{
|
||||
var result = Services.DataTypeService.RenameContainer(id, name, Security.CurrentUser.Id);
|
||||
|
||||
return result
|
||||
? Request.CreateResponse(HttpStatusCode.OK, result.Result)
|
||||
: Request.CreateNotificationValidationErrorResponse(result.Exception.Message);
|
||||
}
|
||||
|
||||
#region ReadOnly actions to return basic data - allow access for: content ,media, members, settings, developer
|
||||
/// <summary>
|
||||
/// Gets the content json for all data types
|
||||
@@ -305,7 +314,7 @@ namespace Umbraco.Web.Editors
|
||||
foreach (var dataType in dataTypes)
|
||||
{
|
||||
var propertyEditor = propertyEditors.SingleOrDefault(x => x.Alias == dataType.Alias);
|
||||
if(propertyEditor != null)
|
||||
if (propertyEditor != null)
|
||||
dataType.HasPrevalues = propertyEditor.PreValueEditor.Fields.Any(); ;
|
||||
}
|
||||
|
||||
|
||||
@@ -153,6 +153,16 @@ namespace Umbraco.Web.Editors
|
||||
: Request.CreateNotificationValidationErrorResponse(result.Exception.Message);
|
||||
}
|
||||
|
||||
public HttpResponseMessage PostRenameContainer(int id, string name)
|
||||
{
|
||||
|
||||
var result = Services.MediaTypeService.RenameContainer(id, name, Security.CurrentUser.Id);
|
||||
|
||||
return result
|
||||
? Request.CreateResponse(HttpStatusCode.OK, result.Result) //return the id
|
||||
: Request.CreateNotificationValidationErrorResponse(result.Exception.Message);
|
||||
}
|
||||
|
||||
public MediaTypeDisplay PostSave(MediaTypeSave contentTypeSave)
|
||||
{
|
||||
var savedCt = PerformPostSave<MediaTypeDisplay, MediaTypeSave, PropertyTypeBasic>(
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Web.Http.ModelBinding;
|
||||
using System.Web.Security;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Identity;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Core.Services;
|
||||
@@ -24,10 +25,18 @@ namespace Umbraco.Web.Editors
|
||||
_userService = userService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the password for a user based on the many different rules and config options
|
||||
/// </summary>
|
||||
/// <param name="currentUser">The user performing the password save action</param>
|
||||
/// <param name="savingUser">The user who's password is being changed</param>
|
||||
/// <param name="passwordModel"></param>
|
||||
/// <param name="userMgr"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<Attempt<PasswordChangedModel>> ChangePasswordWithIdentityAsync(
|
||||
IUser currentUser,
|
||||
IUser savingUser,
|
||||
ChangingPasswordModel passwordModel,
|
||||
ModelStateDictionary modelState,
|
||||
BackOfficeUserManager<BackOfficeIdentityUser> userMgr)
|
||||
{
|
||||
if (passwordModel == null) throw new ArgumentNullException(nameof(passwordModel));
|
||||
@@ -43,7 +52,7 @@ namespace Umbraco.Web.Editors
|
||||
//if this isn't using an IUserAwarePasswordHasher, then fallback to the old way
|
||||
if (membershipPasswordHasher.MembershipProvider.RequiresQuestionAndAnswer)
|
||||
throw new NotSupportedException("Currently the user editor does not support providers that have RequiresQuestionAndAnswer specified");
|
||||
return ChangePasswordWithMembershipProvider(currentUser.Username, passwordModel, membershipPasswordHasher.MembershipProvider);
|
||||
return ChangePasswordWithMembershipProvider(savingUser.Username, passwordModel, membershipPasswordHasher.MembershipProvider);
|
||||
}
|
||||
|
||||
//if we are here, then a IUserAwarePasswordHasher is available, however we cannot proceed in that case if for some odd reason
|
||||
@@ -54,22 +63,38 @@ namespace Umbraco.Web.Editors
|
||||
throw new InvalidOperationException("The membership provider cannot have a password format of " + membershipPasswordHasher.MembershipProvider.PasswordFormat + " and be configured with secured hashed passwords");
|
||||
}
|
||||
|
||||
//Are we resetting the password??
|
||||
//Are we resetting the password?? In ASP.NET Identity APIs, this flag indicates that an admin user is changing another user's password
|
||||
//without knowing the original password.
|
||||
if (passwordModel.Reset.HasValue && passwordModel.Reset.Value)
|
||||
{
|
||||
//if it's the current user, the current user cannot reset their own password
|
||||
if (currentUser.Username == savingUser.Username)
|
||||
{
|
||||
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Password reset is not allowed", new[] { "resetPassword" }) });
|
||||
}
|
||||
|
||||
//if the current user has access to reset/manually change the password
|
||||
if (currentUser.HasSectionAccess(Umbraco.Core.Constants.Applications.Users) == false)
|
||||
{
|
||||
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("The current user is not authorized", new[] { "resetPassword" }) });
|
||||
}
|
||||
|
||||
//ok, we should be able to reset it
|
||||
var resetToken = await userMgr.GeneratePasswordResetTokenAsync(currentUser.Id);
|
||||
var newPass = userMgr.GeneratePassword();
|
||||
var resetResult = await userMgr.ResetPasswordAsync(currentUser.Id, resetToken, newPass);
|
||||
var resetToken = await userMgr.GeneratePasswordResetTokenAsync(savingUser.Id);
|
||||
var newPass = passwordModel.NewPassword.IsNullOrWhiteSpace()
|
||||
? userMgr.GeneratePassword()
|
||||
: passwordModel.NewPassword;
|
||||
|
||||
var resetResult = await userMgr.ResetPasswordAsync(savingUser.Id, resetToken, newPass);
|
||||
|
||||
if (resetResult.Succeeded == false)
|
||||
{
|
||||
var errors = string.Join(". ", resetResult.Errors);
|
||||
_logger.Warn<PasswordChanger>($"Could not reset member password {errors}");
|
||||
_logger.Warn<PasswordChanger>($"Could not reset user password {errors}");
|
||||
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not reset password, errors: " + errors, new[] { "resetPassword" }) });
|
||||
}
|
||||
|
||||
return Attempt.Succeed(new PasswordChangedModel { ResetPassword = newPass });
|
||||
|
||||
return Attempt.Succeed(new PasswordChangedModel());
|
||||
}
|
||||
|
||||
//we're not resetting it so we need to try to change it.
|
||||
@@ -90,12 +115,12 @@ namespace Umbraco.Web.Editors
|
||||
if (passwordModel.OldPassword.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
//if an old password is suplied try to change it
|
||||
var changeResult = await userMgr.ChangePasswordAsync(currentUser.Id, passwordModel.OldPassword, passwordModel.NewPassword);
|
||||
var changeResult = await userMgr.ChangePasswordAsync(savingUser.Id, passwordModel.OldPassword, passwordModel.NewPassword);
|
||||
if (changeResult.Succeeded == false)
|
||||
{
|
||||
var errors = string.Join(". ", changeResult.Errors);
|
||||
_logger.Warn<PasswordChanger>($"Could not change member password {errors}");
|
||||
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, errors: " + errors, new[] { "value" }) });
|
||||
_logger.Warn<PasswordChanger>($"Could not change user password {errors}");
|
||||
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, errors: " + errors, new[] { "oldPassword" }) });
|
||||
}
|
||||
return Attempt.Succeed(new PasswordChangedModel());
|
||||
}
|
||||
@@ -107,7 +132,7 @@ namespace Umbraco.Web.Editors
|
||||
/// <summary>
|
||||
/// Changes password for a member/user given the membership provider and the password change model
|
||||
/// </summary>
|
||||
/// <param name="username"></param>
|
||||
/// <param name="username">The username of the user having their password changed</param>
|
||||
/// <param name="passwordModel"></param>
|
||||
/// <param name="membershipProvider"></param>
|
||||
/// <returns></returns>
|
||||
|
||||
@@ -3,6 +3,9 @@ using AutoMapper;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Web.Mvc;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Web.Trees;
|
||||
using Section = Umbraco.Web.Models.ContentEditing.Section;
|
||||
|
||||
namespace Umbraco.Web.Editors
|
||||
{
|
||||
@@ -14,14 +17,58 @@ namespace Umbraco.Web.Editors
|
||||
{
|
||||
public IEnumerable<Section> GetSections()
|
||||
{
|
||||
|
||||
var sections = Services.SectionService.GetAllowedSections(Security.GetUserId());
|
||||
return sections.Select(Mapper.Map<Core.Models.Section, Section>);
|
||||
|
||||
var sectionModels = sections.Select(Mapper.Map<Core.Models.Section, Section>).ToArray();
|
||||
|
||||
//Check if there are empty dashboards or dashboards that will end up empty based on the current user's access
|
||||
//and add the meta data about them
|
||||
var dashboardHelper = new DashboardHelper(Services.SectionService);
|
||||
//this is a bit nasty since we'll be proxying via the app tree controller but we sort of have to do that
|
||||
//since tree's by nature are controllers and require request contextual data.
|
||||
var appTreeController = new ApplicationTreeController
|
||||
{
|
||||
ControllerContext = ControllerContext
|
||||
};
|
||||
var dashboards = dashboardHelper.GetDashboards(Security.CurrentUser);
|
||||
//now we can add metadata for each section so that the UI knows if there's actually anything at all to render for
|
||||
//a dashboard for a given section, then the UI can deal with it accordingly (i.e. redirect to the first tree)
|
||||
foreach (var section in sectionModels)
|
||||
{
|
||||
var hasDashboards = false;
|
||||
IEnumerable<Tab<DashboardControl>> dashboardsForSection;
|
||||
if (dashboards.TryGetValue(section.Alias, out dashboardsForSection))
|
||||
{
|
||||
if (dashboardsForSection.Any())
|
||||
hasDashboards = true;
|
||||
}
|
||||
|
||||
if (hasDashboards == false)
|
||||
{
|
||||
//get the first tree in the section and get it's root node route path
|
||||
var sectionTrees = appTreeController.GetApplicationTrees(section.Alias, null, null).Result;
|
||||
section.RoutePath = sectionTrees.IsContainer == false
|
||||
? sectionTrees.RoutePath
|
||||
: sectionTrees.Children[0].RoutePath;
|
||||
}
|
||||
}
|
||||
|
||||
return sectionModels;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all the sections that the user has access to
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<Section> GetAllSections()
|
||||
{
|
||||
var sections = Services.SectionService.GetSections();
|
||||
return sections.Select(Mapper.Map<Core.Models.Section, Section>);
|
||||
var mapped = sections.Select(Mapper.Map<Core.Models.Section, Section>);
|
||||
if (Security.CurrentUser.IsAdmin())
|
||||
return mapped;
|
||||
|
||||
return mapped.Where(x => Security.CurrentUser.AllowedSections.Contains(x.Alias)).ToArray();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Services;
|
||||
|
||||
namespace Umbraco.Web.Editors
|
||||
{
|
||||
internal class UserEditorAuthorizationHelper
|
||||
{
|
||||
private readonly IContentService _contentService;
|
||||
private readonly IMediaService _mediaService;
|
||||
private readonly IUserService _userService;
|
||||
private readonly IEntityService _entityService;
|
||||
|
||||
public UserEditorAuthorizationHelper(IContentService contentService, IMediaService mediaService, IUserService userService, IEntityService entityService)
|
||||
{
|
||||
_contentService = contentService;
|
||||
_mediaService = mediaService;
|
||||
_userService = userService;
|
||||
_entityService = entityService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the current user has access to save the user data
|
||||
/// </summary>
|
||||
/// <param name="currentUser">The current user trying to save user data</param>
|
||||
/// <param name="savingUser">The user instance being saved (can be null if it's a new user)</param>
|
||||
/// <param name="startContentIds">The start content ids of the user being saved (can be null or empty)</param>
|
||||
/// <param name="startMediaIds">The start media ids of the user being saved (can be null or empty)</param>
|
||||
/// <param name="userGroupAliases">The user aliases of the user being saved (can be null or empty)</param>
|
||||
/// <returns></returns>
|
||||
public Attempt<string> IsAuthorized(IUser currentUser,
|
||||
IUser savingUser,
|
||||
IEnumerable<int> startContentIds, IEnumerable<int> startMediaIds,
|
||||
IEnumerable<string> userGroupAliases)
|
||||
{
|
||||
var currentIsAdmin = currentUser.IsAdmin();
|
||||
|
||||
// a) A non-admin cannot save an admin
|
||||
|
||||
if (savingUser != null)
|
||||
{
|
||||
if (savingUser.IsAdmin() && currentIsAdmin == false)
|
||||
return Attempt.Fail("The current user is not an administrator so cannot save another administrator");
|
||||
}
|
||||
|
||||
// b) If a start node is changing, a user cannot set a start node on another user that they don't have access to, this even goes for admins
|
||||
|
||||
//only validate any start nodes that have changed.
|
||||
//a user can remove any start nodes and add start nodes that they have access to
|
||||
//but they cannot add a start node that they do not have access to
|
||||
|
||||
var changedStartContentIds = savingUser == null
|
||||
? startContentIds
|
||||
: startContentIds == null
|
||||
? null
|
||||
: startContentIds.Except(savingUser.StartContentIds).ToArray();
|
||||
var changedStartMediaIds = savingUser == null
|
||||
? startMediaIds
|
||||
: startMediaIds == null
|
||||
? null
|
||||
: startMediaIds.Except(savingUser.StartMediaIds).ToArray();
|
||||
var pathResult = AuthorizePath(currentUser, changedStartContentIds, changedStartMediaIds);
|
||||
if (pathResult == false)
|
||||
return pathResult;
|
||||
|
||||
// c) an admin can manage any group or section access
|
||||
|
||||
if (currentIsAdmin)
|
||||
return Attempt<string>.Succeed();
|
||||
|
||||
if (userGroupAliases != null)
|
||||
{
|
||||
var savingGroupAliases = userGroupAliases.ToArray();
|
||||
|
||||
//only validate any groups that have changed.
|
||||
//a non-admin user can remove groups and add groups that they have access to
|
||||
//but they cannot add a group that they do not have access to or that grants them
|
||||
//path or section access that they don't have access to.
|
||||
|
||||
var newGroups = savingUser == null
|
||||
? savingGroupAliases
|
||||
: savingGroupAliases.Except(savingUser.Groups.Select(x => x.Alias)).ToArray();
|
||||
|
||||
var userGroupsChanged = savingUser != null && newGroups.Length > 0;
|
||||
|
||||
if (userGroupsChanged)
|
||||
{
|
||||
// d) A user cannot assign a group to another user that they do not belong to
|
||||
|
||||
var currentUserGroups = currentUser.Groups.Select(x => x.Alias).ToArray();
|
||||
|
||||
foreach (var group in newGroups)
|
||||
{
|
||||
if (currentUserGroups.Contains(group) == false)
|
||||
{
|
||||
return Attempt.Fail("Cannot assign the group " + group + ", the current user is not a member");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Attempt<string>.Succeed();
|
||||
}
|
||||
|
||||
private Attempt<string> AuthorizePath(IUser currentUser, IEnumerable<int> startContentIds, IEnumerable<int> startMediaIds)
|
||||
{
|
||||
if (startContentIds != null)
|
||||
{
|
||||
foreach (var contentId in startContentIds)
|
||||
{
|
||||
if (contentId == Constants.System.Root)
|
||||
{
|
||||
var hasAccess = UserExtensions.HasPathAccess("-1", currentUser.CalculateContentStartNodeIds(_entityService), Constants.System.RecycleBinContent);
|
||||
if (hasAccess == false)
|
||||
return Attempt.Fail("The current user does not have access to the content root");
|
||||
}
|
||||
else
|
||||
{
|
||||
var content = _contentService.GetById(contentId);
|
||||
if (content == null) continue;
|
||||
var hasAccess = currentUser.HasPathAccess(content, _entityService);
|
||||
if (hasAccess == false)
|
||||
return Attempt.Fail("The current user does not have access to the content path " + content.Path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (startMediaIds != null)
|
||||
{
|
||||
foreach (var mediaId in startMediaIds)
|
||||
{
|
||||
if (mediaId == Constants.System.Root)
|
||||
{
|
||||
var hasAccess = UserExtensions.HasPathAccess("-1", currentUser.CalculateMediaStartNodeIds(_entityService), Constants.System.RecycleBinMedia);
|
||||
if (hasAccess == false)
|
||||
return Attempt.Fail("The current user does not have access to the media root");
|
||||
}
|
||||
else
|
||||
{
|
||||
var media = _mediaService.GetById(mediaId);
|
||||
if (media == null) continue;
|
||||
var hasAccess = currentUser.HasPathAccess(media, _entityService);
|
||||
if (hasAccess == false)
|
||||
return Attempt.Fail("The current user does not have access to the media path " + media.Path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Attempt<string>.Succeed();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.Controllers;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Composing;
|
||||
|
||||
namespace Umbraco.Web.Editors
|
||||
{
|
||||
/// <summary>
|
||||
/// Authorizes that the current user has access to the user group Id in the request
|
||||
/// </summary>
|
||||
internal class UserGroupAuthorizationAttribute : AuthorizeAttribute
|
||||
{
|
||||
private readonly string _paramName;
|
||||
private readonly UmbracoContext _umbracoContext;
|
||||
|
||||
/// <summary>
|
||||
/// THIS SHOULD BE ONLY USED FOR UNIT TESTS
|
||||
/// </summary>
|
||||
/// <param name="paramName"></param>
|
||||
/// <param name="umbracoContext"></param>
|
||||
public UserGroupAuthorizationAttribute(string paramName, UmbracoContext umbracoContext)
|
||||
{
|
||||
if (umbracoContext == null) throw new ArgumentNullException("umbracoContext");
|
||||
_paramName = paramName;
|
||||
_umbracoContext = umbracoContext;
|
||||
}
|
||||
|
||||
public UserGroupAuthorizationAttribute(string paramName)
|
||||
{
|
||||
_paramName = paramName;
|
||||
}
|
||||
|
||||
private UmbracoContext GetUmbracoContext()
|
||||
{
|
||||
return _umbracoContext ?? UmbracoContext.Current;
|
||||
}
|
||||
|
||||
protected override bool IsAuthorized(HttpActionContext actionContext)
|
||||
{
|
||||
var umbCtx = GetUmbracoContext();
|
||||
var currentUser = umbCtx.Security.CurrentUser;
|
||||
|
||||
var queryString = actionContext.Request.GetQueryNameValuePairs();
|
||||
|
||||
var ids = queryString.Where(x => x.Key == _paramName).ToArray();
|
||||
if (ids.Length == 0)
|
||||
return base.IsAuthorized(actionContext);
|
||||
|
||||
var intIds = ids.Select(x => x.Value.TryConvertTo<int>()).Where(x => x.Success).Select(x => x.Result).ToArray();
|
||||
var authHelper = new UserGroupEditorAuthorizationHelper(
|
||||
Current.Services.UserService,
|
||||
Current.Services.ContentService,
|
||||
Current.Services.MediaService,
|
||||
Current.Services.EntityService);
|
||||
return authHelper.AuthorizeGroupAccess(currentUser, intIds);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Services;
|
||||
|
||||
namespace Umbraco.Web.Editors
|
||||
{
|
||||
internal class UserGroupEditorAuthorizationHelper
|
||||
{
|
||||
private readonly IUserService _userService;
|
||||
private readonly IContentService _contentService;
|
||||
private readonly IMediaService _mediaService;
|
||||
private readonly IEntityService _entityService;
|
||||
|
||||
public UserGroupEditorAuthorizationHelper(IUserService userService, IContentService contentService, IMediaService mediaService, IEntityService entityService)
|
||||
{
|
||||
_userService = userService;
|
||||
_contentService = contentService;
|
||||
_mediaService = mediaService;
|
||||
_entityService = entityService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Authorize that the current user belongs to these groups
|
||||
/// </summary>
|
||||
/// <param name="currentUser"></param>
|
||||
/// <param name="groupIds"></param>
|
||||
/// <returns></returns>
|
||||
public Attempt<string> AuthorizeGroupAccess(IUser currentUser, params int[] groupIds)
|
||||
{
|
||||
if (currentUser.IsAdmin())
|
||||
return Attempt<string>.Succeed();
|
||||
|
||||
var groups = _userService.GetAllUserGroups(groupIds.ToArray());
|
||||
var groupAliases = groups.Select(x => x.Alias).ToArray();
|
||||
var userGroups = currentUser.Groups.Select(x => x.Alias).ToArray();
|
||||
var missingAccess = groupAliases.Except(userGroups).ToArray();
|
||||
return missingAccess.Length == 0
|
||||
? Attempt<string>.Succeed()
|
||||
: Attempt.Fail("User is not a member of " + string.Join(", ", missingAccess));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Authorize that the current user belongs to these groups
|
||||
/// </summary>
|
||||
/// <param name="currentUser"></param>
|
||||
/// <param name="groupAliases"></param>
|
||||
/// <returns></returns>
|
||||
public Attempt<string> AuthorizeGroupAccess(IUser currentUser, params string[] groupAliases)
|
||||
{
|
||||
if (currentUser.IsAdmin())
|
||||
return Attempt<string>.Succeed();
|
||||
|
||||
var userGroups = currentUser.Groups.Select(x => x.Alias).ToArray();
|
||||
var missingAccess = groupAliases.Except(userGroups).ToArray();
|
||||
return missingAccess.Length == 0
|
||||
? Attempt<string>.Succeed()
|
||||
: Attempt.Fail("User is not a member of " + string.Join(", ", missingAccess));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Authorize that the user is not adding a section to the group that they don't have access to
|
||||
/// </summary>
|
||||
/// <param name="currentUser"></param>
|
||||
/// <param name="currentAllowedSections"></param>
|
||||
/// <param name="proposedAllowedSections"></param>
|
||||
/// <returns></returns>
|
||||
public Attempt<string> AuthorizeSectionChanges(IUser currentUser,
|
||||
IEnumerable<string> currentAllowedSections,
|
||||
IEnumerable<string> proposedAllowedSections)
|
||||
{
|
||||
if (currentUser.IsAdmin())
|
||||
return Attempt<string>.Succeed();
|
||||
|
||||
var sectionsAdded = currentAllowedSections.Except(proposedAllowedSections).ToArray();
|
||||
var sectionAccessMissing = sectionsAdded.Except(currentUser.AllowedSections).ToArray();
|
||||
return sectionAccessMissing.Length > 0
|
||||
? Attempt.Fail("Current user doesn't have access to add these sections " + string.Join(", ", sectionAccessMissing))
|
||||
: Attempt<string>.Succeed();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Authorize that the user is not changing to a start node that they don't have access to (including admins)
|
||||
/// </summary>
|
||||
/// <param name="currentUser"></param>
|
||||
/// <param name="currentContentStartId"></param>
|
||||
/// <param name="proposedContentStartId"></param>
|
||||
/// <param name="currentMediaStartId"></param>
|
||||
/// <param name="proposedMediaStartId"></param>
|
||||
/// <returns></returns>
|
||||
public Attempt<string> AuthorizeStartNodeChanges(IUser currentUser,
|
||||
int? currentContentStartId,
|
||||
int? proposedContentStartId,
|
||||
int? currentMediaStartId,
|
||||
int? proposedMediaStartId)
|
||||
{
|
||||
if (currentContentStartId != proposedContentStartId && proposedContentStartId.HasValue)
|
||||
{
|
||||
var content = _contentService.GetById(proposedContentStartId.Value);
|
||||
if (content != null)
|
||||
{
|
||||
if (currentUser.HasPathAccess(content, _entityService) == false)
|
||||
return Attempt.Fail("Current user doesn't have access to the content path " + content.Path);
|
||||
}
|
||||
}
|
||||
|
||||
if (currentMediaStartId != proposedMediaStartId && proposedMediaStartId.HasValue)
|
||||
{
|
||||
var media = _mediaService.GetById(proposedMediaStartId.Value);
|
||||
if (media != null)
|
||||
{
|
||||
if (currentUser.HasPathAccess(media, _entityService) == false)
|
||||
return Attempt.Fail("Current user doesn't have access to the media path " + media.Path);
|
||||
}
|
||||
}
|
||||
|
||||
return Attempt<string>.Succeed();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,9 @@ using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.Filters;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
@@ -25,9 +27,32 @@ namespace Umbraco.Web.Editors
|
||||
{
|
||||
if (userGroupSave == null) throw new ArgumentNullException(nameof(userGroupSave));
|
||||
|
||||
//authorize that the user has access to save this user group
|
||||
var authHelper = new UserGroupEditorAuthorizationHelper(
|
||||
Services.UserService, Services.ContentService, Services.MediaService, Services.EntityService);
|
||||
var isAuthorized = authHelper.AuthorizeGroupAccess(Security.CurrentUser, userGroupSave.Alias);
|
||||
if (isAuthorized == false)
|
||||
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.Unauthorized, isAuthorized.Result));
|
||||
|
||||
//if sections were added we need to check that the current user has access to that section
|
||||
isAuthorized = authHelper.AuthorizeSectionChanges(Security.CurrentUser,
|
||||
userGroupSave.PersistedUserGroup.AllowedSections,
|
||||
userGroupSave.Sections);
|
||||
if (isAuthorized == false)
|
||||
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.Unauthorized, isAuthorized.Result));
|
||||
|
||||
//if start nodes were changed we need to check that the current user has access to them
|
||||
isAuthorized = authHelper.AuthorizeStartNodeChanges(Security.CurrentUser,
|
||||
userGroupSave.PersistedUserGroup.StartContentId,
|
||||
userGroupSave.StartContentId,
|
||||
userGroupSave.PersistedUserGroup.StartMediaId,
|
||||
userGroupSave.StartMediaId);
|
||||
if (isAuthorized == false)
|
||||
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.Unauthorized, isAuthorized.Result));
|
||||
|
||||
//save the group
|
||||
Services.UserService.Save(userGroupSave.PersistedUserGroup, userGroupSave.Users.ToArray());
|
||||
|
||||
|
||||
//deal with permissions
|
||||
|
||||
//remove ones that have been removed
|
||||
@@ -67,15 +92,31 @@ namespace Umbraco.Web.Editors
|
||||
/// Returns all user groups
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<UserGroupBasic> GetUserGroups()
|
||||
public IEnumerable<UserGroupBasic> GetUserGroups(bool onlyCurrentUserGroups = true)
|
||||
{
|
||||
return Mapper.Map<IEnumerable<IUserGroup>, IEnumerable<UserGroupBasic>>(Services.UserService.GetAllUserGroups());
|
||||
var allGroups = Mapper.Map<IEnumerable<IUserGroup>, IEnumerable<UserGroupBasic>>(Services.UserService.GetAllUserGroups())
|
||||
.ToList();
|
||||
|
||||
var isAdmin = Security.CurrentUser.IsAdmin();
|
||||
if (isAdmin) return allGroups;
|
||||
|
||||
if (onlyCurrentUserGroups == false)
|
||||
{
|
||||
//this user is not an admin so in that case we need to exlude all admin users
|
||||
allGroups.RemoveAt(allGroups.IndexOf(allGroups.Find(basic => basic.Alias == Constants.Security.AdminGroupAlias)));
|
||||
return allGroups;
|
||||
}
|
||||
|
||||
//we cannot return user groups that this user does not have access to
|
||||
var currentUserGroups = Security.CurrentUser.Groups.Select(x => x.Alias).ToArray();
|
||||
return allGroups.Where(x => currentUserGroups.Contains(x.Alias)).ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a user group
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[UserGroupAuthorization("id")]
|
||||
public UserGroupDisplay GetUserGroup(int id)
|
||||
{
|
||||
var found = Services.UserService.GetUserGroupById(id);
|
||||
@@ -89,9 +130,13 @@ namespace Umbraco.Web.Editors
|
||||
|
||||
[HttpPost]
|
||||
[HttpDelete]
|
||||
[UserGroupAuthorization("userGroupIds")]
|
||||
public HttpResponseMessage PostDeleteUserGroups([FromUri] int[] userGroupIds)
|
||||
{
|
||||
var userGroups = Services.UserService.GetAllUserGroups(userGroupIds).ToArray();
|
||||
var userGroups = Services.UserService.GetAllUserGroups(userGroupIds)
|
||||
//never delete the admin group or translators group
|
||||
.Where(x => x.Alias != Constants.Security.AdminGroupAlias && x.Alias != Constants.Security.TranslatorGroupAlias)
|
||||
.ToArray();
|
||||
foreach (var userGroup in userGroups)
|
||||
{
|
||||
Services.UserService.DeleteUserGroup(userGroup);
|
||||
|
||||
@@ -8,6 +8,7 @@ using System.Runtime.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.Controllers;
|
||||
using System.Web.Mvc;
|
||||
using AutoMapper;
|
||||
using Microsoft.AspNet.Identity;
|
||||
@@ -20,11 +21,14 @@ using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Identity;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Web.Mvc;
|
||||
using Umbraco.Web.WebApi;
|
||||
using Umbraco.Web.WebApi.Filters;
|
||||
using ActionFilterAttribute = System.Web.Http.Filters.ActionFilterAttribute;
|
||||
using Constants = Umbraco.Core.Constants;
|
||||
using IUser = Umbraco.Core.Models.Membership.IUser;
|
||||
using Task = System.Threading.Tasks.Task;
|
||||
@@ -124,7 +128,7 @@ namespace Umbraco.Web.Editors
|
||||
var filePath = found.Avatar;
|
||||
|
||||
//if the filePath is already null it will mean that the user doesn't have a custom avatar and their gravatar is currently
|
||||
//being used (if they have one). This means they want to remove their gravatar too which we can do by setting a special value
|
||||
//being used (if they have one). This means they want to remove their gravatar too which we can do by setting a special value
|
||||
//for the avatar.
|
||||
if (filePath.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
@@ -159,11 +163,10 @@ namespace Umbraco.Web.Editors
|
||||
{
|
||||
throw new HttpResponseException(HttpStatusCode.NotFound);
|
||||
}
|
||||
return Mapper.Map<IUser, UserDisplay>(user);
|
||||
var result = Mapper.Map<IUser, UserDisplay>(user);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns a paged users collection
|
||||
/// </summary>
|
||||
@@ -184,10 +187,37 @@ namespace Umbraco.Web.Editors
|
||||
[FromUri]UserState[] userStates = null,
|
||||
string filter = "")
|
||||
{
|
||||
//following the same principle we had in previous versions, we would only show admins to admins, see
|
||||
// https://github.com/umbraco/Umbraco-CMS/blob/dev-v7/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadUsers.cs#L91
|
||||
// so to do that here, we'll need to check if this current user is an admin and if not we should exclude all user who are
|
||||
// also admins
|
||||
|
||||
var excludeUserGroups = new string[0];
|
||||
var isAdmin = Security.CurrentUser.IsAdmin();
|
||||
if (isAdmin == false)
|
||||
{
|
||||
//this user is not an admin so in that case we need to exlude all admin users
|
||||
excludeUserGroups = new[] {Constants.Security.AdminGroupAlias};
|
||||
}
|
||||
|
||||
var filterQuery = Current.DatabaseContext.Query<IUser>();
|
||||
|
||||
//if the current user is not the administrator, then don't include this in the results.
|
||||
var isAdminUser = Security.CurrentUser.Id == 0;
|
||||
if (isAdminUser == false)
|
||||
{
|
||||
filterQuery.Where(x => x.Id != 0);
|
||||
}
|
||||
|
||||
if (filter.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
filterQuery.Where(x => x.Name.Contains(filter) || x.Username.Contains(filter));
|
||||
}
|
||||
|
||||
long pageIndex = pageNumber - 1;
|
||||
long total;
|
||||
var result = Services.UserService.GetAll(pageIndex, pageSize, out total, orderBy, orderDirection, userStates, userGroups, filter);
|
||||
|
||||
var result = Services.UserService.GetAll(pageIndex, pageSize, out total, orderBy, orderDirection, userStates, userGroups, excludeUserGroups, filterQuery);
|
||||
|
||||
var paged = new PagedUserResult(total, pageNumber, pageSize)
|
||||
{
|
||||
Items = Mapper.Map<IEnumerable<UserBasic>>(result),
|
||||
@@ -211,16 +241,29 @@ namespace Umbraco.Web.Editors
|
||||
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState));
|
||||
}
|
||||
|
||||
var existing = Services.UserService.GetByEmail(userSave.Email);
|
||||
if (existing != null)
|
||||
if (UmbracoConfig.For.UmbracoSettings().Security.UsernameIsEmail)
|
||||
{
|
||||
ModelState.AddModelError("Email", "A user with the email already exists");
|
||||
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState));
|
||||
//ensure they are the same if we're using it
|
||||
userSave.Username = userSave.Email;
|
||||
}
|
||||
else
|
||||
{
|
||||
//first validate the username if were showing it
|
||||
CheckUniqueUsername(userSave.Username, null);
|
||||
}
|
||||
CheckUniqueEmail(userSave.Email, null);
|
||||
|
||||
//Perform authorization here to see if the current user can actually save this user with the info being requested
|
||||
var authHelper = new UserEditorAuthorizationHelper(Services.ContentService, Services.MediaService, Services.UserService, Services.EntityService);
|
||||
var canSaveUser = authHelper.IsAuthorized(Security.CurrentUser, null, null, null, userSave.UserGroups);
|
||||
if (canSaveUser == false)
|
||||
{
|
||||
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.Unauthorized, canSaveUser.Result));
|
||||
}
|
||||
|
||||
//we want to create the user with the UserManager, this ensures the 'empty' (special) password
|
||||
//format is applied without us having to duplicate that logic
|
||||
var identityUser = BackOfficeIdentityUser.CreateNew(userSave.Email, userSave.Email, GlobalSettings.DefaultUILanguage);
|
||||
var identityUser = BackOfficeIdentityUser.CreateNew(userSave.Username, userSave.Email, GlobalSettings.DefaultUILanguage);
|
||||
identityUser.Name = userSave.Name;
|
||||
|
||||
var created = await UserManager.CreateAsync(identityUser);
|
||||
@@ -266,7 +309,7 @@ namespace Umbraco.Web.Editors
|
||||
/// <summary>
|
||||
/// Invites a user
|
||||
/// </summary>
|
||||
/// <param name="userSave"></param>
|
||||
/// <param name="userSave"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// This will email the user an invite and generate a token that will be validated in the email
|
||||
@@ -283,25 +326,38 @@ namespace Umbraco.Web.Editors
|
||||
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState));
|
||||
}
|
||||
|
||||
var hasSmtp = GlobalSettings.HasSmtpServerConfigured(RequestContext.VirtualPathRoot);
|
||||
if (hasSmtp == false)
|
||||
if (EmailSender.CanSendRequiredEmail == false)
|
||||
{
|
||||
throw new HttpResponseException(
|
||||
Request.CreateNotificationValidationErrorResponse("No Email server is configured"));
|
||||
}
|
||||
|
||||
var user = Services.UserService.GetByEmail(userSave.Email);
|
||||
if (user != null && (user.LastLoginDate != default(DateTime) || user.EmailConfirmedDate.HasValue))
|
||||
IUser user;
|
||||
if (UmbracoConfig.For.UmbracoSettings().Security.UsernameIsEmail)
|
||||
{
|
||||
ModelState.AddModelError("Email", "A user with the email already exists");
|
||||
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState));
|
||||
//ensure it's the same
|
||||
userSave.Username = userSave.Email;
|
||||
}
|
||||
else
|
||||
{
|
||||
//first validate the username if we're showing it
|
||||
user = CheckUniqueUsername(userSave.Username, u => u.LastLoginDate != default(DateTime) || u.EmailConfirmedDate.HasValue);
|
||||
}
|
||||
user = CheckUniqueEmail(userSave.Email, u => u.LastLoginDate != default(DateTime) || u.EmailConfirmedDate.HasValue);
|
||||
|
||||
//Perform authorization here to see if the current user can actually save this user with the info being requested
|
||||
var authHelper = new UserEditorAuthorizationHelper(Services.ContentService, Services.MediaService, Services.UserService, Services.EntityService);
|
||||
var canSaveUser = authHelper.IsAuthorized(Security.CurrentUser, user, null, null, userSave.UserGroups);
|
||||
if (canSaveUser == false)
|
||||
{
|
||||
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.Unauthorized, canSaveUser.Result));
|
||||
}
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
//we want to create the user with the UserManager, this ensures the 'empty' (special) password
|
||||
//format is applied without us having to duplicate that logic
|
||||
var identityUser = BackOfficeIdentityUser.CreateNew(userSave.Email, userSave.Email, GlobalSettings.DefaultUILanguage);
|
||||
var identityUser = BackOfficeIdentityUser.CreateNew(userSave.Username, userSave.Email, GlobalSettings.DefaultUILanguage);
|
||||
identityUser.Name = userSave.Name;
|
||||
|
||||
var created = await UserManager.CreateAsync(identityUser);
|
||||
@@ -332,7 +388,29 @@ namespace Umbraco.Web.Editors
|
||||
return display;
|
||||
}
|
||||
|
||||
|
||||
private IUser CheckUniqueEmail(string email, Func<IUser, bool> extraCheck)
|
||||
{
|
||||
var user = Services.UserService.GetByEmail(email);
|
||||
if (user != null && (extraCheck == null || extraCheck(user)))
|
||||
{
|
||||
ModelState.AddModelError("Email", "A user with the email already exists");
|
||||
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState));
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
private IUser CheckUniqueUsername(string username, Func<IUser, bool> extraCheck)
|
||||
{
|
||||
var user = Services.UserService.GetByUsername(username);
|
||||
if (user != null && (extraCheck == null || extraCheck(user)))
|
||||
{
|
||||
ModelState.AddModelError(
|
||||
UmbracoConfig.For.UmbracoSettings().Security.UsernameIsEmail ? "Email" : "Username",
|
||||
"A user with the username already exists");
|
||||
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState));
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
private HttpContextBase EnsureHttpContext()
|
||||
{
|
||||
@@ -373,12 +451,15 @@ namespace Umbraco.Web.Editors
|
||||
UserExtensions.GetUserCulture(to.Language, Services.TextService),
|
||||
new[] { userDisplay.Name, from, message, inviteUri.ToString() });
|
||||
|
||||
await UserManager.EmailService.SendAsync(new IdentityMessage
|
||||
{
|
||||
Body = emailBody,
|
||||
Destination = userDisplay.Email,
|
||||
Subject = emailSubject
|
||||
});
|
||||
await UserManager.EmailService.SendAsync(
|
||||
//send the special UmbracoEmailMessage which configures it's own sender
|
||||
//to allow for events to handle sending the message if no smtp is configured
|
||||
new UmbracoEmailMessage(new EmailSender(true))
|
||||
{
|
||||
Body = emailBody,
|
||||
Destination = userDisplay.Email,
|
||||
Subject = emailSubject
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@@ -404,6 +485,14 @@ namespace Umbraco.Web.Editors
|
||||
if (found == null)
|
||||
throw new HttpResponseException(HttpStatusCode.NotFound);
|
||||
|
||||
//Perform authorization here to see if the current user can actually save this user with the info being requested
|
||||
var authHelper = new UserEditorAuthorizationHelper(Services.ContentService, Services.MediaService, Services.UserService, Services.EntityService);
|
||||
var canSaveUser = authHelper.IsAuthorized(Security.CurrentUser, found, userSave.StartContentIds, userSave.StartMediaIds, userSave.UserGroups);
|
||||
if (canSaveUser == false)
|
||||
{
|
||||
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.Unauthorized, canSaveUser.Result));
|
||||
}
|
||||
|
||||
var hasErrors = false;
|
||||
|
||||
var existing = Services.UserService.GetByEmail(userSave.Email);
|
||||
@@ -440,23 +529,24 @@ namespace Umbraco.Web.Editors
|
||||
userSave.Username = userSave.Email;
|
||||
}
|
||||
|
||||
var resetPasswordValue = string.Empty;
|
||||
if (userSave.ChangePassword != null)
|
||||
{
|
||||
var passwordChanger = new PasswordChanger(Logger, Services.UserService);
|
||||
|
||||
var passwordChangeResult = await passwordChanger.ChangePasswordWithIdentityAsync(found, userSave.ChangePassword, ModelState, UserManager);
|
||||
var passwordChangeResult = await passwordChanger.ChangePasswordWithIdentityAsync(Security.CurrentUser, found, userSave.ChangePassword, UserManager);
|
||||
if (passwordChangeResult.Success)
|
||||
{
|
||||
//depending on how the provider is configured, the password may be reset so let's store that for later
|
||||
resetPasswordValue = passwordChangeResult.Result.ResetPassword;
|
||||
|
||||
//need to re-get the user
|
||||
//need to re-get the user
|
||||
found = Services.UserService.GetUserById(intId.Result);
|
||||
}
|
||||
else
|
||||
{
|
||||
hasErrors = true;
|
||||
|
||||
foreach (var memberName in passwordChangeResult.Result.ChangeError.MemberNames)
|
||||
{
|
||||
ModelState.AddModelError(memberName, passwordChangeResult.Result.ChangeError.ErrorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -470,13 +560,9 @@ namespace Umbraco.Web.Editors
|
||||
|
||||
var display = Mapper.Map<UserDisplay>(user);
|
||||
|
||||
//re-map the password reset value (if any)
|
||||
if (resetPasswordValue.IsNullOrWhiteSpace() == false)
|
||||
display.ResetPasswordValue = resetPasswordValue;
|
||||
|
||||
display.AddSuccessNotification(Services.TextService.Localize("speechBubbles/operationSavedHeader"), Services.TextService.Localize("speechBubbles/editUserSaved"));
|
||||
return display;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disables the users with the given user ids
|
||||
@@ -528,7 +614,43 @@ namespace Umbraco.Web.Editors
|
||||
}
|
||||
|
||||
return Request.CreateNotificationSuccessResponse(
|
||||
Services.TextService.Localize("speechBubbles/enableUserSuccess", new[] { users[0].Name }));
|
||||
Services.TextService.Localize("speechBubbles/enableUserSuccess", new[] { users[0].Name }));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unlocks the users with the given user ids
|
||||
/// </summary>
|
||||
/// <param name="userIds"></param>
|
||||
public async Task<HttpResponseMessage> PostUnlockUsers([FromUri]int[] userIds)
|
||||
{
|
||||
if (userIds.Length <= 0)
|
||||
return Request.CreateResponse(HttpStatusCode.OK);
|
||||
|
||||
if (userIds.Length == 1)
|
||||
{
|
||||
var unlockResult = await UserManager.SetLockoutEndDateAsync(userIds[0], DateTimeOffset.Now);
|
||||
if (unlockResult.Succeeded == false)
|
||||
{
|
||||
return Request.CreateValidationErrorResponse(
|
||||
string.Format("Could not unlock for user {0} - error {1}", userIds[0], unlockResult.Errors.First()));
|
||||
}
|
||||
var user = await UserManager.FindByIdAsync(userIds[0]);
|
||||
return Request.CreateNotificationSuccessResponse(
|
||||
Services.TextService.Localize("speechBubbles/unlockUserSuccess", new[] { user.Name }));
|
||||
}
|
||||
|
||||
foreach (var u in userIds)
|
||||
{
|
||||
var unlockResult = await UserManager.SetLockoutEndDateAsync(u, DateTimeOffset.Now);
|
||||
if (unlockResult.Succeeded == false)
|
||||
{
|
||||
return Request.CreateValidationErrorResponse(
|
||||
string.Format("Could not unlock for user {0} - error {1}", u, unlockResult.Errors.First()));
|
||||
}
|
||||
}
|
||||
|
||||
return Request.CreateNotificationSuccessResponse(
|
||||
Services.TextService.Localize("speechBubbles/unlockUsersSuccess", new[] { userIds.Length.ToString() }));
|
||||
}
|
||||
|
||||
public HttpResponseMessage PostSetUserGroupsOnUsers([FromUri]string[] userGroupAliases, [FromUri]int[] userIds)
|
||||
@@ -561,5 +683,6 @@ namespace Umbraco.Web.Editors
|
||||
[DataMember(Name = "userStates")]
|
||||
public IDictionary<UserState, int> UserStates { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -30,8 +30,7 @@ namespace Umbraco.Web.HealthCheck.Checks.Config
|
||||
{
|
||||
get
|
||||
{
|
||||
return TextService.Localize("healthcheck/customErrorsCheckSuccessMessage",
|
||||
new[] { Values.First(v => v.IsRecommended).Value });
|
||||
return TextService.Localize("healthcheck/customErrorsCheckSuccessMessage", new[] { CurrentValue });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -59,12 +59,10 @@ namespace Umbraco.Web.HealthCheck.Checks.Permissions
|
||||
// in ALL circumstances or just some
|
||||
var pathsToCheck = new Dictionary<string, PermissionCheckRequirement>
|
||||
{
|
||||
{ SystemDirectories.AppCode, PermissionCheckRequirement.Optional },
|
||||
{ SystemDirectories.Data, PermissionCheckRequirement.Required },
|
||||
{ SystemDirectories.Packages, PermissionCheckRequirement.Required},
|
||||
{ SystemDirectories.Preview, PermissionCheckRequirement.Required },
|
||||
{ SystemDirectories.AppPlugins, PermissionCheckRequirement.Required },
|
||||
{ SystemDirectories.Bin, PermissionCheckRequirement.Optional },
|
||||
{ SystemDirectories.Config, PermissionCheckRequirement.Optional },
|
||||
{ SystemDirectories.Css, PermissionCheckRequirement.Optional },
|
||||
{ SystemDirectories.Masterpages, PermissionCheckRequirement.Optional },
|
||||
@@ -77,11 +75,32 @@ namespace Umbraco.Web.HealthCheck.Checks.Permissions
|
||||
{ SystemDirectories.Xslt, PermissionCheckRequirement.Optional },
|
||||
};
|
||||
|
||||
//These are special paths to check that will restart an app domain if a file is written to them,
|
||||
//so these need to be tested differently
|
||||
var pathsToCheckWithRestarts = new Dictionary<string, PermissionCheckRequirement>
|
||||
{
|
||||
{ SystemDirectories.AppCode, PermissionCheckRequirement.Optional },
|
||||
{ SystemDirectories.Bin, PermissionCheckRequirement.Optional }
|
||||
};
|
||||
|
||||
// Run checks for required and optional paths for modify permission
|
||||
IEnumerable<string> requiredFailedPaths;
|
||||
IEnumerable<string> optionalFailedPaths;
|
||||
var requiredPathCheckResult = FilePermissionHelper.EnsureDirectories(GetPathsToCheck(pathsToCheck, PermissionCheckRequirement.Required), out requiredFailedPaths);
|
||||
var optionalPathCheckResult = FilePermissionHelper.EnsureDirectories(GetPathsToCheck(pathsToCheck, PermissionCheckRequirement.Optional), out optionalFailedPaths);
|
||||
var requiredPathCheckResult = FilePermissionHelper.EnsureDirectories(
|
||||
GetPathsToCheck(pathsToCheck, PermissionCheckRequirement.Required), out var requiredFailedPaths);
|
||||
var optionalPathCheckResult = FilePermissionHelper.EnsureDirectories(
|
||||
GetPathsToCheck(pathsToCheck, PermissionCheckRequirement.Optional), out var optionalFailedPaths);
|
||||
|
||||
//now check the special folders
|
||||
var requiredPathCheckResult2 = FilePermissionHelper.EnsureDirectories(
|
||||
GetPathsToCheck(pathsToCheckWithRestarts, PermissionCheckRequirement.Required), out var requiredFailedPaths2, writeCausesRestart:true);
|
||||
var optionalPathCheckResult2 = FilePermissionHelper.EnsureDirectories(
|
||||
GetPathsToCheck(pathsToCheckWithRestarts, PermissionCheckRequirement.Optional), out var optionalFailedPaths2, writeCausesRestart: true);
|
||||
|
||||
requiredPathCheckResult = requiredPathCheckResult && requiredPathCheckResult2;
|
||||
optionalPathCheckResult = optionalPathCheckResult && optionalPathCheckResult2;
|
||||
|
||||
//combine the paths
|
||||
requiredFailedPaths = requiredFailedPaths.Concat(requiredFailedPaths2).ToList();
|
||||
optionalFailedPaths = requiredFailedPaths.Concat(optionalFailedPaths2).ToList();
|
||||
|
||||
return GetStatus(requiredPathCheckResult, requiredFailedPaths, optionalPathCheckResult, optionalFailedPaths, PermissionCheckFor.Folder);
|
||||
}
|
||||
|
||||
@@ -50,17 +50,10 @@ namespace Umbraco.Web.HealthCheck.NotificationMethods
|
||||
|
||||
var subject = _textService.Localize("healthcheck/scheduledHealthCheckEmailSubject");
|
||||
|
||||
using (var client = new SmtpClient())
|
||||
var mailSender = new EmailSender();
|
||||
using (var mailMessage = CreateMailMessage(subject, message))
|
||||
{
|
||||
if (client.DeliveryMethod == SmtpDeliveryMethod.Network)
|
||||
{
|
||||
await client.SendMailAsync(mailMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
client.Send(mailMessage);
|
||||
}
|
||||
await mailSender.SendAsync(mailMessage);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace Umbraco.Web.HealthCheck.NotificationMethods
|
||||
{
|
||||
public interface IHealthCheckNotificationMethod
|
||||
{
|
||||
bool Enabled { get; }
|
||||
Task SendAsync(HealthCheckResults results, CancellationToken token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,10 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Web;
|
||||
using HtmlAgilityPack;
|
||||
using Umbraco.Web.WebApi.Filters;
|
||||
|
||||
namespace Umbraco.Web
|
||||
{
|
||||
@@ -20,13 +22,14 @@ namespace Umbraco.Web
|
||||
/// <returns>The text with text line breaks replaced with html linebreaks (<br/>)</returns>
|
||||
public string ReplaceLineBreaksForHtml(string text)
|
||||
{
|
||||
return text.Replace("\n", "<br/>\n");
|
||||
return text.Replace("\r\n", @"<br />").Replace("\n", @"<br />").Replace("\r", @"<br />");
|
||||
}
|
||||
|
||||
public HtmlString StripHtmlTags(string html, params string[] tags)
|
||||
{
|
||||
var doc = new HtmlDocument();
|
||||
doc.LoadHtml("<p>" + html + "</p>");
|
||||
|
||||
var targets = new List<HtmlNode>();
|
||||
|
||||
var nodes = doc.DocumentNode.FirstChild.SelectNodes(".//*");
|
||||
@@ -52,7 +55,7 @@ namespace Umbraco.Web
|
||||
{
|
||||
return new HtmlString(html);
|
||||
}
|
||||
return new HtmlString(doc.DocumentNode.FirstChild.InnerHtml);
|
||||
return new HtmlString(doc.DocumentNode.FirstChild.InnerHtml.Replace(" ", " "));
|
||||
}
|
||||
|
||||
internal string Join(string separator, params object[] args)
|
||||
@@ -89,6 +92,8 @@ namespace Umbraco.Web
|
||||
|
||||
public IHtmlString Truncate(string html, int length, bool addElipsis, bool treatTagsAsContent)
|
||||
{
|
||||
const string hellip = "…";
|
||||
|
||||
using (var outputms = new MemoryStream())
|
||||
{
|
||||
using (var outputtw = new StreamWriter(outputms))
|
||||
@@ -110,7 +115,7 @@ namespace Umbraco.Web
|
||||
isTagClose = false;
|
||||
|
||||
int ic = 0,
|
||||
currentLength = 0,
|
||||
//currentLength = 0,
|
||||
currentTextLength = 0;
|
||||
|
||||
string currentTag = string.Empty,
|
||||
@@ -145,6 +150,10 @@ namespace Umbraco.Web
|
||||
{
|
||||
string thisTag = tagStack.Pop();
|
||||
outputtw.Write("</" + thisTag + ">");
|
||||
if (treatTagsAsContent)
|
||||
{
|
||||
currentTextLength++;
|
||||
}
|
||||
}
|
||||
if (!isTagClose && currentTag.Length > 0)
|
||||
{
|
||||
@@ -152,6 +161,10 @@ namespace Umbraco.Web
|
||||
{
|
||||
tagStack.Push(currentTag);
|
||||
outputtw.Write("<" + currentTag);
|
||||
if (treatTagsAsContent)
|
||||
{
|
||||
currentTextLength++;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(tagContents))
|
||||
{
|
||||
if (tagContents.EndsWith("/"))
|
||||
@@ -209,7 +222,7 @@ namespace Umbraco.Web
|
||||
{
|
||||
var charToWrite = (char)ic;
|
||||
outputtw.Write(charToWrite);
|
||||
currentLength++;
|
||||
//currentLength++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,7 +238,7 @@ namespace Umbraco.Web
|
||||
// Reached truncate limit.
|
||||
if (addElipsis)
|
||||
{
|
||||
outputtw.Write("…");
|
||||
outputtw.Write(hellip);
|
||||
}
|
||||
lengthReached = true;
|
||||
}
|
||||
@@ -239,10 +252,59 @@ namespace Umbraco.Web
|
||||
outputms.Position = 0;
|
||||
using (TextReader outputtr = new StreamReader(outputms))
|
||||
{
|
||||
return new HtmlString(outputtr.ReadToEnd().Replace(" ", " ").Trim());
|
||||
string result = string.Empty;
|
||||
|
||||
string firstTrim = outputtr.ReadToEnd().Replace(" ", " ").Trim();
|
||||
|
||||
//Check to see if there is an empty char between the hellip and the output string
|
||||
//if there is, remove it
|
||||
if (string.IsNullOrWhiteSpace(firstTrim) == false)
|
||||
{
|
||||
result = firstTrim[firstTrim.Length - hellip.Length - 1] == ' ' ? firstTrim.Remove(firstTrim.Length - hellip.Length - 1, 1) : firstTrim;
|
||||
}
|
||||
return new HtmlString(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the length of the words from a html block
|
||||
/// </summary>
|
||||
/// <param name="html">Html text</param>
|
||||
/// <param name="words">Amount of words you would like to measure</param>
|
||||
/// <param name="tagsAsContent"></param>
|
||||
/// <returns></returns>
|
||||
public int WordsToLength(string html, int words)
|
||||
{
|
||||
HtmlDocument doc = new HtmlDocument();
|
||||
doc.LoadHtml(html);
|
||||
|
||||
int wordCount = 0,
|
||||
length = 0,
|
||||
maxWords = words;
|
||||
|
||||
html = StripHtmlTags(html, null).ToString();
|
||||
|
||||
while (length < html.Length)
|
||||
{
|
||||
// Check to see if the current wordCount reached the maxWords allowed
|
||||
if (wordCount.Equals(maxWords)) break;
|
||||
// Check if current char is part of a word
|
||||
while (length < html.Length && char.IsWhiteSpace(html[length]) == false)
|
||||
{
|
||||
length++;
|
||||
}
|
||||
|
||||
wordCount++;
|
||||
|
||||
// Skip whitespace until the next word
|
||||
while (length < html.Length && char.IsWhiteSpace(html[length]) && wordCount.Equals(maxWords) == false)
|
||||
{
|
||||
length++;
|
||||
}
|
||||
}
|
||||
return length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,40 @@ namespace Umbraco.Web
|
||||
/// </remarks>
|
||||
internal static class HttpCookieExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Retrieves an individual cookie from the cookies collection
|
||||
/// </summary>
|
||||
/// <param name="requestHeaders"></param>
|
||||
/// <param name="cookieName"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// Adapted from: https://stackoverflow.com/a/29057304/5018 because there's an issue with .NET WebApi cookie parsing logic
|
||||
/// when using requestHeaders.GetCookies() when an invalid cookie name is present.
|
||||
/// </remarks>
|
||||
public static string GetCookieValue(this HttpRequestHeaders requestHeaders, string cookieName)
|
||||
{
|
||||
foreach (var header in requestHeaders)
|
||||
{
|
||||
if (header.Key.Equals("Cookie", StringComparison.InvariantCultureIgnoreCase) == false)
|
||||
continue;
|
||||
|
||||
var cookiesHeaderValue = header.Value.FirstOrDefault();
|
||||
if (cookiesHeaderValue == null)
|
||||
return null;
|
||||
|
||||
var cookieCollection = cookiesHeaderValue.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var cookieNameValue in cookieCollection)
|
||||
{
|
||||
var parts = cookieNameValue.Split(new[] { '=' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length != 2) continue;
|
||||
if (parts[0].Trim().Equals(cookieName, StringComparison.InvariantCultureIgnoreCase))
|
||||
return parts[1].Trim();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the cookie from the request and the response if it exists
|
||||
/// </summary>
|
||||
|
||||
@@ -40,6 +40,8 @@ namespace Umbraco.Web.Install.Controllers
|
||||
// Update ClientDependency version
|
||||
var clientDependencyConfig = new ClientDependencyConfiguration(_logger);
|
||||
var clientDependencyUpdated = clientDependencyConfig.IncreaseVersionNumber();
|
||||
// Delete ClientDependency temp directories to make sure we get fresh caches
|
||||
var clientDependencyTempFilesDeleted = clientDependencyConfig.ClearTempFiles(HttpContext);
|
||||
|
||||
var result = _umbracoContext.Security.ValidateCurrentUser(false);
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using System.Security.AccessControl;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Web.Composing;
|
||||
using Umbraco.Web.PublishedCache;
|
||||
@@ -41,7 +42,18 @@ namespace Umbraco.Web.Install
|
||||
return report.Count == 0;
|
||||
}
|
||||
|
||||
public static bool EnsureDirectories(string[] dirs, out IEnumerable<string> errors)
|
||||
/// <summary>
|
||||
/// This will test the directories for write access
|
||||
/// </summary>
|
||||
/// <param name="directories"></param>
|
||||
/// <param name="errorReport"></param>
|
||||
/// <param name="writeCausesRestart">
|
||||
/// If this is false, the easiest way to test for write access is to write a temp file, however some folder will cause
|
||||
/// an App Domain restart if a file is written to the folder, so in that case we need to use the ACL APIs which aren't as
|
||||
/// reliable but we cannot write a file since it will cause an app domain restart.
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
public static bool EnsureDirectories(string[] dirs, out IEnumerable<string> errors, bool writeCausesRestart = false)
|
||||
{
|
||||
List<string> temp = null;
|
||||
var success = true;
|
||||
@@ -49,7 +61,7 @@ namespace Umbraco.Web.Install
|
||||
{
|
||||
// we don't want to create/ship unnecessary directories, so
|
||||
// here we just ensure we can access the directory, not create it
|
||||
var tryAccess = TryAccessDirectory(dir);
|
||||
var tryAccess = TryAccessDirectory(dir, !writeCausesRestart);
|
||||
if (tryAccess) continue;
|
||||
|
||||
if (temp == null) temp = new List<string>();
|
||||
@@ -151,8 +163,13 @@ namespace Umbraco.Web.Install
|
||||
|
||||
// tries to create a file
|
||||
// if successful, the file is deleted
|
||||
//
|
||||
// or
|
||||
//
|
||||
// use the ACL APIs to avoid creating files
|
||||
//
|
||||
// if the directory does not exist, do nothing & success
|
||||
public static bool TryAccessDirectory(string dir)
|
||||
public static bool TryAccessDirectory(string dir, bool canWrite)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -161,10 +178,17 @@ namespace Umbraco.Web.Install
|
||||
if (Directory.Exists(dirPath) == false)
|
||||
return true;
|
||||
|
||||
var filePath = dirPath + "/" + CreateRandomName() + ".tmp";
|
||||
File.WriteAllText(filePath, "This is an Umbraco internal test file. It is safe to delete it.");
|
||||
File.Delete(filePath);
|
||||
return true;
|
||||
if (canWrite)
|
||||
{
|
||||
var filePath = dirPath + "/" + CreateRandomName() + ".tmp";
|
||||
File.WriteAllText(filePath, "This is an Umbraco internal test file. It is safe to delete it.");
|
||||
File.Delete(filePath);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return HasWritePermission(dirPath);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -172,6 +196,42 @@ namespace Umbraco.Web.Install
|
||||
}
|
||||
}
|
||||
|
||||
private static bool HasWritePermission(string path)
|
||||
{
|
||||
var writeAllow = false;
|
||||
var writeDeny = false;
|
||||
var accessControlList = Directory.GetAccessControl(path);
|
||||
if (accessControlList == null)
|
||||
return false;
|
||||
AuthorizationRuleCollection accessRules;
|
||||
try
|
||||
{
|
||||
accessRules = accessControlList.GetAccessRules(true, true, typeof(System.Security.Principal.SecurityIdentifier));
|
||||
if (accessRules == null)
|
||||
return false;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
//This is not 100% accurate btw because it could turn out that the current user doesn't
|
||||
//have access to read the current permissions but does have write access.
|
||||
//I think this is an edge case however
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (FileSystemAccessRule rule in accessRules)
|
||||
{
|
||||
if ((FileSystemRights.Write & rule.FileSystemRights) != FileSystemRights.Write)
|
||||
continue;
|
||||
|
||||
if (rule.AccessControlType == AccessControlType.Allow)
|
||||
writeAllow = true;
|
||||
else if (rule.AccessControlType == AccessControlType.Deny)
|
||||
writeDeny = true;
|
||||
}
|
||||
|
||||
return writeAllow && writeDeny == false;
|
||||
}
|
||||
|
||||
// tries to write into a file
|
||||
// fails if the directory does not exist
|
||||
private static bool TryWriteFile(string file)
|
||||
|
||||
@@ -20,8 +20,21 @@ namespace Umbraco.Web.Models
|
||||
public string OldPassword { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Set to true if the password is to be reset - only valid when: EnablePasswordReset = true
|
||||
/// Set to true if the password is to be reset
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This operator is different between using ASP.NET Identity APIs and Membership APIs.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// When using Membership APIs, this is only valid when: EnablePasswordReset = true and it will reset the password to something auto generated.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// When using ASP.NET Identity APIs this needs to be set if an administrator user that has access to the Users section is changing another users
|
||||
/// password. This flag is required to indicate that the oldPassword value is not required and that we are in fact performing a password reset and
|
||||
/// then a password change if the executing user has access to do so.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
[DataMember(Name = "reset")]
|
||||
public bool? Reset { get; set; }
|
||||
|
||||
|
||||
@@ -1,15 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http.Formatting;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.ModelBinding;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Validation;
|
||||
using Umbraco.Web.Models.Trees;
|
||||
using Umbraco.Web.Trees;
|
||||
|
||||
namespace Umbraco.Web.Models.ContentEditing
|
||||
{
|
||||
@@ -57,6 +49,8 @@ namespace Umbraco.Web.Models.ContentEditing
|
||||
/// </remarks>
|
||||
[DataMember(Name = "allowedActions")]
|
||||
public IEnumerable<string> AllowedActions { get; set; }
|
||||
|
||||
|
||||
[DataMember(Name = "isBlueprint")]
|
||||
public bool IsBlueprint { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Umbraco.Web.Models.ContentEditing
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Represents a section (application) in the back office
|
||||
/// </summary>
|
||||
[DataContract(Name = "section", Namespace = "")]
|
||||
public class Section
|
||||
{
|
||||
|
||||
[DataMember(Name = "name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
@@ -24,5 +17,11 @@ namespace Umbraco.Web.Models.ContentEditing
|
||||
[DataMember(Name = "alias")]
|
||||
public string Alias { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// In some cases a custom route path can be specified so that when clicking on a section it goes to this
|
||||
/// path instead of the normal dashboard path
|
||||
/// </summary>
|
||||
[DataMember(Name = "routePath")]
|
||||
public string RoutePath { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,10 +27,14 @@ namespace Umbraco.Web.Models.ContentEditing
|
||||
public string EmailHash { get; set; }
|
||||
|
||||
[Obsolete("This should not be used it exists for legacy reasons only, use user groups instead, it will be removed in future versions")]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[ReadOnly(true)]
|
||||
[DataMember(Name = "userType")]
|
||||
public string UserType { get; set; }
|
||||
public string UserType { get; set; }
|
||||
|
||||
[ReadOnly(true)]
|
||||
[DataMember(Name = "userGroups")]
|
||||
public string[] UserGroups { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets/sets the number of seconds for the user's auth ticket to expire
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Runtime.Serialization;
|
||||
@@ -38,6 +39,40 @@ namespace Umbraco.Web.Models.ContentEditing
|
||||
[DataMember(Name = "resetPasswordValue")]
|
||||
[ReadOnly(true)]
|
||||
public string ResetPasswordValue { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A readonly value showing the user's current calculated start content ids
|
||||
/// </summary>
|
||||
[DataMember(Name = "calculatedStartContentIds")]
|
||||
[ReadOnly(true)]
|
||||
public IEnumerable<EntityBasic> CalculatedStartContentIds { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A readonly value showing the user's current calculated start media ids
|
||||
/// </summary>
|
||||
[DataMember(Name = "calculatedStartMediaIds")]
|
||||
[ReadOnly(true)]
|
||||
public IEnumerable<EntityBasic> CalculatedStartMediaIds { get; set; }
|
||||
|
||||
[DataMember(Name = "failedPasswordAttempts")]
|
||||
[ReadOnly(true)]
|
||||
public int FailedPasswordAttempts { get; set; }
|
||||
|
||||
[DataMember(Name = "lastLockoutDate")]
|
||||
[ReadOnly(true)]
|
||||
public DateTime LastLockoutDate { get; set; }
|
||||
|
||||
[DataMember(Name = "lastPasswordChangeDate")]
|
||||
[ReadOnly(true)]
|
||||
public DateTime LastPasswordChangeDate { get; set; }
|
||||
|
||||
[DataMember(Name = "createDate")]
|
||||
[ReadOnly(true)]
|
||||
public DateTime CreateDate { get; set; }
|
||||
|
||||
[DataMember(Name = "updateDate")]
|
||||
[ReadOnly(true)]
|
||||
public DateTime UpdateDate { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@ using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration;
|
||||
|
||||
namespace Umbraco.Web.Models.ContentEditing
|
||||
{
|
||||
@@ -18,7 +20,10 @@ namespace Umbraco.Web.Models.ContentEditing
|
||||
[DataMember(Name = "email", IsRequired = true)]
|
||||
[Required]
|
||||
[EmailAddress]
|
||||
public string Email { get; set; }
|
||||
public string Email { get; set; }
|
||||
|
||||
[DataMember(Name = "username")]
|
||||
public string Username { get; set; }
|
||||
|
||||
[DataMember(Name = "message")]
|
||||
public string Message { get; set; }
|
||||
@@ -26,7 +31,10 @@ namespace Umbraco.Web.Models.ContentEditing
|
||||
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
||||
{
|
||||
if (UserGroups.Any() == false)
|
||||
yield return new ValidationResult("A user must be assigned to at least one group", new[] { "UserGroups" });
|
||||
yield return new ValidationResult("A user must be assigned to at least one group", new[] { "UserGroups" });
|
||||
|
||||
if (UmbracoConfig.For.UmbracoSettings().Security.UsernameIsEmail == false && Username.IsNullOrWhiteSpace())
|
||||
yield return new ValidationResult("A username cannot be empty", new[] { "Username" });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Web;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Web.Composing;
|
||||
using Umbraco.Web.Security;
|
||||
|
||||
namespace Umbraco.Web.Models
|
||||
@@ -22,9 +23,9 @@ namespace Umbraco.Web.Models
|
||||
|
||||
private LoginStatusModel(bool doLookup)
|
||||
{
|
||||
if (doLookup && HttpContext.Current != null)
|
||||
if (doLookup && Current.UmbracoContext != null)
|
||||
{
|
||||
var helper = new MembershipHelper(new HttpContextWrapper(HttpContext.Current));
|
||||
var helper = new MembershipHelper(Current.UmbracoContext);
|
||||
var model = helper.GetCurrentLoginStatus();
|
||||
if (model != null)
|
||||
{
|
||||
|
||||
@@ -10,8 +10,8 @@ using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Composing;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Web.Trees;
|
||||
using Umbraco.Web.Routing;
|
||||
using Umbraco.Web.Trees;
|
||||
using Umbraco.Web._Legacy.Actions;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
@@ -31,7 +31,8 @@ namespace Umbraco.Web.Models.Mapping
|
||||
|
||||
//FROM IContent TO ContentItemDisplay
|
||||
CreateMap<IContent, ContentItemDisplay>()
|
||||
.ForMember(dest => dest.Udi, opt => opt.MapFrom(src => Udi.Create(Constants.UdiEntityType.Document, src.Key)))
|
||||
.ForMember(dest => dest.Udi, opt => opt.MapFrom(src =>
|
||||
Udi.Create(src.IsBlueprint ? Constants.UdiEntityType.DocumentBluePrint : Constants.UdiEntityType.Document, src.Key)))
|
||||
.ForMember(dest => dest.Owner, opt => opt.ResolveUsing(src => contentOwnerResolver.Resolve(src)))
|
||||
.ForMember(dest => dest.Updater, opt => opt.ResolveUsing(src => creatorResolver.Resolve(src)))
|
||||
.ForMember(dest => dest.Icon, opt => opt.MapFrom(src => src.ContentType.Icon))
|
||||
@@ -59,7 +60,8 @@ namespace Umbraco.Web.Models.Mapping
|
||||
|
||||
//FROM IContent TO ContentItemBasic<ContentPropertyBasic, IContent>
|
||||
CreateMap<IContent, ContentItemBasic<ContentPropertyBasic, IContent>>()
|
||||
.ForMember(dest => dest.Udi, opt => opt.MapFrom(src => Udi.Create(Constants.UdiEntityType.Document, src.Key)))
|
||||
.ForMember(dest => dest.Udi, opt => opt.MapFrom(src =>
|
||||
Udi.Create(src.IsBlueprint ? Constants.UdiEntityType.DocumentBluePrint : Constants.UdiEntityType.Document, src.Key)))
|
||||
.ForMember(dest => dest.Owner, opt => opt.ResolveUsing(src => contentOwnerResolver.Resolve(src)))
|
||||
.ForMember(dest => dest.Updater, opt => opt.ResolveUsing(src => creatorResolver.Resolve(src)))
|
||||
.ForMember(dest => dest.Icon, opt => opt.MapFrom(src => src.ContentType.Icon))
|
||||
@@ -70,7 +72,8 @@ namespace Umbraco.Web.Models.Mapping
|
||||
|
||||
//FROM IContent TO ContentItemDto<IContent>
|
||||
CreateMap<IContent, ContentItemDto<IContent>>()
|
||||
.ForMember(dest => dest.Udi, opt => opt.MapFrom(src => Udi.Create(Constants.UdiEntityType.Document, src.Key)))
|
||||
.ForMember(dest => dest.Udi, opt => opt.MapFrom(src =>
|
||||
Udi.Create(src.IsBlueprint ? Constants.UdiEntityType.DocumentBluePrint : Constants.UdiEntityType.Document, src.Key)))
|
||||
.ForMember(dest => dest.Owner, opt => opt.ResolveUsing(src => contentOwnerResolver.Resolve(src)))
|
||||
.ForMember(dest => dest.HasPublishedVersion, opt => opt.MapFrom(src => src.HasPublishedVersion))
|
||||
.ForMember(dest => dest.Updater, opt => opt.Ignore())
|
||||
|
||||
@@ -14,9 +14,8 @@ namespace Umbraco.Web.Models.Mapping
|
||||
_textService = textService;
|
||||
|
||||
CreateMap<Core.Models.Section, Section>()
|
||||
.ForMember(
|
||||
dto => dto.Name,
|
||||
expression => expression.MapFrom(section => _textService.Localize("sections/" + section.Alias, (IDictionary<string, string>)null)))
|
||||
.ForMember(dest => dest.RoutePath, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => _textService.Localize("sections/" + src.Alias, (IDictionary<string, string>)null)))
|
||||
.ReverseMap(); //backwards too!
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,13 +223,33 @@ namespace Umbraco.Web.Models.Mapping
|
||||
display.AssignedPermissions = allAssignedPermissions;
|
||||
});
|
||||
|
||||
//Important! Currently we are never mapping to multiple UserDisplay objects but if we start doing that
|
||||
// this will cause an N+1 and we'll need to change how this works.
|
||||
CreateMap<IUser, UserDisplay>()
|
||||
.ForMember(dest => dest.Avatars, opt => opt.MapFrom(user => user.GetCurrentUserAvatarUrls(userService, runtimeCache)))
|
||||
.ForMember(dest => dest.Username, opt => opt.MapFrom(user => user.Username))
|
||||
.ForMember(dest => dest.LastLoginDate, opt => opt.MapFrom(user => user.LastLoginDate == default(DateTime) ? null : (DateTime?)user.LastLoginDate))
|
||||
.ForMember(dest => dest.UserGroups, opt => opt.MapFrom(user => user.Groups))
|
||||
.ForMember(dest => dest.StartContentIds, opt => opt.UseValue(Enumerable.Empty<EntityBasic>()))
|
||||
.ForMember(dest => dest.StartMediaIds, opt => opt.UseValue(Enumerable.Empty<EntityBasic>()))
|
||||
.ForMember(
|
||||
dest => dest.CalculatedStartContentIds,
|
||||
opt => opt.MapFrom(src => GetStartNodeValues(
|
||||
src.CalculateContentStartNodeIds(entityService),
|
||||
textService, entityService, UmbracoObjectTypes.Document, "content/contentRoot")))
|
||||
.ForMember(
|
||||
dest => dest.CalculatedStartMediaIds,
|
||||
opt => opt.MapFrom(src => GetStartNodeValues(
|
||||
src.CalculateMediaStartNodeIds(entityService),
|
||||
textService, entityService, UmbracoObjectTypes.Media, "media/mediaRoot")))
|
||||
.ForMember(
|
||||
dest => dest.StartContentIds,
|
||||
opt => opt.MapFrom(src => GetStartNodeValues(
|
||||
src.StartContentIds.ToArray(),
|
||||
textService, entityService, UmbracoObjectTypes.Document, "content/contentRoot")))
|
||||
.ForMember(
|
||||
dest => dest.StartMediaIds,
|
||||
opt => opt.MapFrom(src => GetStartNodeValues(
|
||||
src.StartMediaIds.ToArray(),
|
||||
textService, entityService, UmbracoObjectTypes.Media, "media/mediaRoot")))
|
||||
.ForMember(dest => dest.Culture, opt => opt.MapFrom(user => user.GetUserCulture(textService)))
|
||||
.ForMember(
|
||||
dest => dest.AvailableCultures,
|
||||
@@ -247,40 +267,7 @@ namespace Umbraco.Web.Models.Mapping
|
||||
.ForMember(dest => dest.ResetPasswordValue, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Alias, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Trashed, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.AdditionalData, opt => opt.Ignore())
|
||||
.AfterMap((user, display) =>
|
||||
{
|
||||
//Important! Currently we are never mapping to multiple UserDisplay objects but if we start doing that
|
||||
// this will cause an N+1 and we'll need to change how this works.
|
||||
|
||||
var startContentIds = user.StartContentIds.ToArray();
|
||||
if (startContentIds.Length > 0)
|
||||
{
|
||||
//TODO: Update GetAll to be able to pass in a parameter like on the normal Get to NOT load in the entire object!
|
||||
var startNodes = new List<EntityBasic>();
|
||||
if (startContentIds.Contains(-1))
|
||||
{
|
||||
startNodes.Add(RootNode(textService.Localize("content/contentRoot")));
|
||||
}
|
||||
var contentItems = entityService.GetAll(UmbracoObjectTypes.Document, startContentIds);
|
||||
startNodes.AddRange(Mapper.Map<IEnumerable<IUmbracoEntity>, IEnumerable<EntityBasic>>(contentItems));
|
||||
display.StartContentIds = startNodes;
|
||||
|
||||
|
||||
}
|
||||
var startMediaIds = user.StartMediaIds.ToArray();
|
||||
if (startMediaIds.Length > 0)
|
||||
{
|
||||
var startNodes = new List<EntityBasic>();
|
||||
if (startContentIds.Contains(-1))
|
||||
{
|
||||
startNodes.Add(RootNode(textService.Localize("media/mediaRoot")));
|
||||
}
|
||||
var mediaItems = entityService.GetAll(UmbracoObjectTypes.Media, startMediaIds);
|
||||
startNodes.AddRange(Mapper.Map<IEnumerable<IUmbracoEntity>, IEnumerable<EntityBasic>>(mediaItems));
|
||||
display.StartMediaIds = startNodes;
|
||||
}
|
||||
});
|
||||
.ForMember(dest => dest.AdditionalData, opt => opt.Ignore());
|
||||
|
||||
CreateMap<IUser, UserBasic>()
|
||||
//Loading in the user avatar's requires an external request if they don't have a local file avatar, this means that initial load of paging may incur a cost
|
||||
@@ -317,20 +304,23 @@ namespace Umbraco.Web.Models.Mapping
|
||||
dest => dest.EmailHash,
|
||||
opt => opt.MapFrom(user => user.Email.ToLowerInvariant().Trim().GenerateHash()))
|
||||
.ForMember(dest => dest.SecondsUntilTimeout, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.UserGroups, opt => opt.Ignore())
|
||||
.AfterMap((user, detail) =>
|
||||
{
|
||||
//we need to map the legacy UserType
|
||||
//the best we can do here is to return the user's first user group as a IUserType object
|
||||
//but we should attempt to return any group that is the built in ones first
|
||||
var groups = user.Groups.ToArray();
|
||||
detail.UserGroups = user.Groups.Select(x => x.Alias).ToArray();
|
||||
|
||||
if (groups.Length == 0)
|
||||
{
|
||||
//In backwards compatibility land, a user type cannot be null! so we need to return a fake one.
|
||||
//In backwards compatibility land, a user type cannot be null! so we need to return a fake one.
|
||||
detail.UserType = "temp";
|
||||
}
|
||||
else
|
||||
{
|
||||
var builtIns = new[] { Constants.Security.AdminGroupAlias, "writer", "editor", "translator" };
|
||||
var builtIns = new[] { Constants.Security.AdminGroupAlias, "writer", "editor", Constants.Security.TranslatorGroupAlias };
|
||||
var foundBuiltIn = groups.FirstOrDefault(x => builtIns.Contains(x.Alias));
|
||||
if (foundBuiltIn != null)
|
||||
{
|
||||
@@ -361,6 +351,22 @@ namespace Umbraco.Web.Models.Mapping
|
||||
.ForMember(dest => dest.SessionId, opt => opt.MapFrom(user => user.SecurityStamp.IsNullOrWhiteSpace() ? Guid.NewGuid().ToString("N") : user.SecurityStamp));
|
||||
}
|
||||
|
||||
private IEnumerable<EntityBasic> GetStartNodeValues(int[] startNodeIds,
|
||||
ILocalizedTextService textService, IEntityService entityService, UmbracoObjectTypes objectType,
|
||||
string localizedKey)
|
||||
{
|
||||
if (startNodeIds.Length <= 0)
|
||||
return Enumerable.Empty<EntityBasic>();
|
||||
|
||||
var startNodes = new List<EntityBasic>();
|
||||
if (startNodeIds.Contains(-1))
|
||||
startNodes.Add(RootNode(textService.Localize(localizedKey)));
|
||||
|
||||
var mediaItems = entityService.GetAll(objectType, startNodeIds);
|
||||
startNodes.AddRange(Mapper.Map<IEnumerable<IUmbracoEntity>, IEnumerable<EntityBasic>>(mediaItems));
|
||||
return startNodes;
|
||||
}
|
||||
|
||||
private void MapUserGroupBasic(ISectionService sectionService, IEntityService entityService, ILocalizedTextService textService, dynamic group, UserGroupBasic display)
|
||||
{
|
||||
var allSections = sectionService.GetSections();
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Web;
|
||||
using System.Web.Mvc;
|
||||
using Umbraco.Web.Composing;
|
||||
using Umbraco.Web.Security;
|
||||
|
||||
namespace Umbraco.Web.Models
|
||||
@@ -24,9 +25,9 @@ namespace Umbraco.Web.Models
|
||||
private ProfileModel(bool doLookup)
|
||||
{
|
||||
MemberProperties = new List<UmbracoProperty>();
|
||||
if (doLookup)
|
||||
if (doLookup && Current.UmbracoContext != null)
|
||||
{
|
||||
var helper = new MembershipHelper(new HttpContextWrapper(HttpContext.Current));
|
||||
var helper = new MembershipHelper(Current.UmbracoContext);
|
||||
var model = helper.GetCurrentMemberProfileModel();
|
||||
MemberProperties = model.MemberProperties;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.ComponentModel.DataAnnotations;
|
||||
using System.Web;
|
||||
using System.Web.Mvc;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Web.Composing;
|
||||
using Umbraco.Web.Security;
|
||||
|
||||
namespace Umbraco.Web.Models
|
||||
@@ -29,9 +30,9 @@ namespace Umbraco.Web.Models
|
||||
MemberProperties = new List<UmbracoProperty>();
|
||||
LoginOnSuccess = true;
|
||||
CreatePersistentLoginCookie = true;
|
||||
if (doLookup && HttpContext.Current != null)
|
||||
if (doLookup && Current.UmbracoContext != null)
|
||||
{
|
||||
var helper = new MembershipHelper(new HttpContextWrapper(HttpContext.Current));
|
||||
var helper = new MembershipHelper(Current.UmbracoContext);
|
||||
var model = helper.CreateRegistrationModel(MemberTypeAlias);
|
||||
MemberProperties = model.MemberProperties;
|
||||
}
|
||||
|
||||
@@ -10,8 +10,14 @@ namespace Umbraco.Web.PropertyEditors
|
||||
/// <summary>
|
||||
/// The constructor will setup the property editor based on the attribute if one is found
|
||||
/// </summary>
|
||||
public TextAreaPropertyEditor(ILogger logger) : base(logger)
|
||||
public TextAreaPropertyEditor(ILogger logger)
|
||||
: base(logger)
|
||||
{ }
|
||||
|
||||
protected override PropertyValueEditor CreateValueEditor()
|
||||
{
|
||||
return new TextOnlyValueEditor(base.CreateValueEditor());
|
||||
}
|
||||
|
||||
protected override PreValueEditor CreatePreValueEditor()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Core.Services;
|
||||
|
||||
namespace Umbraco.Web.PropertyEditors
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom value editor which ensures that the value stored is just plain text and that
|
||||
/// no magic json formatting occurs when translating it to and from the database values
|
||||
/// </summary>
|
||||
public class TextOnlyValueEditor : PropertyValueEditorWrapper
|
||||
{
|
||||
public TextOnlyValueEditor(PropertyValueEditor wrapped) : base(wrapped)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A method used to format the database value to a value that can be used by the editor
|
||||
/// </summary>
|
||||
/// <param name="property"></param>
|
||||
/// <param name="propertyType"></param>
|
||||
/// <param name="dataTypeService"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// The object returned will always be a string and if the database type is not a valid string type an exception is thrown
|
||||
/// </remarks>
|
||||
public override object ConvertDbToEditor(Property property, PropertyType propertyType, IDataTypeService dataTypeService)
|
||||
{
|
||||
if (property.Value == null) return string.Empty;
|
||||
|
||||
switch (GetDatabaseType())
|
||||
{
|
||||
case DataTypeDatabaseType.Ntext:
|
||||
case DataTypeDatabaseType.Nvarchar:
|
||||
return property.Value.ToString();
|
||||
case DataTypeDatabaseType.Integer:
|
||||
case DataTypeDatabaseType.Decimal:
|
||||
case DataTypeDatabaseType.Date:
|
||||
default:
|
||||
throw new InvalidOperationException("The " + typeof(TextOnlyValueEditor) + " can only be used with string based property editors");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Core.Services;
|
||||
|
||||
namespace Umbraco.Web.PropertyEditors
|
||||
{
|
||||
@@ -17,8 +16,14 @@ namespace Umbraco.Web.PropertyEditors
|
||||
/// <summary>
|
||||
/// The constructor will setup the property editor based on the attribute if one is found
|
||||
/// </summary>
|
||||
public TextboxPropertyEditor(ILogger logger) : base(logger)
|
||||
public TextboxPropertyEditor(ILogger logger)
|
||||
: base(logger)
|
||||
{ }
|
||||
|
||||
protected override PropertyValueEditor CreateValueEditor()
|
||||
{
|
||||
return new TextOnlyValueEditor(base.CreateValueEditor());
|
||||
}
|
||||
|
||||
protected override PreValueEditor CreatePreValueEditor()
|
||||
{
|
||||
|
||||
@@ -240,7 +240,7 @@ namespace Umbraco.Web.Routing
|
||||
/// <remarks>Eg the relative part of <c>/foo/bar/nil</c> to domain <c>example.com/foo</c> is <c>/bar/nil</c>.</remarks>
|
||||
public static string PathRelativeToDomain(Uri domainUri, string path)
|
||||
{
|
||||
return path.Substring(domainUri.AbsolutePath.Length).EnsureStartsWith('/');
|
||||
return path.Substring(domainUri.GetAbsolutePathDecoded().Length).EnsureStartsWith('/');
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -61,7 +61,11 @@ namespace Umbraco.Web.Routing
|
||||
GetRolesForLogin = getRolesForLogin ?? (s => Roles.Provider.GetRolesForUser(s));
|
||||
}
|
||||
|
||||
private Func<string, IEnumerable<string>> GetRolesForLogin { get; }
|
||||
// fixme
|
||||
// in 7.7 this is cached in the PublishedContentRequest, which ... makes little sense
|
||||
// killing it entirely, if we need cache, just implement it properly !!
|
||||
// this is all soooo weird
|
||||
public Func<string, IEnumerable<string>> GetRolesForLogin { get; }
|
||||
|
||||
public PublishedContentRequest CreateRequest(UmbracoContext umbracoContext, Uri uri = null)
|
||||
{
|
||||
|
||||
@@ -150,6 +150,7 @@ namespace Umbraco.Web.Redirects
|
||||
|
||||
private static void ContentService_Moving(IContentService sender, MoveEventArgs<IContent> e)
|
||||
{
|
||||
//TODO: Use the new e.EventState to track state between Moving/Moved events!
|
||||
Moving = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -71,8 +71,8 @@ namespace Umbraco.Web.Scheduling
|
||||
var results = new HealthCheckResults(checks);
|
||||
results.LogResults();
|
||||
|
||||
// Send using registered notification methods
|
||||
foreach (var notificationMethod in _notifications)
|
||||
// Send using registered notification methods that are enabled
|
||||
foreach (var notificationMethod in _notifications.Where(x => x.Enabled))
|
||||
await notificationMethod.SendAsync(results, token);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,9 @@ namespace Umbraco.Web.Scheduling
|
||||
|
||||
public override async Task<bool> PerformRunAsync(CancellationToken token)
|
||||
{
|
||||
if (Suspendable.ScheduledPublishing.CanRun == false)
|
||||
return true; // repeat, later
|
||||
|
||||
switch (_runtime.ServerRole)
|
||||
{
|
||||
case ServerRole.Slave:
|
||||
|
||||
@@ -120,6 +120,9 @@ namespace Umbraco.Web.Search
|
||||
|
||||
static void MemberCacheRefresherUpdated(MemberCacheRefresher sender, CacheRefresherEventArgs args)
|
||||
{
|
||||
if (Suspendable.ExamineEvents.CanIndex == false)
|
||||
return;
|
||||
|
||||
switch (args.MessageType)
|
||||
{
|
||||
case MessageType.RefreshById:
|
||||
@@ -162,6 +165,9 @@ namespace Umbraco.Web.Search
|
||||
|
||||
static void MediaCacheRefresherUpdated(MediaCacheRefresher sender, CacheRefresherEventArgs args)
|
||||
{
|
||||
if (Suspendable.ExamineEvents.CanIndex == false)
|
||||
return;
|
||||
|
||||
if (args.MessageType != MessageType.RefreshByPayload)
|
||||
throw new NotSupportedException();
|
||||
|
||||
@@ -208,6 +214,9 @@ namespace Umbraco.Web.Search
|
||||
|
||||
static void ContentCacheRefresherUpdated(ContentCacheRefresher sender, CacheRefresherEventArgs args)
|
||||
{
|
||||
if (Suspendable.ExamineEvents.CanIndex == false)
|
||||
return;
|
||||
|
||||
if (args.MessageType != MessageType.RefreshByPayload)
|
||||
throw new NotSupportedException();
|
||||
|
||||
|
||||
@@ -45,7 +45,8 @@ namespace Umbraco.Web.Security.Identity
|
||||
services.MemberTypeService,
|
||||
services.EntityService,
|
||||
services.ExternalLoginService,
|
||||
userMembershipProvider));
|
||||
userMembershipProvider,
|
||||
UmbracoConfig.For.UmbracoSettings().Content));
|
||||
|
||||
app.SetBackOfficeUserManagerType<BackOfficeUserManager, BackOfficeIdentityUser>();
|
||||
|
||||
@@ -74,7 +75,8 @@ namespace Umbraco.Web.Security.Identity
|
||||
(options, owinContext) => BackOfficeUserManager.Create(
|
||||
options,
|
||||
customUserStore,
|
||||
userMembershipProvider));
|
||||
userMembershipProvider,
|
||||
UmbracoConfig.For.UmbracoSettings().Content));
|
||||
|
||||
app.SetBackOfficeUserManagerType<BackOfficeUserManager, BackOfficeIdentityUser>();
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Web;
|
||||
using System.Web.Security;
|
||||
using LightInject;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
@@ -17,6 +19,8 @@ using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Editors;
|
||||
using Umbraco.Web.Security.Providers;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Routing;
|
||||
using MPE = global::Umbraco.Core.Security.MembershipProviderExtensions;
|
||||
|
||||
namespace Umbraco.Web.Security
|
||||
@@ -30,26 +34,48 @@ namespace Umbraco.Web.Security
|
||||
private readonly RoleProvider _roleProvider;
|
||||
private readonly HttpContextBase _httpContext;
|
||||
private readonly IPublishedMemberCache _memberCache;
|
||||
private readonly UmbracoContext _umbracoContext;
|
||||
|
||||
// fixme - inject!
|
||||
private readonly IMemberService _memberService = Current.Services.MemberService;
|
||||
private readonly IMemberTypeService _memberTypeService = Current.Services.MemberTypeService;
|
||||
private readonly IUserService _userService = Current.Services.UserService;
|
||||
private readonly CacheHelper _applicationCache = Current.ApplicationCache;
|
||||
private readonly ILogger _logger = Current.Logger;
|
||||
[Inject]
|
||||
private IMemberService MemberService { get; set; }
|
||||
|
||||
[Inject]
|
||||
private IMemberTypeService MemberTypeService { get; set; }
|
||||
|
||||
[Inject]
|
||||
private IUserService UserService { get; set; }
|
||||
|
||||
[Inject]
|
||||
private IPublicAccessService PublicAccessService { get; set; }
|
||||
|
||||
[Inject]
|
||||
private CacheHelper ApplicationCache { get; set; }
|
||||
|
||||
[Inject]
|
||||
private ILogger Logger { get; set; }
|
||||
|
||||
[Inject]
|
||||
private FacadeRouter Router { get; set; }
|
||||
|
||||
#region Constructors
|
||||
|
||||
// used here and there for IMember operations (not front-end stuff, no need for _memberCache)
|
||||
|
||||
[Obsolete("Use the constructor specifying an UmbracoContext")]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public MembershipHelper(HttpContextBase httpContext)
|
||||
{
|
||||
if (httpContext == null) throw new ArgumentNullException(nameof(httpContext));
|
||||
_httpContext = httpContext;
|
||||
_membershipProvider = MPE.GetMembersMembershipProvider();
|
||||
_roleProvider = Roles.Enabled ? Roles.Provider : new MembersRoleProvider(_memberService);
|
||||
_roleProvider = Roles.Enabled ? Roles.Provider : new MembersRoleProvider(MemberService);
|
||||
|
||||
// _memberCache remains null - not supposed to use it
|
||||
// alternatively we'd need to get if from the 'current' UmbracoContext?
|
||||
|
||||
// helpers are *not* instanciated by the container so we have to
|
||||
// get our dependencies injected manually, through properties.
|
||||
Current.Container.InjectProperties(this);
|
||||
}
|
||||
|
||||
// used everywhere
|
||||
@@ -63,10 +89,16 @@ namespace Umbraco.Web.Security
|
||||
if (umbracoContext == null) throw new ArgumentNullException(nameof(umbracoContext));
|
||||
if (membershipProvider == null) throw new ArgumentNullException(nameof(membershipProvider));
|
||||
if (roleProvider == null) throw new ArgumentNullException(nameof(roleProvider));
|
||||
|
||||
_httpContext = umbracoContext.HttpContext;
|
||||
_umbracoContext = umbracoContext;
|
||||
_membershipProvider = membershipProvider;
|
||||
_roleProvider = roleProvider;
|
||||
_memberCache = umbracoContext.Facade.MemberCache;
|
||||
|
||||
// helpers are *not* instanciated by the container so we have to
|
||||
// get our dependencies injected manually, through properties.
|
||||
Current.Container.InjectProperties(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -81,6 +113,54 @@ namespace Umbraco.Web.Security
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a document object is protected by the "Protect Pages" functionality in umbraco
|
||||
/// </summary>
|
||||
/// <param name="path">The full path of the document object to check</param>
|
||||
/// <returns>True if the document object is protected</returns>
|
||||
public virtual bool IsProtected(string path)
|
||||
{
|
||||
//this is a cached call
|
||||
return PublicAccessService.IsProtected(path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the current user has access to a document
|
||||
/// </summary>
|
||||
/// <param name="path">The full path of the document object to check</param>
|
||||
/// <returns>True if the current user has access or if the current document isn't protected</returns>
|
||||
public virtual bool MemberHasAccess(string path)
|
||||
{
|
||||
//cache this in the request cache
|
||||
return ApplicationCache.RequestCache.GetCacheItem<bool>(string.Format("{0}.{1}-{2}", typeof(MembershipHelper), "MemberHasAccess", path), () =>
|
||||
{
|
||||
if (IsProtected(path))
|
||||
{
|
||||
return IsLoggedIn() && HasAccess(path, Roles.Provider);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This will check if the member has access to this path
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
/// <param name="roleProvider"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// This is essentially the same as the PublicAccessServiceExtensions.HasAccess however this will use the PCR cache
|
||||
/// of the already looked up roles for the member so this doesn't need to happen more than once.
|
||||
/// This does a safety check in case of things like unit tests where there is no PCR and if that is the case it will use
|
||||
/// lookup the roles directly.
|
||||
/// </remarks>
|
||||
private bool HasAccess(string path, RoleProvider roleProvider)
|
||||
{
|
||||
return _umbracoContext.PublishedContentRequest == null
|
||||
? PublicAccessService.HasAccess(path, CurrentUserName, roleProvider.GetRolesForUser)
|
||||
: PublicAccessService.HasAccess(path, CurrentUserName, Router.GetRolesForLogin);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the current membership provider is the Umbraco built-in one.
|
||||
/// </summary>
|
||||
@@ -149,7 +229,7 @@ namespace Umbraco.Web.Security
|
||||
}
|
||||
}
|
||||
|
||||
_memberService.Save(member);
|
||||
MemberService.Save(member);
|
||||
|
||||
//reset the FormsAuth cookie since the username might have changed
|
||||
FormsAuthentication.SetAuthCookie(member.Username, true);
|
||||
@@ -184,7 +264,7 @@ namespace Umbraco.Web.Security
|
||||
|
||||
if (status != MembershipCreateStatus.Success) return null;
|
||||
|
||||
var member = _memberService.GetByUsername(membershipUser.UserName);
|
||||
var member = MemberService.GetByUsername(membershipUser.UserName);
|
||||
member.Name = model.Name;
|
||||
|
||||
if (model.MemberProperties != null)
|
||||
@@ -196,7 +276,7 @@ namespace Umbraco.Web.Security
|
||||
}
|
||||
}
|
||||
|
||||
_memberService.Save(member);
|
||||
MemberService.Save(member);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -399,7 +479,7 @@ namespace Umbraco.Web.Security
|
||||
if (provider.IsUmbracoMembershipProvider())
|
||||
{
|
||||
memberTypeAlias = memberTypeAlias ?? Constants.Conventions.MemberTypes.DefaultAlias;
|
||||
var memberType = _memberTypeService.Get(memberTypeAlias);
|
||||
var memberType = MemberTypeService.Get(memberTypeAlias);
|
||||
if (memberType == null)
|
||||
throw new InvalidOperationException("Could not find a member type with alias " + memberTypeAlias);
|
||||
|
||||
@@ -644,7 +724,7 @@ namespace Umbraco.Web.Security
|
||||
/// <returns></returns>
|
||||
public virtual Attempt<PasswordChangedModel> ChangePassword(string username, ChangingPasswordModel passwordModel, MembershipProvider membershipProvider)
|
||||
{
|
||||
var passwordChanger = new PasswordChanger(_logger, _userService);
|
||||
var passwordChanger = new PasswordChanger(Logger, UserService);
|
||||
return passwordChanger.ChangePasswordWithMembershipProvider(username, passwordModel, membershipProvider);
|
||||
}
|
||||
|
||||
@@ -709,7 +789,7 @@ namespace Umbraco.Web.Security
|
||||
/// <returns></returns>
|
||||
private IMember GetCurrentPersistedMember()
|
||||
{
|
||||
return _applicationCache.RequestCache.GetCacheItem<IMember>(
|
||||
return ApplicationCache.RequestCache.GetCacheItem<IMember>(
|
||||
GetCacheKey("GetCurrentPersistedMember"), () =>
|
||||
{
|
||||
var provider = _membershipProvider;
|
||||
@@ -719,7 +799,7 @@ namespace Umbraco.Web.Security
|
||||
throw new NotSupportedException("An IMember model can only be retreived when using the built-in Umbraco membership providers");
|
||||
}
|
||||
var username = provider.GetCurrentUserName();
|
||||
var member = _memberService.GetByUsername(username);
|
||||
var member = MemberService.GetByUsername(username);
|
||||
return member;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -92,6 +92,13 @@ namespace Umbraco.Web.Security.Providers
|
||||
}
|
||||
}
|
||||
|
||||
protected override Attempt<string> GetRawPassword(string username)
|
||||
{
|
||||
var found = MemberService.GetByUsername(username);
|
||||
if (found == null) return Attempt<string>.Fail();
|
||||
return Attempt.Succeed(found.RawPasswordValue);
|
||||
}
|
||||
|
||||
public override string DefaultMemberTypeAlias
|
||||
{
|
||||
get
|
||||
|
||||
@@ -37,10 +37,10 @@ namespace Umbraco.Web.Security.Providers
|
||||
|
||||
protected abstract MembershipUser ConvertToMembershipUser(TEntity entity);
|
||||
|
||||
private bool _allowManuallyChangingPassword = true;
|
||||
private bool _allowManuallyChangingPassword = false;
|
||||
|
||||
/// <summary>
|
||||
/// For backwards compatibility, this provider supports this option by default it is true
|
||||
/// For backwards compatibility, this provider supports this option by default it is false
|
||||
/// </summary>
|
||||
public override bool AllowManuallyChangingPassword
|
||||
{
|
||||
@@ -66,7 +66,7 @@ namespace Umbraco.Web.Security.Providers
|
||||
// Initialize base provider class
|
||||
base.Initialize(name, config);
|
||||
|
||||
_allowManuallyChangingPassword = config.GetValue("allowManuallyChangingPassword", true);
|
||||
_allowManuallyChangingPassword = config.GetValue("allowManuallyChangingPassword", false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -45,10 +45,36 @@ namespace Umbraco.Web.Security.Providers
|
||||
return entity.AsConcreteMembershipUser(Name, true);
|
||||
}
|
||||
|
||||
private bool _allowManuallyChangingPassword = false;
|
||||
private bool _enablePasswordReset = false;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the membership provider is configured to allow users to reset their passwords.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
/// <returns>true if the membership provider supports password reset; otherwise, false. The default is FALSE for users.</returns>
|
||||
public override bool EnablePasswordReset
|
||||
{
|
||||
get { return _enablePasswordReset; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For backwards compatibility, this provider supports this option by default it is FALSE for users
|
||||
/// </summary>
|
||||
public override bool AllowManuallyChangingPassword
|
||||
{
|
||||
get { return _allowManuallyChangingPassword; }
|
||||
}
|
||||
|
||||
public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
|
||||
{
|
||||
base.Initialize(name, config);
|
||||
|
||||
if (config == null) { throw new ArgumentNullException("config"); }
|
||||
|
||||
_allowManuallyChangingPassword = config.GetValue("allowManuallyChangingPassword", false);
|
||||
_enablePasswordReset = config.GetValue("enablePasswordReset", false);
|
||||
|
||||
// test for membertype (if not specified, choose the first member type available)
|
||||
// We'll support both names for legacy reasons: defaultUserTypeAlias & defaultUserGroupAlias
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Security;
|
||||
using Microsoft.AspNet.Identity.Owin;
|
||||
using Microsoft.Owin;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Identity;
|
||||
using Umbraco.Web.Composing;
|
||||
using GlobalSettings = Umbraco.Core.Configuration.GlobalSettings;
|
||||
@@ -48,11 +49,11 @@ namespace Umbraco.Web.Security
|
||||
IEnumerable<string> allowGroups = null,
|
||||
IEnumerable<int> allowMembers = null)
|
||||
{
|
||||
if (HttpContext.Current == null || Current.RuntimeState.Level != RuntimeLevel.Run)
|
||||
if (Current.UmbracoContext == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var helper = new MembershipHelper(new HttpContextWrapper(HttpContext.Current));
|
||||
var helper = new MembershipHelper(Current.UmbracoContext);
|
||||
return helper.IsMemberAuthorized(allowAll, allowTypes, allowGroups, allowMembers);
|
||||
}
|
||||
|
||||
@@ -184,8 +185,8 @@ namespace Umbraco.Web.Security
|
||||
{
|
||||
var membershipProvider = Core.Security.MembershipProviderExtensions.GetUsersMembershipProvider();
|
||||
return membershipProvider != null ? membershipProvider.GetUser(username, setOnline) : null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the current user to see if they have access to the specified app
|
||||
/// </summary>
|
||||
@@ -306,29 +307,28 @@ namespace Umbraco.Web.Security
|
||||
/// <summary>
|
||||
/// Checks if the specified user as access to the app
|
||||
/// </summary>
|
||||
/// <param name="app"></param>
|
||||
/// <param name="section"></param>
|
||||
/// <param name="user"></param>
|
||||
/// <returns></returns>
|
||||
internal virtual bool UserHasAppAccess(string app, IUser user)
|
||||
internal virtual bool UserHasSectionAccess(string section, IUser user)
|
||||
{
|
||||
var apps = user.AllowedSections;
|
||||
return apps.Any(uApp => uApp.InvariantEquals(app));
|
||||
return user.HasSectionAccess(section);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the specified user by username as access to the app
|
||||
/// </summary>
|
||||
/// <param name="app"></param>
|
||||
/// <param name="section"></param>
|
||||
/// <param name="username"></param>
|
||||
/// <returns></returns>
|
||||
internal bool UserHasAppAccess(string app, string username)
|
||||
internal bool UserHasSectionAccess(string section, string username)
|
||||
{
|
||||
var user = _userService.GetByUsername(username);
|
||||
if (user == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return UserHasAppAccess(app, user);
|
||||
return user.HasSectionAccess(section);
|
||||
}
|
||||
|
||||
[Obsolete("Returns the current user's unique umbraco sesion id - this cannot be set and isn't intended to be used in your code")]
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
using System;
|
||||
using Examine;
|
||||
using Examine.Providers;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Web.Cache;
|
||||
|
||||
namespace Umbraco.Web
|
||||
{
|
||||
internal static class Suspendable
|
||||
{
|
||||
public static class PageCacheRefresher
|
||||
{
|
||||
private static bool _tried, _suspended;
|
||||
|
||||
public static bool CanRefreshDocumentCacheFromDatabase
|
||||
{
|
||||
get
|
||||
{
|
||||
// trying a full refresh
|
||||
if (_suspended == false) return true;
|
||||
_tried = true; // remember we tried
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// trying a partial update
|
||||
// ok if not suspended, or if we haven't done a full already
|
||||
public static bool CanUpdateDocumentCache => _suspended == false || _tried == false;
|
||||
|
||||
public static void SuspendDocumentCache()
|
||||
{
|
||||
Current.ProfilingLogger.Logger.Info(typeof (PageCacheRefresher), "Suspend document cache.");
|
||||
_suspended = true;
|
||||
}
|
||||
|
||||
public static void ResumeDocumentCache()
|
||||
{
|
||||
_suspended = false;
|
||||
|
||||
Current.ProfilingLogger.Logger.Info(typeof (PageCacheRefresher), $"Resume document cache (reload:{(_tried ? "true" : "false")}).");
|
||||
|
||||
if (_tried == false) return;
|
||||
_tried = false;
|
||||
|
||||
var pageRefresher = Current.CacheRefreshers[ContentCacheRefresher.UniqueId];
|
||||
pageRefresher.RefreshAll();
|
||||
}
|
||||
}
|
||||
|
||||
public static class ExamineEvents
|
||||
{
|
||||
private static bool _tried, _suspended;
|
||||
|
||||
public static bool CanIndex
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_suspended == false) return true;
|
||||
_tried = true; // remember we tried
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void SuspendIndexers()
|
||||
{
|
||||
Current.ProfilingLogger.Logger.Info(typeof (ExamineEvents), "Suspend indexers.");
|
||||
_suspended = true;
|
||||
}
|
||||
|
||||
public static void ResumeIndexers()
|
||||
{
|
||||
_suspended = false;
|
||||
|
||||
Current.ProfilingLogger.Logger.Info(typeof (ExamineEvents), $"Resume indexers (rebuild:{(_tried ? "true" : "false")}).");
|
||||
|
||||
if (_tried == false) return;
|
||||
_tried = false;
|
||||
|
||||
// fixme - could we fork this on a background thread?
|
||||
foreach (BaseIndexProvider indexer in ExamineManager.Instance.IndexProviderCollection)
|
||||
{
|
||||
indexer.RebuildIndex();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class ScheduledPublishing
|
||||
{
|
||||
private static bool _suspended;
|
||||
|
||||
public static bool CanRun => _suspended == false;
|
||||
|
||||
public static void Suspend()
|
||||
{
|
||||
Current.ProfilingLogger.Logger.Info(typeof (ScheduledPublishing), "Suspend scheduled publishing.");
|
||||
_suspended = true;
|
||||
}
|
||||
|
||||
public static void Resume()
|
||||
{
|
||||
Current.ProfilingLogger.Logger.Info(typeof (ScheduledPublishing), "Resume scheduled publishing.");
|
||||
_suspended = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ using System.Threading.Tasks;
|
||||
using System.Web.Http;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Composing;
|
||||
using Umbraco.Web.Models.Trees;
|
||||
using Umbraco.Web.Mvc;
|
||||
using Umbraco.Web.WebApi;
|
||||
@@ -36,7 +35,7 @@ namespace Umbraco.Web.Trees
|
||||
var rootId = Constants.System.Root.ToString(CultureInfo.InvariantCulture);
|
||||
|
||||
//find all tree definitions that have the current application alias
|
||||
var appTrees = Current.Services.ApplicationTreeService.GetApplicationTrees(application, onlyInitialized).ToArray();
|
||||
var appTrees = Services.ApplicationTreeService.GetApplicationTrees(application, onlyInitialized).ToArray();
|
||||
|
||||
if (appTrees.Length == 1 || string.IsNullOrEmpty(tree) == false )
|
||||
{
|
||||
@@ -122,8 +121,8 @@ namespace Umbraco.Web.Trees
|
||||
|
||||
//if the root node has a route path, we cannot create a single root section because by specifying the route path this would
|
||||
//override the dashboard route and that means there can be no dashboard for that section which is a breaking change.
|
||||
if (string.IsNullOrWhiteSpace(rootNode.Result.RoutePath) == false
|
||||
&& rootNode.Result.RoutePath != "#"
|
||||
if (string.IsNullOrWhiteSpace(rootNode.Result.RoutePath) == false
|
||||
&& rootNode.Result.RoutePath != "#"
|
||||
&& rootNode.Result.RoutePath != application)
|
||||
{
|
||||
//null indicates this cannot be converted
|
||||
|
||||
@@ -90,8 +90,9 @@ namespace Umbraco.Web.Trees
|
||||
var menu = new MenuItemCollection();
|
||||
|
||||
if (id == Constants.System.Root.ToInvariantString())
|
||||
{
|
||||
// root actions
|
||||
{
|
||||
// root actions
|
||||
menu.Items.Add<ActionNew>(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias)));
|
||||
menu.Items.Add<RefreshNode, ActionRefresh>(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), true);
|
||||
return menu;
|
||||
}
|
||||
|
||||
@@ -35,20 +35,7 @@ namespace Umbraco.Web.Trees
|
||||
public class ContentTreeController : ContentTreeControllerBase, ISearchableTree
|
||||
{
|
||||
private readonly UmbracoTreeSearcher _treeSearcher = new UmbracoTreeSearcher();
|
||||
|
||||
protected override TreeNode CreateRootNode(FormDataCollection queryStrings)
|
||||
{
|
||||
var node = base.CreateRootNode(queryStrings);
|
||||
|
||||
// if the user's start node is not default, then ensure the root doesn't have a menu
|
||||
if (UserStartNodes.Contains(Constants.System.Root) == false)
|
||||
{
|
||||
node.MenuUrl = "";
|
||||
}
|
||||
node.Name = Services.TextService.Localize("sections/"+ Constants.Trees.Content);
|
||||
return node;
|
||||
}
|
||||
|
||||
|
||||
protected override int RecycleBinId => Constants.System.RecycleBinContent;
|
||||
|
||||
protected override bool RecycleBinSmells => Services.ContentService.RecycleBinSmells();
|
||||
@@ -112,9 +99,12 @@ namespace Umbraco.Web.Trees
|
||||
{
|
||||
var menu = new MenuItemCollection();
|
||||
|
||||
// if the user's start node is not the root then ensure the root menu is empty/doesn't exist
|
||||
// if the user's start node is not the root then the only menu item to display is refresh
|
||||
if (UserStartNodes.Contains(Constants.System.Root) == false)
|
||||
{
|
||||
menu.Items.Add<RefreshNode, ActionRefresh>(
|
||||
Services.TextService.Localize(string.Concat("actions/", ActionRefresh.Instance.Alias)),
|
||||
true);
|
||||
return menu;
|
||||
}
|
||||
|
||||
@@ -158,6 +148,16 @@ namespace Umbraco.Web.Trees
|
||||
throw new HttpResponseException(HttpStatusCode.NotFound);
|
||||
}
|
||||
|
||||
//if the user has no path access for this node, all they can do is refresh
|
||||
if (Security.CurrentUser.HasPathAccess(item, Services.EntityService, RecycleBinId) == false)
|
||||
{
|
||||
var menu = new MenuItemCollection();
|
||||
menu.Items.Add<RefreshNode, ActionRefresh>(
|
||||
Services.TextService.Localize(string.Concat("actions/", ActionRefresh.Instance.Alias)),
|
||||
true);
|
||||
return menu;
|
||||
}
|
||||
|
||||
var nodeMenu = GetAllNodeMenuItems(item);
|
||||
var allowedMenuItems = GetAllowedUserMenuItemsForNode(item);
|
||||
|
||||
@@ -193,14 +193,7 @@ namespace Umbraco.Web.Trees
|
||||
protected override bool HasPathAccess(string id, FormDataCollection queryStrings)
|
||||
{
|
||||
var entity = GetEntityFromId(id);
|
||||
if (entity == null)
|
||||
return false;
|
||||
|
||||
var content = Services.ContentService.GetById(entity.Id);
|
||||
if (content == null)
|
||||
return false;
|
||||
|
||||
return Security.CurrentUser.HasPathAccess(content, Services.EntityService);
|
||||
return HasPathAccess(entity, queryStrings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
@@ -52,8 +54,48 @@ namespace Umbraco.Web.Trees
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Ensure the noAccess metadata is applied for the root node if in dialog mode and the user doesn't have path access to it
|
||||
/// </summary>
|
||||
/// <param name="queryStrings"></param>
|
||||
/// <returns></returns>
|
||||
protected override TreeNode CreateRootNode(FormDataCollection queryStrings)
|
||||
{
|
||||
var node = base.CreateRootNode(queryStrings);
|
||||
|
||||
if (IsDialog(queryStrings) && UserStartNodes.Contains(Constants.System.Root) == false)
|
||||
{
|
||||
node.AdditionalData["noAccess"] = true;
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
protected abstract TreeNode GetSingleTreeNode(IUmbracoEntity e, string parentId, FormDataCollection queryStrings);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="TreeNode"/> for the <see cref="IUmbracoEntity"/> and
|
||||
/// attaches some meta data to the node if the user doesn't have start node access to it when in dialog mode
|
||||
/// </summary>
|
||||
/// <param name="e"></param>
|
||||
/// <param name="parentId"></param>
|
||||
/// <param name="queryStrings"></param>
|
||||
/// <returns></returns>
|
||||
internal TreeNode GetSingleTreeNodeWithAccessCheck(IUmbracoEntity e, string parentId, FormDataCollection queryStrings)
|
||||
{
|
||||
bool hasPathAccess;
|
||||
var entityIsAncestorOfStartNodes = Security.CurrentUser.IsInBranchOfStartNode(e, Services.EntityService, RecycleBinId, out hasPathAccess);
|
||||
if (entityIsAncestorOfStartNodes == false)
|
||||
return null;
|
||||
|
||||
var treeNode = GetSingleTreeNode(e, parentId, queryStrings);
|
||||
if (hasPathAccess == false)
|
||||
{
|
||||
treeNode.AdditionalData["noAccess"] = true;
|
||||
}
|
||||
return treeNode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the
|
||||
/// </summary>
|
||||
@@ -68,13 +110,7 @@ namespace Umbraco.Web.Trees
|
||||
/// Returns the user's start node for this tree
|
||||
/// </summary>
|
||||
protected abstract int[] UserStartNodes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the tree nodes for the given id
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="queryStrings"></param>
|
||||
/// <returns></returns>
|
||||
|
||||
protected virtual TreeNodeCollection PerformGetTreeNodes(string id, FormDataCollection queryStrings)
|
||||
{
|
||||
var nodes = new TreeNodeCollection();
|
||||
@@ -82,9 +118,10 @@ namespace Umbraco.Web.Trees
|
||||
var altStartId = string.Empty;
|
||||
if (queryStrings.HasKey(TreeQueryStringParameters.StartNodeId))
|
||||
altStartId = queryStrings.GetValue<string>(TreeQueryStringParameters.StartNodeId);
|
||||
var rootIdString = Constants.System.Root.ToString(CultureInfo.InvariantCulture);
|
||||
|
||||
//check if a request has been made to render from a specific start node
|
||||
if (string.IsNullOrEmpty(altStartId) == false && altStartId != "undefined" && altStartId != Constants.System.Root.ToString(CultureInfo.InvariantCulture))
|
||||
if (string.IsNullOrEmpty(altStartId) == false && altStartId != "undefined" && altStartId != rootIdString)
|
||||
{
|
||||
id = altStartId;
|
||||
|
||||
@@ -110,10 +147,42 @@ namespace Umbraco.Web.Trees
|
||||
}
|
||||
}
|
||||
|
||||
var entities = GetChildEntities(id);
|
||||
nodes.AddRange(entities.Select(entity => GetSingleTreeNode(entity, id, queryStrings)).Where(node => node != null));
|
||||
var entities = GetChildEntities(id).ToList();
|
||||
|
||||
//If we are looking up the root and there is more than one node ...
|
||||
//then we want to lookup those nodes' 'site' nodes and render those so that the
|
||||
//user has some context of where they are in the tree, this is generally for pickers in a dialog.
|
||||
//for any node they don't have access too, we need to add some metadata
|
||||
if (id == rootIdString && entities.Count > 1)
|
||||
{
|
||||
var siteNodeIds = new List<int>();
|
||||
//put into array since we might modify the list
|
||||
foreach (var e in entities.ToArray())
|
||||
{
|
||||
var pathParts = e.Path.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (pathParts.Length < 2)
|
||||
continue; // this should never happen but better to check
|
||||
|
||||
int siteNodeId;
|
||||
if (int.TryParse(pathParts[1], out siteNodeId) == false)
|
||||
continue;
|
||||
|
||||
//we'll look up this
|
||||
siteNodeIds.Add(siteNodeId);
|
||||
}
|
||||
var siteNodes = Services.EntityService.GetAll(UmbracoObjectType, siteNodeIds.ToArray())
|
||||
.DistinctBy(e => e.Id)
|
||||
.ToArray();
|
||||
|
||||
//add site nodes
|
||||
nodes.AddRange(siteNodes.Select(e => GetSingleTreeNodeWithAccessCheck(e, id, queryStrings)).Where(node => node != null));
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
nodes.AddRange(entities.Select(e => GetSingleTreeNodeWithAccessCheck(e, id, queryStrings)).Where(node => node != null));
|
||||
return nodes;
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract MenuItemCollection PerformGetMenuForNode(string id, FormDataCollection queryStrings);
|
||||
|
||||
@@ -150,8 +219,21 @@ namespace Umbraco.Web.Trees
|
||||
/// <param name="id"></param>
|
||||
/// <param name="queryStrings"></param>
|
||||
/// <returns></returns>
|
||||
//we should remove this in v8, it's now here for backwards compat only
|
||||
protected abstract bool HasPathAccess(string id, FormDataCollection queryStrings);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true or false if the current user has access to the node based on the user's allowed start node (path) access
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
/// <param name="queryStrings"></param>
|
||||
/// <returns></returns>
|
||||
protected bool HasPathAccess(IUmbracoEntity entity, FormDataCollection queryStrings)
|
||||
{
|
||||
if (entity == null) return false;
|
||||
return Security.CurrentUser.HasPathAccess(entity, Services.EntityService, RecycleBinId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures the recycle bin is appended when required (i.e. user has access to the root and it's not in dialog mode)
|
||||
/// </summary>
|
||||
@@ -210,7 +292,7 @@ namespace Umbraco.Web.Trees
|
||||
/// </remarks>
|
||||
private TreeNodeCollection GetTreeNodesInternal(string id, FormDataCollection queryStrings)
|
||||
{
|
||||
IUmbracoEntity current = GetEntityFromId(id);
|
||||
var current = GetEntityFromId(id);
|
||||
|
||||
//before we get the children we need to see if this is a container node
|
||||
|
||||
@@ -239,6 +321,7 @@ namespace Umbraco.Web.Trees
|
||||
menu.Items.Add<ActionRefresh>(Services.TextService.Localize("actions", ActionRefresh.Instance.Alias), true);
|
||||
return menu;
|
||||
}
|
||||
|
||||
return PerformGetMenuForNode(id, queryStrings);
|
||||
}
|
||||
|
||||
@@ -268,10 +351,11 @@ namespace Umbraco.Web.Trees
|
||||
internal IEnumerable<MenuItem> GetAllowedUserMenuItemsForNode(IUmbracoEntity dd)
|
||||
{
|
||||
var permission = Services.UserService.GetPermissions(Security.CurrentUser, dd.Path);
|
||||
var actions = global::Umbraco.Web._Legacy.Actions.Action.FromEntityPermission(permission);
|
||||
|
||||
var actions = global::Umbraco.Web._Legacy.Actions.Action.FromEntityPermission(permission)
|
||||
.ToList();
|
||||
|
||||
// A user is allowed to delete their own stuff
|
||||
if (dd.CreatorId == Security.CurrentUser.Id && actions.Contains(ActionDelete.Instance) == false)
|
||||
if (dd.CreatorId == Security.GetUserId() && actions.Contains(ActionDelete.Instance) == false)
|
||||
actions.Add(ActionDelete.Instance);
|
||||
|
||||
return actions.Select(x => new MenuItem(x));
|
||||
@@ -290,34 +374,71 @@ namespace Umbraco.Web.Trees
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// this will parse the string into either a GUID or INT
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
internal Tuple<Guid?, int?> GetIdentifierFromString(string id)
|
||||
{
|
||||
Guid idGuid;
|
||||
int idInt;
|
||||
Udi idUdi;
|
||||
|
||||
if (Guid.TryParse(id, out idGuid))
|
||||
{
|
||||
return new Tuple<Guid?, int?>(idGuid, null);
|
||||
}
|
||||
if (int.TryParse(id, out idInt))
|
||||
{
|
||||
return new Tuple<Guid?, int?>(null, idInt);
|
||||
}
|
||||
if (Udi.TryParse(id, out idUdi))
|
||||
{
|
||||
var guidUdi = idUdi as GuidUdi;
|
||||
if (guidUdi != null)
|
||||
return new Tuple<Guid?, int?>(guidUdi.Guid, null);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get an entity via an id that can be either an integer, Guid or UDI
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// This object has it's own contextual cache for these lookups
|
||||
/// </remarks>
|
||||
internal IUmbracoEntity GetEntityFromId(string id)
|
||||
{
|
||||
IUmbracoEntity entity;
|
||||
return _entityCache.GetOrAdd(id, s =>
|
||||
{
|
||||
IUmbracoEntity entity;
|
||||
|
||||
if (Guid.TryParse(id, out Guid idGuid))
|
||||
{
|
||||
entity = Services.EntityService.GetByKey(idGuid, UmbracoObjectType);
|
||||
}
|
||||
else if (int.TryParse(id, out int idInt))
|
||||
{
|
||||
entity = Services.EntityService.Get(idInt, UmbracoObjectType);
|
||||
}
|
||||
else if (Udi.TryParse(id, out Udi idUdi))
|
||||
{
|
||||
var guidUdi = idUdi as GuidUdi;
|
||||
entity = guidUdi != null ? Services.EntityService.GetByKey(guidUdi.Guid, UmbracoObjectType) : null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (Guid.TryParse(s, out Guid idGuid))
|
||||
{
|
||||
entity = Services.EntityService.GetByKey(idGuid, UmbracoObjectType);
|
||||
}
|
||||
else if (int.TryParse(s, out int idInt))
|
||||
{
|
||||
entity = Services.EntityService.Get(idInt, UmbracoObjectType);
|
||||
}
|
||||
else if (Udi.TryParse(s, out Udi idUdi))
|
||||
{
|
||||
var guidUdi = idUdi as GuidUdi;
|
||||
entity = guidUdi != null ? Services.EntityService.GetByKey(guidUdi.Guid, UmbracoObjectType) : null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return entity;
|
||||
return entity;
|
||||
});
|
||||
}
|
||||
|
||||
private readonly ConcurrentDictionary<string, IUmbracoEntity> _entityCache = new ConcurrentDictionary<string, IUmbracoEntity>();
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user