EnVisageOnline/Main/Source/EnVisage/Code/Audit/AuditProxy.cs

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