junian / Standard.Licensing

Easy-to-use licensing library for .NET Framework, Mono, .NET Core, and MAUI / Xamarin products
https://junian.dev/Standard.Licensing/
MIT License
554 stars 127 forks source link

LicenseType is not validated #21

Closed mph911 closed 6 months ago

mph911 commented 2 years ago

A license is returned as valid despite the different license types, e.g. if License.LicenseType = LicneseType.Trail and one tries to use the following example from your documentation and using the following code:

var passPhrase = "TestPassword101";
var keyGenerator = Standard.Licensing.Security.Cryptography.KeyGenerator.Create();
var keyPair = keyGenerator.GenerateKeyPair();
var privateKey = keyPair.ToEncryptedPrivateKeyString (passPhrase);
var publicKey = keyPair.ToPublicKeyString();

var license = License.New()
    .WithUniqueIdentifier (Guid.NewGuid())
    .As (LicenseType.Trial)
    .ExpiresAt (DateTime.Now.AddDays (30))
    .WithMaximumUtilization (5)
    .WithProductFeatures (new Dictionary<string, string>
        {
            {"Sales Module", "yes"},
            {"Purchase Module", "yes"},
            {"Maximum Transactions", "10000"}
        })
    .LicensedTo ("John Doe", "john.doe@example.com")
    .CreateAndSignWithPrivateKey (privateKey, passPhrase);

var savedLicense = license.ToString();

var licenseToVerify = License.Load (savedLicense);

var validationFailures = license.Validate()
                                .ExpirationDate()
                                .When (lic => lic.Type == LicenseType.Standard)
                                .And()
                                .Signature (publicKey)
                                .AssertValidLicense();

if (validationFailures.Any())
{
    foreach (var failure in validationFailures)
        Console.WriteLine (failure.GetType().Name + ": " + failure.Message + " - " + failure.HowToResolve);
}
else
    Console.WriteLine ("License valid!");

var errors = license.Validate()
       .AssertThat (lic => lic.Type == LicenseType.Standard,
                    new GeneralValidationFailure ("License type wrong!"))
       .AssertValidLicense().ToList();

Console.WriteLine ($"\nErrors found: {errors.Any ()}");

if (errors.Any())
{
    foreach (var failure in errors)
        Console.WriteLine (failure.GetType().Name + ": " + failure.Message + " - " + failure.HowToResolve);
}
else
    Console.WriteLine ("License valid!");

then no validationFailures.Any() == true! but should be false.

When using the Assert.That then the error is detected as can be seen if the above code is run.

Either I did not understand the purpose of the When clause or it is an error.

In the former case, I'd be happy to learn the purpose and in the later to learn about the correction.

Cheers,

Peter

RFC1920 commented 2 years ago

For what it's worth, I am seeing the same. The only difference seems to be that the expiration date comes back as 12/31/9999 5:59:59 PM for Standard vs. Trial which is whatever you had set.

mph911 commented 2 years ago

OK. But that means that the test in the When clause is not used, or do I get it wrong?

RFC1920 commented 2 years ago

I would tend to agree, or I also completely misunderstand the purpose of it.

RFC1920 commented 2 years ago

Seems that I can also modify the signature and ID randomly and the license still validates as Trial with the expiration of 12/31/9999 :(

AntiGuideAkquinet commented 1 year ago

I just checked your provided example and stepped through the logic.

It seem as if the When validation condition is not meant to be used as a check as in

"This is only valid if it is a standard license"

but

"Only check the expiration date if/when this is a standard license"
The code that is responsible for this behaviour can currently be found here Standard.Licensing/Validation/ValidationChainBuilder.cs:76 and looks like this:

public IEnumerable<IValidationFailure> AssertValidLicense()
{
    CompleteValidatorChain();

    while (validators.Count > 0)
    {
        var validator = validators.Dequeue();
        if (validator.ValidateWhen != null && !validator.ValidateWhen(license))
            continue;

        if (!validator.Validate(license))
            yield return validator.FailureResult
                         ?? new GeneralValidationFailure
                                {
                                    Message = "License validation failed!"
                                };
    }
}
AntiGuideAkquinet commented 1 year ago

The issue you describe does indeed seem like a bug:

Seems that I can also modify the signature and ID randomly and the license still validates as Trial with the expiration of 12/31/9999 :(

The example code provides an IEnumerable<IValidationFailure> which seems to return an error like it should the first time it is enumerated. Sadly a second enumeration provides no failures.

grafik

I opened issue #28 to track this.