547 lines
19 KiB
C#
547 lines
19 KiB
C#
using EnVisage.Code.Exceptions;
|
|
using Newtonsoft.Json;
|
|
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Net.Http;
|
|
using System.Net.Http.Headers;
|
|
using System.Threading.Tasks;
|
|
using System.Web.Mvc;
|
|
using System.Xml;
|
|
using System.Xml.Serialization;
|
|
using Audit.Core;
|
|
using Audit.Mvc;
|
|
using EnVisage.Code.Audit.Attributes;
|
|
using EnVisage.Code.Audit.Configuration;
|
|
using EnVisage.Code.Audit.DataProviders;
|
|
using Prevu.Core.Audit.Model;
|
|
using NLog;
|
|
using Prevu.Core.Audit.Model.ResponseModels;
|
|
|
|
namespace EnVisage.Code
|
|
{
|
|
public class AuditProxy
|
|
{
|
|
#region Private
|
|
|
|
private static int _unauthorizedRequestsCount = 0;
|
|
private static readonly int _maxUnauthorizedAttempts = 2;
|
|
|
|
private static string _apiUrl = null;
|
|
private static string _tenantId = null;
|
|
private static string _password = null;
|
|
private static string _domainId = null;
|
|
private static string _accessToken = null;
|
|
|
|
private static readonly ConcurrentDictionary<string, List<HistorySaveDetail>> _historyCache = new ConcurrentDictionary<string, List<HistorySaveDetail>>();
|
|
private static readonly ConcurrentDictionary<string, List<EventRequestBaseModel>> _eventCache = new ConcurrentDictionary<string, List<EventRequestBaseModel>>();
|
|
|
|
private static readonly Logger _logger = LogManager.GetCurrentClassLogger();
|
|
|
|
#endregion
|
|
|
|
#region Models
|
|
|
|
public class AuditSaveModel
|
|
{
|
|
public string EntityId { get; set; }
|
|
|
|
public string UserId { get; set; }
|
|
|
|
public string Data { get; set; }
|
|
|
|
public int Version { get; set; }
|
|
|
|
public string TenantId { get; set; }
|
|
|
|
public string DomainId { get; set; }
|
|
|
|
public string EventType { get; set; }
|
|
|
|
public Int64 Duration { get; set; }
|
|
|
|
public int? ResponseStatusCode { get; set; }
|
|
}
|
|
|
|
public class HistorySaveModel
|
|
{
|
|
public string TransactionId { get; set; }
|
|
public string ModifiedBy { get; set; }
|
|
public string DomainId { get; set; }
|
|
public string TenantId { get; set; }
|
|
public List<HistorySaveModelEntity> Entities { get; set; }
|
|
}
|
|
|
|
public class HistorySaveModelEntity
|
|
{
|
|
public string EntityId { get; set; }
|
|
public string Details { get; set; }
|
|
}
|
|
|
|
public class HistorySaveDetailType
|
|
{
|
|
public string DetailType { get; set; }
|
|
public List<HistorySaveDetail> Details { get; set; }
|
|
}
|
|
|
|
public class HistorySaveDetail
|
|
{
|
|
[XmlIgnore]
|
|
public string GroupKey { get; set; }
|
|
public string DetailId { get; set; }
|
|
[XmlIgnore]
|
|
public string DetailType { get; set; }
|
|
public string ModificationType { get; set; }
|
|
public List<HistorySaveItemPropertyModel> Properties { get; set; } = new List<HistorySaveItemPropertyModel>();
|
|
}
|
|
|
|
public class HistorySaveItemPropertyModel
|
|
{
|
|
public string Name { get; set; }
|
|
public string OldValue { get; set; }
|
|
public string NewValue { get; set; }
|
|
}
|
|
|
|
#endregion
|
|
|
|
public static void Initialize(string apiUrl, string tenantId, string password, string domainId)
|
|
{
|
|
#region Arguments Validation
|
|
|
|
if (string.IsNullOrWhiteSpace(apiUrl))
|
|
throw new ArgumentNullException(nameof(apiUrl));
|
|
|
|
if (string.IsNullOrWhiteSpace(tenantId))
|
|
throw new ArgumentNullException(nameof(tenantId));
|
|
|
|
if (string.IsNullOrWhiteSpace(password))
|
|
throw new ArgumentNullException(nameof(password));
|
|
|
|
if (string.IsNullOrWhiteSpace(domainId))
|
|
throw new ArgumentNullException(nameof(domainId));
|
|
|
|
#endregion
|
|
|
|
_apiUrl = apiUrl;
|
|
_tenantId = tenantId;
|
|
_password = password;
|
|
_domainId = domainId;
|
|
}
|
|
|
|
#region History
|
|
public static void LogHistory(string transactionId, string groupKey, string entityId, string entityType, string modificationType, IEnumerable<HistorySaveItemPropertyModel> properties)
|
|
{
|
|
#region Arguments Validation
|
|
|
|
if (string.IsNullOrWhiteSpace(transactionId))
|
|
throw new ArgumentNullException(nameof(transactionId));
|
|
|
|
if (string.IsNullOrWhiteSpace(groupKey) && string.IsNullOrWhiteSpace(entityId))
|
|
throw new ArgumentException("Either groupKey or entityId must exist");
|
|
|
|
if (string.IsNullOrWhiteSpace(entityType))
|
|
throw new ArgumentNullException(nameof(entityType));
|
|
|
|
if (string.IsNullOrWhiteSpace(modificationType))
|
|
throw new ArgumentNullException(nameof(modificationType));
|
|
|
|
#endregion
|
|
|
|
var saveModel = new HistorySaveDetail
|
|
{
|
|
GroupKey = groupKey,
|
|
DetailId = entityId,
|
|
DetailType = entityType,
|
|
ModificationType = modificationType,
|
|
};
|
|
if (properties != null)
|
|
saveModel.Properties.AddRange(properties);
|
|
|
|
var saveModelList = new List<HistorySaveDetail> { saveModel };
|
|
|
|
LogHistory(transactionId, saveModelList);
|
|
}
|
|
|
|
public static void LogHistory(string transactionId, IEnumerable<HistorySaveDetail> entities)
|
|
{
|
|
#region Arguments Validation
|
|
|
|
if (string.IsNullOrWhiteSpace(transactionId))
|
|
throw new ArgumentNullException(nameof(transactionId));
|
|
|
|
if (entities == null || !entities.Any())
|
|
throw new ArgumentNullException(nameof(entities));
|
|
|
|
#endregion
|
|
|
|
if (!_historyCache.ContainsKey(transactionId))
|
|
_historyCache[transactionId] = new List<HistorySaveDetail>();
|
|
|
|
_historyCache[transactionId].AddRange(entities);
|
|
}
|
|
|
|
|
|
public static async Task CommitHistoryChanges(string transactionId, string userId)
|
|
{
|
|
#region Arguments Validation
|
|
|
|
if (string.IsNullOrWhiteSpace(transactionId))
|
|
throw new ArgumentNullException(nameof(transactionId));
|
|
|
|
if (string.IsNullOrWhiteSpace(userId))
|
|
throw new ArgumentNullException(nameof(userId));
|
|
|
|
#endregion
|
|
|
|
if (!_historyCache.ContainsKey(transactionId))
|
|
return;
|
|
|
|
var entities = _historyCache[transactionId];
|
|
var details = entities.GroupBy(x => x.GroupKey ?? x.DetailId)
|
|
.ToDictionary(x => x.Key, g => g.GroupBy(s => s.DetailType)
|
|
.ToDictionary(s => s.Key, s => s.ToList()));
|
|
|
|
var saveModel = new HistorySaveModel
|
|
{
|
|
DomainId = _domainId,
|
|
TenantId = _tenantId,
|
|
TransactionId = transactionId,
|
|
ModifiedBy = userId,
|
|
Entities = details.Select(x => new HistorySaveModelEntity
|
|
{
|
|
EntityId = x.Key,
|
|
Details = x.Value.Select(s => new HistorySaveDetailType
|
|
{
|
|
DetailType = s.Key,
|
|
Details = s.Value
|
|
}).ToList().Serialize()
|
|
}).ToList()
|
|
};
|
|
|
|
var result = await AuthorizeAndMakeRequest(async (accessToken) =>
|
|
{
|
|
using (var client = CreateAuditClient(accessToken))
|
|
{
|
|
return await client.PostAsJsonAsync("History/Log", saveModel);
|
|
}
|
|
});
|
|
if (result == null)
|
|
throw new Exception("LogHistory result is undefined");
|
|
|
|
if (!result.IsSuccessStatusCode)
|
|
{
|
|
var message = $@"
|
|
Message: {result.ReasonPhrase}
|
|
Model: {saveModel.Serialize()}";
|
|
|
|
_logger.Log(LogLevel.Info, message);
|
|
}
|
|
|
|
ClearHistoryChanges(transactionId);
|
|
}
|
|
|
|
public static void ClearHistoryChanges(string transactionId)
|
|
{
|
|
#region Arguments Validation
|
|
|
|
if (string.IsNullOrWhiteSpace(transactionId))
|
|
throw new ArgumentNullException(nameof(transactionId));
|
|
|
|
#endregion
|
|
|
|
var value = new List<HistorySaveDetail>();
|
|
if (_historyCache.ContainsKey(transactionId))
|
|
_historyCache.TryRemove(transactionId, out value);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Audit
|
|
public static async Task LogAudit(AuditEvent entity, string userId)
|
|
{
|
|
#region Arguments Validation
|
|
|
|
if (string.IsNullOrWhiteSpace(userId))
|
|
throw new ArgumentNullException(nameof(userId));
|
|
|
|
if (entity == null)
|
|
throw new ArgumentNullException(nameof(entity));
|
|
|
|
#endregion
|
|
|
|
|
|
var data = entity.ToJson();
|
|
var dataXml = JsonConvert.DeserializeXmlNode(data, "root");
|
|
|
|
var saveModel = new AuditSaveModel
|
|
{
|
|
DomainId = _domainId,
|
|
TenantId = _tenantId,
|
|
UserId = userId,
|
|
Data = dataXml.InnerXml,
|
|
Duration = entity.Duration,
|
|
EventType = entity.EventType,
|
|
ResponseStatusCode = entity.GetMvcAuditAction().ResponseStatusCode
|
|
};
|
|
|
|
var result = await AuthorizeAndMakeRequest(async (accessToken) =>
|
|
{
|
|
using (var client = CreateAuditClient(accessToken))
|
|
{
|
|
return await client.PostAsJsonAsync("Audit/Log", saveModel);
|
|
}
|
|
});
|
|
if (result == null)
|
|
throw new Exception("LogHistory result is undefined");
|
|
|
|
if (!result.IsSuccessStatusCode)
|
|
{
|
|
var message = $@"
|
|
Message: {result.ReasonPhrase}
|
|
Model: {saveModel.Serialize()}";
|
|
|
|
_logger.Log(LogLevel.Info, message);
|
|
}
|
|
}
|
|
public static void InitializeAudit()
|
|
{
|
|
var configuration = AuditConfigurationSection.Current;
|
|
if (configuration.Enabled)
|
|
{
|
|
GlobalFilters.Filters.Add(new PrevuAuditAttribute());
|
|
Configuration.Setup().UseCustomProvider(new AuditCustomDataProvider());
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Events
|
|
|
|
public static void LogEvent(string transactionId, EventRequestBaseModel eventModel)
|
|
{
|
|
#region Arguments Validation
|
|
|
|
if (string.IsNullOrWhiteSpace(transactionId))
|
|
throw new ArgumentNullException(nameof(transactionId));
|
|
|
|
if (eventModel == null)
|
|
return;
|
|
|
|
#endregion
|
|
|
|
if (!_eventCache.ContainsKey(transactionId))
|
|
_eventCache[transactionId] = new List<EventRequestBaseModel>();
|
|
|
|
_eventCache[transactionId].Add(eventModel);
|
|
}
|
|
|
|
public static void LogEvent(string transactionId, IEnumerable<EventRequestBaseModel> eventModels)
|
|
{
|
|
#region Arguments Validation
|
|
|
|
if (string.IsNullOrWhiteSpace(transactionId))
|
|
throw new ArgumentNullException(nameof(transactionId));
|
|
|
|
if (eventModels == null || !eventModels.Any())
|
|
return;
|
|
|
|
#endregion
|
|
|
|
if (!_eventCache.ContainsKey(transactionId))
|
|
_eventCache[transactionId] = new List<EventRequestBaseModel>();
|
|
|
|
_eventCache[transactionId].AddRange(eventModels);
|
|
}
|
|
|
|
public static async Task CommitEventChanges(string transactionId)
|
|
{
|
|
#region Arguments Validation
|
|
|
|
if (string.IsNullOrWhiteSpace(transactionId))
|
|
throw new ArgumentNullException(nameof(transactionId));
|
|
|
|
#endregion
|
|
|
|
if (!_eventCache.ContainsKey(transactionId))
|
|
throw new InvalidTransactionException(transactionId);
|
|
|
|
var entities = _eventCache[transactionId];
|
|
var saveModel = new List<EventRequestModel>();
|
|
foreach (var entity in entities)
|
|
{
|
|
saveModel.Add(new EventRequestModel
|
|
{
|
|
TransactionId = transactionId,
|
|
EntityId = entity.EntityId,
|
|
DomainId = new Guid(_domainId),
|
|
TenantId = new Guid(_tenantId),
|
|
ClassificationKey = entity.ClassificationKey,
|
|
Comment = entity.Comment,
|
|
NewValue = entity.NewValue,
|
|
OldValue = entity.OldValue,
|
|
UserId = entity.UserId,
|
|
ProjectId = entity.ProjectId,
|
|
ScenarioId = entity.ScenarioId
|
|
});
|
|
}
|
|
|
|
var result = await AuthorizeAndMakeRequest(async (accessToken) =>
|
|
{
|
|
using (var client = CreateAuditClient(accessToken))
|
|
{
|
|
return await client.PostAsJsonAsync("event/logmany", saveModel);
|
|
}
|
|
});
|
|
if (result == null)
|
|
throw new Exception("LogEvent result is undefined");
|
|
|
|
if (result.IsSuccessStatusCode)
|
|
ClearEventChanges(transactionId);
|
|
}
|
|
|
|
public static void ClearEventChanges(string transactionId)
|
|
{
|
|
#region Arguments Validation
|
|
|
|
if (string.IsNullOrWhiteSpace(transactionId))
|
|
throw new ArgumentNullException(nameof(transactionId));
|
|
|
|
#endregion
|
|
|
|
var value = new List<EventRequestBaseModel>();
|
|
if (_eventCache.ContainsKey(transactionId))
|
|
_eventCache.TryRemove(transactionId, out value);
|
|
}
|
|
|
|
public static async Task<EventGetResponseModel> GetEvents(Guid entityId, int take = 25, int skip = 0, string orderBy = "EntityId", bool isOrderAsc = true)
|
|
{
|
|
var result = await AuthorizeAndMakeRequest(async (accessToken) =>
|
|
{
|
|
using (var client = CreateAuditClient(accessToken))
|
|
{
|
|
return await client.GetAsync($"event/get/?entityId={entityId}&take={take}&skip={skip}&orderBy={orderBy}&isOrderAsc={isOrderAsc}");
|
|
}
|
|
});
|
|
return await ReturnEvents(result);
|
|
}
|
|
|
|
public static async Task<EventGetResponseModel> GetEvents(IEnumerable<Guid> entityId, int take = 25, int skip = 0, string orderBy = "EntityId", bool isOrderAsc = true)
|
|
{
|
|
var result = await AuthorizeAndMakeRequest(async (accessToken) =>
|
|
{
|
|
using (var client = CreateAuditClient(accessToken))
|
|
{
|
|
var entityIdStr = string.Empty;
|
|
foreach (var entityIdGuid in entityId)
|
|
{
|
|
entityIdStr = entityIdStr + "entityId=" + entityIdGuid + "&";
|
|
}
|
|
return await client.GetAsync($"event/GetByMany/?{entityIdStr}take={take}&skip={skip}&orderBy={orderBy}&isOrderAsc={isOrderAsc}");
|
|
}
|
|
});
|
|
return await ReturnEvents(result);
|
|
}
|
|
|
|
#region Private Event Methods
|
|
|
|
private static async Task<EventGetResponseModel> ReturnEvents(HttpResponseMessage result)
|
|
{
|
|
if (result == null)
|
|
throw new Exception("GetEvents result is undefined");
|
|
|
|
if (result.IsSuccessStatusCode)
|
|
{
|
|
var responseBody = await result.Content.ReadAsStringAsync();
|
|
var response = JsonConvert.DeserializeObject<ResponseModel<EventGetResponseModel>>(responseBody);
|
|
if (response.Success)
|
|
{
|
|
return response.Result;
|
|
}
|
|
}
|
|
return new EventGetResponseModel();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#endregion
|
|
|
|
#region Private Methods
|
|
|
|
private static Task<string> GetAuthToken(string tenantId, string password)
|
|
{
|
|
// TODO: Audit - temp solution until we decided to create shared OAuth service
|
|
return Task.Run(() => Properties.Settings.Default.TenantAccessToken);
|
|
|
|
// TODO: Audit - uncomment this block when we have OAuth service
|
|
|
|
//var accessTokenKey = "access_token";
|
|
//var pairs = new Dictionary<string, string>
|
|
//{
|
|
// { "grant_type", "password" },
|
|
// { "username", tenantId },
|
|
// { "password", password },
|
|
//};
|
|
//var content = new FormUrlEncodedContent(pairs);
|
|
|
|
//using (var client = CreateAuditClient())
|
|
//{
|
|
// var response = await client.PostAsync("/Token", content);
|
|
// var result = await response.Content.ReadAsStringAsync();
|
|
// var tokenDictionary = JsonConvert.DeserializeObject<Dictionary<string, string>>(result);
|
|
|
|
// if (tokenDictionary == null || !tokenDictionary.ContainsKey(accessTokenKey) || string.IsNullOrWhiteSpace(tokenDictionary[accessTokenKey]))
|
|
// throw new AccessTokenNullException($"Cannot get {accessTokenKey} for tenantId = {tenantId}");
|
|
|
|
// return tokenDictionary[accessTokenKey];
|
|
//}
|
|
}
|
|
|
|
private static async Task<HttpResponseMessage> AuthorizeAndMakeRequest(Func<string, Task<HttpResponseMessage>> request)
|
|
{
|
|
if (request == null)
|
|
throw new ArgumentNullException(nameof(request));
|
|
|
|
if (string.IsNullOrWhiteSpace(_accessToken))
|
|
_accessToken = await GetAuthToken(_tenantId, _password);
|
|
|
|
var result = await request(_accessToken);
|
|
if (result == null)
|
|
throw new Exception("Delegate request got null");
|
|
|
|
if (result.StatusCode == HttpStatusCode.Unauthorized)
|
|
{
|
|
_unauthorizedRequestsCount++;
|
|
|
|
if (_unauthorizedRequestsCount < _maxUnauthorizedAttempts)
|
|
{
|
|
_accessToken = await GetAuthToken(_tenantId, _password);
|
|
return await AuthorizeAndMakeRequest(request);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_unauthorizedRequestsCount = 0;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private static HttpClient CreateAuditClient(string accessToken = null)
|
|
{
|
|
var client = new HttpClient();
|
|
client.BaseAddress = new Uri(_apiUrl);
|
|
client.DefaultRequestHeaders.Accept.Clear();
|
|
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
|
|
|
if (!string.IsNullOrWhiteSpace(accessToken))
|
|
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
|
|
|
|
return client;
|
|
}
|
|
|
|
#endregion
|
|
|
|
}
|
|
} |