using System; using System.Text; using System.Threading.Tasks; using System.IO; using System.Collections.Generic; using System.Net.Http; using AyaNova.Util; using AyaNova.Models; using System.Linq; using Microsoft.Extensions.Logging; using Microsoft.EntityFrameworkCore; //JSON KEY using Org.BouncyCastle.Security; using Org.BouncyCastle.OpenSsl; using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace AyaNova.Core { internal static class License { //License server addresses private const string LICENSE_SERVER_URL_ROCKFISH = "https://rockfish.ayanova.com/"; private const string LICENSE_SERVER_URL_IO = "https://io.ayanova.com/"; private const string LICENSE_SERVER_URL_EUROPA = "https://europa.ayanova.com/"; private const string LICENSE_SERVER_URL_CALLISTO = "https://callisto.ayanova.com/"; // #if (DEBUG) // private const string LICENSE_SERVER_URL = "http://localhost:3001/"; // #else // private const string LICENSE_SERVER_URL = "https://rockfish.ayanova.com/"; // #endif //Unlicensed token private const string UNLICENSED_TOKEN = "UNLICENSED"; //REVOKED token private const string REVOKED_TOKEN = "REVOKED"; //Scheduleable users private const string SERVICE_TECHS_FEATURE_NAME = "ServiceTechs"; //Accounting add-on private const string ACCOUNTING_FEATURE_NAME = "Accounting"; //This feature name means it's a trial key private const string TRIAL_FEATURE_NAME = "TrialMode"; //This feature name means it's a SAAS or rental mode key for month to month hosted service private const string RENTAL_FEATURE_NAME = "Subscription"; //Trial key magic number for development and testing, all other guids will be fully licensed // private static Guid TEST_TRIAL_KEY_DBID = new Guid("{A6D18A8A-5613-4979-99DA-80D07641A2FE}"); //Current license key, can be empty private static AyaNovaLicenseKey _ActiveLicense = new AyaNovaLicenseKey(); //The license dbid, separate from the server dbid private static string LicenseDbId { get; set; } #region license classes //DTO object returned on license query internal class LicenseFeature { //name of feature / product public string Feature { get; set; } //Optional count for items that require it public long Count { get; set; } } //DTO object for parsed key internal class AyaNovaLicenseKey { public enum LicenseStatus { NONE = 0,//fast track ActiveTrial = 1,//slow track ExpiredTrial = 2,//fast track ActivePurchased = 3,//slow track ExpiredPurchased = 4,//fast track Revoked = 5//slow track } public AyaNovaLicenseKey() { Features = new List(); RegisteredTo = UNLICENSED_TOKEN; Id = RegisteredTo; } /// /// Fetch the license status of the feature in question /// /// /// LicenseFeature object or null if there is no license public LicenseFeature GetLicenseFeature(string Feature) { if (IsEmpty) return null; string lFeature = Feature.ToLowerInvariant(); foreach (LicenseFeature l in Features) { if (l.Feature.ToLowerInvariant() == lFeature) { return l; } } return null; } public long ActiveNumber { get { return GetLicenseFeature(SERVICE_TECHS_FEATURE_NAME).Count; } } /// /// Check for the existance of license feature /// /// /// bool public bool HasLicenseFeature(string Feature) { if (IsEmpty) return false; string lFeature = Feature.ToLowerInvariant(); foreach (LicenseFeature l in Features) { if (l.Feature.ToLowerInvariant() == lFeature) { return true; } } return false; } public LicenseStatus Status { get { //TEST // return LicenseStatus.Revoked; if (string.IsNullOrWhiteSpace(RegisteredTo) || RegisteredTo == UNLICENSED_TOKEN) return LicenseStatus.NONE; if (RegisteredTo == REVOKED_TOKEN) return LicenseStatus.Revoked; if (TrialLicense && !LicenseExpired) return LicenseStatus.ActiveTrial; if (TrialLicense && LicenseExpired) return LicenseStatus.ExpiredTrial; if (!TrialLicense && !LicenseExpired) return LicenseStatus.ActivePurchased; if (!TrialLicense && LicenseExpired) return LicenseStatus.ExpiredPurchased; throw new System.Exception("License::Status - unable to determine license status"); } } //Has any kind of valid license that is active //used for auth route checking to allow for fixing this issue public bool KeyDoesNotNeedAttention { get { return (Status == LicenseStatus.ActivePurchased) || (Status == LicenseStatus.ActiveTrial); } } public bool IsEmpty { get { //Key is empty if it's not registered to anyone or there are no features in it return string.IsNullOrWhiteSpace(RegisteredTo) || (Features == null || Features.Count == 0); } } public bool WillExpire { get { return LicenseExpiration < DateUtil.EmptyDateValue; } } public bool LicenseExpired { get { return LicenseExpiration < DateTime.Now; } } public bool MaintenanceExpired { get { return MaintenanceExpiration < DateTime.Now; } } public bool TrialLicense { get { return HasLicenseFeature(TRIAL_FEATURE_NAME); } } public bool RentalLicense { get { return HasLicenseFeature(RENTAL_FEATURE_NAME); } } public string LicenseFormat { get; set; } public string Id { get; set; } public string RegisteredTo { get; set; } public string DbId { get; set; } public DateTime LicenseExpiration { get; set; } public DateTime MaintenanceExpiration { get; set; } public List Features { get; set; } } #endregion #region sample v8 key // private static string SAMPLE_KEY = @"[KEY // { // ""Key"": { // ""LicenseFormat"": ""8"", // ""Id"": ""34-1516288681"", <----Customer id followed by key serial id // ""RegisteredTo"": ""Super TestCo"", or "REVOKED" if revoked // ""DBID"": ""df558559-7f8a-4c7b-955c-959ebcdf71f3"", // ""LicenseExpiration"": ""2019-01-18T07:18:01.2329138-08:00"", <--- UTC, DateTime if perpetual license 1/1/5555 indicates not expiring // ""MaintenanceExpiration"": ""2019-01-18T07:18:01.2329138-08:00"", <-- UTC, DateTime support and updates subscription runs out, applies to all features // ""Features"": { // ""Feature"": [ // { // ""Name"": ""Scheduleable users"", // ""Count"":""10"", // }, // { // ""Name"": ""Accounting"" // }, // { // ""Name"": ""TrialMode""<---means is a trial key // }, // { // ""Name"": ""Subscription"" <----Means it's an SAAS/Rental key // } // ] // } // } // } // KEY] // [SIGNATURE // HEcY3JCVwK9HFXEFnldUEPXP4Q7xoZfMZfOfx1cYmfVF3MVWePyZ9dqVZcY7pk3RmR1BbhQdhpljsYLl+ZLTRhNa54M0EFa/bQnBnbwYZ70EQl8fz8WOczYTEBo7Sm5EyC6gSHtYZu7yRwBvhQzpeMGth5uWnlfPb0dMm0DQM7PaqhdWWW9GCSOdZmFcxkFQ8ERLDZhVMbd8PJKyLvZ+sGMrmYTAIoL0tqa7nrxYkM71uJRTAmQ0gEl4bJdxiV825U1J+buNQuTZdacZKEPSjQQkYou10jRbReUmP2vDpvu+nA1xdJe4b5LlyQL+jiIXH17lf93xlCUb0UkDpu8iNQ== // SIGNATURE]\"; #endregion #region Exposed properties //The database id value stored in the schema table internal static string ServerDbId { get; private set; } internal static bool LicenseConsentRequired { get; private set; } /// /// Fetch a summary of the license key for displaying to the end user /// /// string containing current license information internal static string LicenseInfo { get { StringBuilder sb = new StringBuilder(); if (ActiveKey.IsEmpty) { sb.AppendLine(UNLICENSED_TOKEN); sb.AppendLine($"Server DB ID: {ServerDbId}"); } else { if (ActiveKey.TrialLicense) sb.AppendLine("TRIAL LICENSE FOR EVALUATION PURPOSES ONLY"); sb.AppendLine($"Registered to: {ActiveKey.RegisteredTo}"); //sb.AppendLine($"Key ID: {ActiveKey.Id}"); //sb.AppendLine($"Server DB ID: {ServerDbId}"); sb.AppendLine($"Type: {(ActiveKey.RentalLicense ? "Subscription" : "Perpetual")}"); if (ActiveKey.WillExpire) sb.AppendLine($"License expires: {DateUtil.ServerDateTimeString(ActiveKey.LicenseExpiration)}"); sb.AppendLine($"Maintenance subscription expires: {DateUtil.ServerDateTimeString(ActiveKey.MaintenanceExpiration)}"); sb.AppendLine("Features:"); foreach (LicenseFeature l in ActiveKey.Features) { //don't show the rental or trial features if (l.Feature != TRIAL_FEATURE_NAME && l.Feature != RENTAL_FEATURE_NAME) { if (l.Count != 0) sb.AppendLine($"{l.Feature} - {l.Count}"); else sb.AppendLine($"{l.Feature}"); } } } return sb.ToString(); } } /// /// Fetch a summary of the license key for displaying in the log /// /// string containing current license information internal static string LicenseInfoLogFormat { get { StringBuilder sb = new StringBuilder(); if (ActiveKey.IsEmpty) { return $"UNLICENSED, DB ID: {LicenseDbId}"; } else { if (ActiveKey.TrialLicense) sb.Append("TRIAL, "); sb.Append($"regto: {ActiveKey.RegisteredTo}, "); sb.Append($"keyid: {ActiveKey.Id}, "); sb.Append($"dbid: {LicenseDbId}, "); // sb.Append($"serverdbid: {ServerDbId}"); sb.Append($"type: {(ActiveKey.RentalLicense ? "subscription" : "perpetual")}, "); if (ActiveKey.WillExpire) sb.Append($"exp: {DateUtil.ServerDateTimeString(ActiveKey.LicenseExpiration)} (utc), "); sb.Append($"maint. sub. exps: {DateUtil.ServerDateTimeString(ActiveKey.MaintenanceExpiration)} (utc), "); sb.Append("feat:"); foreach (LicenseFeature l in ActiveKey.Features) { //don't show the rental or trial features if (l.Feature != TRIAL_FEATURE_NAME && l.Feature != RENTAL_FEATURE_NAME) { if (l.Count != 0) sb.Append($"{l.Feature} - {l.Count}, "); else sb.Append($"{l.Feature}, "); } } } return sb.ToString().Trim().TrimEnd(','); } } /// /// Fetch a summary of the license key for displaying to the end user /// via the API /// /// JSON object containing current license information internal static object LicenseInfoAsJson { get { Newtonsoft.Json.Linq.JObject o = Newtonsoft.Json.Linq.JObject.FromObject(new { license = new { serverDbId = ServerDbId, licensedTo = ActiveKey.RegisteredTo, dbId = ActiveKey.DbId, keySerial = ActiveKey.Id, licenseExpiration = ActiveKey.LicenseExpiration, licenseWillExpire = ActiveKey.WillExpire, maintenanceExpired = ActiveKey.MaintenanceExpired, maintenanceExpiration = ActiveKey.MaintenanceExpiration, features = from f in ActiveKey.Features orderby f.Feature select new { Feature = f.Feature, Count = f.Count } } }); return o; } } /// /// Returns the active key /// /// internal static AyaNovaLicenseKey ActiveKey { get { return _ActiveLicense; } } #endregion #region EULA consent /// /// Set consent flag for license agreement /// internal static async Task FlagEULA(AyContext ct, ILogger log) { try { var CurrentInDbKeyRecord = await ct.License.OrderBy(z => z.Id).FirstOrDefaultAsync(); if (CurrentInDbKeyRecord == null) throw new ApplicationException("E1020 - Can't update EULA agreement, no key record found"); //Update current license CurrentInDbKeyRecord.LicenseAgree = true; await ct.SaveChangesAsync(); } catch (Exception ex) { var msg = "E1020 - Error during EULA agreement flagging"; log.LogError(ex, msg); throw new ApplicationException(msg, ex); } LicenseConsentRequired = false; } #endregion #region Trial license request handling /// /// Request a key /// /// Result string internal static async Task RequestTrialAsync(RequestTrial trialRequest, ILogger log) { trialRequest.DbId = ServerDbId; log.LogDebug($"Requesting trial license for DBID {LicenseDbId}"); string sUrl = $"{LICENSE_SERVER_URL_ROCKFISH}rvr"; try { var content = new StringContent(JsonConvert.SerializeObject(trialRequest), Encoding.UTF8, "application/json"); var client = ServiceProviderProvider.HttpClientFactory.CreateClient(); var res = await client.PostAsync(sUrl, content); var responseText = await res.Content.ReadAsStringAsync(); if (res.IsSuccessStatusCode) { if (string.IsNullOrWhiteSpace(responseText)) return "ok"; else return responseText; } else return $"E1020 - Error requesting trial license key: {res.ReasonPhrase}"; } catch (Exception ex) { var msg = "E1020 - Error requesting trial license key see log for details"; log.LogError(ex, msg); return msg; } } #endregion trial license request handling #region License fetching and handling public class dtoFetchRequest { public string DbId { get; set; } } /// /// Fetch a key, validate it and install it in the db then initialize with it /// /// Result string #if (DEBUG) internal static async Task FetchKeyAsync(AyaNova.Api.ControllerHelpers.ApiServerState apiServerState, AyContext ct, ILogger log, bool calledFromInternalJob, bool devTestTrial = false) #else internal static async Task FetchKeyAsync(AyaNova.Api.ControllerHelpers.ApiServerState apiServerState, AyContext ct, ILogger log, bool calledFromInternalJob) #endif { if (calledFromInternalJob) log.LogDebug($"Fetching license for DBID {LicenseDbId} (called by job)"); else log.LogInformation($"Fetching license for DBID {LicenseDbId}"); var FetchRequest = new dtoFetchRequest() { DbId = LicenseDbId }; string LicenseUrlParameter = "rvf"; #if (DEBUG) if (devTestTrial) { LicenseUrlParameter += "?dtt=true";//signal to rockfish to provide a key immediately for dev testing } #endif try { var content = new StringContent(JsonConvert.SerializeObject(FetchRequest), Encoding.UTF8, "application/json"); var client = ServiceProviderProvider.HttpClientFactory.CreateClient(); HttpResponseMessage res = null; res = await DoFetchAsync(LicenseUrlParameter, content, client); if (res.IsSuccessStatusCode) { var responseText = await res.Content.ReadAsStringAsync(); var responseJson = JObject.Parse(responseText); var keyText = responseJson["data"]["key"].Value(); AyaNovaLicenseKey ParsedKey = Parse(keyText, log); if (ParsedKey != null) { await InstallAsync(keyText, ParsedKey, apiServerState, ct, log); //Log the license info so it's on the record log.LogInformation($"License - new key installed [{AyaNova.Core.License.LicenseInfoLogFormat}]"); return "ok"; } return $"E1020 - Error fetching license key: No key was returned"; } else { //some kind of server error?? if ((int)res.StatusCode > 499) { return $"E1020 - License server error fetching license key, contact technical support: {res.ReasonPhrase}"; } //If it's NOT FOUND, that's not an error, just a normal response to be expected if (res.StatusCode == System.Net.HttpStatusCode.NotFound) { return "notfound"; } return $"E1020 - Error fetching license key: {res.ReasonPhrase}"; } } catch (Exception ex) { if (ex is System.Net.Http.HttpRequestException) {//communications issue or rockfish down? if (!calledFromInternalJob) { //user wants to see this: var msg = $"E1020 - COMM Error fetching / installing license key: {ex.Message}"; //we want it in the log as this is on demand so it won't fill up the log and tech support might need to see this log.LogError(ex, msg); return msg; } else { //happened when called from JOB, don't want to fill up the log with this error so just logdebug it and move on var msg = $"E1020 - COMM Error fetching / installing license key (From Job)"; log.LogDebug(ex, msg); return msg; } } else { //some other error if (calledFromInternalJob) throw; var msg = "E1020 - Error fetching / installing license key"; log.LogError(ex, msg); return msg; } } } private static async Task DoFetchAsync(string LicenseUrlParameters, StringContent content, HttpClient client) { var LicenseServer = string.Empty; for (int x = 0; x < 4; x++) { try { switch (x) { case 0: LicenseServer = LICENSE_SERVER_URL_ROCKFISH; break; case 1: LicenseServer = LICENSE_SERVER_URL_IO; break; case 2: LicenseServer = LICENSE_SERVER_URL_EUROPA; break; case 3: LicenseServer = LICENSE_SERVER_URL_CALLISTO; break; } return await client.PostAsync($"{LicenseServer}{LicenseUrlParameters}", content); } catch (System.Net.Http.HttpRequestException) { if (x == 3) throw; } } return null; } /// /// Initialize the license /// /// internal static async Task InitializeAsync(AyaNova.Api.ControllerHelpers.ApiServerState apiServerState, AyContext ct, ILogger log) { log.LogDebug("Initializing license"); try { //First fetch the schema db id for the servers database, the license must match var schema = await ct.SchemaVersion.AsNoTracking().SingleOrDefaultAsync(); //if (schema == null || schema.Id == Guid.Empty) if (schema == null || string.IsNullOrWhiteSpace(schema.Id)) { //cryptic message deliberately, this is probably caused by someone trying to circumvent licensing var msg = "E1030 - Database integrity check failed (2). Contact support."; apiServerState.SetOpsOnly(msg); log.LogCritical(msg); return; } ServerDbId = schema.Id; //Fetch key from db as no tracking so doesn't hang round if need to immediately clear and then re-add the key Models.License ldb = await ct.License.AsNoTracking().SingleOrDefaultAsync(); //Non existent license should restrict server to ops routes only with closed API if (ldb == null) { ldb = new Models.License(); ldb.DbId = ServerDbId; ldb.Key = "none"; ldb.LicenseAgree = false; ct.License.Add(ldb); await ct.SaveChangesAsync(); } //ensure DB ID // if (ldb.DbId == Guid.Empty) if (string.IsNullOrWhiteSpace(ldb.DbId)) { ldb.DbId = ServerDbId; ldb.LicenseAgree = false; //Convert the no tracking record fetched above to tracking //this is required because a prior call to initialize before dumping the db would mean the license is still in memory in the context ct.Entry(ldb).State = Microsoft.EntityFrameworkCore.EntityState.Modified; await ct.SaveChangesAsync(); } //Get it early and set it here so that it can be displayed early to the user even if not licensed LicenseDbId = ldb.DbId; //someone must agree to the license on first login from the client, this stores that LicenseConsentRequired = !ldb.LicenseAgree; if (ldb.Key == "none") { var msg = "E1020 - License key not found in database, running in unlicensed mode"; apiServerState.SetSystemLock(msg); log.LogWarning(msg); return; } //Validate the key AyaNovaLicenseKey k = Parse(ldb.Key, log); if (k == null) { var msg = "E1020 - License key in database is not valid, running in unlicensed mode"; apiServerState.SetSystemLock(msg); log.LogCritical(msg); return; } _ActiveLicense = k; if (_ActiveLicense.LicenseExpired) { var msg = $"E1020 - License key expired {DateUtil.ServerDateTimeString(_ActiveLicense.LicenseExpiration)}"; apiServerState.SetSystemLock(msg); log.LogCritical(msg); return; } if (_ActiveLicense.Status == AyaNovaLicenseKey.LicenseStatus.Revoked) { var msg = $"E1020 - License key revoked"; apiServerState.SetSystemLock(msg); log.LogCritical(msg); return; } //Has someone been trying funny business with the active techs in the db? if (await AyaNova.Biz.UserBiz.ActiveCountAsync() > _ActiveLicense.ActiveNumber) { var msg = $"E1020 - Active count exceeded capacity"; apiServerState.SetSystemLock(msg); log.LogCritical(msg); return; } //Key is ok, might not have been on first boot so check and clear if locked //This works for now because system lock only means license lock //if ever changed for other purposes then need to handle that see serverstate for ideas if (apiServerState.IsSystemLocked) { apiServerState.ClearSystemLock(); } log.LogDebug("License key OK"); } catch (Exception ex) { var msg = "E1020 - Error initializing license key"; log.LogCritical(ex, msg); apiServerState.SetSystemLock(msg); throw new ApplicationException(msg, ex); } } /// /// Install key to db /// private static async Task InstallAsync(string RawTextNewKey, AyaNovaLicenseKey ParsedNewKey, AyaNova.Api.ControllerHelpers.ApiServerState apiServerState, AyContext ct, ILogger log) { try { var CurrentInDbKeyRecord = await ct.License.OrderBy(z => z.Id).FirstOrDefaultAsync(); if (CurrentInDbKeyRecord == null) throw new ApplicationException("E1020 - Can't install key, no key record found"); if (ParsedNewKey == null) { throw new ApplicationException("E1020 - License.Install -> key could not be parsed"); } //Can't install a trial into a non-empty db if (ParsedNewKey.TrialLicense && !await DbUtil.DBIsEmptyAsync(ct, log)) { throw new ApplicationException("E1020 - Can't install a trial key into a non empty AyaNova database. Erase the database first."); } //TECHCOUNT - new license causes exceeding count? long NewTechCount = ParsedNewKey.GetLicenseFeature(SERVICE_TECHS_FEATURE_NAME).Count; if (await AyaNova.Biz.UserBiz.ActiveCountAsync() > NewTechCount) { //attempt to set enough of the eldest last login techs to inactive await AyaNova.Biz.UserBiz.DeActivateExcessiveTechs(NewTechCount, log); if (await AyaNova.Biz.UserBiz.ActiveCountAsync() > NewTechCount) throw new ApplicationException("E1020 - Can't install key, too many active techs and / or subcontractors in database. Deactivate enough to install key."); } //Update current license CurrentInDbKeyRecord.Key = RawTextNewKey; await ct.SaveChangesAsync(); } catch (Exception ex) { var msg = "E1020 - Error installing license key"; log.LogError(ex, msg); throw new ApplicationException(msg, ex); } finally { await InitializeAsync(apiServerState, ct, log); } return true; } #endregion #region PARSE and Validate key /// /// Parses and validates the integrity of a passed in textual license key /// /// a populated key if valid or else null private static AyaNovaLicenseKey Parse(string k, ILogger log) { AyaNovaLicenseKey key = new AyaNovaLicenseKey(); log.LogDebug("Validating license"); if (string.IsNullOrWhiteSpace(k)) { throw new ApplicationException("E1020 - License.Parse -> License key is empty and can't be validated"); } try { if (!k.Contains("[KEY") || !k.Contains("KEY]") || !k.Contains("[SIGNATURE") || !k.Contains("SIGNATURE]")) { throw new ApplicationException("E1020 - License.Parse -> License key is missing required delimiters"); } string keyNoWS = System.Text.RegularExpressions.Regex.Replace(StringUtil.Extract(k, "[KEY", "KEY]").Trim(), "(\"(?:[^\"\\\\]|\\\\.)*\")|\\s+", "$1"); string keySig = StringUtil.Extract(k, "[SIGNATURE", "SIGNATURE]").Trim(); //bugbug second time around after installing key, keysig has cr/lf characters in it after this extract method runs, not sure wtf as it isnt there the first time #region Check Signature //***** NOTE: this is our real 2016 public key var publicPem = @"-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz7wrvLDcKVMZ31HFGBnL WL08IodYIV5VJkKy1Z0n2snprhSiu3izxTyz+SLpftvKHJpky027ii7l/pL9Bo3J cjU5rKrxXavnE7TuYPjXn16dNLd0K/ERSU+pXLmUaVN0nUWuGuUMoGJMEXoulS6p JiG11yu3BM9fL2Nbj0C6a+UwzEHFmns3J/daZOb4gAzMUdJfh9OJ0+wRGzR8ZxyC 99Na2gDmqYglUkSMjwLTL/HbgwF4OwmoQYJBcET0Wa6Gfb17SaF8XRBV5ZtpCsbS tkthGeoXZkFriB9c1eFQLKpBYQo2DW3H1MPG2nAlQZLbkJj5cSh7/t1bRF08m6P+ EQIDAQAB -----END PUBLIC KEY-----"; PemReader pr = new PemReader(new StringReader(publicPem)); var KeyParameter = (Org.BouncyCastle.Crypto.AsymmetricKeyParameter)pr.ReadObject(); var signer = SignerUtilities.GetSigner("SHA256WITHRSA"); signer.Init(false, KeyParameter); var expectedSig = Convert.FromBase64String(keySig); var msgBytes = Encoding.UTF8.GetBytes(keyNoWS); signer.BlockUpdate(msgBytes, 0, msgBytes.Length); if (!signer.VerifySignature(expectedSig)) { throw new ApplicationException("E1020 - License.Parse -> License key failed integrity check and is not valid"); } #endregion check signature #region Get Values Newtonsoft.Json.Linq.JToken token = Newtonsoft.Json.Linq.JObject.Parse(keyNoWS); key.LicenseFormat = (string)token.SelectToken("Key.LicenseFormat"); if (key.LicenseFormat != "8") throw new ApplicationException($"E1020 - License.Parse -> License key format {key.LicenseFormat} not recognized"); key.Id = (string)token.SelectToken("Key.Id"); key.RegisteredTo = (string)token.SelectToken("Key.RegisteredTo"); key.DbId = (string)token.SelectToken("Key.DBID"); if (key.DbId != ServerDbId) throw new ApplicationException($"E1020 - License.Parse -> License key does not match this server"); key.LicenseExpiration = (DateTime)token.SelectToken("Key.LicenseExpiration"); key.MaintenanceExpiration = (DateTime)token.SelectToken("Key.MaintenanceExpiration"); //FEATURES Newtonsoft.Json.Linq.JArray p = (Newtonsoft.Json.Linq.JArray)token.SelectToken("Key.Features"); for (int z = 0; z < p.Count; z++) { LicenseFeature lf = new LicenseFeature(); lf.Feature = (string)p[z].SelectToken("Name"); if (p[z].SelectToken("Count") != null) { lf.Count = (long)p[z].SelectToken("Count"); } else { lf.Count = 0; } key.Features.Add(lf); } #endregion get values //Check if attempting to use a build of AyaNova that is newer than maintenance subscription expiry if (MExBB(Util.FileUtil.GetLinkerTimestampUtc(System.Reflection.Assembly.GetExecutingAssembly()), key)) { Console.WriteLine("E1020 - Not licensed for this version of AyaNova. Fix: downgrade back to previous version in use or contact technical support for options."); //NOTE: VERSION-TOO-NEW bit below is checked for in startup.cs DO NOT remove this without fixing the side effects throw new ApplicationException("E1020 VERSION-TOO-NEW - Not licensed for this version of AyaNova. Fix: downgrade back to previous version in use or contact technical support for options."); } //All is well return key return key; } catch (Exception ex) { var msg = "E1020 - License key not valid"; log.LogError(ex, msg); throw new ApplicationException(msg, ex); } } #endregion #region Validate Build date against maintenance date in license //MaintenanceExpirationBeforeBuild? internal static bool MExBB(DateTime dtB, AyaNovaLicenseKey k) { //Is maintenance expiration date an earlier date than the current build date? //return true if a problem or false if ok //Do not worry about evaluation / trial licenses //users can upgrade them any time if (k.TrialLicense) return false; //Do not worry about rental SAAS licenses, only we can upgrade them if(k.RentalLicense) return false; //Ok, it's a perpetual license and not a trial so check away return k.MaintenanceExpiration < dtB; } #endregion }//eoc }//eons