221 lines
8.8 KiB
C#
221 lines
8.8 KiB
C#
using CloudBuilder.Core.DatabaseAccessor;
|
|
using CloudBuilder.Core.DatabaseAccessor.Entity;
|
|
using CloudBuilder.Core.DependencyInjection.Request;
|
|
using CloudBuilder.Core.Policy;
|
|
using CloudBuilder.Core.Service;
|
|
using CloudBuilder.Gui;
|
|
using CloudBuilder.Security.Entity;
|
|
using CloudBuilder.Security.Policy;
|
|
using CloudBuilder.Security.SearchCriteria;
|
|
using DocumentFormat.OpenXml.Spreadsheet;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
namespace CloudBuilder.Security.Service
|
|
{
|
|
public class SecurityLockService : ISecurityLockService
|
|
{
|
|
|
|
|
|
//if key is non string type, it is expected to convert to string before using this process
|
|
|
|
//usage:
|
|
//1. UI call Lock to hold entity, exception would be thrown if entity held by others
|
|
//2. upon finish edition, UI call ConsumeLock to free the reserved lock,
|
|
// exception would be thrown if the lock is overrided either by super user or after expiery
|
|
|
|
public const double ACTIVE_WINDOW_MINUTES = -30D;//allow lock 30 minutes
|
|
|
|
private SecurityMessagePolicy securityMessagePolicy;
|
|
private readonly IApplicationService service;
|
|
private readonly ILocaleMessageManager localeMessageManager;
|
|
private readonly IContext context;
|
|
private IRepository<SecurityLockEntity> repository;
|
|
public SecurityLockService(IApplicationService service, ILocaleMessageManager localeMessageManager, IContext context)
|
|
{
|
|
this.service = service;
|
|
this.localeMessageManager = localeMessageManager;
|
|
this.context = context;
|
|
repository = service.GetRepository<IRepository<SecurityLockEntity>>();
|
|
securityMessagePolicy = service.ServiceProvider.GetRequiredKeyedService<SecurityMessagePolicy>(typeof(SecurityMessagePolicy).Name);
|
|
}
|
|
|
|
[PermissionOperate(name: SecurityPermissionPolicy.SecurityLock, operate: Operate.Find)]
|
|
public SecurityLockEntity FindByKey(SecurityLockEntityKey key)
|
|
{
|
|
return repository.DetachedEntities.Where(x => 1 == 1
|
|
&& x.EntityName == key.EntityName
|
|
&& x.PkeyId == key.PkeyId
|
|
&& x.LockedByActor == key.LockedByActor
|
|
).FirstOrDefault()!;
|
|
}
|
|
|
|
//[PermissionOperate(name: SecurityPermissionPolicy.SecurityLock, operate: Operate.Find)]
|
|
public dynamic FindBySearchCriteria(SecurityLockSearchCriteria criteria)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
//[PermissionOperate(name: SecurityPermissionPolicy.SecurityLock, operate: Operate.Print)]
|
|
public dynamic PrintBySearchCriteria(SecurityLockSearchCriteria criteria)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
[PermissionOperate(name: SecurityPermissionPolicy.SecurityLock, operate: Operate.Insert)]
|
|
[UnitOfWork]
|
|
public SecurityLockEntity Insert(SecurityLockEntity data)
|
|
{
|
|
repository.InsertNow(data);
|
|
|
|
return data;
|
|
}
|
|
|
|
[PermissionOperate(name: SecurityPermissionPolicy.SecurityLock, operate: Operate.Update)]
|
|
[UnitOfWork]
|
|
public SecurityLockEntity Update(SecurityLockEntity data)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
|
|
[PermissionOperate(name: SecurityPermissionPolicy.SecurityLock, operate: Operate.Delete)]
|
|
public void Delete(SecurityLockEntityKey key)
|
|
{
|
|
SecurityLockEntity ent = repository.DetachedEntities.Where(x => 1 == 1
|
|
&& x.EntityName == key.EntityName
|
|
&& x.PkeyId == key.PkeyId
|
|
&& x.LockedByActor == key.LockedByActor
|
|
).FirstOrDefault()!;
|
|
if (ent != null) { repository.DeleteNow(ent); }
|
|
}
|
|
|
|
[AllowAnonymous]
|
|
public bool IsLocked(string entityName, string key)
|
|
{
|
|
SecurityLockEntity ent = null;
|
|
return IsLocked(entityName, key, out ent);
|
|
}
|
|
|
|
public bool IsLocked(string entityName, string key, out SecurityLockEntity ent)
|
|
{
|
|
//find active lock
|
|
ent = FindActiveLock(entityName, key);
|
|
if (ent != null) return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
private SecurityLockEntity FindActiveLock(string entityName, string key)
|
|
{
|
|
SecurityLockEntity entLock = FindSingleByEntityNameAndPkeyId(entityName, key);
|
|
|
|
if (entLock == null) return null;
|
|
|
|
DateTime effectiveDt = DateTime.Now.AddMinutes(ACTIVE_WINDOW_MINUTES);
|
|
if (entLock.CreatedDatetime.CompareTo(effectiveDt) < 0)
|
|
{
|
|
//it's an expired lock, remove it
|
|
repository.DeleteNow(entLock);
|
|
return null;
|
|
}
|
|
|
|
//it's an effective one
|
|
return entLock;
|
|
}
|
|
|
|
private SecurityLockEntity FindSingleByEntityNameAndPkeyId(string entityName, string key)
|
|
{
|
|
SecurityLockEntity[] entLocks = FindByEntityNameAndPkeyId(entityName, key);
|
|
if (entLocks == null || entLocks.Length <= 0) return null;
|
|
return entLocks[0];
|
|
}
|
|
|
|
private SecurityLockEntity[] FindByEntityNameAndPkeyId(string entityName, string key)
|
|
{
|
|
SecurityLockEntity[] entLocks = repository.DetachedEntities.Where(x => 1 == 1
|
|
&& x.EntityName == entityName
|
|
&& x.PkeyId == key
|
|
).OrderByDescending(x => x.CreatedDatetime).ToArray();
|
|
|
|
if (entLocks == null && entLocks.Length <= 0) return null;
|
|
|
|
return entLocks;
|
|
}
|
|
|
|
[AllowAnonymous]
|
|
public void Lock(string entityName, string key, string username)
|
|
{
|
|
Lock(entityName, key, username, false);
|
|
}
|
|
|
|
private void Lock(string entityName, string key, string username, bool forceLock)
|
|
{
|
|
if (!forceLock)
|
|
{
|
|
SecurityLockEntity actLock = FindActiveLock(entityName, key);
|
|
if (actLock != null && !actLock.LockedByActor.Equals(username))
|
|
{
|
|
//"{0} [{1}] is locked by [{2}] on [{3}]";
|
|
//throw new ValidatedException(entityName + " [" + key + "] is locked by [" + actLock.LockedByActor + "] on [" + actLock.CreatedDatetime + "]");
|
|
ISecurityUserService securityLockService = service.ServiceProvider.GetService<ISecurityUserService>()!;
|
|
var user = securityLockService.FindByStaffId(actLock.LockedByActor);
|
|
string lockedByActor = actLock.LockedByActor;
|
|
|
|
if (user != null) lockedByActor = user.UserLocalName;
|
|
|
|
throw new ValidatedException(securityMessagePolicy.ComposeMessage(SecurityMessagePolicy.LOCKED, localeMessageManager.ComposeMessage(typeof(SecurityLockService).Name, entityName), key,
|
|
lockedByActor,
|
|
actLock.CreatedDatetime.GetFormattedDateString("yyyy-MM-dd HH:mm:ss")));
|
|
}
|
|
|
|
}
|
|
|
|
ClearLock(entityName, key);
|
|
|
|
|
|
SecurityLockEntity newLock = new SecurityLockEntity();
|
|
newLock.EntityName = entityName;
|
|
newLock.PkeyId = key;
|
|
newLock.LockedByActor = username;
|
|
Insert(newLock);
|
|
}
|
|
|
|
[AllowAnonymous]
|
|
public void ForceLock(string entityName, string key, string username)
|
|
{
|
|
Lock(entityName, key, username, true);
|
|
}
|
|
|
|
[AllowAnonymous]
|
|
public void ConsumeLock(string entityName, string key, string username)
|
|
{
|
|
SecurityLockEntity actLock = FindActiveLock(entityName, key);
|
|
if (actLock == null) return;
|
|
if (!actLock.LockedByActor.Equals(username))
|
|
{
|
|
ISecurityUserService securityLockService = service.ServiceProvider.GetService<ISecurityUserService>()!;
|
|
var user = securityLockService.FindByStaffId(actLock.LockedByActor);
|
|
string lockedByActor = actLock.LockedByActor;
|
|
|
|
if (user != null) lockedByActor = user.UserLocalName;
|
|
|
|
throw new ValidatedException(securityMessagePolicy.ComposeMessage(SecurityMessagePolicy.EXPIRED, entityName, key, lockedByActor, actLock.CreatedDatetime));
|
|
//throw new ValidatedException("Your lock is expired or force removed. " + entityName + " [" + key + "] is locked by [" + actLock.LockedByActor + "] on [" + actLock.CreatedDatetime + "]");
|
|
}
|
|
|
|
ClearLock(entityName, key);
|
|
}
|
|
|
|
[AllowAnonymous]
|
|
public void ClearLock(string entityName, string key)
|
|
{
|
|
//find all the locks, then purge
|
|
SecurityLockEntity[] entities = FindByEntityNameAndPkeyId(entityName, key);
|
|
if (entities == null || entities.Length <= 0) return;
|
|
|
|
repository.DeleteNow(entities);
|
|
}
|
|
}
|
|
}
|