Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions src/Standard.Licensing.Tests/LicenseSignatureTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ private static DateTime ConvertToRfc1123(DateTime dateTime)
{
return DateTime.ParseExact(
dateTime.ToUniversalTime().ToString("r", CultureInfo.InvariantCulture)
, "r", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal);
, "r", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal);
}

[Test]
Expand Down Expand Up @@ -80,13 +80,23 @@ public void Can_Generate_And_Validate_Signature_With_Empty_License()
Assert.That(license.VerifySignature(publicKey), Is.True);
}

[Test]
public void Can_Generate_And_Validate_Signature_With_Standard_License()
public static IEnumerable<TestCaseData> LocalAndUtc
{
get
{
yield return new TestCaseData(DateTime.Now.AddMinutes(1));
yield return new TestCaseData(DateTime.Now.AddYears(1));
yield return new TestCaseData(DateTime.UtcNow.AddMinutes(1));
yield return new TestCaseData(DateTime.UtcNow.AddYears(1));
}
}

[Test, TestCaseSource(nameof(LocalAndUtc))]
public void Can_Generate_And_Validate_Signature_With_Standard_License(DateTime expirationDate)
{
var licenseId = Guid.NewGuid();
var customerName = "Max Mustermann";
var customerEmail = "[email protected]";
var expirationDate = DateTime.Now.AddYears(1);
var productFeatures = new Dictionary<string, string>
{
{"Sales Module", "yes"},
Expand Down
92 changes: 87 additions & 5 deletions src/Standard.Licensing.Tests/LicenseValidationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ public void Can_Validate_Invalid_Signature()
[Test]
public void Can_Validate_Expired_ExpirationDate()
{
var publicKey = "";
var licenseData = @"<License>
<Id>77d4c193-6088-4c64-9663-ed7398ae8c1a</Id>
<Type>Trial</Type>
Expand All @@ -118,6 +117,8 @@ public void Can_Validate_Expired_ExpirationDate()

var license = License.Load(licenseData);

Assert.That(license.Expiration.Kind, Is.EqualTo(DateTimeKind.Utc));

var validationResults = license
.Validate()
.ExpirationDate()
Expand All @@ -126,13 +127,52 @@ public void Can_Validate_Expired_ExpirationDate()
Assert.That(validationResults, Is.Not.Null);
Assert.That(validationResults.Count(), Is.EqualTo(1));
Assert.That(validationResults.FirstOrDefault(), Is.TypeOf<LicenseExpiredValidationFailure>());

}

[Test]
public void Can_Validate_Expired_ExpirationDate_CustomDateTime()
public void Can_Validate_NotExpired_ExpirationDate()
{
var licenseData = @"<License>
<Id>77d4c193-6088-4c64-9663-ed7398ae8c1a</Id>
<Type>Trial</Type>
<Expiration>Fri, 31 Dec 2100 23:00:00 GMT</Expiration>
<Quantity>1</Quantity>
<Customer>
<Name>John Doe</Name>
<Email>[email protected]</Email>
</Customer>
<LicenseAttributes />
<ProductFeatures />
<Signature>MEUCIQCCEDAldOZHHIKvYZRDdzUP4V51y23d6deeK5jIFy27GQIgDz2CndjBh4Vb8tiC3FGQ6fn3GKt8d/P5+luJH0cWv+I=</Signature>
</License>";

var license = License.Load(licenseData);

Assert.That(license.Expiration.Kind, Is.EqualTo(DateTimeKind.Utc));

var validationResults = license
.Validate()
.ExpirationDate()
.AssertValidLicense().ToList();

Assert.That(validationResults, Is.Not.Null);
Assert.That(validationResults.Count(), Is.EqualTo(0));
}

public static IEnumerable<TestCaseData> LocalAndUtcExpired
{
get
{
yield return new TestCaseData(new DateTime(1899, 12, 31, 23, 01, 0, DateTimeKind.Utc));
yield return new TestCaseData(new DateTime(1899, 12, 31, 23, 01, 0, DateTimeKind.Utc).ToLocalTime());
yield return new TestCaseData(new DateTime(1899, 12, 31, 23, 30, 0, DateTimeKind.Utc));
yield return new TestCaseData(new DateTime(1899, 12, 31, 23, 30, 0, DateTimeKind.Utc).ToLocalTime());
}
}

[Test, TestCaseSource(nameof(LocalAndUtcExpired))]
public void Can_Validate_Expired_ExpirationDate_CustomDateTime(DateTime expirationDate)
{
var publicKey = "";
var licenseData = @"<License>
<Id>77d4c193-6088-4c64-9663-ed7398ae8c1a</Id>
<Type>Trial</Type>
Expand All @@ -149,15 +189,57 @@ public void Can_Validate_Expired_ExpirationDate_CustomDateTime()

var license = License.Load(licenseData);

Assert.That(license.Expiration.Kind, Is.EqualTo(DateTimeKind.Utc));

var validationResults = license
.Validate()
.ExpirationDate(systemDateTime: new DateTime(1900, 1, 2, 0, 0, 0, DateTimeKind.Utc))
.ExpirationDate(systemDateTime: expirationDate)
.AssertValidLicense().ToList();

Assert.That(validationResults, Is.Not.Null);
Assert.That(validationResults.Count(), Is.EqualTo(1));
Assert.That(validationResults.FirstOrDefault(), Is.TypeOf<LicenseExpiredValidationFailure>());
}

public static IEnumerable<TestCaseData> LocalAndUtcNotExpired
{
get
{
yield return new TestCaseData(new DateTime(1899, 12, 31, 22, 59, 0, DateTimeKind.Utc));
yield return new TestCaseData(new DateTime(1899, 12, 31, 22, 59, 0, DateTimeKind.Utc).ToLocalTime());
yield return new TestCaseData(new DateTime(1899, 12, 31, 22, 30, 0, DateTimeKind.Utc));
yield return new TestCaseData(new DateTime(1899, 12, 31, 22, 30, 0, DateTimeKind.Utc).ToLocalTime());
}
}

[Test, TestCaseSource(nameof(LocalAndUtcNotExpired))]
public void Can_Validate_NotExpired_ExpirationDate_CustomDateTime(DateTime expirationDate)
{
var licenseData = @"<License>
<Id>77d4c193-6088-4c64-9663-ed7398ae8c1a</Id>
<Type>Trial</Type>
<Expiration>Sun, 31 Dec 1899 23:00:00 GMT</Expiration>
<Quantity>1</Quantity>
<Customer>
<Name>John Doe</Name>
<Email>[email protected]</Email>
</Customer>
<LicenseAttributes />
<ProductFeatures />
<Signature>MEUCIQCCEDAldOZHHIKvYZRDdzUP4V51y23d6deeK5jIFy27GQIgDz2CndjBh4Vb8tiC3FGQ6fn3GKt8d/P5+luJH0cWv+I=</Signature>
</License>";

var license = License.Load(licenseData);

Assert.That(license.Expiration.Kind, Is.EqualTo(DateTimeKind.Utc));

var validationResults = license
.Validate()
.ExpirationDate(systemDateTime: expirationDate)
.AssertValidLicense().ToList();

Assert.That(validationResults, Is.Not.Null);
Assert.That(validationResults.Count(), Is.EqualTo(0));
}

[Test]
Expand Down
25 changes: 21 additions & 4 deletions src/Standard.Licensing/License.cs
Original file line number Diff line number Diff line change
Expand Up @@ -165,21 +165,38 @@ public LicenseAttributes AdditionalAttributes
}

/// <summary>
/// Gets or sets the expiration date of this <see cref="License"/>.
/// Gets or sets the expiration date in UTC of this <see cref="License"/>.
/// Use this property to set the expiration date for a trial license
/// or the expiration of support & subscription updates for a standard license.
/// </summary>
/// <remarks>
/// The parameter can be in UTC because <see cref="DateTime.ToUniversalTime"/>
/// does not modify a UTC value.
/// </remarks>
/// <param name="value">The expiration date of the <see cref="License"/> in local time or UTC.</param>
/// <returns>The expiration date of the <see cref="License"/> in UTC.</returns>
public DateTime Expiration
{
get
{
return
DateTime.ParseExact(
GetTag("Expiration") ??
DateTime.MaxValue.ToUniversalTime().ToString("r", CultureInfo.InvariantCulture)
, "r", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal);
DateTime.MaxValue.ToString("r", CultureInfo.InvariantCulture)
, "r", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal);
}
set
{
if (value.Kind == DateTimeKind.Unspecified)
{
throw new ArgumentException($"The {nameof(value)} must be in local time or UTC.", nameof(value));
}

if (!IsSigned)
{
SetTag("Expiration", value.ToUniversalTime().ToString("r", CultureInfo.InvariantCulture));
}
}
set { if (!IsSigned) SetTag("Expiration", value.ToUniversalTime().ToString("r", CultureInfo.InvariantCulture)); }
}

