Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/3x licensing updates #56

Merged
merged 13 commits into from
Feb 2, 2024
Merged
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## [3.4.0]

## Added

- **Alpha**: Introduced subscription usage monitoring based on customer feedback

## [3.3.3]

### Added
Expand Down
17 changes: 17 additions & 0 deletions src/FakeXrmEasy.Core/CommercialLicense/EnvironmentReader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;

namespace FakeXrmEasy.Core.CommercialLicense
{
internal interface IEnvironmentReader
{
string GetEnvironmentVariable(string variableName);
}

internal class EnvironmentReader: IEnvironmentReader
{
public string GetEnvironmentVariable(string variableName)
{
return Environment.GetEnvironmentVariable(variableName);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;

namespace FakeXrmEasy.Core.CommercialLicense.Exceptions
{
/// <summary>
/// Exception raised when the current number of users calculated based on the usage of your current subscription is greater than the maximum number of users in your current subscription
/// </summary>
public class ConsiderUpgradingPlanException: Exception
{
/// <summary>
/// Default constructor
/// </summary>
/// <param name="currentNumberOfUsers"></param>
/// <param name="allowedNumberOfUsers"></param>
public ConsiderUpgradingPlanException(long currentNumberOfUsers, long allowedNumberOfUsers) :
base($"Your current subscription allows up to {allowedNumberOfUsers.ToString()}, however, {currentNumberOfUsers.ToString()} are currently using it. Please consider upgrading your current plan.")
{

}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System;

namespace FakeXrmEasy.Core.CommercialLicense.Exceptions
{
/// <summary>
/// Exception raised when the license key is invalid or malformed
/// </summary>
public class InvalidLicenseKeyException: Exception
{
/// <summary>
/// Default constructor
/// </summary>
public InvalidLicenseKeyException() : base("The license key is invalid")
{

}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System;

namespace FakeXrmEasy.Core.CommercialLicense.Exceptions
{
/// <summary>
/// Exception thrown if the info about the current subscription plan is unknown
/// </summary>
public class NoSubscriptionPlanInfoException: Exception
{
/// <summary>
/// Default constructor
/// </summary>
public NoSubscriptionPlanInfoException() : base("The current subscription info is unknown")
{

}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System;

namespace FakeXrmEasy.Core.CommercialLicense.Exceptions
{
/// <summary>
/// Throws an exception when your current usage of FakeXrmEasy could not be retrieved
/// </summary>
public class NoUsageFoundException: Exception
{
/// <summary>
/// Default constructor
/// </summary>
public NoUsageFoundException() : base("No info about your current usage of FakeXrmEasy was found")
{

}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System;

namespace FakeXrmEasy.Core.CommercialLicense.Exceptions
{
/// <summary>
/// Exception raised when your current subscription expired and you exceeded the allowed renewal time window
/// </summary>
public class RenewalRequestExpiredException: Exception
{
/// <summary>
/// Throws an exception where the current subscription expired
/// </summary>
/// <param name="expiredOn"></param>
public RenewalRequestExpiredException(DateTime expiredOn) : base($"The current subscription expired on '{expiredOn.ToLongDateString()}' and a renewal license was not applied on time. Please request a new subscription license.")
{

}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System;

namespace FakeXrmEasy.Core.CommercialLicense.Exceptions
{
/// <summary>
/// The current subscription expired
/// </summary>
public class SubscriptionExpiredException: Exception
{
/// <summary>
/// Throws an exception where the current subscription expired
/// </summary>
/// <param name="expiredOn"></param>
public SubscriptionExpiredException(DateTime expiredOn) : base($"The current subscription expired on {expiredOn.ToLongDateString()}")
{

}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;

namespace FakeXrmEasy.Core.CommercialLicense.Exceptions
{
/// <summary>
/// Exception raised when the grace period for requesting an upgrade has expired
/// </summary>
public class UpgradeRequestExpiredException: Exception
{
/// <summary>
/// Default constructor
/// </summary>
/// <param name="firstRequested"></param>
public UpgradeRequestExpiredException(DateTime firstRequested) :
base($"You requested a subscription upgrade on '{firstRequested.ToShortDateString()}', however, the new subscription details or upgrade progress has not been completed within the allowed upgrade window. Please contact your line manager and raise a support ticket")
{

}
}
}
46 changes: 46 additions & 0 deletions src/FakeXrmEasy.Core/CommercialLicense/SubscriptionInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using System;
using FakeXrmEasy.Abstractions.CommercialLicense;

namespace FakeXrmEasy.Core.CommercialLicense
{
/// <summary>
/// Contains info about the current subscription
/// </summary>
internal class SubscriptionInfo: ISubscriptionInfo
{
/// <summary>
/// The CustomerId
/// </summary>
public string CustomerId { get; set; }

/// <summary>
/// SKU
/// </summary>
public StockKeepingUnits SKU { get; set; }

/// <summary>
/// True if the current subscription auto-renews
/// </summary>
public bool AutoRenews { get; set; }

/// <summary>
/// The current billing cycle type
/// </summary>
public SubscriptionBillingCycleType BillingType { get; set; }

/// <summary>
/// Max number of users allowed in the current subscription
/// </summary>
public long NumberOfUsers { get; set; }

/// <summary>
/// The subscription start date
/// </summary>
public DateTime StartDate { get; set; }

/// <summary>
/// The subscription's end date
/// </summary>
public DateTime EndDate { get; set; }
}
}
53 changes: 53 additions & 0 deletions src/FakeXrmEasy.Core/CommercialLicense/SubscriptionManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System;
using System.Globalization;
using System.Linq;
using System.Text;
using FakeXrmEasy.Abstractions.CommercialLicense;
using FakeXrmEasy.Core.CommercialLicense.Exceptions;

namespace FakeXrmEasy.Core.CommercialLicense
{
internal static class SubscriptionManager
{
internal static ISubscriptionInfo _subscriptionInfo;
internal static readonly object _subscriptionInfoLock = new object();

internal static ISubscriptionUsage _subscriptionUsage;
internal static readonly object _subscriptionUsageLock = new object();

internal static bool _renewalRequested = false;
internal static bool _upgradeRequested = false;

private static void SetLicenseKey(string licenseKey)
{
lock (_subscriptionInfoLock)
{
if (_subscriptionInfo == null)
{
var subscriptionPlanManager = new SubscriptionPlanManager();
_subscriptionInfo = subscriptionPlanManager.GetSubscriptionInfoFromKey(licenseKey);
}
}
}

internal static void SetSubscriptionStorageProvider(ISubscriptionStorageProvider subscriptionStorageProvider,
IUserReader userReader,
bool upgradeRequested,
bool renewalRequested)
{
SetLicenseKey(subscriptionStorageProvider.GetLicenseKey());

lock (_subscriptionUsageLock)
{
if (_subscriptionUsage == null)
{
_upgradeRequested = upgradeRequested;
_renewalRequested = renewalRequested;

var usageManager = new SubscriptionUsageManager();
_subscriptionUsage = usageManager.ReadAndUpdateUsage(_subscriptionInfo, subscriptionStorageProvider, userReader, _upgradeRequested);
}
}
}
}
}
64 changes: 64 additions & 0 deletions src/FakeXrmEasy.Core/CommercialLicense/SubscriptionPlanManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using System;
using System.Globalization;
using System.Text;
using FakeXrmEasy.Abstractions.CommercialLicense;
using FakeXrmEasy.Core.CommercialLicense.Exceptions;

namespace FakeXrmEasy.Core.CommercialLicense
{
internal class SubscriptionPlanManager
{
internal string GenerateHash(string input)
{
using (System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create())
{
byte[] inputBytes = Encoding.UTF8.GetBytes(input);
byte[] hashBytes = md5.ComputeHash(inputBytes);

StringBuilder sb = new StringBuilder();
for (int i = 0; i < hashBytes.Length; i++)
{
sb.Append(hashBytes[i].ToString("X2"));
}
return sb.ToString();
}
}

internal ISubscriptionInfo GetSubscriptionInfoFromKey(string licenseKey)
{
try
{
var encodedBaseKey = licenseKey.Substring(0, licenseKey.Length - 32);
var hash = licenseKey.Substring(licenseKey.Length - 32, 32);
var computedHash = GenerateHash(encodedBaseKey);

if (!computedHash.Equals(hash))
{
throw new InvalidLicenseKeyException();
}

var decodedBaseKey = Encoding.UTF8.GetString(Convert.FromBase64String(encodedBaseKey));
var baseKeyParts = decodedBaseKey.Split('-');

var expiryDate = DateTime.ParseExact(baseKeyParts[4], "yyyyMMdd", CultureInfo.InvariantCulture);
var numberOfUsers = int.Parse(baseKeyParts[3]);

var sku = (StockKeepingUnits) Enum.Parse(typeof(StockKeepingUnits), baseKeyParts[0]);
var autoRenews = "1".Equals(baseKeyParts[2]);

return new SubscriptionInfo()
{
SKU = sku,
CustomerId = baseKeyParts[1],
NumberOfUsers = numberOfUsers,
EndDate = expiryDate,
AutoRenews = autoRenews
};
}
catch
{
throw new InvalidLicenseKeyException();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System;
using FakeXrmEasy.Abstractions.CommercialLicense;

namespace FakeXrmEasy.Core.CommercialLicense
{
internal class SubscriptionUpgradeRequest: ISubscriptionUpgradeRequest
{
public DateTime FirstRequestDate { get; set; }
public long PreviousNumberOfUsers { get; set; }
}
}
34 changes: 34 additions & 0 deletions src/FakeXrmEasy.Core/CommercialLicense/SubscriptionUsage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using FakeXrmEasy.Abstractions.CommercialLicense;

namespace FakeXrmEasy.Core.CommercialLicense
{
/// <summary>
/// Contains info about the current subscription usage
/// </summary>
internal class SubscriptionUsage: ISubscriptionUsage
{
/// <summary>
/// The last time the current subscription usage was checked
/// </summary>
public DateTime LastTimeChecked { get; set; }

/// <summary>
/// Information about all the users
/// </summary>
public ICollection<ISubscriptionUserInfo> Users { get; set; }

/// <summary>
/// Contains info for a requested upgrade
/// </summary>
public ISubscriptionUpgradeRequest UpgradeInfo { get; set; }

internal SubscriptionUsage()
{
Users = new List<ISubscriptionUserInfo>();
LastTimeChecked = DateTime.UtcNow;
UpgradeInfo = null;
}
}
}
Loading
Loading