This commit is contained in:
2018-12-17 23:37:46 +00:00
parent 0514d86b4f
commit 677388910a
8 changed files with 140 additions and 107 deletions

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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)
// {

View File

@@ -86,7 +86,7 @@ namespace AyaNova.Generator
//=================================================================
try
{
await JobsBiz.ProcessJobsAsync(ct);
await JobsBiz.ProcessJobsAsync(ct, serverState);
}
catch (Exception ex)
{

View File

@@ -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;
}
}
}

View File

@@ -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");