LSXPrime / Aegis

Aegis is a robust and flexible .NET licensing library that simplifies the implementation of various licensing models for your applications. It offers strong security features, both online and offline validation, and easy integration with your existing projects. Securely manage your software licenses with Aegis.
MIT License
188 stars 5 forks source link

RegistryDateTimeProvider needs improvement #10

Open xmenxwk opened 20 hours ago

xmenxwk commented 20 hours ago

While its customizable but it only solves half the problem. Here are some test scenarios.

License Expires on 31 Jan 2024 and on 30th, User changes the system date to 1st Jan 2024. The provider will fail because it will keep reading 30th from registry.

Another is, everyday a user keep setting system date to same day the app was installed. It will never expire.

LSXPrime commented 18 hours ago

RegistryDateTimeProvider is a sample, not a core component, its primary purpose is to demonstrate the IValidationRule feature. The library's main focus is on licensing and validation, but it does not have control over system-side date and time manipulation.

but surely you can fix it by adapting RegistryDateTimeProvider to access current DateTime using any external API

using Microsoft.Win32;
using System.Net.Http;
using System.Text.Json;

namespace Aegis.Sample.Validation.DateTime.Windows;

public interface IDateTimeProvider
{
    System.DateTime UtcNow { get; }
}

public class RegistryDateTimeProvider : IDateTimeProvider
{
    private const string RegistryKeyPath = @"SOFTWARE\LSXPrime\Aegis";
    private const string LastStartTimeValueName = "LastStartTime";
    private const string TimeApiUrl = "https://timeapi.io/api/timezone/zone?timeZone=Europe%2FAmsterdam";

    private readonly HttpClient _httpClient;

    public bool Mock { get; set; } // Mock the current system time for testing purposes

    public RegistryDateTimeProvider(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    public System.DateTime UtcNow
    {
        get
        {
            var lastStartTime = GetLastStartTimeFromRegistry();
            var currentUtcNow = GetCurrentUtcTimeFromApi();

            if (lastStartTime > currentUtcNow)
            {
                return lastStartTime;
            }

            SetLastStartTimeInRegistry(currentUtcNow);
            return currentUtcNow;
        }
    }

    private System.DateTime GetCurrentUtcTimeFromApi()
    {
        var response = _httpClient.GetAsync(TimeApiUrl).Result;
        response.EnsureSuccessStatusCode();

        var content = response.Content.ReadAsStringAsync().Result;
        var timeData = JsonSerializer.Deserialize<TimeApiResponse>(content);

        return System.DateTime.Parse(timeData.currentLocalTime).ToUniversalTime();
    }

    private System.DateTime GetLastStartTimeFromRegistry()
    {
        using var key = Registry.CurrentUser.OpenSubKey(RegistryKeyPath);
        var lastStartTimeTicks = key?.GetValue(LastStartTimeValueName) as long? ?? 0;
        return new System.DateTime(lastStartTimeTicks, DateTimeKind.Utc).AddHours(Mock ? 2 : 0);
    }

    private void SetLastStartTimeInRegistry(System.DateTime dateTime)
    {
        using var key = Registry.CurrentUser.CreateSubKey(RegistryKeyPath);
        key?.SetValue(LastStartTimeValueName, dateTime.Ticks, RegistryValueKind.QWord);
    }

    private class TimeApiResponse
    {
        public string currentLocalTime { get; set; }
    }
}
xmenxwk commented 18 hours ago

Checking online is very good alternative but it won't work for offline apps, since most of my apps are very little dependent on online connectivity. And can't force users to always be online so I might have to think some other way.