Skip to content

Commit a33e4e5

Browse files
Load trialstartdate from ravenDB (#4446)
* Load trialstartdate from ravenDB * Check for trial start date in the future * Add tests * Add more tests * Fix the test * Rename test namespace --------- Co-authored-by: SzymonPobiega <[email protected]>
1 parent f35ae99 commit a33e4e5

File tree

10 files changed

+278
-8
lines changed

10 files changed

+278
-8
lines changed

src/ServiceControl.LicenseManagement/LicenseDetails.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
namespace ServiceControl.LicenseManagement
22
{
33
using System;
4+
using System.Collections.Generic;
45
using Particular.Licensing;
56

67
public class LicenseDetails
@@ -25,6 +26,28 @@ public class LicenseDetails
2526
public bool WarnUserUpgradeProtectionHasExpired { get; private init; }
2627
public string Status { get; private init; }
2728

29+
public static LicenseDetails TrialFromEndDate(DateOnly endDate)
30+
{
31+
return FromLicense(new License
32+
{
33+
LicenseType = "Trial",
34+
ExpirationDate = endDate.ToDateTime(TimeOnly.MinValue),
35+
IsExtendedTrial = false,
36+
ValidApplications = new List<string> { "All" }
37+
});
38+
}
39+
40+
public static LicenseDetails TrialExpired()
41+
{
42+
return FromLicense(new License
43+
{
44+
LicenseType = "Trial",
45+
ExpirationDate = DateTime.UtcNow.Date.AddDays(-2), //HasLicenseDateExpired uses a grace period of 1 day
46+
IsExtendedTrial = false,
47+
ValidApplications = new List<string> { "All" }
48+
});
49+
}
50+
2851
internal static LicenseDetails FromLicense(License license)
2952
{
3053
LicenseStatus licenseStatus = license.GetLicenseStatus();
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
namespace ServiceControl.Persistence.RavenDB
2+
{
3+
using System.Threading;
4+
using System.Threading.Tasks;
5+
6+
class LicenseLicenseMetadataProvider(IRavenSessionProvider sessionProvider) : ILicenseLicenseMetadataProvider
7+
{
8+
public async Task<TrialMetadata> GetLicenseMetadata(CancellationToken cancellationToken)
9+
{
10+
using (var session = await sessionProvider.OpenSession(cancellationToken: cancellationToken))
11+
{
12+
return await session.LoadAsync<TrialMetadata>(TrialMetadata.TrialMetadataId, cancellationToken);
13+
}
14+
}
15+
16+
public async Task InsertLicenseMetadata(TrialMetadata licenseMetadata, CancellationToken cancellationToken)
17+
{
18+
using (var session = await sessionProvider.OpenSession(cancellationToken: cancellationToken))
19+
{
20+
await session.StoreAsync(licenseMetadata, TrialMetadata.TrialMetadataId, cancellationToken);
21+
await session.SaveChangesAsync(cancellationToken);
22+
}
23+
}
24+
}
25+
}

src/ServiceControl.Persistence.RavenDB/RavenPersistence.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ public void AddPersistence(IServiceCollection services)
6565
services.AddSingleton<IRetryDocumentDataStore, RetryDocumentDataStore>();
6666
services.AddSingleton<IRetryHistoryDataStore, RetryHistoryDataStore>();
6767
services.AddSingleton<IEndpointSettingsStore, EndpointSettingsStore>();
68+
69+
services.AddSingleton<ILicenseLicenseMetadataProvider, LicenseLicenseMetadataProvider>();
6870
}
6971

7072
public void AddInstaller(IServiceCollection services)
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
namespace ServiceControl.Persistence.Tests.RavenDB.LicenseMetadata
2+
{
3+
using System;
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
using Microsoft.Extensions.DependencyInjection;
7+
using NUnit.Framework;
8+
using ServiceControl.Persistence.RavenDB;
9+
10+
[TestFixture]
11+
class LicenseMetadataTests : RavenPersistenceTestBase
12+
{
13+
public LicenseMetadataTests() =>
14+
RegisterServices = services =>
15+
{
16+
services.AddSingleton<LicenseLicenseMetadataProvider>();
17+
};
18+
19+
[Test]
20+
public async Task LicenseMetadata_can_be_saved()
21+
{
22+
var licenseMetadataService = ServiceProvider.GetRequiredService<LicenseLicenseMetadataProvider>();
23+
24+
var metaData = new TrialMetadata
25+
{
26+
TrialStartDate = DateOnly.FromDateTime(DateTime.UtcNow.AddDays(14))
27+
};
28+
29+
await licenseMetadataService.InsertLicenseMetadata(metaData, CancellationToken.None);
30+
31+
var result = await licenseMetadataService.GetLicenseMetadata(CancellationToken.None);
32+
33+
Assert.That(result.TrialStartDate, Is.EqualTo(metaData.TrialStartDate));
34+
}
35+
}
36+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
namespace ServiceControl.Persistence
2+
{
3+
using System.Threading;
4+
using System.Threading.Tasks;
5+
6+
public interface ILicenseLicenseMetadataProvider
7+
{
8+
Task<TrialMetadata> GetLicenseMetadata(CancellationToken cancellationToken);
9+
Task InsertLicenseMetadata(TrialMetadata licenseMetadata, CancellationToken cancellationToken);
10+
}
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
namespace ServiceControl.Persistence
2+
{
3+
using System;
4+
5+
public class TrialMetadata
6+
{
7+
public DateOnly TrialStartDate { get; set; }
8+
9+
public static string TrialMetadataId = "metadata/trialinformation";
10+
}
11+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
namespace ServiceControl.UnitTests.Licensing
2+
{
3+
using System;
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
using LicenseManagement;
7+
using NUnit.Framework;
8+
using Particular.ServiceControl.Licensing;
9+
using Persistence;
10+
11+
[TestFixture]
12+
public class ActiveLicenseTests
13+
{
14+
[Test]
15+
public async Task Stores_trial_start_date_if_not_found()
16+
{
17+
var today = DateTime.UtcNow.Date;
18+
var trialLicense = LicenseDetails.TrialFromEndDate(DateOnly.FromDateTime(today.AddDays(6)));
19+
var metadataProvider = new FakeMetadataProvider();
20+
21+
var checkedDetails = await ActiveLicense.EnsureTrialLicenseIsValid(trialLicense, metadataProvider, CancellationToken.None);
22+
23+
Assert.That(metadataProvider.Metadata, Is.Not.Null);
24+
Assert.That(metadataProvider.Metadata.TrialStartDate, Is.Not.EqualTo(DateOnly.FromDateTime(today.AddDays(6))));
25+
26+
Assert.That(checkedDetails.ExpirationDate, Is.EqualTo(today.AddDays(6)));
27+
Assert.That(checkedDetails.HasLicenseExpired(), Is.EqualTo(false));
28+
}
29+
30+
[Test]
31+
public async Task Overrides_future_disk_trial_start_date_with_db_value_non_expired()
32+
{
33+
var today = DateTime.UtcNow.Date;
34+
var trialLicense = LicenseDetails.TrialFromEndDate(DateOnly.FromDateTime(today.AddDays(66)));
35+
var metadataProvider = new FakeMetadataProvider(new TrialMetadata
36+
{
37+
TrialStartDate = DateOnly.FromDateTime(today.AddDays(-10))
38+
});
39+
40+
var checkedDetails = await ActiveLicense.EnsureTrialLicenseIsValid(trialLicense, metadataProvider, CancellationToken.None);
41+
42+
Assert.That(checkedDetails.ExpirationDate, Is.EqualTo(today.AddDays(-10).AddDays(14)));
43+
Assert.That(checkedDetails.HasLicenseExpired(), Is.False);
44+
}
45+
46+
[Test]
47+
public async Task Overrides_past_disk_trial_start_date_with_db_value_non_expired()
48+
{
49+
var today = DateTime.UtcNow.Date;
50+
var trialLicense = LicenseDetails.TrialFromEndDate(DateOnly.FromDateTime(today.AddDays(-66)));
51+
var metadataProvider = new FakeMetadataProvider(new TrialMetadata
52+
{
53+
TrialStartDate = DateOnly.FromDateTime(today.AddDays(-10))
54+
});
55+
56+
var checkedDetails = await ActiveLicense.EnsureTrialLicenseIsValid(trialLicense, metadataProvider, CancellationToken.None);
57+
58+
Assert.That(checkedDetails.ExpirationDate, Is.EqualTo(today.AddDays(-10).AddDays(14)));
59+
Assert.That(checkedDetails.HasLicenseExpired(), Is.False);
60+
}
61+
62+
[Test]
63+
public async Task Overrides_disk_trial_start_date_with_db_value_expired()
64+
{
65+
var today = DateTime.UtcNow.Date;
66+
var trialLicense = LicenseDetails.TrialFromEndDate(DateOnly.FromDateTime(today.AddDays(-7)));
67+
var metadataProvider = new FakeMetadataProvider(new TrialMetadata
68+
{
69+
TrialStartDate = DateOnly.FromDateTime(today.AddDays(-20))
70+
});
71+
72+
var checkedDetails = await ActiveLicense.EnsureTrialLicenseIsValid(trialLicense, metadataProvider, CancellationToken.None);
73+
74+
Assert.That(checkedDetails.ExpirationDate, Is.EqualTo(today.AddDays(-20).AddDays(14)));
75+
Assert.That(checkedDetails.HasLicenseExpired(), Is.True);
76+
}
77+
78+
[Test]
79+
public async Task Detects_tempered_trial_date_in_db_and_voids_license()
80+
{
81+
var today = DateTime.UtcNow.Date;
82+
var trialLicense = LicenseDetails.TrialFromEndDate(DateOnly.FromDateTime(today.AddDays(14)));
83+
var metadataProvider = new FakeMetadataProvider(new TrialMetadata
84+
{
85+
TrialStartDate = DateOnly.FromDateTime(today.AddDays(20))
86+
});
87+
88+
var checkedDetails = await ActiveLicense.EnsureTrialLicenseIsValid(trialLicense, metadataProvider, CancellationToken.None);
89+
90+
Assert.That(checkedDetails.ExpirationDate, Is.LessThan(today));
91+
Assert.That(checkedDetails.HasLicenseExpired(), Is.True);
92+
}
93+
94+
class FakeMetadataProvider : ILicenseLicenseMetadataProvider
95+
{
96+
public FakeMetadataProvider()
97+
{
98+
}
99+
100+
public FakeMetadataProvider(TrialMetadata metadata)
101+
{
102+
Metadata = metadata;
103+
}
104+
105+
public TrialMetadata Metadata { get; set; }
106+
107+
public Task<TrialMetadata> GetLicenseMetadata(CancellationToken cancellationToken)
108+
{
109+
return Task.FromResult(Metadata);
110+
}
111+
112+
public Task InsertLicenseMetadata(TrialMetadata licenseMetadata, CancellationToken cancellationToken)
113+
{
114+
Metadata = licenseMetadata;
115+
return Task.CompletedTask;
116+
}
117+
}
118+
}
119+
}

src/ServiceControl.UnitTests/ServiceControl.UnitTests.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,15 @@
1212

1313
<ItemGroup>
1414
<PackageReference Include="GitHubActionsTestLogger" />
15-
<PackageReference Include="Microsoft.Extensions.TimeProvider.Testing"/>
15+
<PackageReference Include="Microsoft.Extensions.TimeProvider.Testing" />
1616
<PackageReference Include="Microsoft.NET.Test.Sdk" />
1717
<PackageReference Include="NServiceBus.Testing" />
1818
<PackageReference Include="NUnit" />
1919
<PackageReference Include="NUnit.Analyzers" />
2020
<PackageReference Include="NUnit3TestAdapter" />
2121
<PackageReference Include="Particular.Approvals" />
2222
<PackageReference Include="PublicApiGenerator" />
23-
<PackageReference Include="System.Linq.Async"/>
23+
<PackageReference Include="System.Linq.Async" />
2424
</ItemGroup>
2525

2626
</Project>

src/ServiceControl/Licensing/ActiveLicense.cs

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
namespace Particular.ServiceControl.Licensing
22
{
3+
using System.Threading.Tasks;
4+
using System.Threading;
5+
using System;
36
using global::ServiceControl.LicenseManagement;
7+
using global::ServiceControl.Persistence;
48
using NServiceBus.Logging;
59

610
public class ActiveLicense
@@ -9,7 +13,7 @@ public class ActiveLicense
913

1014
public bool IsValid { get; set; }
1115

12-
internal LicenseDetails Details { get; set; }
16+
public LicenseDetails Details { get; set; }
1317

1418
public void Refresh()
1519
{
@@ -22,6 +26,43 @@ public void Refresh()
2226
Details = detectedLicense.Details;
2327
}
2428

29+
public async Task EnsureTrialLicenseIsValid(ILicenseLicenseMetadataProvider licenseLicenseMetadataProvider, CancellationToken cancellationToken)
30+
{
31+
Details = await EnsureTrialLicenseIsValid(Details, licenseLicenseMetadataProvider, cancellationToken);
32+
}
33+
34+
internal static async Task<LicenseDetails> EnsureTrialLicenseIsValid(LicenseDetails licenseDetails, ILicenseLicenseMetadataProvider licenseLicenseMetadataProvider, CancellationToken cancellationToken)
35+
{
36+
if (licenseDetails.LicenseType.Equals("trial", StringComparison.OrdinalIgnoreCase))
37+
{
38+
var metadata = await licenseLicenseMetadataProvider.GetLicenseMetadata(cancellationToken);
39+
if (metadata == null)
40+
{
41+
//No start date stored in the database, store one.
42+
metadata = new TrialMetadata
43+
{
44+
//The trial period is 14 days
45+
TrialStartDate = DateOnly.FromDateTime(licenseDetails.ExpirationDate.Value.AddDays(14))
46+
};
47+
48+
await licenseLicenseMetadataProvider.InsertLicenseMetadata(metadata, cancellationToken);
49+
return licenseDetails;
50+
}
51+
if (metadata.TrialStartDate >= DateOnly.FromDateTime(DateTime.Now))
52+
{
53+
// Someone has tampered with the date stored in RavenDB, set the license to expired
54+
return LicenseDetails.TrialExpired();
55+
}
56+
if (DateOnly.FromDateTime(licenseDetails.ExpirationDate ?? DateTime.MinValue) != metadata.TrialStartDate.AddDays(14))
57+
{
58+
//The trial end date stored by the license component has been tempered with or reset, use the RavenDB value
59+
return LicenseDetails.TrialFromEndDate(metadata.TrialStartDate.AddDays(14));
60+
}
61+
//Otherwise use the trial date stored by the licensing component
62+
}
63+
return licenseDetails;
64+
}
65+
2566
static readonly ILog Logger = LogManager.GetLogger(typeof(ActiveLicense));
2667
}
2768
}

src/ServiceControl/Licensing/LicenseCheckHostedService.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,22 @@
44
using System.Threading;
55
using System.Threading.Tasks;
66
using global::ServiceControl.Infrastructure.BackgroundTasks;
7+
using global::ServiceControl.Persistence;
78
using Microsoft.Extensions.Hosting;
89
using NServiceBus.Logging;
910

10-
class LicenseCheckHostedService(ActiveLicense activeLicense, IAsyncTimer scheduler) : IHostedService
11+
class LicenseCheckHostedService(ActiveLicense activeLicense, ILicenseLicenseMetadataProvider licenseLicenseMetadataProvider, IAsyncTimer scheduler) : IHostedService
1112
{
1213
public Task StartAsync(CancellationToken cancellationToken)
1314
{
1415
var due = TimeSpan.FromHours(8);
15-
timer = scheduler.Schedule(_ =>
16+
timer = scheduler.Schedule(async _ =>
1617
{
1718
activeLicense.Refresh();
18-
return ScheduleNextExecutionTask;
19-
}, due, due, ex => Logger.Error("Unhandled error while refreshing the license.", ex));
19+
await activeLicense.EnsureTrialLicenseIsValid(licenseLicenseMetadataProvider, cancellationToken);
20+
21+
return TimerJobExecutionResult.ScheduleNextExecution;
22+
}, TimeSpan.FromTicks(0), due, ex => Logger.Error("Unhandled error while refreshing the license.", ex));
2023
return Task.CompletedTask;
2124
}
2225

@@ -25,6 +28,5 @@ public Task StartAsync(CancellationToken cancellationToken)
2528
TimerJob timer;
2629

2730
static readonly ILog Logger = LogManager.GetLogger<LicenseCheckHostedService>();
28-
static readonly Task<TimerJobExecutionResult> ScheduleNextExecutionTask = Task.FromResult(TimerJobExecutionResult.ScheduleNextExecution);
2931
}
3032
}

0 commit comments

Comments
 (0)