/// <summary>
Expand Down
11 changes: 10 additions & 1 deletion src/Standard.Licensing/LicenseBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,19 @@ public ILicenseBuilder As(LicenseType type)
/// <summary>
/// Sets the expiration date of the <see cref="License"/>.
/// </summary>
/// <param name="date">The expiration date of the <see cref="License"/>.</param>
/// <remarks>
/// The parameter can be in UTC because ToUniversalTime()
/// does not modify a UTC value.
/// </remarks>
/// <param name="date">The expiration date of the <see cref="License"/> in local time or UTC.</param>
/// <returns>The <see cref="ILicenseBuilder"/>.</returns>
public ILicenseBuilder ExpiresAt(DateTime date)
{
if (date.Kind == DateTimeKind.Unspecified)
{
throw new ArgumentException($"The {nameof(date)} must be in local time or UTC.", nameof(date));
}

license.Expiration = date.ToUniversalTime();
return this;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,18 @@ public static IValidationChain ExpirationDate(this IStartValidationChain validat
/// <summary>
/// Validates if the license has been expired.
/// </summary>
/// <remarks>
/// The parameter can be in UTC because <see cref="DateTime.ToUniversalTime"/>
/// does not modify a UTC value.
/// </remarks>
/// <param name="validationChain">The current <see cref="IStartValidationChain"/>.</param>
/// <param name="systemDateTime">The System DateTime to compare to, default is DateTime.Now. Can be changed to NTP / other internet API times.</param>
/// <param name="systemDateTime">The system DateTime to compare to in local time or UTC, default is DateTime.Now. Can be changed to NTP / other internet API times.</param>
/// <returns>An instance of <see cref="IStartValidationChain"/>.</returns>
public static IValidationChain ExpirationDate(this IStartValidationChain validationChain, DateTime systemDateTime)
{
var validationChainBuilder = (validationChain as ValidationChainBuilder);
var validator = validationChainBuilder.StartValidatorChain();
validator.Validate = license => license.Expiration > systemDateTime;
validator.Validate = license => license.Expiration > systemDateTime.ToUniversalTime();

validator.FailureResult = new LicenseExpiredValidationFailure()
{
Expand Down