Skip to content

Instantly share code, notes, and snippets.

@hawjeh
Created December 4, 2023 03:47
Show Gist options
  • Save hawjeh/5044cfe31f674bfb79b6e62bd2b3695b to your computer and use it in GitHub Desktop.
Save hawjeh/5044cfe31f674bfb79b6e62bd2b3695b to your computer and use it in GitHub Desktop.
Sitefinity Access Logs to Azure Storage
@using Newtonsoft.Json;
@model SitefinityWebApp.Mvc.Models.ReportModel
@{
Layout = "~/Mvc/Views/Shared/Admin/_AdminLayout.cshtml";
}
@section head{
<style>
thead[role=rowgroup] {
background-color: #c5c5c5;
}
#grid {
font-size: 1.1em;
}
.k-button {
font-size: 1.3rem;
}
</style>
}
@section body{
<div class="sfWorkArea @Model.CssClass">
<h1>Custom Audit Report</h1>
<span class="text-danger">TODO: Filter</span>
<div id="grid"></div>
</div>
}
@section customScript {
<script src="~/assets/jszip.min.js"></script>
<script>
var data = @MvcHtmlString.Create(JsonConvert.SerializeObject(Model.AuditEntities));
$(function () {
$("#grid").kendoGrid({
toolbar: ["excel"],
excel: {
allPages: true,
fileName: "Custom Audit Report Export.xlsx"
},
dataSource: {
data: data,
sort: { field: "FormattedTimestamp", dir: "desc" },
schema: {
model: { id: "RowKey" }
},
pageSize: 100
},
sortable: true,
pageable: true,
filterable: false,
columnMenu: false,
reorderable: true,
resizable: true,
persistSelection: false,
columns: [
{ selectable: true, width: "30px" },
{ field: "FormattedTimestamp" },
{ field: "AccountName" },
{ field: "Email" },
{ field: "Roles" },
{ field: "Application" },
{ field: "Provider" },
{ field: "Status" },
{ field: "IpAddress", title: "Ip Address" }
]
});
})
</script>
}
using Azure;
using Azure.Data.Tables;
using System;
using System.Collections.Generic;
using System.Linq;
using Telerik.Sitefinity.Configuration;
namespace SitefinityWebApp
{
public class AuditEntity : ITableEntity
{
public string PartitionKey { get; set; }
public string RowKey { get; set; }
public DateTimeOffset? Timestamp { get; set; }
public ETag ETag { get; set; }
public string AccountName { get; set; }
public string AccountId { get; set; }
public string Email { get; set; }
public string Roles { get; set; }
public string Application { get; set; }
public string Provider { get; set; }
public string Status { get; set; }
public string IpAddress { get; set; }
public string FormattedTimestamp
{
get
{
return Timestamp.HasValue ? Timestamp.Value.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss") : "";
}
}
}
public class AuditServices
{
private static TableClient AzureTableClient()
{
var config = Config.Get<DemoSettingConfig>();
var sasCredential = new AzureSasCredential(config.SasConnString);
var serviceClient = new TableServiceClient(new Uri(config.AzureStorageTableUrl), sasCredential);
serviceClient.CreateTableIfNotExists("logs");
var tableClient = serviceClient.GetTableClient("logs");
return tableClient;
}
public static void LogToAzureStorageTable(DateTime logDateTime, string accountName, string accountId,
string email, string roles, string provider, string loginResult, string ipAddress)
{
try
{
var partitionKey = AuditConstant.UserLogin;
var rowKey = string.Format(AuditConstant.RowKeyFormat, logDateTime.ToLocalTime().ToString(AuditConstant.RowDateFormat), Guid.NewGuid().ToString());
var entity = new AuditEntity
{
PartitionKey = partitionKey,
RowKey = rowKey,
Timestamp = logDateTime,
AccountName = accountName,
AccountId = accountId,
Email = email,
Roles = roles,
Application = AppConstant.Application,
Provider = provider,
Status = loginResult,
IpAddress = ipAddress
};
var responses = AzureTableClient().UpsertEntity(entity);
}
catch (Exception ex)
{
throw ex;
}
}
public static List<AuditEntity> GetLogFromAzureStorageTable(DateTime from, DateTime to, string email = null, string provider = null)
{
var partitionKey = AuditConstant.UserLogin;
// limitation: 1000 records - to enhance
var auditEntities = AzureTableClient().Query<AuditEntity>(x => x.PartitionKey == partitionKey && x.Timestamp >= from && x.Timestamp < to.AddDays(1)).ToList();
if (!email.IsNullOrEmpty())
{
auditEntities = auditEntities.Where(x => x.Email == email).ToList();
}
if (!provider.IsNullOrEmpty())
{
auditEntities = auditEntities.Where(x => x.Provider == provider).ToList();
}
return auditEntities;
}
}
}
using System;
using Telerik.Sitefinity.Abstractions;
using Telerik.Sitefinity.Configuration;
using Telerik.Sitefinity.Services;
using Telerik.Sitefinity.Web.Events;
namespace SitefinityWebApp
{
public class Global : System.Web.HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
Bootstrapper.Bootstrapped += Bootstrapper_Bootstrapped;
}
private void Bootstrapper_Bootstrapped(object sender, EventArgs e)
{
// Register Configs
Config.RegisterSection<DemoSettingConfig>();
// Track user access
EventHub.Subscribe<ILoginCompletedEvent>(evt => LoginCompletedEventHandler(evt));
}
private void LoginCompletedEventHandler(ILoginCompletedEvent eventInfo)
{
var userServices = new UserServices();
userServices.LogUserAccess(eventInfo.LoginResult, eventInfo.UserId, eventInfo.IpAddress);
}
}
}
using System.Collections.Generic;
namespace SitefinityWebApp.Mvc.Models
{
public class ReportModel : BaseModel
{
public List<AuditEntity> AuditEntities { get; set; } = new List<AuditEntity>();
}
}using SitefinityWebApp.Mvc.Models;
using System;
using System.ComponentModel;
using System.Web.Mvc;
using Telerik.Sitefinity.Frontend.Mvc.Infrastructure.Controllers;
using Telerik.Sitefinity.Mvc;
namespace SitefinityWebApp.Mvc.Controllers
{
[ControllerToolboxItem(
Title = WidgetConstant.Report,
Name = WidgetConstant.Report,
SectionName = WidgetConstant.AdminWidget)]
public class ReportController : Controller
{
private ReportModel _model;
[TypeConverter(typeof(ExpandableObjectConverter))]
public virtual ReportModel Model
{
get
{
if (this._model == null)
this._model = ControllerModelFactory.GetModel<ReportModel>(this.GetType());
return this._model;
}
}
public ReportController() { }
public ActionResult Index()
{
if (Model.CurrentView == "AuditReport")
{
var from = DateTime.Now.AddDays(-30);
var to = DateTime.Now;
Model.AuditEntities = AuditServices.GetLogFromAzureStorageTable(
from: new DateTime(from.Year, from.Month, from.Day, 0, 0, 0),
to: new DateTime(to.Year, to.Month, to.Day, 0, 0, 0));
}
return View(Model.CurrentView, Model);
}
}
}
using System.Collections.Generic;
namespace SitefinityWebApp.Mvc.Models
{
public class ReportModel : BaseModel
{
public List<AuditEntity> AuditEntities { get; set; } = new List<AuditEntity>();
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using Telerik.Sitefinity.Security;
using Telerik.Sitefinity.Security.Model;
namespace SitefinityWebApp
{
public class UserServices
{
private readonly UserManager userManager;
private readonly UserProfileManager userProfileManager;
private readonly RoleManager roleManager;
private readonly RoleManager roleManagerApp;
public UserServices()
{
userManager = UserManager.GetManager();
userProfileManager = UserProfileManager.GetManager();
roleManager = RoleManager.GetManager();
roleManagerApp = RoleManager.GetManager("AppRoles");
}
public void LogUserAccess(UserLoggingReason loginResult, string userId, string ipAddress)
{
try
{
var userGuid = new Guid(userId);
var user = userManager.GetUser(userGuid);
if (user != null)
{
var userProfile = userProfileManager.GetUserProfile<SitefinityProfile>(user);
AuditServices.LogToAzureStorageTable(
logDateTime: DateTime.Now.ToUniversalTime(),
accountName: userProfile.FirstName + " " + userProfile.LastName,
accountId: user.Id.ToString(),
email: user.Email,
roles: string.Join(", ", GetUserRole(user.Id).Select(x => x.Name).ToArray()),
provider: user.ExternalProviderName.IsNullOrEmpty() ? "default" : user.ExternalProviderName,
loginResult: loginResult.ToString(),
ipAddress: ipAddress);
}
}
catch (Exception ex)
{
throw ex;
}
}
private IEnumerable<Role> GetUserRole(Guid id)
{
var roleDef = roleManager.GetRolesForUser(id);
var roleApp = roleManagerApp.GetRolesForUser(id);
return roleDef.Concat(roleApp);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment