Skip to content

Commit 62ac543

Browse files
Merge branch 'v8/8.8' into v8/8.9
2 parents 7bc220b + 5d8eed3 commit 62ac543

File tree

3 files changed

+128
-5
lines changed

3 files changed

+128
-5
lines changed
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
using System.Collections.Specialized;
2+
using System.Web.Security;
3+
using Moq;
4+
using NUnit.Framework;
5+
using Umbraco.Core;
6+
using Umbraco.Core.Cache;
7+
using Umbraco.Core.Composing;
8+
using Umbraco.Core.Logging;
9+
using Umbraco.Core.Models;
10+
using Umbraco.Core.Services;
11+
using Umbraco.Core.Sync;
12+
using Umbraco.Tests.Integration;
13+
using Umbraco.Tests.TestHelpers;
14+
using Umbraco.Tests.TestHelpers.Entities;
15+
using Umbraco.Tests.Testing;
16+
using Umbraco.Web;
17+
using Umbraco.Web.Cache;
18+
using Umbraco.Web.Security.Providers;
19+
20+
namespace Umbraco.Tests.Membership
21+
{
22+
[TestFixture]
23+
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerFixture)]
24+
public class MembersMembershipProviderTests : TestWithDatabaseBase
25+
{
26+
private MembersMembershipProvider MembersMembershipProvider { get; set; }
27+
private IDistributedCacheBinder DistributedCacheBinder { get; set; }
28+
29+
public IMemberService MemberService => Current.Factory.GetInstance<IMemberService>();
30+
public IMemberTypeService MemberTypeService => Current.Factory.GetInstance<IMemberTypeService>();
31+
public ILogger Logger => Current.Factory.GetInstance<ILogger>();
32+
33+
public override void SetUp()
34+
{
35+
base.SetUp();
36+
37+
MembersMembershipProvider = new MembersMembershipProvider(MemberService, MemberTypeService);
38+
39+
MembersMembershipProvider.Initialize("test", new NameValueCollection { { "passwordFormat", MembershipPasswordFormat.Clear.ToString() } });
40+
41+
DistributedCacheBinder = new DistributedCacheBinder(new DistributedCache(), Mock.Of<IUmbracoContextFactory>(), Logger);
42+
DistributedCacheBinder.BindEvents(true);
43+
}
44+
45+
[TearDown]
46+
public void Teardown()
47+
{
48+
DistributedCacheBinder?.UnbindEvents();
49+
DistributedCacheBinder = null;
50+
}
51+
52+
protected override void Compose()
53+
{
54+
base.Compose();
55+
56+
// the cache refresher component needs to trigger to refresh caches
57+
// but then, it requires a lot of plumbing ;(
58+
// FIXME: and we cannot inject a DistributedCache yet
59+
// so doing all this mess
60+
Composition.RegisterUnique<IServerMessenger, ContentEventsTests.LocalServerMessenger>();
61+
Composition.RegisterUnique(f => Mock.Of<IServerRegistrar>());
62+
Composition.WithCollectionBuilder<CacheRefresherCollectionBuilder>()
63+
.Add(() => Composition.TypeLoader.GetCacheRefreshers());
64+
}
65+
66+
protected override AppCaches GetAppCaches()
67+
{
68+
// this is what's created core web runtime
69+
return new AppCaches(
70+
new DeepCloneAppCache(new ObjectCacheAppCache()),
71+
NoAppCache.Instance,
72+
new IsolatedCaches(type => new DeepCloneAppCache(new ObjectCacheAppCache())));
73+
}
74+
75+
/// <summary>
76+
/// MembersMembershipProvider.ValidateUser is expected to increase the number of failed attempts and also read that same number.
77+
/// </summary>
78+
/// <remarks>
79+
/// This test requires the caching to be enabled, as it already is correct in the database.
80+
/// Shows the error described here: https://github.com/umbraco/Umbraco-CMS/issues/9861
81+
/// </remarks>
82+
[Test]
83+
public void ValidateUser__must_lock_out_users_after_max_attempts_of_wrong_password()
84+
{
85+
// Arrange
86+
IMemberType memberType = MockedContentTypes.CreateSimpleMemberType();
87+
ServiceContext.MemberTypeService.Save(memberType);
88+
var member = MockedMember.CreateSimpleMember(memberType, "test", "[email protected]", "password","test");
89+
ServiceContext.MemberService.Save(member);
90+
91+
var wrongPassword = "wrongPassword";
92+
var numberOfFailedAttempts = MembersMembershipProvider.MaxInvalidPasswordAttempts+2;
93+
94+
// Act
95+
var memberBefore = ServiceContext.MemberService.GetById(member.Id);
96+
for (int i = 0; i < numberOfFailedAttempts; i++)
97+
{
98+
MembersMembershipProvider.ValidateUser(member.Username, wrongPassword);
99+
}
100+
var memberAfter = ServiceContext.MemberService.GetById(member.Id);
101+
102+
// Assert
103+
Assert.Multiple(() =>
104+
{
105+
Assert.AreEqual(0 , memberBefore.FailedPasswordAttempts, "Expected 0 failed password attempts before");
106+
Assert.IsFalse(memberBefore.IsLockedOut, "Expected the member NOT to be locked out before");
107+
108+
Assert.AreEqual(MembersMembershipProvider.MaxInvalidPasswordAttempts, memberAfter.FailedPasswordAttempts, "Expected exactly the max possible failed password attempts after");
109+
Assert.IsTrue(memberAfter.IsLockedOut, "Expected the member to be locked out after");
110+
});
111+
112+
}
113+
}
114+
}

