This commit is contained in:
@@ -75,6 +75,7 @@ TODO CLIENT STUFF
|
||||
TODO SERVER STUFF
|
||||
|
||||
- Add license violation check - valid number of techs for license in multiple places
|
||||
- do a big seed and see if it fails
|
||||
|
||||
- Trial license ROCKFISH up it to 1000 techs to cover huge seeding.
|
||||
|
||||
|
||||
@@ -419,7 +419,7 @@ namespace AyaNova
|
||||
if (TESTING_REFRESH_DB)
|
||||
{
|
||||
AyaNova.Core.License.Fetch(apiServerState, dbContext, _log);
|
||||
Util.Seeder.SeedDatabase(Util.Seeder.SeedLevel.SmallOneManShopTrialDataSet, -8);//#############################################################################################
|
||||
Util.Seeder.SeedDatabase(Util.Seeder.SeedLevel.LargeCorporateMultiRegionalTrialDataSet, -8);//#############################################################################################
|
||||
}
|
||||
//TESTING
|
||||
#endif
|
||||
|
||||
@@ -282,7 +282,7 @@ namespace AyaNova.Biz
|
||||
/// Process all jobs (stock jobs and those found in operations table)
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal static async Task ProcessJobsAsync(AyContext ct)
|
||||
internal static async Task ProcessJobsAsync(AyContext ct, AyaNova.Api.ControllerHelpers.ApiServerState serverState)
|
||||
{
|
||||
//Flush metrics report before anything else happens
|
||||
log.LogTrace("Flushing metrics to reporters");
|
||||
@@ -344,7 +344,18 @@ namespace AyaNova.Biz
|
||||
//Health check / metrics
|
||||
await CoreJobMetricsSnapshot.DoJobAsync(ct);
|
||||
|
||||
//License check??
|
||||
//License check
|
||||
long CurrentActiveCount = UserBiz.ActiveCount;
|
||||
long LicensedUserCount = AyaNova.Core.License.ActiveKey.ActiveNumber;
|
||||
// log.LogInformation("JobsBiz::Checking license active count");
|
||||
if (CurrentActiveCount > LicensedUserCount)
|
||||
{
|
||||
var msg = $"E1020 - Active count exceeded capacity";
|
||||
serverState.SetSystemLock(msg);
|
||||
log.LogCritical(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
//Notifications
|
||||
|
||||
|
||||
@@ -30,9 +30,14 @@ namespace AyaNova.Biz
|
||||
SeedOrImportRelaxedRulesMode = false;//default
|
||||
}
|
||||
|
||||
//todo:
|
||||
//then after that go into widget and anywhere else that there is this associated type code being called for event and search and implement everywhere,
|
||||
//then update seeder code to use it and get back on to the main critical path again in the todo
|
||||
//This is where active tech license consumers are accounted for
|
||||
internal static long ActiveCount
|
||||
{
|
||||
get
|
||||
{
|
||||
return ServiceProviderProvider.DBContext.User.Where(x => x.Active == true && (x.UserType == UserType.Schedulable || x.UserType == UserType.Subcontractor)).LongCount();
|
||||
}
|
||||
}
|
||||
|
||||
internal static UserBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext)
|
||||
{
|
||||
@@ -53,35 +58,34 @@ namespace AyaNova.Biz
|
||||
inObj.Salt = Hasher.GenerateSalt();
|
||||
inObj.Password = Hasher.hash(inObj.Salt, inObj.Password);
|
||||
|
||||
inObj.OwnerId = UserId;
|
||||
inObj.Tags = TagUtil.NormalizeTags(inObj.Tags);
|
||||
//Seeder sets user options in advance so no need to create them here in that case
|
||||
if (inObj.UserOptions == null)
|
||||
inObj.UserOptions = new UserOptions(UserId);
|
||||
|
||||
|
||||
Validate(inObj, null);
|
||||
if (HasErrors)
|
||||
return null;
|
||||
else
|
||||
{
|
||||
//do stuff with User
|
||||
User outObj = inObj;
|
||||
outObj.OwnerId = UserId;
|
||||
outObj.Tags = TagUtil.NormalizeTags(outObj.Tags);
|
||||
//Seeder sets user options in advance so no need to create them here in that case
|
||||
if (outObj.UserOptions == null)
|
||||
outObj.UserOptions = new UserOptions(UserId);
|
||||
|
||||
await ct.User.AddAsync(outObj);
|
||||
await ct.User.AddAsync(inObj);
|
||||
//save to get Id
|
||||
await ct.SaveChangesAsync();
|
||||
|
||||
//Handle child and associated items
|
||||
|
||||
//Log event
|
||||
EventLogProcessor.LogEventToDatabase(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct);
|
||||
EventLogProcessor.LogEventToDatabase(new Event(UserId, inObj.Id, BizType, AyaEvent.Created), ct);
|
||||
|
||||
//SEARCH INDEXING
|
||||
//Search.ProcessNewObjectKeywords( UserLocaleId, outObj.Id, BizType, outObj.Name, outObj.EmployeeNumber, outObj.Notes, outObj.Name);
|
||||
var SearchParams = new Search.SearchIndexProcessObjectParameters(UserLocaleId, outObj.Id, BizType, outObj.Name);
|
||||
SearchParams.AddWord(outObj.Notes).AddWord(outObj.Name).AddWord(outObj.EmployeeNumber).AddWord(outObj.Tags);
|
||||
//SEARCH INDEXING
|
||||
var SearchParams = new Search.SearchIndexProcessObjectParameters(UserLocaleId, inObj.Id, BizType, inObj.Name);
|
||||
SearchParams.AddWord(inObj.Notes).AddWord(inObj.Name).AddWord(inObj.EmployeeNumber).AddWord(inObj.Tags);
|
||||
Search.ProcessNewObjectKeywords(SearchParams);
|
||||
|
||||
return outObj;
|
||||
return inObj;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -93,36 +97,34 @@ namespace AyaNova.Biz
|
||||
//This is a new user so it will have been posted with a password in plaintext which needs to be salted and hashed
|
||||
inObj.Salt = Hasher.GenerateSalt();
|
||||
inObj.Password = Hasher.hash(inObj.Salt, inObj.Password);
|
||||
inObj.OwnerId = UserId;
|
||||
inObj.Tags = TagUtil.NormalizeTags(inObj.Tags);
|
||||
//Seeder sets user options in advance so no need to create them here in that case
|
||||
if (inObj.UserOptions == null)
|
||||
inObj.UserOptions = new UserOptions(UserId);
|
||||
|
||||
Validate(inObj, null);
|
||||
if (HasErrors)
|
||||
return null;
|
||||
else
|
||||
{
|
||||
//do stuff with User
|
||||
User outObj = inObj;
|
||||
outObj.OwnerId = UserId;
|
||||
outObj.Tags = TagUtil.NormalizeTags(outObj.Tags);
|
||||
//Seeder sets user options in advance so no need to create them here in that case
|
||||
if (outObj.UserOptions == null)
|
||||
outObj.UserOptions = new UserOptions(UserId);
|
||||
|
||||
TempContext.User.Add(outObj);
|
||||
TempContext.User.Add(inObj);
|
||||
|
||||
//save to get Id
|
||||
TempContext.SaveChanges();
|
||||
|
||||
//Handle child and associated items
|
||||
|
||||
//Log event
|
||||
EventLogProcessor.LogEventToDatabase(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), TempContext);
|
||||
EventLogProcessor.LogEventToDatabase(new Event(UserId, inObj.Id, BizType, AyaEvent.Created), TempContext);
|
||||
|
||||
//SEARCH INDEXING
|
||||
// Search.ProcessNewObjectKeywords(UserLocaleId, outObj.Id, BizType, outObj.Name, outObj.EmployeeNumber, outObj.Notes, outObj.Name);
|
||||
var SearchParams = new Search.SearchIndexProcessObjectParameters(UserLocaleId, outObj.Id, BizType, outObj.Name);
|
||||
SearchParams.AddWord(outObj.Notes).AddWord(outObj.Name).AddWord(outObj.EmployeeNumber).AddWord(outObj.Tags);
|
||||
var SearchParams = new Search.SearchIndexProcessObjectParameters(UserLocaleId, inObj.Id, BizType, inObj.Name);
|
||||
SearchParams.AddWord(inObj.Notes).AddWord(inObj.Name).AddWord(inObj.EmployeeNumber).AddWord(inObj.Tags);
|
||||
Search.ProcessNewObjectKeywords(SearchParams);
|
||||
|
||||
return outObj;
|
||||
return inObj;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -229,51 +231,6 @@ namespace AyaNova.Biz
|
||||
//get picklist (paged)
|
||||
internal ApiPagedResponse<NameIdItem> GetPickList(IUrlHelper Url, string routeName, PagingOptions pagingOptions)
|
||||
{
|
||||
// pagingOptions.Offset = pagingOptions.Offset ?? PagingOptions.DefaultOffset;
|
||||
// pagingOptions.Limit = pagingOptions.Limit ?? PagingOptions.DefaultLimit;
|
||||
|
||||
// NameIdItem[] items;
|
||||
// int totalRecordCount = 0;
|
||||
|
||||
// if (!string.IsNullOrWhiteSpace(q))
|
||||
// {
|
||||
// items = await ct.User
|
||||
// .AsNoTracking()
|
||||
// .Where(m => EF.Functions.ILike(m.Name, q))
|
||||
// .OrderBy(m => m.Name)
|
||||
// .Skip(pagingOptions.Offset.Value)
|
||||
// .Take(pagingOptions.Limit.Value)
|
||||
// .Select(m => new NameIdItem()
|
||||
// {
|
||||
// Id = m.Id,
|
||||
// Name = m.Name
|
||||
// }).ToArrayAsync();
|
||||
|
||||
// totalRecordCount = await ct.User.Where(m => EF.Functions.ILike(m.Name, q)).CountAsync();
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// items = await ct.User
|
||||
// .AsNoTracking()
|
||||
// .OrderBy(m => m.Name)
|
||||
// .Skip(pagingOptions.Offset.Value)
|
||||
// .Take(pagingOptions.Limit.Value)
|
||||
// .Select(m => new NameIdItem()
|
||||
// {
|
||||
// Id = m.Id,
|
||||
// Name = m.Name
|
||||
// }).ToArrayAsync();
|
||||
|
||||
// totalRecordCount = await ct.User.CountAsync();
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// var pageLinks = new PaginationLinkBuilder(Url, routeName, null, pagingOptions, totalRecordCount).PagingLinksObject();
|
||||
|
||||
// ApiPagedResponse<NameIdItem> pr = new ApiPagedResponse<NameIdItem>(items, pageLinks);
|
||||
// return pr;
|
||||
|
||||
pagingOptions.Offset = pagingOptions.Offset ?? PagingOptions.DefaultOffset;
|
||||
pagingOptions.Limit = pagingOptions.Limit ?? PagingOptions.DefaultLimit;
|
||||
|
||||
@@ -287,8 +244,6 @@ namespace AyaNova.Biz
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//UPDATE
|
||||
//
|
||||
@@ -301,24 +256,24 @@ namespace AyaNova.Biz
|
||||
inObj.OwnerId = dbObj.OwnerId;
|
||||
|
||||
//Get a snapshot of the original db value object before changes
|
||||
User SnapshotObj = new User();
|
||||
CopyObject.Copy(dbObj, SnapshotObj);
|
||||
User SnapshotOfOriginalDBObj = new User();
|
||||
CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj);
|
||||
|
||||
//Update the db object with the PUT object values
|
||||
CopyObject.Copy(inObj, dbObj, "Id, Salt");
|
||||
dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags);
|
||||
|
||||
//Is the user updating the password?
|
||||
if (!string.IsNullOrWhiteSpace(inObj.Password) && SnapshotObj.Password != inObj.Password)
|
||||
if (!string.IsNullOrWhiteSpace(inObj.Password) && SnapshotOfOriginalDBObj.Password != inObj.Password)
|
||||
{
|
||||
//YES password is being updated:
|
||||
dbObj.Password = Hasher.hash(SnapshotObj.Salt, inObj.Password);
|
||||
dbObj.Password = Hasher.hash(SnapshotOfOriginalDBObj.Salt, inObj.Password);
|
||||
}
|
||||
else
|
||||
{
|
||||
//No, use the snapshot password value
|
||||
dbObj.Password = SnapshotObj.Password;
|
||||
dbObj.Salt = SnapshotObj.Salt;
|
||||
dbObj.Password = SnapshotOfOriginalDBObj.Password;
|
||||
dbObj.Salt = SnapshotOfOriginalDBObj.Salt;
|
||||
}
|
||||
|
||||
|
||||
@@ -326,7 +281,7 @@ namespace AyaNova.Biz
|
||||
//this will allow EF to check it out
|
||||
ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = inObj.ConcurrencyToken;
|
||||
|
||||
Validate(dbObj, SnapshotObj);
|
||||
Validate(dbObj, SnapshotOfOriginalDBObj);
|
||||
if (HasErrors)
|
||||
return false;
|
||||
|
||||
@@ -349,22 +304,22 @@ namespace AyaNova.Biz
|
||||
if (!ValidateJsonPatch<User>.Validate(this, objectPatch)) return false;
|
||||
|
||||
//make a snapshot of the original for validation but update the original to preserve workflow
|
||||
User snapshotObj = new User();
|
||||
CopyObject.Copy(dbObj, snapshotObj);
|
||||
User SnapshotOfOriginalDBObj = new User();
|
||||
CopyObject.Copy(dbObj, SnapshotOfOriginalDBObj);
|
||||
|
||||
//Do the patching
|
||||
objectPatch.ApplyTo(dbObj);
|
||||
dbObj.Tags = TagUtil.NormalizeTags(dbObj.Tags);
|
||||
|
||||
//Is the user patching the password?
|
||||
if (!string.IsNullOrWhiteSpace(dbObj.Password) && dbObj.Password != snapshotObj.Password)
|
||||
if (!string.IsNullOrWhiteSpace(dbObj.Password) && dbObj.Password != SnapshotOfOriginalDBObj.Password)
|
||||
{
|
||||
//YES password is being updated:
|
||||
dbObj.Password = Hasher.hash(dbObj.Salt, dbObj.Password);
|
||||
}
|
||||
|
||||
ct.Entry(dbObj).OriginalValues["ConcurrencyToken"] = concurrencyToken;
|
||||
Validate(dbObj, snapshotObj);
|
||||
Validate(dbObj, SnapshotOfOriginalDBObj);
|
||||
if (HasErrors)
|
||||
return false;
|
||||
|
||||
@@ -424,11 +379,36 @@ namespace AyaNova.Biz
|
||||
bool isNew = currentObj == null;
|
||||
|
||||
|
||||
if (isNew) //Yes, no currentObj
|
||||
{
|
||||
|
||||
//do we need to check the license situation?
|
||||
if (proposedObj.IsTech && proposedObj.Active)
|
||||
{
|
||||
//Yes, it might be affected depending on things
|
||||
long CurrentActiveCount = UserBiz.ActiveCount;
|
||||
long LicensedUserCount = AyaNova.Core.License.ActiveKey.ActiveNumber;
|
||||
|
||||
if (isNew)
|
||||
{
|
||||
//This operation is about to consume one more license, check that we are not at the limit already
|
||||
CheckActiveForValidation(CurrentActiveCount, LicensedUserCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
//did anything that might affect licensing change?
|
||||
if (!currentObj.IsTech || (!currentObj.Active))//currently not a tech or if it is it's not active
|
||||
{
|
||||
//going from non tech to tech and active
|
||||
//Yes, this is about to consume one more license, check that we are not at the limit already
|
||||
CheckActiveForValidation(CurrentActiveCount, LicensedUserCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// TODO: check user count if not new to see if affected that way
|
||||
//also check user count in general to see if it's exceeded
|
||||
//And maybe check it in login as well as a good central spot or wherever makes sense
|
||||
|
||||
//OwnerId required
|
||||
if (!isNew)
|
||||
{
|
||||
@@ -543,6 +523,14 @@ namespace AyaNova.Biz
|
||||
return;
|
||||
}
|
||||
|
||||
private void CheckActiveForValidation(long CurrentActiveCount, long LicensedUserCount)
|
||||
{
|
||||
if (CurrentActiveCount >= LicensedUserCount)
|
||||
{
|
||||
AddError(ValidationErrorType.InvalidOperation, null, "LT:ErrorSecurityUserCapacity");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Can delete?
|
||||
private void ValidateCanDelete(User inObj)
|
||||
@@ -560,7 +548,7 @@ namespace AyaNova.Biz
|
||||
//There's only one rule - have they done anything eventlog worthy yet?
|
||||
if (ct.Event.Select(m => m).Where(m => m.OwnerId == inObj.Id).Count() > 0)
|
||||
{
|
||||
AddError(ValidationErrorType.InvalidOperation, "user", "[E_ACTIVE_NOT_DELETABLE] This user shows activity in the database and can not be deleted. Set inactive instead.");
|
||||
AddError(ValidationErrorType.InvalidOperation, "user", "LT:ErrorDBForeignKeyViolation");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -332,6 +332,8 @@ namespace AyaNova.Biz
|
||||
//run validation and biz rules
|
||||
if (isNew)
|
||||
{
|
||||
//WARNING: this is not really the "current" object, it's been modified already by caller
|
||||
|
||||
// //NEW widgets must be active
|
||||
// if (inObj.Active == null || ((bool)inObj.Active) == false)
|
||||
// {
|
||||
|
||||
@@ -86,7 +86,7 @@ namespace AyaNova.Generator
|
||||
//=================================================================
|
||||
try
|
||||
{
|
||||
await JobsBiz.ProcessJobsAsync(ct);
|
||||
await JobsBiz.ProcessJobsAsync(ct, serverState);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -46,7 +46,15 @@ namespace AyaNova.Models
|
||||
|
||||
public User()
|
||||
{
|
||||
Tags = new List<string>();
|
||||
Tags = new List<string>();
|
||||
}
|
||||
|
||||
public bool IsTech
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.UserType == UserType.Subcontractor || this.UserType == UserType.Schedulable;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -104,6 +104,13 @@ namespace AyaNova.Core
|
||||
return null;
|
||||
}
|
||||
|
||||
public long ActiveNumber
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetLicenseFeature(SERVICE_TECHS_FEATURE_NAME).Count;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check for the existance of license feature
|
||||
@@ -446,7 +453,7 @@ namespace AyaNova.Core
|
||||
|
||||
if (ldb.Key == "none")
|
||||
{
|
||||
var msg = "License key not found in database, running in unlicensed mode";
|
||||
var msg = "E1020 - License key not found in database, running in unlicensed mode";
|
||||
apiServerState.SetSystemLock(msg);
|
||||
log.LogWarning(msg);
|
||||
return;
|
||||
@@ -456,9 +463,9 @@ namespace AyaNova.Core
|
||||
AyaNovaLicenseKey k = Parse(ldb.Key, log);
|
||||
if (k == null)
|
||||
{
|
||||
var msg = "Error: License key in database is not valid, running in unlicensed mode";
|
||||
var msg = "E1020 - License key in database is not valid, running in unlicensed mode";
|
||||
apiServerState.SetSystemLock(msg);
|
||||
log.LogError(msg);
|
||||
log.LogCritical(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -466,9 +473,18 @@ namespace AyaNova.Core
|
||||
|
||||
if (_ActiveLicense.LicenseExpired)
|
||||
{
|
||||
var msg = $"License key expired {DateUtil.ServerDateTimeString(_ActiveLicense.LicenseExpiration)}";
|
||||
var msg = $"E1020 - License key expired {DateUtil.ServerDateTimeString(_ActiveLicense.LicenseExpiration)}";
|
||||
apiServerState.SetSystemLock(msg);
|
||||
log.LogWarning(msg);
|
||||
log.LogCritical(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
//Has someone been trying funny business with the active techs in the db?
|
||||
if (AyaNova.Biz.UserBiz.ActiveCount > _ActiveLicense.ActiveNumber)
|
||||
{
|
||||
var msg = $"E1020 - Active count exceeded capacity";
|
||||
apiServerState.SetSystemLock(msg);
|
||||
log.LogCritical(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -484,7 +500,8 @@ namespace AyaNova.Core
|
||||
catch (Exception ex)
|
||||
{
|
||||
var msg = "E1020 - Error initializing license key";
|
||||
log.LogError(ex, msg);
|
||||
log.LogCritical(ex, msg);
|
||||
apiServerState.SetSystemLock(msg);
|
||||
throw new ApplicationException(msg, ex);
|
||||
}
|
||||
}
|
||||
@@ -503,7 +520,7 @@ namespace AyaNova.Core
|
||||
|
||||
if (ParsedNewKey == null)
|
||||
{
|
||||
throw new ApplicationException("License.Install -> key could not be parsed");
|
||||
throw new ApplicationException("E1020 - License.Install -> key could not be parsed");
|
||||
}
|
||||
|
||||
//Can't install a trial into a non-empty db
|
||||
@@ -512,6 +529,12 @@ namespace AyaNova.Core
|
||||
throw new ApplicationException("E1020 - Can't install a trial key into a non empty AyaNova database. Erase the database first.");
|
||||
}
|
||||
|
||||
//TODO: TECHCOUNT - new license causes exceeding count?
|
||||
if (AyaNova.Biz.UserBiz.ActiveCount > ParsedNewKey.GetLicenseFeature(SERVICE_TECHS_FEATURE_NAME).Count)
|
||||
{
|
||||
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;
|
||||
//LOOKAT: reason, resultcode etc
|
||||
@@ -547,7 +570,7 @@ namespace AyaNova.Core
|
||||
|
||||
if (string.IsNullOrWhiteSpace(k))
|
||||
{
|
||||
throw new ApplicationException("License.Parse -> License key is empty and can't be validated");
|
||||
throw new ApplicationException("E1020 - License.Parse -> License key is empty and can't be validated");
|
||||
}
|
||||
|
||||
try
|
||||
@@ -557,7 +580,7 @@ namespace AyaNova.Core
|
||||
!k.Contains("[SIGNATURE") ||
|
||||
!k.Contains("SIGNATURE]"))
|
||||
{
|
||||
throw new ApplicationException("License.Parse -> License key is missing required delimiters");
|
||||
throw new ApplicationException("E1020 - License.Parse -> License key is missing required delimiters");
|
||||
|
||||
}
|
||||
|
||||
@@ -588,7 +611,7 @@ EQIDAQAB
|
||||
signer.BlockUpdate(msgBytes, 0, msgBytes.Length);
|
||||
if (!signer.VerifySignature(expectedSig))
|
||||
{
|
||||
throw new ApplicationException("License.Parse -> License key failed integrity check and is not valid");
|
||||
throw new ApplicationException("E1020 - License.Parse -> License key failed integrity check and is not valid");
|
||||
}
|
||||
|
||||
#endregion check signature
|
||||
@@ -598,7 +621,7 @@ EQIDAQAB
|
||||
|
||||
key.LicenseFormat = (string)token.SelectToken("Key.LicenseFormat");
|
||||
if (key.LicenseFormat != "2018")
|
||||
throw new ApplicationException($"License.Parse -> License key format {key.LicenseFormat} not recognized");
|
||||
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 = (Guid)token.SelectToken("Key.DBID");
|
||||
|
||||
Reference in New Issue
Block a user