Skip to content

Commit

Permalink
Merge pull request #56 from DynamicsValue/feature/3x-licensing-updates
Browse files Browse the repository at this point in the history
Feature/3x licensing updates
  • Loading branch information
jordimontana82 authored Feb 2, 2024
2 parents 3a2932a + b0d4ea8 commit db60348
Show file tree
Hide file tree
Showing 30 changed files with 1,174 additions and 25 deletions.
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

0 comments on commit db60348

Please sign in to comment.