src/Umbraco.Tests/Umbraco.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@
132132
<Compile Include="Logging\LogviewerTests.cs" />
133133
<Compile Include="Manifest\ManifestContentAppTests.cs" />
134134
<Compile Include="Mapping\MappingTests.cs" />
135+
<Compile Include="Membership\MembersMembershipProviderTests.cs" />
135136
<Compile Include="Migrations\MigrationPlanTests.cs" />
136137
<Compile Include="Migrations\MigrationTests.cs" />
137138
<Compile Include="ModelsBuilder\BuilderTests.cs" />

src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
using System.Configuration.Provider;
44
using System.Linq;
55
using System.Text;
6-
using System.Web;
76
using System.Web.Configuration;
87
using System.Web.Security;
98
using Umbraco.Core;
@@ -14,7 +13,6 @@
1413
using Umbraco.Core.Security;
1514
using Umbraco.Core.Services;
1615
using Umbraco.Web.Composing;
17-
using Umbraco.Core.Models.Identity;
1816

1917
namespace Umbraco.Web.Security.Providers
2018
{
@@ -357,7 +355,7 @@ public override MembershipUser GetUser(string username, bool userIsOnline)
357355
member.LastLoginDate = now;
358356
member.UpdateDate = now;
359357
}
360-
358+
361359
}
362360

363361
return ConvertToMembershipUser(member);
@@ -604,11 +602,21 @@ internal virtual ValidateUserResult PerformValidateUser(string username, string
604602
{
605603
// when upgrading from 7.2 to 7.3 trying to save will throw
606604
if (UmbracoVersion.Current >= new Version(7, 3, 0, 0))
607-
MemberService.Save(member, false);
605+
{
606+
// We need to raise event to ensure caches are updated. (e.g. the cache that uses username as key).
607+
// Even that this is a heavy operation, because indexes are updates, we consider that okay, as it
608+
// is still cheap to do a successful login.
609+
MemberService.Save(member, true);
610+
}
611+
608612
}
609613
else
610614
{
611-
// set the last login date without full save (fast, no locks)
615+
// set the last login date without full save (fast, no locks).
616+
// We do not update caches. This is to the best of our knowledge okay, as this info are only stored
617+
// because it is required by the membership provider.
618+
// If we one day have to revisit this, we will most likely need to spilt the events in membership info
619+
// saved and umbraco info saved. We don't want to update indexes etc when it is just membership info that is saved
612620
MemberService.SetLastLogin(member.Username, member.LastLoginDate);
613621
}
614622

0 commit comments

Comments
 (0)