krishna-vijas
7 months ago
22 changed files with 3139 additions and 0 deletions
-
25CDP-LTS-80.sln
-
264CDP-LTS-80/.gitignore
-
404CDP-LTS-80/AuditDB.cs
-
244CDP-LTS-80/AuditDocument.cs
-
35CDP-LTS-80/CDP-LTS-80.csproj
-
54CDP-LTS-80/CDPBlobStorage.cs
-
427CDP-LTS-80/CDPDB.cs
-
207CDP-LTS-80/CDPLite.cs
-
49CDP-LTS-80/Constants.cs
-
99CDP-LTS-80/ContactsDocument.cs
-
251CDP-LTS-80/EventProcessor.cs
-
145CDP-LTS-80/FileRevision.cs
-
93CDP-LTS-80/GroupsDocument.cs
-
317CDP-LTS-80/Helpers.cs
-
181CDP-LTS-80/MailProcessor.cs
-
14CDP-LTS-80/Program.cs
-
113CDP-LTS-80/Properties/ServiceDependencies/cdpfuncs - Web Deploy/profile.arm.json
-
174CDP-LTS-80/Properties/ServiceDependencies/cdpfuncs - Zip Deploy/profile.arm.json
-
9CDP-LTS-80/Properties/launchSettings.json
-
11CDP-LTS-80/Properties/serviceDependencies.json
-
11CDP-LTS-80/Properties/serviceDependencies.local.json
-
12CDP-LTS-80/host.json
@ -0,0 +1,25 @@ |
|||
|
|||
Microsoft Visual Studio Solution File, Format Version 12.00 |
|||
# Visual Studio Version 17 |
|||
VisualStudioVersion = 17.9.34622.214 |
|||
MinimumVisualStudioVersion = 10.0.40219.1 |
|||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CDP-LTS-80", "CDP-LTS-80\CDP-LTS-80.csproj", "{C9E49B28-1B40-4C62-AF86-C674677D3AB9}" |
|||
EndProject |
|||
Global |
|||
GlobalSection(SolutionConfigurationPlatforms) = preSolution |
|||
Debug|Any CPU = Debug|Any CPU |
|||
Release|Any CPU = Release|Any CPU |
|||
EndGlobalSection |
|||
GlobalSection(ProjectConfigurationPlatforms) = postSolution |
|||
{C9E49B28-1B40-4C62-AF86-C674677D3AB9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |
|||
{C9E49B28-1B40-4C62-AF86-C674677D3AB9}.Debug|Any CPU.Build.0 = Debug|Any CPU |
|||
{C9E49B28-1B40-4C62-AF86-C674677D3AB9}.Release|Any CPU.ActiveCfg = Release|Any CPU |
|||
{C9E49B28-1B40-4C62-AF86-C674677D3AB9}.Release|Any CPU.Build.0 = Release|Any CPU |
|||
EndGlobalSection |
|||
GlobalSection(SolutionProperties) = preSolution |
|||
HideSolutionNode = FALSE |
|||
EndGlobalSection |
|||
GlobalSection(ExtensibilityGlobals) = postSolution |
|||
SolutionGuid = {E8ECBA60-90CA-458E-9EA3-072BCD050B60} |
|||
EndGlobalSection |
|||
EndGlobal |
@ -0,0 +1,264 @@ |
|||
## Ignore Visual Studio temporary files, build results, and |
|||
## files generated by popular Visual Studio add-ons. |
|||
|
|||
# Azure Functions localsettings file |
|||
local.settings.json |
|||
|
|||
# User-specific files |
|||
*.suo |
|||
*.user |
|||
*.userosscache |
|||
*.sln.docstates |
|||
|
|||
# User-specific files (MonoDevelop/Xamarin Studio) |
|||
*.userprefs |
|||
|
|||
# Build results |
|||
[Dd]ebug/ |
|||
[Dd]ebugPublic/ |
|||
[Rr]elease/ |
|||
[Rr]eleases/ |
|||
x64/ |
|||
x86/ |
|||
bld/ |
|||
[Bb]in/ |
|||
[Oo]bj/ |
|||
[Ll]og/ |
|||
|
|||
# Visual Studio 2015 cache/options directory |
|||
.vs/ |
|||
# Uncomment if you have tasks that create the project's static files in wwwroot |
|||
#wwwroot/ |
|||
|
|||
# MSTest test Results |
|||
[Tt]est[Rr]esult*/ |
|||
[Bb]uild[Ll]og.* |
|||
|
|||
# NUNIT |
|||
*.VisualState.xml |
|||
TestResult.xml |
|||
|
|||
# Build Results of an ATL Project |
|||
[Dd]ebugPS/ |
|||
[Rr]eleasePS/ |
|||
dlldata.c |
|||
|
|||
# DNX |
|||
project.lock.json |
|||
project.fragment.lock.json |
|||
artifacts/ |
|||
|
|||
*_i.c |
|||
*_p.c |
|||
*_i.h |
|||
*.ilk |
|||
*.meta |
|||
*.obj |
|||
*.pch |
|||
*.pdb |
|||
*.pgc |
|||
*.pgd |
|||
*.rsp |
|||
*.sbr |
|||
*.tlb |
|||
*.tli |
|||
*.tlh |
|||
*.tmp |
|||
*.tmp_proj |
|||
*.log |
|||
*.vspscc |
|||
*.vssscc |
|||
.builds |
|||
*.pidb |
|||
*.svclog |
|||
*.scc |
|||
|
|||
# Chutzpah Test files |
|||
_Chutzpah* |
|||
|
|||
# Visual C++ cache files |
|||
ipch/ |
|||
*.aps |
|||
*.ncb |
|||
*.opendb |
|||
*.opensdf |
|||
*.sdf |
|||
*.cachefile |
|||
*.VC.db |
|||
*.VC.VC.opendb |
|||
|
|||
# Visual Studio profiler |
|||
*.psess |
|||
*.vsp |
|||
*.vspx |
|||
*.sap |
|||
|
|||
# TFS 2012 Local Workspace |
|||
$tf/ |
|||
|
|||
# Guidance Automation Toolkit |
|||
*.gpState |
|||
|
|||
# ReSharper is a .NET coding add-in |
|||
_ReSharper*/ |
|||
*.[Rr]e[Ss]harper |
|||
*.DotSettings.user |
|||
|
|||
# JustCode is a .NET coding add-in |
|||
.JustCode |
|||
|
|||
# TeamCity is a build add-in |
|||
_TeamCity* |
|||
|
|||
# DotCover is a Code Coverage Tool |
|||
*.dotCover |
|||
|
|||
# NCrunch |
|||
_NCrunch_* |
|||
.*crunch*.local.xml |
|||
nCrunchTemp_* |
|||
|
|||
# MightyMoose |
|||
*.mm.* |
|||
AutoTest.Net/ |
|||
|
|||
# Web workbench (sass) |
|||
.sass-cache/ |
|||
|
|||
# Installshield output folder |
|||
[Ee]xpress/ |
|||
|
|||
# DocProject is a documentation generator add-in |
|||
DocProject/buildhelp/ |
|||
DocProject/Help/*.HxT |
|||
DocProject/Help/*.HxC |
|||
DocProject/Help/*.hhc |
|||
DocProject/Help/*.hhk |
|||
DocProject/Help/*.hhp |
|||
DocProject/Help/Html2 |
|||
DocProject/Help/html |
|||
|
|||
# Click-Once directory |
|||
publish/ |
|||
|
|||
# Publish Web Output |
|||
*.[Pp]ublish.xml |
|||
*.azurePubxml |
|||
# TODO: Comment the next line if you want to checkin your web deploy settings |
|||
# but database connection strings (with potential passwords) will be unencrypted |
|||
#*.pubxml |
|||
*.publishproj |
|||
|
|||
# Microsoft Azure Web App publish settings. Comment the next line if you want to |
|||
# checkin your Azure Web App publish settings, but sensitive information contained |
|||
# in these scripts will be unencrypted |
|||
PublishScripts/ |
|||
|
|||
# NuGet Packages |
|||
*.nupkg |
|||
# The packages folder can be ignored because of Package Restore |
|||
**/packages/* |
|||
# except build/, which is used as an MSBuild target. |
|||
!**/packages/build/ |
|||
# Uncomment if necessary however generally it will be regenerated when needed |
|||
#!**/packages/repositories.config |
|||
# NuGet v3's project.json files produces more ignoreable files |
|||
*.nuget.props |
|||
*.nuget.targets |
|||
|
|||
# Microsoft Azure Build Output |
|||
csx/ |
|||
*.build.csdef |
|||
|
|||
# Microsoft Azure Emulator |
|||
ecf/ |
|||
rcf/ |
|||
|
|||
# Windows Store app package directories and files |
|||
AppPackages/ |
|||
BundleArtifacts/ |
|||
Package.StoreAssociation.xml |
|||
_pkginfo.txt |
|||
|
|||
# Visual Studio cache files |
|||
# files ending in .cache can be ignored |
|||
*.[Cc]ache |
|||
# but keep track of directories ending in .cache |
|||
!*.[Cc]ache/ |
|||
|
|||
# Others |
|||
ClientBin/ |
|||
~$* |
|||
*~ |
|||
*.dbmdl |
|||
*.dbproj.schemaview |
|||
*.jfm |
|||
*.pfx |
|||
*.publishsettings |
|||
node_modules/ |
|||
orleans.codegen.cs |
|||
|
|||
# Since there are multiple workflows, uncomment next line to ignore bower_components |
|||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) |
|||
#bower_components/ |
|||
|
|||
# RIA/Silverlight projects |
|||
Generated_Code/ |
|||
|
|||
# Backup & report files from converting an old project file |
|||
# to a newer Visual Studio version. Backup files are not needed, |
|||
# because we have git ;-) |
|||
_UpgradeReport_Files/ |
|||
Backup*/ |
|||
UpgradeLog*.XML |
|||
UpgradeLog*.htm |
|||
|
|||
# SQL Server files |
|||
*.mdf |
|||
*.ldf |
|||
|
|||
# Business Intelligence projects |
|||
*.rdl.data |
|||
*.bim.layout |
|||
*.bim_*.settings |
|||
|
|||
# Microsoft Fakes |
|||
FakesAssemblies/ |
|||
|
|||
# GhostDoc plugin setting file |
|||
*.GhostDoc.xml |
|||
|
|||
# Node.js Tools for Visual Studio |
|||
.ntvs_analysis.dat |
|||
|
|||
# Visual Studio 6 build log |
|||
*.plg |
|||
|
|||
# Visual Studio 6 workspace options file |
|||
*.opt |
|||
|
|||
# Visual Studio LightSwitch build output |
|||
**/*.HTMLClient/GeneratedArtifacts |
|||
**/*.DesktopClient/GeneratedArtifacts |
|||
**/*.DesktopClient/ModelManifest.xml |
|||
**/*.Server/GeneratedArtifacts |
|||
**/*.Server/ModelManifest.xml |
|||
_Pvt_Extensions |
|||
|
|||
# Paket dependency manager |
|||
.paket/paket.exe |
|||
paket-files/ |
|||
|
|||
# FAKE - F# Make |
|||
.fake/ |
|||
|
|||
# JetBrains Rider |
|||
.idea/ |
|||
*.sln.iml |
|||
|
|||
# CodeRush |
|||
.cr/ |
|||
|
|||
# Python Tools for Visual Studio (PTVS) |
|||
__pycache__/ |
|||
*.pyc |
@ -0,0 +1,404 @@ |
|||
using Microsoft.Azure.Cosmos; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace CDP |
|||
{ |
|||
internal class AuditDB |
|||
{ |
|||
//https://learn.microsoft.com/en-us/azure/azure-functions/manage-connections?tabs=csharp#azure-cosmos-db-clients
|
|||
private static Lazy<CosmosClient> lazyClient = new Lazy<CosmosClient>(InitializeCosmosClient); |
|||
private static CosmosClient cosmosClient => lazyClient.Value; |
|||
private static string DatabaseName = "CDP"; |
|||
private static string FileAuditContainer = "FileAudits"; |
|||
private static string UserAuditContainer = "UserAudits"; |
|||
private static string GroupAuditContainer = "GroupAudits"; |
|||
private static string TenantAuditContainer = "TenantAudits"; |
|||
private static CosmosClient InitializeCosmosClient() |
|||
{ |
|||
// Perform any initialization here
|
|||
var uri = "https://cdplite.documents.azure.com:443/"; |
|||
var authKey = "VPbg8RpzyI3XwhC2o0dIUtYFs33ghxORCqZeNAyg8vg4HWUBjM41BUxP0qLFXEvFh6ewQY1uKv52ACDbsEN1AQ=="; |
|||
|
|||
return new CosmosClient(uri, authKey); |
|||
} |
|||
|
|||
public static async Task UpdateAuditDocument(AuditDocument al, string containerName) |
|||
{ |
|||
if (al.Records.Count == 0) |
|||
return; |
|||
|
|||
List<AuditDocument> lal = await SplitAuditlog(al); |
|||
|
|||
Container container = cosmosClient.GetContainer(DatabaseName, containerName); |
|||
|
|||
foreach (AuditDocument ial in lal) |
|||
{ |
|||
ItemResponse<AuditDocument> r = await container.UpsertItemAsync(ial, new PartitionKey(ial.id)); |
|||
} |
|||
|
|||
await UpdateMetadata(container, lal); |
|||
} |
|||
|
|||
static async Task UpdateMetadata(Container container, List<AuditDocument> lal) |
|||
{ |
|||
bool update = false; |
|||
string pKey = GetMetaDocumentKey(lal[0].DocId); |
|||
MetadataDocument md = null; |
|||
try |
|||
{ |
|||
ItemResponse<MetadataDocument> response = |
|||
await container.ReadItemAsync<MetadataDocument>(pKey, new PartitionKey(pKey)); |
|||
md = response.Resource; |
|||
} |
|||
catch (CosmosException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound) |
|||
{ |
|||
md = new MetadataDocument() |
|||
{ |
|||
id = lal[0].DocId + "-meta", |
|||
PartitionKeys = new List<string>() |
|||
}; |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
// Helpers.LogIt(e.Message);
|
|||
return; |
|||
} |
|||
|
|||
if (md == null) |
|||
{ |
|||
// Helpers.LogIt("Something ugly happened!");
|
|||
return; |
|||
} |
|||
|
|||
foreach (AuditDocument log in lal) |
|||
{ |
|||
if (md.PartitionKeys.Contains(log.id)) |
|||
continue; |
|||
md.PartitionKeys.Add(log.id); |
|||
update = true; |
|||
} |
|||
|
|||
if (update) |
|||
{ |
|||
try |
|||
{ |
|||
ItemResponse<MetadataDocument> r = await container.UpsertItemAsync(md, new PartitionKey(pKey)); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
// Helpers.LogIt(e.Message);
|
|||
return; |
|||
} |
|||
} |
|||
} |
|||
|
|||
public static async Task<AuditDocument> GetAuditDocument(string key, string ContainerName) |
|||
{ |
|||
try |
|||
{ |
|||
Container container = cosmosClient.GetContainer(DatabaseName, ContainerName); |
|||
ItemResponse<AuditDocument> response = await container.ReadItemAsync<AuditDocument>(key, new PartitionKey(key)); |
|||
if (response == null) |
|||
return null; |
|||
|
|||
AuditDocument t = response.Resource; |
|||
return t; |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
static async Task<MetadataDocument> GetMetadataDocument(string id, string ContainerName) |
|||
{ |
|||
MetadataDocument md = null; |
|||
string pKey = GetMetaDocumentKey(id); |
|||
|
|||
PartitionKey partitionKey = new PartitionKeyBuilder() |
|||
.Add(id) |
|||
.Build(); |
|||
Container container = cosmosClient.GetContainer(DatabaseName, ContainerName); |
|||
try |
|||
{ |
|||
ItemResponse<MetadataDocument> response = |
|||
await container.ReadItemAsync<MetadataDocument>(pKey, partitionKey); |
|||
md = response.Resource; |
|||
} |
|||
catch (CosmosException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound) |
|||
{ |
|||
md = new MetadataDocument() |
|||
{ |
|||
id = id, |
|||
PartitionKeys = new List<string>() |
|||
}; |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
// Helpers.LogIt(e.Message);
|
|||
return null; |
|||
} |
|||
|
|||
if (md == null) |
|||
{ |
|||
// Helpers.LogIt("Something ugly happened!");
|
|||
return null; |
|||
} |
|||
return md; |
|||
} |
|||
|
|||
public static async Task<List<AuditRecord>> GetAuditRecordsBetweenDates(string DocId, DateTime startDate, DateTime endDate, string ContainerName) |
|||
{ |
|||
var results = new List<AuditRecord>(); |
|||
Container container = cosmosClient.GetContainer(DatabaseName, ContainerName); |
|||
|
|||
// Fetch the metadata document for the customer ID
|
|||
var metadataDocumentId = GetMetaDocumentKey(DocId); |
|||
MetadataDocument metadataDocument = null; |
|||
try |
|||
{ |
|||
var metadataDocumentResponse = |
|||
await container.ReadItemAsync<MetadataDocument>(metadataDocumentId, new PartitionKey(metadataDocumentId)); |
|||
metadataDocument = metadataDocumentResponse.Resource; |
|||
} |
|||
catch (CosmosException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound) |
|||
{ |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
// Helpers.LogIt(e.Message);
|
|||
} |
|||
|
|||
if (metadataDocument == null) |
|||
return results; |
|||
|
|||
// Determine the partition keys within the date range
|
|||
var partitionKeysInRange = metadataDocument.PartitionKeys |
|||
.Where(pk => IsPartitionKeyInRange(pk, startDate, endDate)) |
|||
.ToList(); |
|||
|
|||
// Fetch the audit records for each partition key within the date range
|
|||
foreach (var partitionKey in partitionKeysInRange) |
|||
{ |
|||
|
|||
ItemResponse<AuditDocument> response = await container.ReadItemAsync<AuditDocument>(partitionKey, new PartitionKey(partitionKey)); |
|||
if (response == null) |
|||
continue; |
|||
|
|||
AuditDocument t = response.Resource; |
|||
results.AddRange(t.Records); |
|||
} |
|||
|
|||
return results; |
|||
} |
|||
|
|||
static private bool IsPartitionKeyInRange(string partitionKey, DateTime startDate, DateTime endDate) |
|||
{ |
|||
var partitionKeyParts = partitionKey.Split('-'); |
|||
var length = partitionKeyParts.Length; |
|||
|
|||
var year = int.Parse(partitionKeyParts[length - 3]); |
|||
var dayOfYear = int.Parse(partitionKeyParts[length - 2]); |
|||
var partitionKeyDate = new DateTime(year, 1, 1).AddDays(dayOfYear - 1); |
|||
|
|||
return partitionKeyDate >= startDate && partitionKeyDate <= endDate; |
|||
} |
|||
|
|||
public static async Task<bool> AppendRecord(string id, AuditRecord rec, string ContainerName) |
|||
{ |
|||
try |
|||
{ |
|||
var metadataDocument = await GetMetadataDocument(id, ContainerName); |
|||
if (metadataDocument == null) |
|||
return false; |
|||
|
|||
string dayKey = metadataDocument.GetLatestKeyForDay(rec.EventTime); |
|||
AuditDocument al = await GetAuditDocument(dayKey, ContainerName); |
|||
if (al == null && ContainerName == FileAuditContainer) |
|||
al = new AuditDocument() { DocId = rec.FileId }; |
|||
else if (al == null && ContainerName == TenantAuditContainer) |
|||
al = new AuditDocument() { DocId = rec.AppKey }; |
|||
else if (al == null && ContainerName == UserAuditContainer) |
|||
al = new AuditDocument() { DocId = rec.UserId }; |
|||
else if (al == null && ContainerName == GroupAuditContainer) |
|||
al = new AuditDocument() { DocId = rec.GroupId }; |
|||
al.Records.Add(rec); |
|||
await UpdateAuditDocument(al, ContainerName); |
|||
return true; |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
public static async Task<bool> AppendRecordsList(string id, List<AuditRecord> rec, string ContainerName) |
|||
{ |
|||
try |
|||
{ |
|||
var metadataDocument = await GetMetadataDocument(id, ContainerName); |
|||
if (metadataDocument == null || rec.Count <= 0) |
|||
return false; |
|||
|
|||
string dayKey = metadataDocument.GetLatestKeyForDay(rec[0].EventTime); |
|||
AuditDocument al = await GetAuditDocument(dayKey, ContainerName); |
|||
/*if (al == null && ContainerName == FileAuditContainer) |
|||
al = new AuditDocument() { DocId = rec.FileId };*/ |
|||
if (al == null && ContainerName == TenantAuditContainer) |
|||
al = new AuditDocument() { DocId = rec[0].AppKey }; |
|||
else if (al == null && ContainerName == UserAuditContainer) |
|||
al = new AuditDocument() { DocId = rec[0].UserId }; |
|||
|
|||
rec.ForEach(record => |
|||
{ |
|||
al.Records.Add(record); |
|||
}); |
|||
await UpdateAuditDocument(al, ContainerName); |
|||
return true; |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
public static async Task<GroupAuditDocument> GetGroupAuditDocument(string appKey, string groupId) |
|||
{ |
|||
Container container = cosmosClient.GetContainer(DatabaseName, GroupAuditContainer); // get the Group Container
|
|||
|
|||
GroupAuditDocument ad = new GroupAuditDocument() { GroupId = groupId }; |
|||
try |
|||
{ |
|||
// Store the unique identifier
|
|||
string id = groupId; |
|||
|
|||
PartitionKey partitionKey = new PartitionKeyBuilder() |
|||
.Add(appKey) |
|||
.Add(groupId) |
|||
.Build(); |
|||
|
|||
// Perform a point read
|
|||
ItemResponse<GroupAuditDocument> r = await container.ReadItemAsync<GroupAuditDocument>(id, partitionKey); |
|||
return r; |
|||
} |
|||
|
|||
catch (Exception e) |
|||
{ |
|||
return null; |
|||
} |
|||
} |
|||
public static async Task AddTenantAuditRecord(FileAuditRecord far) |
|||
{ |
|||
TenantAuditRecord uar = new TenantAuditRecord() |
|||
{ |
|||
Action = far.Action, |
|||
AppKey = far.AppKey, |
|||
EventTime = far.EventTime, |
|||
FileId = far.FileId, |
|||
GroupId = far.GroupId, |
|||
Message = far.Message, |
|||
UserId = far.UserId |
|||
}; |
|||
|
|||
// cosmosClient is init'd by the static on line 21
|
|||
Container container = cosmosClient.GetContainer(DatabaseName, TenantAuditContainer); |
|||
// Build the full partition key path
|
|||
PartitionKey partitionKey = new PartitionKeyBuilder() |
|||
.Add(uar.AppKey) |
|||
.Build(); |
|||
|
|||
TenantAuditDocument ad = await GetTenantAuditDocument(uar.AppKey); |
|||
if (ad == null) |
|||
ad = new TenantAuditDocument() { AppKey = uar.AppKey }; |
|||
|
|||
ad.Records.Add(uar); |
|||
try |
|||
{ |
|||
ItemResponse<TenantAuditDocument> r = await container.UpsertItemAsync(ad, partitionKey); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
Console.WriteLine(e); |
|||
} |
|||
} |
|||
|
|||
public static async Task<TenantAuditDocument> GetTenantAuditDocument(string appKey) |
|||
{ |
|||
Container container = cosmosClient.GetContainer(DatabaseName, TenantAuditContainer); // get the Group Container
|
|||
|
|||
TenantAuditDocument ad = new TenantAuditDocument() { AppKey = appKey }; |
|||
try |
|||
{ |
|||
// Store the unique identifier
|
|||
string id = appKey; |
|||
|
|||
PartitionKey partitionKey = new PartitionKeyBuilder() |
|||
.Add(appKey) |
|||
.Build(); |
|||
|
|||
// Perform a point read
|
|||
ItemResponse<TenantAuditDocument> r = await container.ReadItemAsync<TenantAuditDocument>(id, partitionKey); |
|||
return r; |
|||
} |
|||
|
|||
catch (Exception e) |
|||
{ |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
static async Task<List<AuditDocument>> SplitAuditlog(AuditDocument al) |
|||
{ |
|||
List<AuditDocument> lal = new List<AuditDocument>(); |
|||
var sortedRecords = al.Records.OrderBy(record => record.EventTime).ToList(); |
|||
|
|||
var currentGroup = new List<AuditRecord>(); |
|||
var currentGroupSize = 0; |
|||
|
|||
int MaxDocumentSizeInBytes = 2 * 1024 * 1024; // 2MB
|
|||
|
|||
int index = al.Index; // start for index passed in
|
|||
foreach (var record in sortedRecords) |
|||
{ |
|||
var recordSize = record.CalculateRecordSize(); |
|||
|
|||
if (currentGroupSize + recordSize > MaxDocumentSizeInBytes) |
|||
{ |
|||
AuditDocument i = new AuditDocument(); |
|||
i.Index = index++; |
|||
i.Records = currentGroup; |
|||
lal.Add(i); |
|||
|
|||
currentGroup = new List<AuditRecord>(); |
|||
currentGroupSize = 0; |
|||
} |
|||
|
|||
currentGroup.Add(record); |
|||
currentGroupSize += recordSize; |
|||
} |
|||
|
|||
if (currentGroup.Any()) |
|||
{ |
|||
AuditDocument i = new AuditDocument(); |
|||
i.DocId = al.DocId; |
|||
i.Index = index++; |
|||
i.Records = currentGroup; |
|||
lal.Add(i); |
|||
} |
|||
|
|||
return lal; |
|||
} |
|||
|
|||
static string GetMetaDocumentKey(string id) |
|||
{ |
|||
return $"{id}-meta"; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,244 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Diagnostics; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace CDP |
|||
{ |
|||
public class AuditDocument |
|||
{ |
|||
public string id |
|||
{ |
|||
get |
|||
{ |
|||
Debug.Assert(Records.Count > 0); |
|||
AuditRecord ar = Records[0]; |
|||
return GenPartitionKey(DocId, ar.EventTime, Index); |
|||
} |
|||
} |
|||
public string DocId { get; set; } |
|||
public int Index { get; set; } |
|||
public List<AuditRecord> Records { get; set; } |
|||
|
|||
public AuditDocument() |
|||
{ |
|||
DocId = string.Empty; |
|||
Index = 0; |
|||
Records = new List<AuditRecord>(); |
|||
} |
|||
|
|||
public static string GenRootPartitionKey(string id, DateTime dt) |
|||
{ |
|||
return string.Format($"{id}-{dt.Year}-{dt.DayOfYear}"); |
|||
} |
|||
|
|||
public static string GenPartitionKey(string id, DateTime dt, int index = 0) |
|||
{ |
|||
return GenRootPartitionKey(id, dt) + string.Format($"-{index}"); |
|||
} |
|||
} |
|||
|
|||
public class UserAuditDocument : AuditDocument |
|||
{ |
|||
public string UserId { get; set; } |
|||
public string id |
|||
{ |
|||
get |
|||
{ |
|||
Debug.Assert(Records.Count > 0); |
|||
AuditRecord ar = Records[0]; |
|||
return GenPartitionKey(UserId, ar.EventTime, Index); |
|||
} |
|||
} |
|||
} |
|||
public class GroupAuditDocument : AuditDocument |
|||
{ |
|||
public string GroupId { get; set; } |
|||
public string id |
|||
{ |
|||
get |
|||
{ |
|||
Debug.Assert(Records.Count > 0); |
|||
AuditRecord ar = Records[0]; |
|||
return GenPartitionKey(GroupId, ar.EventTime, Index); |
|||
} |
|||
} |
|||
} |
|||
public class TenantAuditDocument : AuditDocument |
|||
{ |
|||
public string AppKey { get; set; } |
|||
public string id |
|||
{ |
|||
get |
|||
{ |
|||
Debug.Assert(Records.Count > 0); |
|||
AuditRecord ar = Records[0]; |
|||
return GenPartitionKey(AppKey, ar.EventTime, Index); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public class AuditRecord |
|||
{ |
|||
public string AppKey { get; set; } |
|||
public string FileId { get; set; } |
|||
public string FileName { get; set; } |
|||
public string UserId { get; set; } |
|||
public string GroupId { get; set; } |
|||
public string Action { get; set; } |
|||
public string Message { get; set; } |
|||
public DateTime EventTime { get; set; } |
|||
public virtual string id { get; } |
|||
|
|||
public AuditRecord() |
|||
{ |
|||
EventTime = DateTime.UtcNow; |
|||
} |
|||
|
|||
public AuditRecord(string appKey, string fileId, string fileName, string userId, string groupId, string action, string message) |
|||
{ |
|||
AppKey = appKey; |
|||
FileId = fileId; |
|||
FileName = fileName; |
|||
UserId = userId; |
|||
GroupId = groupId; |
|||
Action = action; |
|||
Message = message; |
|||
EventTime = DateTime.UtcNow; |
|||
} |
|||
|
|||
public int CalculateRecordSize() |
|||
{ |
|||
int size = 0; |
|||
|
|||
// Calculate the size of each property
|
|||
size += 16; // sizeof(DateTime); // EventTime
|
|||
size += Encoding.UTF8.GetByteCount(UserId); // UserId
|
|||
size += sizeof(int); // EventType
|
|||
size += Encoding.UTF8.GetByteCount(Message); // Message
|
|||
return size; |
|||
} |
|||
|
|||
} |
|||
public class FileAuditRecord : AuditRecord |
|||
{ |
|||
public override string id |
|||
{ |
|||
get |
|||
{ |
|||
return FileId; |
|||
} |
|||
} |
|||
|
|||
public FileAuditRecord() : base() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public FileAuditRecord(string appKey, string fileId, string fileName, string userId, string groupId, string action, string message) : base(appKey, fileId, fileName, userId, groupId, action, message) |
|||
{ |
|||
} |
|||
|
|||
} |
|||
public class TenantAuditRecord : AuditRecord |
|||
{ |
|||
|
|||
public override string id |
|||
{ |
|||
get |
|||
{ |
|||
return AppKey; |
|||
} |
|||
} |
|||
|
|||
public TenantAuditRecord() : base() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public TenantAuditRecord(string appKey, string fileId, string fileName, string userId, string groupId, string action, string message) : base(appKey, fileId, fileName, userId, groupId, action, message) |
|||
{ |
|||
} |
|||
|
|||
} |
|||
|
|||
public class UserAuditRecord : AuditRecord |
|||
{ |
|||
|
|||
public override string id |
|||
{ |
|||
get |
|||
{ |
|||
return UserId; |
|||
} |
|||
} |
|||
|
|||
public UserAuditRecord() : base() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public UserAuditRecord(string appKey, string fileId, string fileName, string userId, string groupId, string action, string message) : base(appKey, fileId, fileName, userId, groupId, action, message) |
|||
{ |
|||
} |
|||
|
|||
} |
|||
|
|||
public class GroupAuditRecord : AuditRecord |
|||
{ |
|||
|
|||
public override string id |
|||
{ |
|||
get |
|||
{ |
|||
return GroupId; |
|||
} |
|||
} |
|||
|
|||
public GroupAuditRecord() : base() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public GroupAuditRecord(string appKey, string fileId, string fileName, string userId, string groupId, string action, string message) : base(appKey, fileId, fileName, userId, groupId, action, message) |
|||
{ |
|||
} |
|||
|
|||
} |
|||
|
|||
public class MetadataDocument |
|||
{ |
|||
public string id { get; set; } |
|||
public List<string> PartitionKeys { get; set; } |
|||
|
|||
public MetadataDocument() |
|||
{ |
|||
PartitionKeys = new List<string>(); |
|||
id = id + "-meta"; |
|||
} |
|||
public string GetLatestKeyForDay(DateTime dt) |
|||
{ |
|||
string rootKey = AuditDocument.GenRootPartitionKey(id, dt); |
|||
List<string> dayKeys = new List<string>(); |
|||
PartitionKeys.Sort(); |
|||
foreach (string key in PartitionKeys) |
|||
{ |
|||
var partitionKeyParts = key.Split('-'); |
|||
var year = int.Parse(partitionKeyParts[1]); |
|||
var dayOfYear = int.Parse(partitionKeyParts[2]); |
|||
if (year == dt.Year && dayOfYear == dt.DayOfYear) |
|||
{ |
|||
dayKeys.Add(key); |
|||
} |
|||
} |
|||
|
|||
if (dayKeys.Count == 0) |
|||
return AuditDocument.GenPartitionKey(id, dt); |
|||
|
|||
return dayKeys.Last(); |
|||
} |
|||
} |
|||
|
|||
} |
@ -0,0 +1,35 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
<PropertyGroup> |
|||
<TargetFramework>net8.0</TargetFramework> |
|||
<AzureFunctionsVersion>v4</AzureFunctionsVersion> |
|||
<OutputType>Exe</OutputType> |
|||
<ImplicitUsings>enable</ImplicitUsings> |
|||
<Nullable>enable</Nullable> |
|||
<RootNamespace>CDP</RootNamespace> |
|||
</PropertyGroup> |
|||
<ItemGroup> |
|||
<FrameworkReference Include="Microsoft.AspNetCore.App" /> |
|||
<PackageReference Include="Azure.Security.KeyVault.Secrets" Version="4.6.0" /> |
|||
<PackageReference Include="Azure.Storage.Blobs" Version="12.19.1" /> |
|||
<PackageReference Include="Microsoft.Azure.Cosmos" Version="3.39.1" /> |
|||
<PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.22.0" /> |
|||
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.1.0" /> |
|||
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore" Version="1.2.1" /> |
|||
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.ServiceBus" Version="5.17.0" /> |
|||
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.17.2" /> |
|||
<PackageReference Include="Microsoft.ApplicationInsights.WorkerService" Version="2.22.0" /> |
|||
<PackageReference Include="Microsoft.Azure.Functions.Worker.ApplicationInsights" Version="1.2.0" /> |
|||
</ItemGroup> |
|||
<ItemGroup> |
|||
<None Update="host.json"> |
|||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> |
|||
</None> |
|||
<None Update="local.settings.json"> |
|||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> |
|||
<CopyToPublishDirectory>Never</CopyToPublishDirectory> |
|||
</None> |
|||
</ItemGroup> |
|||
<ItemGroup> |
|||
<Using Include="System.Threading.ExecutionContext" Alias="ExecutionContext" /> |
|||
</ItemGroup> |
|||
</Project> |
@ -0,0 +1,54 @@ |
|||
using Azure.Storage.Blobs; |
|||
using Microsoft.AspNetCore.Http; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Microsoft.Azure.Functions.Worker; |
|||
using Microsoft.Extensions.Logging; |
|||
|
|||
namespace CDP |
|||
{ |
|||
public class CDPBlobStorage |
|||
{ |
|||
private readonly ILogger<CDPBlobStorage> _logger; |
|||
|
|||
public CDPBlobStorage(ILogger<CDPBlobStorage> logger) |
|||
{ |
|||
_logger = logger; |
|||
} |
|||
|
|||
static internal async Task<IActionResult> UploadBlobInternal(FileUploadDto dto, ILogger _logger) |
|||
{ |
|||
string Connection = Constants.StorageConnString; |
|||
|
|||
try |
|||
{ |
|||
byte[] fileBytes = Convert.FromBase64String(dto.FileContent); |
|||
var blobSvc = new BlobServiceClient(Connection); |
|||
var blobClient = blobSvc.GetBlobContainerClient(dto.AppKey.ToLower()); |
|||
|
|||
if (!blobClient.Exists()) |
|||
{ |
|||
await blobClient.CreateAsync(); |
|||
} |
|||
|
|||
var blob = blobClient.GetBlobClient(string.Format("{0}-{1}", dto.FileId, dto.VersionId)); |
|||
Stream fs = new MemoryStream(fileBytes); |
|||
var res = await blob.UploadAsync(fs); |
|||
return new OkObjectResult(res); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger.LogError(ex.ToString()); |
|||
return new BadRequestObjectResult(ex); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public class FileUploadDto |
|||
{ |
|||
public required string AppKey { get; set; } |
|||
public required string FileId { get; set; } |
|||
public required string FileContent { get; set; } |
|||
|
|||
public required string VersionId { get; set; } |
|||
} |
|||
} |
@ -0,0 +1,427 @@ |
|||
using Microsoft.Azure.Cosmos; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace CDP |
|||
{ |
|||
public class CDPDB |
|||
{ |
|||
//https://learn.microsoft.com/en-us/azure/azure-functions/manage-connections?tabs=csharp#azure-cosmos-db-clients
|
|||
private static Lazy<CosmosClient> lazyClient = new Lazy<CosmosClient>(InitializeCosmosClient); |
|||
private static CosmosClient cosmosClient => lazyClient.Value; |
|||
private static string DatabaseName = "CDP"; |
|||
private static string FilesContainerName = "Files"; |
|||
|
|||
private static CosmosClient InitializeCosmosClient() |
|||
{ |
|||
// Perform any initialization here
|
|||
var uri = "https://cdplite.documents.azure.com:443/"; |
|||
var authKey = "VPbg8RpzyI3XwhC2o0dIUtYFs33ghxORCqZeNAyg8vg4HWUBjM41BUxP0qLFXEvFh6ewQY1uKv52ACDbsEN1AQ=="; |
|||
|
|||
return new CosmosClient(uri, authKey); |
|||
} |
|||
|
|||
public static async Task<FileRecord> AddFile(string appKey, string fileId, string userId, string aesKey, AccessPolicy ac) |
|||
{ |
|||
Container container = cosmosClient.GetContainer(DatabaseName, FilesContainerName); // get the Files Container
|
|||
|
|||
FileRecord fr = new FileRecord() |
|||
{ |
|||
AppKey = appKey, |
|||
FileId = fileId, |
|||
UserId = userId, |
|||
Policy = ac |
|||
}; |
|||
try |
|||
{ |
|||
ItemResponse<FileRecord> r = await container.CreateItemAsync(fr); //
|
|||
return r; |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
public static async Task<FileRecord> UpsertFile(string appKey, string fileId, string fileName, string userId, string aesKey, AccessPolicy ac) |
|||
{ |
|||
Container container = cosmosClient.GetContainer(DatabaseName, FilesContainerName); // get the Files Container
|
|||
|
|||
FileRecord fr = new FileRecord() |
|||
{ |
|||
AppKey = appKey, |
|||
FileId = fileId, |
|||
FileName = fileName, |
|||
UserId = userId, |
|||
Policy = ac |
|||
}; |
|||
try |
|||
{ |
|||
ItemResponse<FileRecord> r = await container.UpsertItemAsync(fr); //
|
|||
return r; |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
return null; |
|||
} |
|||
} |
|||
public static async Task<FileRecord> GetFile(string appKey, string fileId, string userId) |
|||
{ |
|||
Container container = cosmosClient.GetContainer(DatabaseName, FilesContainerName); // get the Files Container
|
|||
|
|||
FileRecord dto = new FileRecord() |
|||
{ |
|||
AppKey = appKey, |
|||
FileId = fileId, |
|||
UserId = userId, |
|||
}; |
|||
try |
|||
{ |
|||
// Store the unique identifier
|
|||
string id = dto.id; |
|||
|
|||
// Build the full partition key path
|
|||
PartitionKey partitionKey = new PartitionKeyBuilder() |
|||
.Add(appKey) |
|||
.Add(fileId) |
|||
.Add(userId) |
|||
.Build(); |
|||
|
|||
// Perform a point read
|
|||
ItemResponse<FileRecord> r = await container.ReadItemAsync<FileRecord>(id, partitionKey); |
|||
return r; |
|||
} |
|||
|
|||
catch (Exception e) |
|||
{ |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
public static async Task revokePolicies(string appKey, string userEmail, string contactEmail) |
|||
{ |
|||
Task<List<FileRecord>> allFilesTask = GetAllOwnerFiles(appKey, userEmail.ToLower()); |
|||
List<FileRecord> allFiles = await allFilesTask; |
|||
if (allFiles == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
foreach (FileRecord file in allFiles) |
|||
{ |
|||
string fileId = file.FileId; |
|||
string fileName = file.FileName; |
|||
if (fileId == null) { continue; } |
|||
string userId = Helpers.HashAndShortenText(contactEmail.ToLower()); |
|||
AccessPolicy ac = new AccessPolicy() |
|||
{ |
|||
Access = "None", |
|||
Email = contactEmail.ToLower(), |
|||
Group = null, |
|||
GroupId = "", |
|||
Key = "" |
|||
}; |
|||
|
|||
string message = string.Format($"{contactEmail} contact deleted by {userEmail} and hence, access is revoked on file having id {fileId}"); |
|||
string action = "Delete contact"; |
|||
// await new CDPLite(null).AddAudits(appKey, fileId, userId, "", action, message);
|
|||
|
|||
await UpsertFile(appKey, fileId, fileName, userId, "", ac); |
|||
} |
|||
} |
|||
|
|||
public static async Task revokeRegisteredUserPolicies(string appKey, string userEmail, string contactEmail, string registeredAdmin) |
|||
{ |
|||
Task<List<FileRecord>> allFiles = GetAllOwnerFiles(appKey, userEmail.ToLower()); |
|||
foreach (FileRecord file in await allFiles) |
|||
{ |
|||
string fileId = file.FileId; |
|||
string fileName = file.FileName; |
|||
if (fileId == null) |
|||
{ continue; } |
|||
string userId = Helpers.HashAndShortenText(contactEmail.ToLower()); |
|||
AccessPolicy ac = new AccessPolicy() |
|||
{ |
|||
Access = "None", |
|||
Email = contactEmail.ToLower(), |
|||
Group = null, |
|||
GroupId = "", |
|||
Key = "" |
|||
}; |
|||
|
|||
string message = string.Format($"{contactEmail} Registered User deleted by {registeredAdmin} and hence, access is revoked on all the Tenant files having id {fileId}"); |
|||
string action = "Delete Registered User"; |
|||
// await new CDPLite(null).AddAudits(appKey, fileId, userId, "", action, message);
|
|||
|
|||
await UpsertFile(appKey, fileId, fileName, userId, "", ac); |
|||
} |
|||
} |
|||
|
|||
public static async Task<List<FileRecord>> GetAllOwnerFiles(string appKey, string userId) |
|||
{ |
|||
Container container = cosmosClient.GetContainer(DatabaseName, FilesContainerName); // get the Files Container
|
|||
|
|||
var queryText = "SELECT * FROM c WHERE c.AppKey = @appKey AND c.Policy.Email = @userId AND c.Policy.Access = 'Owner'"; |
|||
var queryDefinition = new QueryDefinition(queryText) |
|||
.WithParameter("@appKey", appKey) |
|||
.WithParameter("@userId", userId); |
|||
|
|||
var queryResultSetIterator = container.GetItemQueryIterator<FileRecord>(queryDefinition); |
|||
|
|||
var results = new List<FileRecord>(); |
|||
while (queryResultSetIterator.HasMoreResults) |
|||
{ |
|||
var response = await queryResultSetIterator.ReadNextAsync(); |
|||
results.AddRange(response.ToList()); |
|||
} |
|||
return results; |
|||
} |
|||
|
|||
public static async Task<List<FileRecord>> GetPoliciesForFile(string appKey, string fileId) |
|||
{ |
|||
Container container = cosmosClient.GetContainer(DatabaseName, FilesContainerName); // get the Files Container
|
|||
|
|||
var queryText = "SELECT * FROM c WHERE c.AppKey = @appKey AND c.FileId = @fileId"; |
|||
var queryDefinition = new QueryDefinition(queryText) |
|||
.WithParameter("@appKey", appKey) |
|||
.WithParameter("@fileId", fileId); |
|||
|
|||
var queryResultSetIterator = container.GetItemQueryIterator<FileRecord>(queryDefinition); |
|||
|
|||
var results = new List<FileRecord>(); |
|||
while (queryResultSetIterator.HasMoreResults) |
|||
{ |
|||
var response = await queryResultSetIterator.ReadNextAsync(); |
|||
foreach (FileRecord fileRecord in response) |
|||
{ |
|||
fileRecord.Policy.Key = "nunya"; |
|||
} |
|||
results.AddRange(response.ToList()); |
|||
} |
|||
return results; |
|||
} |
|||
} |
|||
|
|||
class AddFileDto |
|||
{ |
|||
public string AppKey { get; set; } // the customer's AppKey
|
|||
public string Email { get; set; } // This is owner of the file...they will get a manage policy
|
|||
public string FileName { get; set; } // user and group auditing is pretty worthless without this (less we do gobs of queries)
|
|||
public string GroupId { get; set; } |
|||
|
|||
} |
|||
|
|||
class DeleteUserContactsDto |
|||
{ |
|||
public string AppKey { get; set; } |
|||
public string UserEmail { get; set; } |
|||
public string ContactEmail { get; set; } |
|||
} |
|||
class DeleteRegisteredUserDto |
|||
{ |
|||
public string AppKey { get; set; } |
|||
public string UserEmail { get; set; } |
|||
public string ContactEmail { get; set; } |
|||
public string AdminEmail { get; set; } |
|||
} |
|||
|
|||
class AddUserGroupDto |
|||
{ |
|||
public string AppKey { get; set; } |
|||
public string EmailId { get; set; } |
|||
public string GroupName { get; set; } |
|||
public string GroupDescription { get; set; } |
|||
public List<Contact> Contacts { get; set; } |
|||
} |
|||
class GetUserGroupDto |
|||
{ |
|||
public string AppKey { get; set; } |
|||
public string EmailId { get; set; } |
|||
} |
|||
|
|||
class Contact |
|||
{ |
|||
public string Name { get; set; } |
|||
public string Email { get; set; } |
|||
public string Company { get; set; } |
|||
public string Phone { get; set; } |
|||
public string Address { get; set; } |
|||
} |
|||
|
|||
class AddUserContactsDto |
|||
{ |
|||
public string AppKey { get; set; } |
|||
public string EmailId { get; set; } |
|||
public string ContactName { get; set; } |
|||
public string ContactEmail { get; set; } |
|||
public string ContactCompany { get; set; } |
|||
public string ContactPhone { get; set; } |
|||
public string ContactAddress { get; set; } |
|||
} |
|||
|
|||
class GetContactsDto |
|||
{ |
|||
public string Email { get; set; } |
|||
public string AppKey { get; set; } |
|||
} |
|||
class AddFileUserDto |
|||
{ |
|||
public string AppKey { get; set; } // the customer's AppKey
|
|||
public string FileId { get; set; } |
|||
public string Email { get; set; } // email of user with power to add a user
|
|||
public string EmailToAdd { get; set; } |
|||
public GroupsRecord Group { get; set; } |
|||
public string GroupId { get; set; } |
|||
public string Policy { get; set; } // Manage/Read/Write/etc...
|
|||
public string FileName { get; set; } // user and group auditing is pretty worthless without this (less we do gobs of queries)
|
|||
} |
|||
class GetFileForUserDto |
|||
{ |
|||
public string AppKey { get; set; } // the customer's AppKey
|
|||
public string FileId { get; set; } |
|||
public string Email { get; set; } // email of user with power to add a user
|
|||
public string FileName { get; set; } // user and group auditing is pretty worthless without this (less we do gobs of queries)
|
|||
} |
|||
|
|||
class GetPoliciesForFileDto |
|||
{ |
|||
public string AppKey { get; set; } // the customer's AppKey
|
|||
public string FileId { get; set; } |
|||
} |
|||
|
|||
class GetFileDto |
|||
{ |
|||
public string AppKey { get; set; } // the customer's AppKey
|
|||
public string FileId { get; set; } |
|||
public string Email { get; set; } |
|||
|
|||
} |
|||
class CreateSessionDto |
|||
{ |
|||
public string AppKey { get; set; } // the customer's AppKey
|
|||
public string CallbackUrl { get; set; } |
|||
public string Payload { get; set; } |
|||
public string WebHookUrl { get; set; } |
|||
} |
|||
class GetAuditLogForFileDto |
|||
{ |
|||
public string FileId { get; set; } |
|||
} |
|||
|
|||
class GetAuditLogForUserDto |
|||
{ |
|||
public string Email { get; set; } |
|||
} |
|||
class GetAuditLogForTenantDto |
|||
{ |
|||
public string AppKey { get; set; } |
|||
} |
|||
class AddAccessViolationDto |
|||
{ |
|||
public String FileId { get; set; } |
|||
public string AppKey { get; set; } |
|||
public string FileName { get; set; } |
|||
} |
|||
class GetAuditLogForGroupDto |
|||
{ |
|||
public string GroupId { get; set; } |
|||
} |
|||
class GetTenantDto |
|||
{ |
|||
public string AppKey { get; set; } // the customer's AppKey
|
|||
} |
|||
|
|||
class GetGroupDto |
|||
{ |
|||
public string GroupId { get; set; } |
|||
} |
|||
|
|||
class DeleteGroupDto |
|||
{ |
|||
public string GroupId { get; set; } |
|||
public string AppKey { get; set; } |
|||
public string UserEmail { get; set; } |
|||
} |
|||
class UpdateGroupDto |
|||
{ |
|||
public string AppKey { get; set; } |
|||
public string UserEmail { get; set; } |
|||
public string GroupId { get; set; } |
|||
public string GroupName { get; set; } |
|||
public string GroupDescription { get; set; } |
|||
public List<Contact> Contacts { get; set; } |
|||
} |
|||
class CreateClientQRCodeDto |
|||
{ |
|||
public string SignalRConnectionId { get; set; } |
|||
public string AppKey { get; set; } // the customer's AppKey
|
|||
public string Payload { get; set; } |
|||
} |
|||
class GetJwtsQRCode |
|||
{ |
|||
public string SignalRConnectionId { get; set; } |
|||
public List<string> AppKeys { get; set; } |
|||
} |
|||
class ClientJwts |
|||
{ |
|||
public List<string> HashedEmails { get; set; } |
|||
public List<string> Jwts { get; set; } |
|||
|
|||
public ClientJwts() |
|||
{ |
|||
HashedEmails = new List<string>(); |
|||
Jwts = new List<string>(); |
|||
} |
|||
} |
|||
|
|||
public class FileRecord |
|||
{ |
|||
public string id |
|||
{ |
|||
get |
|||
{ |
|||
string s = Helpers.HashAndShortenText(UserId + FileId); |
|||
// Cosmos db doesn't like '/', '\', '?', '#' and base64 strings can have '/'
|
|||
return s.Replace('/', '-'); |
|||
|
|||
} |
|||
} |
|||
|
|||
public string AppKey { get; set; } // the customer's AppKey
|
|||
public string FileId { get; set; } |
|||
public string FileName { get; set; } |
|||
public string UserId { get; set; } // This is owner of the file...they will get a manage policy
|
|||
public AccessPolicy Policy { get; set; } |
|||
} |
|||
|
|||
public class AccessPolicy |
|||
{ |
|||
public string Email { get; set; } // for sanity's sake. The end user will probably want this anyway.
|
|||
public GroupsRecord Group { get; set; } |
|||
public string GroupId { get; set; } |
|||
public string Access { get; set; } // Read Only, Manage, Print. CSV...opting for usability over a sexier bit flag.
|
|||
public string Key { get; set; } // encrypted with Constants.PublicKey
|
|||
|
|||
public Boolean CheckAccess(string accessToCheck) |
|||
{ |
|||
// Split the input string into an array of entries using commas as the separator
|
|||
string[] entries = Access.Split(','); |
|||
|
|||
// Iterate through the entries and check if any match the target string
|
|||
foreach (string entry in entries) |
|||
{ |
|||
// Use StringComparison.OrdinalIgnoreCase for case-insensitive comparison
|
|||
if (entry.Trim().Equals(accessToCheck, StringComparison.OrdinalIgnoreCase)) |
|||
{ |
|||
return true; // Found a match
|
|||
} |
|||
} |
|||
|
|||
// No matching entry found
|
|||
return false; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,207 @@ |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Microsoft.Azure.Cosmos; |
|||
using Microsoft.Extensions.Logging; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace CDP |
|||
{ |
|||
public class CDPLite |
|||
{ |
|||
private readonly ILogger<CDPLite> _logger; |
|||
private static string FileAuditContainer = "FileAudits"; |
|||
private static string UserAuditContainer = "UserAudits"; |
|||
private static string GroupAuditContainer = "GroupAudits"; |
|||
private static string TenantAuditContainer = "TenantAudits"; |
|||
|
|||
public CDPLite(ILogger<CDPLite> log) |
|||
{ |
|||
_logger = log; |
|||
} |
|||
|
|||
internal static async Task<IActionResult> AddFileUserInternal(AddFileUserDto dto) |
|||
{ |
|||
// check to see if the email has the power to add a user
|
|||
string userId = Helpers.HashAndShortenText(dto.Email.ToLower()); |
|||
|
|||
FileRecord fr = await CDPDB.GetFile(dto.AppKey, dto.FileId, userId); |
|||
if (fr == null) |
|||
{ |
|||
string message = string.Format($"{dto.Email} attempted to add/change access policy for {dto.EmailToAdd} on {dto.FileName} file having {dto.FileId} id, but didn't have ANY access"); |
|||
Console.WriteLine(message); |
|||
string action = "Policy change failed"; |
|||
await AddAudits(dto.AppKey, dto.FileId, dto.FileName, userId, "", action, message); |
|||
|
|||
return new BadRequestObjectResult(new { error = true, message = "File not found for user " + dto.Email }); |
|||
} |
|||
|
|||
if ((!fr.Policy.CheckAccess("Manage")) && (!fr.Policy.CheckAccess("Owner"))) |
|||
{ |
|||
string message = string.Format($"{dto.Email} attempted to add/change access policy for {dto.EmailToAdd} on {dto.FileName} file having {dto.FileId} id, but didn't have manage access"); |
|||
Console.WriteLine(message); |
|||
string action = "Policy change failed"; |
|||
await AddAudits(dto.AppKey, dto.FileId, dto.FileName, userId, "", action, message); |
|||
return new BadRequestObjectResult(new { error = true, message = $"{dto.Email} doesn't have the rights to add a user." }); |
|||
} |
|||
|
|||
string fileId = dto.FileId; |
|||
string fileName = dto.FileName; |
|||
string userIdToAdd = ""; |
|||
|
|||
if (dto.EmailToAdd != "") |
|||
{ |
|||
userIdToAdd = Helpers.HashAndShortenText(dto.EmailToAdd.ToLower()); |
|||
} |
|||
else if (dto.Group != null) |
|||
{ |
|||
userIdToAdd = dto.GroupId; |
|||
} |
|||
else if (dto.Group != null) |
|||
{ |
|||
userIdToAdd = dto.GroupId; |
|||
} |
|||
|
|||
AccessPolicy ac = new AccessPolicy() |
|||
{ |
|||
Access = dto.Policy, |
|||
Email = dto.EmailToAdd.ToLower(), |
|||
Group = dto.Group, |
|||
GroupId = dto.GroupId, |
|||
Key = "" |
|||
}; |
|||
|
|||
fr = await CDPDB.UpsertFile(dto.AppKey, fileId, fileName, userIdToAdd, "", ac); |
|||
|
|||
if (dto.EmailToAdd != "") |
|||
{ |
|||
string message = string.Format($"{dto.Email} added/changed the access policy for User : {dto.EmailToAdd} to {dto.Policy} on {fileName} file having {fileId} id"); |
|||
string action = "Policy change"; |
|||
await AddAudits(dto.AppKey, fileId, fileName, userId, "", action, message); |
|||
} |
|||
|
|||
if (dto.Group != null) |
|||
{ |
|||
string message = string.Format($"{dto.Email} added/changed the access policy for Group : {dto.Group} to {dto.Policy} on {fileName} file having {fileId} id"); |
|||
string action = "Policy change"; |
|||
await AddAudits(dto.AppKey, fileId, fileName, "", dto.Group.id, action, message); |
|||
} |
|||
return new OkObjectResult(fr); |
|||
} |
|||
|
|||
public static async Task AddAudits(string appKey, string fileId, string fileName, string userId, string groupid, string action, string message) |
|||
{ |
|||
if (string.IsNullOrEmpty(appKey) || string.IsNullOrEmpty(fileId) || string.IsNullOrEmpty(action) || string.IsNullOrEmpty(message)) |
|||
{ |
|||
Console.WriteLine(string.Format("something weird? appKey, fileId, action, message: {0} {1} {2} {3}", appKey, fileId, action, message)); |
|||
return; |
|||
} |
|||
|
|||
|
|||
AuditRecord faRec = new FileAuditRecord() |
|||
{ |
|||
AppKey = appKey, |
|||
FileId = fileId, |
|||
FileName = fileName, |
|||
UserId = userId, |
|||
GroupId = groupid, |
|||
Action = action, |
|||
Message = message, |
|||
EventTime = DateTime.UtcNow, |
|||
}; |
|||
Console.WriteLine("Adding File Audit Record"); |
|||
await AuditDB.AppendRecord(faRec.id, faRec, FileAuditContainer); |
|||
|
|||
AuditRecord faRecTenant = new TenantAuditRecord() |
|||
{ |
|||
AppKey = appKey, |
|||
FileId = fileId, |
|||
FileName = fileName, |
|||
UserId = userId, |
|||
GroupId = groupid, |
|||
Action = action, |
|||
Message = message, |
|||
EventTime = DateTime.UtcNow, |
|||
}; |
|||
|
|||
await AuditDB.AppendRecord(faRecTenant.id, faRecTenant, TenantAuditContainer); |
|||
|
|||
if (!string.IsNullOrEmpty(groupid)) |
|||
{ |
|||
AuditRecord faRecGroup = new GroupAuditRecord() |
|||
{ |
|||
AppKey = appKey, |
|||
FileId = fileId, |
|||
FileName = fileName, |
|||
UserId = userId, |
|||
GroupId = groupid, |
|||
Action = action, |
|||
Message = message, |
|||
EventTime = DateTime.UtcNow, |
|||
}; |
|||
|
|||
await AuditDB.AppendRecord(faRecGroup.id, faRecGroup, GroupAuditContainer); |
|||
} |
|||
|
|||
AuditRecord faRecUser = new UserAuditRecord() |
|||
{ |
|||
AppKey = appKey, |
|||
FileId = fileId, |
|||
FileName = fileName, |
|||
UserId = userId, |
|||
GroupId = groupid, |
|||
Action = action, |
|||
Message = message, |
|||
EventTime = DateTime.UtcNow, |
|||
}; |
|||
|
|||
await AuditDB.AppendRecord(faRecUser.id, faRecUser, UserAuditContainer); |
|||
|
|||
} |
|||
/// <summary>
|
|||
/// Adds the audit record on a background thread.
|
|||
/// </summary>
|
|||
private static async Task AddFileAudit(AuditRecord far) |
|||
{ |
|||
await AuditDB.AppendRecord(far.id, far, FileAuditContainer); |
|||
|
|||
} |
|||
|
|||
private static async Task AddUserAudit(AuditRecord far) |
|||
{ |
|||
await AuditDB.AppendRecord(far.id, far, UserAuditContainer); |
|||
} |
|||
|
|||
private static async Task AddTenantAudit(AuditRecord far) |
|||
{ |
|||
await Task.Run(async () => |
|||
{ |
|||
try |
|||
{ |
|||
await AuditDB.AppendRecord(far.id, far, TenantAuditContainer); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
} |
|||
}); |
|||
|
|||
} |
|||
|
|||
private static async Task AddGroupAudit(AuditRecord far) |
|||
{ |
|||
await Task.Run(async () => |
|||
{ |
|||
try |
|||
{ |
|||
await AuditDB.AppendRecord(far.id, far, GroupAuditContainer); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
} |
|||
}); |
|||
|
|||
} |
|||
} |
|||
} |
@ -0,0 +1,49 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace CDP |
|||
{ |
|||
internal class Constants |
|||
{ |
|||
// from the CDP application under [email protected] in Circle Auth
|
|||
public const string CDPAppKey = "appPy5zJHNDPCw59SkngUtcKg6MNxXnjn7P1"; |
|||
public const string CDPWriteKey = "write2tAFa4TskesMaJp6vjoN8GpCh1ETCYQhr"; |
|||
public const string CDPReadKey = "read92jY7v1uc3bza7FD56cgH3vCZjY81eAhp"; |
|||
public const string CDPUrl = "https://circleaccess.circlesecurity.ai/login/appPy5zJHNDPCw59SkngUtcKg6MNxXnjn7P1"; |
|||
public const string KeyVaultURI = "https://cdpkeys.vault.azure.net/"; |
|||
|
|||
public static string PublicKey = "<RSAKeyValue><Modulus>2Pr5sV9KYnMhrB9s4AtpLlTioGx/Ckb1dHi1lUV261clhJRgkEvyiA7HJ6OD4sJnNMSSJYAx+O31YNnfXzFweJYIyf3pWOE1s96Kr0cahD5PoiX61lttyMDkmtOrNvSnIfD5AvnIUs+I79UvQOaZdzLtCRFnOhBGKkY5Q+xRCis+UqIRMAnxZISWaRjeblYGUtJjrP6KiNuZBrsKscA9H8yWHafmMWdOCZCkpPEY3LYrLFG8mlvih/4Rl18aQKHTqxST4BUp72Sz3fLBlqCykE3KTlj0kGg5J7aEU3LcJaGVyiMN8MuMdDDlp2SCraf/aibqNQFNmXI/R1nyiSxbRQ==</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>"; |
|||
|
|||
/// this is a special customer code that let's us know that WE are calling the webservice.
|
|||
public static string CircleCustomerCode = "CIRCLE"; |
|||
|
|||
/// the Dashboard app needs to be able to enumerate ALL the circles.
|
|||
public static string CircleDashboardApp = "Dashboard"; |
|||
|
|||
public const string WebServicePublicKeyXML = "<RSAKeyValue><Modulus>uACdaJ9ODsQNk5aJ1NHJQ+CRcxQhjVAylguBS6SmF4HUeGpuXZdUbock2BnzCpP1kWXyROT8rn7J5El1rqWfzZejeRTI8ha7TeaiVk1iCblYkNls0StRlIX/E1xBYk/bLFTHWwte8dYAw1NKRl8yDYj8/8U1hSHMQhBlARbqO8Fng0JnWKeqRgDeBqcpD0sQreQxy1mqKbbfqGvs7oPkD7yWvMUnldiiuUl8gBA777EmsMGPOptr88UGY9nrW/Lb3CM768hSekaXTd6V4tV3/PpOaLkJhiDY/rCH5CVhLM4C7h/zXamgILTCVA8G6RthcM4foowZf+YPaQOh1FsPLtAGlrUjGOA606U5jxcz+4KlAbMw1ZMuBNzXAv5VANLtmgQWBG8bjA3IfUZqaAzXWJEtmguTzyNYnJf9C4KXuW47Eqt2TE7XmqeJcMAdts3OmVecDAJHV6z7u9v1BKRj8EWhuSlpA2K2JG57D4KjLf0iqahD0LxrHx/Zv3Wd+HIwbMpcWYecZZ8hpzk4jQzHC401yMlXo5SWZysMPwaAHx57g4Ll8UBFfxYHVYkXM4nX0Ef1n0LuldDAoWe5PYLwmjtPrKhS+VF50JkgLR9zCohe8R7itJTAt7umJmMTeSP4DvRylo0hDdnxph7FnCxYM+ykD1MjNqN08Htg90lGIQk=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>"; |
|||
public const string WebServicePrivateKey4kXML = "<RSAKeyValue><Modulus>uACdaJ9ODsQNk5aJ1NHJQ+CRcxQhjVAylguBS6SmF4HUeGpuXZdUbock2BnzCpP1kWXyROT8rn7J5El1rqWfzZejeRTI8ha7TeaiVk1iCblYkNls0StRlIX/E1xBYk/bLFTHWwte8dYAw1NKRl8yDYj8/8U1hSHMQhBlARbqO8Fng0JnWKeqRgDeBqcpD0sQreQxy1mqKbbfqGvs7oPkD7yWvMUnldiiuUl8gBA777EmsMGPOptr88UGY9nrW/Lb3CM768hSekaXTd6V4tV3/PpOaLkJhiDY/rCH5CVhLM4C7h/zXamgILTCVA8G6RthcM4foowZf+YPaQOh1FsPLtAGlrUjGOA606U5jxcz+4KlAbMw1ZMuBNzXAv5VANLtmgQWBG8bjA3IfUZqaAzXWJEtmguTzyNYnJf9C4KXuW47Eqt2TE7XmqeJcMAdts3OmVecDAJHV6z7u9v1BKRj8EWhuSlpA2K2JG57D4KjLf0iqahD0LxrHx/Zv3Wd+HIwbMpcWYecZZ8hpzk4jQzHC401yMlXo5SWZysMPwaAHx57g4Ll8UBFfxYHVYkXM4nX0Ef1n0LuldDAoWe5PYLwmjtPrKhS+VF50JkgLR9zCohe8R7itJTAt7umJmMTeSP4DvRylo0hDdnxph7FnCxYM+ykD1MjNqN08Htg90lGIQk=</Modulus><Exponent>AQAB</Exponent><P>4bkQ5edFb+tRaLIdUOZCkGuRvhUObS0B2sXdnmJz0eYN0ydfXJAOiqadbTK+AF3ScOmGvJYGEJTNFMBvkhNz6tSH9XHV7CriMikc1f6R+o1SYGwOMikDCGJVRS/S+GMXwC/QwxhhUNGl0gSyDByeW3D2nehzIfk0NAdUDoww06G4csn9kdDOSQMr68ZOvJaQWs/t957Q7k0ySfMLDlMdzlJFxTtTT8HXWh1drS+SrkE1T+GLrAQS7PDvKzxsKvr2VIqF3wPcfygxYEzK7Wap5qK9ULBM19Z2ulJqKR467qFxmt1T11l1cHPoLxFNikqWA/dbfYVsSR4Y/U8GZcXgZw==</P><Q>0K7wUFfZdTkjrnb/QXF0fKlIlRB6m9rYsFPht3PAp0xGKUzHsTgu551dBkv15F2P6J1bHEllaOmP9AC+qbf+1QJWCyyVzmecbjmq8kPrGBRoid5MQ49VQU2XbKopUQOHEnYROn8HSsmJpN567+tf2itVwkXUvMphtXtxjfXLFk2YtAcTlJDaj92ljCUQh6k4FL9EKkdkABbca9fpIWaHqjSj22dltAmB1BaXRb6D9+Dcp7Jd/mYs12xRyfk22N1zb0uXPTbVGDhrP66DnotvU5YltiZAE5MYapRS3+se6uNNKF0jjg/1t+u0VoeZKLlf8spGKz/0zZP1G/aB5vlNDw==</Q><DP>Ge5x44ebMY1eL+HufkHFueOxvZ2bp604/lIm8LAs0BtW0UA/s1mVYhZcFZRzwYUFzRT29qlhjvwZR14IM0bq1TqPWAI46kXO8KZKeschLCCaKxue0eJmR8tMMoOZABIaQ2j62b7uRyOeoi8J6VxMUQL8Qg3XVDrV5XhGDtnKLjXxA7by25KacEbd/9srZn0Bnn0uwHjNVeMmeNpkFcQ0s2Q+QbdcDdieDes7vM6vUjeJkWC+9AcPyYqwX040uwdTz0iOsp018qHCcmbAFV/q+uUNTLzERzOLNkKLkae4a3u+mmT84cuUM1QNg4LAXrfM3i3Ho/b1Br1fb5JpkL2X1w==</DP><DQ>aMRK9asFC2riVQZ849gXyTYbsGqTg4d9XhjeqwE76UHLv5RCdOBl+/Xu6+hazImEZB4LzcmzVdZcZP2yuvYaw8nmdLmvg+5UhKoC/pnGf1JPoVevJgPho2VW7kEHeRqyRzxrLdj0SkGgDtt7ENQ0TQ2QgnYYdaeNIlWVDtYlXU8bqNYcqGuIrs1x/UfmYpU8CH51tFwfAkR36NR+wDv/RB4lXVMtphNX0Kh+zY72Jg4H+VD9C2k0ffptnV62gG29NFTY2uyaUwyQG2p7va729xncWPQfGzSv/2HzsWMTJI11ARyxavLsQ6fb480zwVQ/rV6Ll88GEeeEGMPLN7BVVQ==</DQ><InverseQ>E6CFaPj+cfQA3ZtkCaPACc8xWBFdioBdpHH5ZIMzniFm/DPo/NdR5WkD1qcLS2AQcYE8vFdHa/xWk4c+8FLdYeJmQlE/kD74Dll42K2M2GARbHwKRm+o0PqNCUySB75ysl0uVV1h+TJ+X4o9IOgLPPSgkQgsy7/qj9YwKzICHOmV54fyNljgh13llpEYxNV97dK27GuWjBzVqzwDwg1ZywZa7xuX3vRhOkJfxfZUqBEScaDpT49aYSQP3zu1260Lq6q+tz1vajv3wfj/q0jCULtYKivK2KbNaJCvjzu2oKLSC3YYfC0OzUp90///XcKjfd2V8ku6elxZBUc/gWmKbg==</InverseQ><D>HgwLqGALUSH2zZkRnO09HpZ3agFpkTf9ar8qN/4FGWWcGqXWq/XgVcQ/4gIqzBlpSnsJWvLByITv1xWf4Asv01Tl3wnj3pSTtZ7p94nfbpMTwWOf/3BBfC19f93a14EO22hkvZtXv8ogicT6Jg+5jrhGCEZd/QWN42Pl+o2RZnyouKiJU9LdXV0a5J87d0Hc0WioMPrNbM5uK4SkRjbHcUJsoxrW4gNkXzl+2TLNdtDI7qhNCVcEA+SyrG+TnYpFGOewC0rFHS6V2qN0enw9gdQmJbfBtGN8y+pr5nesLLLfi58wEKV4fp7F9RAXIs3T1aXzSIn0SAx7i7yKGrFcAxQJJ4R3GgVEm+A7E1ha19sS1Ts73R3aKaL7cyb6vcgRatgoOIyCHB4NP4fN8bdzCXwA/6dYCcZc23A6aGH9TbWAq5ESe5v6xR7t0z3WUNEd/JyBcKrxVwcSBBxuGakyILDUK0j+i3XkEwizhsxQXzcH3Ae/bIA1sZRsE5S+mzhs2WSVhBZMjzMYt08/mTBb1jvrTkK9pUICiqtrJBt6gbb2AIMQUwidMb9vpyAeSpdswE1EB3GGer6/I6rPjtUeUBvSf/xyopPjNbeDq80Oi1klobuPkJJ4pyomy79mU8HGntvKvFuyAyFYJbUbte7AMPYMKbheZlb8JBe9PxyBtRk=</D></RSAKeyValue>"; |
|||
|
|||
public static string SMSHeader = "|||||"; |
|||
|
|||
public static string StorageConnString = "DefaultEndpointsProtocol=https;AccountName=cdplitestorage;AccountKey=J0bIKDLjj0WrTAB+7srwPKf+eVEZMUhl7dQ5m8htT2GxuZLNq8c0UMSBCbJyGyiRrMdB695va1Ym+AStuN4Lnw==;EndpointSuffix=core.windows.net"; |
|||
public const string ServiceBusConnString = "ServiceBusConnectionString"; |
|||
public const string SvcBusConnectionString = "Endpoint=sb://cdpevents.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=wZG33V1vooOKDSh1vkr39GSNL4iLxn+S9+ASbNLXmR4="; |
|||
|
|||
#if DEBUG
|
|||
public const string AzureFuncBase = "https://circlecloudfuncstaging.azurewebsites.net"; |
|||
#else
|
|||
public const string AzureFuncBase = "https://circlecloudfunc.azurewebsites.net"; |
|||
#endif
|
|||
|
|||
#if DEBUG
|
|||
/// <summary>
|
|||
/// only used to generate a signature for testing.
|
|||
/// </summary>
|
|||
public static string PrivateKey = |
|||
"<RSAKeyValue><Modulus>2Pr5sV9KYnMhrB9s4AtpLlTioGx/Ckb1dHi1lUV261clhJRgkEvyiA7HJ6OD4sJnNMSSJYAx+O31YNnfXzFweJYIyf3pWOE1s96Kr0cahD5PoiX61lttyMDkmtOrNvSnIfD5AvnIUs+I79UvQOaZdzLtCRFnOhBGKkY5Q+xRCis+UqIRMAnxZISWaRjeblYGUtJjrP6KiNuZBrsKscA9H8yWHafmMWdOCZCkpPEY3LYrLFG8mlvih/4Rl18aQKHTqxST4BUp72Sz3fLBlqCykE3KTlj0kGg5J7aEU3LcJaGVyiMN8MuMdDDlp2SCraf/aibqNQFNmXI/R1nyiSxbRQ==</Modulus><Exponent>AQAB</Exponent><P>4kGKeLglNcE27T+0ZphHqM9Uj9ndixz2a497ADxyfUXiwqDrzsMC9z2tACAXg09DatQd7hj2AMUb4plSfFYtb0UhyeizHRHEHDjuLwKWaF3SvGamCSyRoaQfkwJh1+7bo3oJ6q//0XiHTfHottZ5+MPO7AwSWQIG+gC0Bm/F9GM=</P><Q>9YFFLtcfIrv/vs6Dm16nlXp4NShZZsgVTXnn+ZqaMBRAaco8k6igjXEjt/PxmWllLl2xYBoufWjc/ZijYOp8jeRnd2CeZy7dps9R0h7DUcLsLOz/WFk4yKYWDYZ7yoKBBzfH4Pghvt4gbCBtJRW6tvQUcy+6F8tpy0HBsYmd3jc=</Q><DP>mZyj9egYI1HmB9fblFhJNrxlDjF9r6hfSZGlkfh4RsM+/KMi1kMibVgrb81aCWW0QQB4vaGmy8kHcKZmEVK1EDmggiQCD3dzbTHdzH72PS+OFymeCtZ+nX7/jODuKxvZdTkzKGqLxd0OCAZ42EHvl8zhXPOpNVMiB3SYxVH31T8=</DP><DQ>Tk+q6tMSCnlfjdTAJEpgOI01VPeG9QMz0F/ngDYxos6C8XviMNOkSxng4+e7lE0LSmv2jYosdDKD85zB2MNTcct+uZSnUpCfgXs2RUks0qLTxUzf8qWbOF3KQQw55pIUaGIBryBXW1PtkbASQLcatQtMjqWVvctT9tMF3wTfx6c=</DQ><InverseQ>3NKnr2InC4yvhpwD5yamPE0sGs6qwEncJHnqfNLskbP5xtGmO9lYBHRKQkHxzNkPRHiJGFXl92VjMegNsHOkM9lA03dY0mFqn96lhBxrPThfMrsed70BiueujgKQHQItS9qK6SRQ4K3po/bJgzhZolcM2/UCHBtGEH2r9fCuS5U=</InverseQ><D>1JBSSuRqT7Ywd5m1pGspWH1KIxUHA79uOPMEGL8beARAufhN/S6Z45ZunjN/QYdnafdkoR5MVmxv5birLmuVnlyFMbTwg+TlIlfSj1Hd9U3B37zLVnHtRC1MBy1z3vugPA0BNVwUVqP/p8y2ZSNDltWqBycodGZ7BnETKqp43bafRihA2IKbf4YCH5OIB+UF9Giv/uhYbap46Yz7vfUI1QBZjseDBRv/x0qb632Isp0ktEBQ/gby5YmO0aVeX/ihCPm/J/l095b8Lq3WA05KladoWdHQLCda//6DqfO0uxiFI6Z/hAYa+ahmrLjUvlFD8wReQjL6V1Te/6nCgRdYZQ==</D></RSAKeyValue>"; |
|||
#endif
|
|||
} |
|||
} |
@ -0,0 +1,99 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace CDP |
|||
{ |
|||
public class ContactsDocument |
|||
{ |
|||
public string id |
|||
{ |
|||
get |
|||
{ |
|||
return GenPartitionKey(AppKey, UserId, Index); |
|||
} |
|||
} |
|||
public string AppKey { get; set; } |
|||
public string UserId { get; set; } |
|||
public int Index { get; set; } |
|||
public List<ContactRecord> Records { get; set; } |
|||
|
|||
public ContactsDocument() |
|||
{ |
|||
Index = 0; |
|||
Records = new List<ContactRecord>(); |
|||
} |
|||
|
|||
public static string GenRootPartitionKey(string id, DateTime dt) |
|||
{ |
|||
return string.Format($"{id}-{dt.Year}-{dt.DayOfYear}"); |
|||
} |
|||
|
|||
public static string GenPartitionKey(string appKey, string userId, int index = 0) |
|||
{ |
|||
return string.Format($"{appKey}-{userId}-{index}"); |
|||
} |
|||
} |
|||
|
|||
public class ContactRecord |
|||
{ |
|||
public string Name { get; set; } |
|||
public string Email { get; set; } |
|||
public string Phone { get; set; } |
|||
public string Address { get; set; } |
|||
public string Company { get; set; } |
|||
public DateTime EventTime { get; set; } |
|||
|
|||
public ContactRecord() |
|||
{ |
|||
EventTime = DateTime.UtcNow; |
|||
} |
|||
|
|||
public ContactRecord(string name, string email, string phone, string address, string company) |
|||
{ |
|||
Name = name; |
|||
Email = email; |
|||
Phone = phone; |
|||
Address = address; |
|||
Company = company; |
|||
EventTime = DateTime.UtcNow; |
|||
} |
|||
|
|||
public int CalculateRecordSize() |
|||
{ |
|||
int size = 0; |
|||
|
|||
size += 16; // sizeof(DateTime); // EventTime
|
|||
size += Encoding.UTF8.GetByteCount(Name) == 0 ? 20 : Encoding.UTF8.GetByteCount(Name); // UserId
|
|||
size += Encoding.UTF8.GetByteCount(Email) == 0 ? 50 : Encoding.UTF8.GetByteCount(Email); |
|||
size += Encoding.UTF8.GetByteCount(Phone) == 0 ? 15 : Encoding.UTF8.GetByteCount(Phone); |
|||
size += Encoding.UTF8.GetByteCount(Address) == 0 ? 70 : Encoding.UTF8.GetByteCount(Address); |
|||
size += Encoding.UTF8.GetByteCount(Company) == 0 ? 20 : Encoding.UTF8.GetByteCount(Company); |
|||
|
|||
size += sizeof(int); |
|||
return size; |
|||
} |
|||
|
|||
} |
|||
public class MetadataDocumentContact |
|||
{ |
|||
public string id { get; set; } |
|||
public List<string> PartitionKeys { get; set; } |
|||
|
|||
public MetadataDocumentContact() |
|||
{ |
|||
PartitionKeys = new List<string>(); |
|||
id = id + "-meta"; |
|||
} |
|||
public string GetLatestKey(string appKey, string userId) |
|||
{ |
|||
PartitionKeys.Sort(); |
|||
if (PartitionKeys.Count == 0) |
|||
return GroupsDocument.GenPartitionKey(appKey, userId); |
|||
|
|||
return PartitionKeys.Last(); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,251 @@ |
|||
using System; |
|||
using System.Text.Json; |
|||
using System.Threading.Tasks; |
|||
using Azure.Storage.Blobs; |
|||
using Azure.Storage.Blobs.Models; |
|||
using Azure.Messaging.ServiceBus; |
|||
using Microsoft.Azure.Functions.Worker; |
|||
using Microsoft.Extensions.Logging; |
|||
using Google.Protobuf.Reflection; |
|||
|
|||
namespace CDP |
|||
{ |
|||
public class EventProcessor |
|||
{ |
|||
private static string FileAuditContainer = "FileAudits"; |
|||
private static string UserAuditContainer = "UserAudits"; |
|||
private static string GroupAuditContainer = "GroupAudits"; |
|||
private static string TenantAuditContainer = "TenantAudits"; |
|||
private readonly ILogger<EventProcessor> _logger; |
|||
|
|||
public EventProcessor(ILogger<EventProcessor> logger) |
|||
{ |
|||
_logger = logger; |
|||
} |
|||
|
|||
[Function("ProcessEvent")] |
|||
public async Task ProcessCDPJob( |
|||
[ServiceBusTrigger("mail-events-queue", Connection = "ServiceBusConnectionString")] |
|||
ServiceBusReceivedMessage message, |
|||
ServiceBusMessageActions messageActions) |
|||
{ |
|||
_logger.LogInformation("Message ID: {id}", message.MessageId); |
|||
_logger.LogInformation("Message Body: {body}", message.Body); |
|||
_logger.LogInformation("Message Content-Type: {contentType}", message.ContentType); |
|||
Job cdpJob; |
|||
try |
|||
{ |
|||
cdpJob = JsonSerializer.Deserialize<Job>(message.Body); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger.LogError("received incorrect structure in body: {0} got exception: {1}", message.Body, ex); |
|||
await messageActions.CompleteMessageAsync(message); |
|||
return; |
|||
} |
|||
if (cdpJob != null) |
|||
{ |
|||
switch (cdpJob.EventType) |
|||
{ |
|||
case JobType.MailMetaProcessing: |
|||
await ProcessMailMetaJob(cdpJob); |
|||
break; |
|||
case JobType.KeyVaultInsertion: |
|||
_logger.LogInformation("key vault insertion processing"); |
|||
await ProcessKeyVaultInsertionJob(cdpJob); |
|||
break; |
|||
case JobType.AddAudits: |
|||
_logger.LogInformation("Adding cdp audits"); |
|||
await ProcessAuditEvent(cdpJob); |
|||
break; |
|||
case JobType.KeyVaultInsertionBatch: |
|||
_logger.LogInformation("batch key vault insertion processing"); |
|||
await ProcessKeyVaultBatchJob(cdpJob); |
|||
break; |
|||
case JobType.AddAuditsBatch: |
|||
_logger.LogInformation("batch audit insertion in progress"); |
|||
await ProcessAuditsBatchJob(cdpJob); |
|||
break; |
|||
default: |
|||
break; |
|||
} |
|||
} |
|||
// Complete the message
|
|||
await messageActions.CompleteMessageAsync(message); |
|||
} |
|||
|
|||
internal async Task ProcessAuditEvent(Job job) |
|||
{ |
|||
AuditEventMetadata auditEventMetadata = JsonSerializer.Deserialize<AuditEventMetadata>(job.JobMetadata); |
|||
await CDPLite.AddAudits(job.AppKey, auditEventMetadata.FileId, auditEventMetadata.FileName, auditEventMetadata.UserId, "", auditEventMetadata.Action, auditEventMetadata.Message); |
|||
return; |
|||
} |
|||
|
|||
internal async Task ProcessKeyVaultBatchJob(Job job) |
|||
{ |
|||
KeyVaultService kvs = new KeyVaultService(Constants.KeyVaultURI); |
|||
List<KeyVaultEvent> events = new List<KeyVaultEvent>(); |
|||
try |
|||
{ |
|||
events = JsonSerializer.Deserialize<List<KeyVaultEvent>>(job.JobMetadata); |
|||
events.ForEach(async (KeyVaultEvent evt) => |
|||
{ |
|||
await kvs.SetSecretAsync(evt.FileId, evt.AESKey); |
|||
}); |
|||
} catch (Exception ex) |
|||
{ |
|||
_logger.LogError("Error parsing job: {1} keyvault event metadata error: {0}", ex.ToString(), JsonSerializer.Serialize(job)); |
|||
return; |
|||
} |
|||
} |
|||
|
|||
internal async Task ProcessAuditsBatchJob(Job job) |
|||
{ |
|||
try |
|||
{ |
|||
List<AuditRecord> tenantAuditRecords = new List<AuditRecord>(); |
|||
List<AuditRecord> userAuditRecords = new List<AuditRecord>(); |
|||
List<AuditEventMetadata> auditEventMetadata = JsonSerializer.Deserialize<List<AuditEventMetadata>>(job.JobMetadata); |
|||
for(int i = 0; i<auditEventMetadata.Count; i++) |
|||
{ |
|||
FileAuditRecord fileAuditRecord = new FileAuditRecord |
|||
{ |
|||
Action = auditEventMetadata[i].Action, |
|||
FileId = auditEventMetadata[i].FileId, |
|||
FileName = auditEventMetadata[i].FileName, |
|||
Message = auditEventMetadata[i].Message, |
|||
UserId = auditEventMetadata[i].UserId, |
|||
GroupId = "" |
|||
}; |
|||
await AuditDB.AppendRecord(fileAuditRecord.id, fileAuditRecord, FileAuditContainer); |
|||
TenantAuditRecord auditRecord = new TenantAuditRecord |
|||
{ |
|||
AppKey = job.AppKey, |
|||
Action = auditEventMetadata[i].Action, |
|||
FileId = auditEventMetadata[i].FileId, |
|||
FileName = auditEventMetadata[i].FileName, |
|||
Message = auditEventMetadata[i].Message, |
|||
UserId = auditEventMetadata[i].UserId, |
|||
GroupId = "", |
|||
EventTime = DateTime.UtcNow, |
|||
}; |
|||
|
|||
tenantAuditRecords.Add(auditRecord); |
|||
|
|||
UserAuditRecord userAuditRecord = new UserAuditRecord |
|||
{ |
|||
AppKey = job.AppKey, |
|||
FileId = auditEventMetadata[i].FileId, |
|||
FileName = auditEventMetadata[i].FileName, |
|||
Message = auditEventMetadata[i].Message, |
|||
UserId = auditEventMetadata[i].UserId, |
|||
Action = auditEventMetadata[i].Action, |
|||
EventTime = DateTime.UtcNow, |
|||
GroupId = "" |
|||
}; |
|||
userAuditRecords.Add(userAuditRecord); |
|||
} |
|||
|
|||
await AuditDB.AppendRecordsList(job.AppKey, tenantAuditRecords, TenantAuditContainer); |
|||
await AuditDB.AppendRecordsList(userAuditRecords[0].UserId, userAuditRecords, UserAuditContainer); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger.LogError($"{ex.Message}"); |
|||
} |
|||
return; |
|||
} |
|||
|
|||
internal async Task ProcessKeyVaultInsertionJob(Job cdpJob) |
|||
{ |
|||
KeyVaultService kvs = new KeyVaultService(Constants.KeyVaultURI); |
|||
KeyVaultEvent vaultEvent; |
|||
try |
|||
{ |
|||
vaultEvent = JsonSerializer.Deserialize<KeyVaultEvent>(cdpJob.JobMetadata); |
|||
} catch (Exception ex) |
|||
{ |
|||
_logger.LogError("Error parsing keyvault event metadata error: {0}",ex.ToString()); |
|||
return; |
|||
} |
|||
await kvs.SetSecretAsync(vaultEvent.FileId, vaultEvent.AESKey); |
|||
return; |
|||
} |
|||
|
|||
internal async Task ProcessMailMetaJob(Job cdpJob) |
|||
{ |
|||
string Connection = Constants.StorageConnString; |
|||
var blobServiceClient = new BlobServiceClient(Connection); |
|||
var blobClient = blobServiceClient.GetBlobContainerClient(cdpJob.AppKey.ToLower()); |
|||
var blob = blobClient.GetBlobClient(string.Format("{0}", cdpJob.Id)); |
|||
var exists = await blob.ExistsAsync(); |
|||
if (!exists) |
|||
{ |
|||
return; |
|||
} |
|||
BlobDownloadInfo blobDownloadInfo = await blob.DownloadAsync(); |
|||
_logger.LogDebug($"Exists?: {exists.ToString()}"); |
|||
_logger.LogDebug($"Download resp: {blobDownloadInfo.Details}"); |
|||
using (MemoryStream ms = new MemoryStream()) |
|||
{ |
|||
await blobDownloadInfo.Content.CopyToAsync(ms); |
|||
byte[] byteArray = ms.ToArray(); |
|||
string mailRecJson = System.Text.Encoding.UTF8.GetString(byteArray); |
|||
_logger.LogInformation("got event json from blob: " + mailRecJson); |
|||
MailRecord record = JsonSerializer.Deserialize<MailRecord>(mailRecJson); |
|||
await MailProcessor.ProcessMailEvent(record, _logger); |
|||
} |
|||
return; |
|||
} |
|||
} |
|||
|
|||
public class MailRecord |
|||
{ |
|||
public required string AppKey { get; set; } |
|||
public required string MailId { get; set; } |
|||
public required string SenderEmail { get; set; } |
|||
public required List<string> Attachments { get; set; } |
|||
public required List<string> ReceiverEmails { get; set; } |
|||
public required List<AttachmentDetails> AttachmentDetails { get; set; } |
|||
} |
|||
|
|||
public class AttachmentDetails |
|||
{ |
|||
public required string FileId { get; set; } |
|||
public required string HeadRevisionId { get; set; } |
|||
public string? FileType { get; set; } |
|||
public required string FileName { get; set; } |
|||
} |
|||
|
|||
public class Job |
|||
{ |
|||
public required string Id { get; set; } |
|||
public required string AppKey { get; set; } |
|||
public required JobType EventType { get; set; } |
|||
public string? JobMetadata { get; set; } |
|||
} |
|||
|
|||
public enum JobType |
|||
{ |
|||
MailMetaProcessing, |
|||
KeyVaultInsertion, |
|||
AddAudits, |
|||
KeyVaultInsertionBatch, |
|||
AddAuditsBatch |
|||
} |
|||
public class KeyVaultEvent |
|||
{ |
|||
public required string FileId { get; set; } |
|||
public required string AESKey { get; set; } |
|||
} |
|||
|
|||
public class AuditEventMetadata |
|||
{ |
|||
public required string FileId { get; set; } |
|||
public string FileName { get; set; } |
|||
public string UserId { get; set; } |
|||
public string GroupId { get; set; } |
|||
public string Action { get; set; } |
|||
public string Message { get; set; } |
|||
} |
|||
} |
@ -0,0 +1,145 @@ |
|||
using Microsoft.AspNetCore.Http; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Microsoft.Azure.Cosmos; |
|||
using Microsoft.Azure.Functions.Worker; |
|||
using Microsoft.Extensions.Logging; |
|||
using System.Net; |
|||
|
|||
namespace CDP |
|||
{ |
|||
public class FileRevision |
|||
{ |
|||
private readonly ILogger<FileRevision> _logger; |
|||
private static Lazy<CosmosClient> lazyClient = new Lazy<CosmosClient>(InitializeCosmosClient); |
|||
private static CosmosClient cosmosClient => lazyClient.Value; |
|||
private static string DatabaseName = "CDP"; |
|||
private static string ContainerName = "RevisionsContainer"; |
|||
|
|||
public FileRevision(ILogger<FileRevision> logger) |
|||
{ |
|||
_logger = logger; |
|||
} |
|||
|
|||
private static CosmosClient InitializeCosmosClient() |
|||
{ |
|||
// Perform any initialization here
|
|||
var uri = "https://cdplite.documents.azure.com:443/"; |
|||
var authKey = "VPbg8RpzyI3XwhC2o0dIUtYFs33ghxORCqZeNAyg8vg4HWUBjM41BUxP0qLFXEvFh6ewQY1uKv52ACDbsEN1AQ=="; |
|||
|
|||
return new CosmosClient(uri, authKey); |
|||
} |
|||
|
|||
static internal async Task<IActionResult> AddRevisionInternal(AddRevisionDocumentDto doc, ILogger _logger) |
|||
{ |
|||
try |
|||
{ |
|||
Container container = cosmosClient.GetContainer(DatabaseName, ContainerName); |
|||
_logger.LogInformation("container id: ", container); |
|||
RevisionDocument document = new RevisionDocument() |
|||
{ |
|||
//AppKey = doc.AppKey,
|
|||
//FileId = doc.FileId,
|
|||
RevisionId = doc.RevisionId, |
|||
EditedBy = doc.EditedBy, |
|||
Comments = doc.Comments, |
|||
RevisionDate = doc.RevisionDate |
|||
}; |
|||
string id = Helpers.HashAndShortenText(doc.AppKey + doc.FileId); |
|||
PartitionKey partitionKey = new PartitionKeyBuilder() |
|||
.Add(doc.AppKey) |
|||
.Add(doc.FileId) |
|||
.Build(); |
|||
|
|||
var res = await AddOrUpdateRevision(container, id, partitionKey, document, doc.AppKey, doc.FileId); |
|||
return new OkObjectResult(res); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger.LogError(ex.ToString()); |
|||
return new StatusCodeResult(500); |
|||
} |
|||
} |
|||
|
|||
private async static Task<RevisionEntry> AddOrUpdateRevision(Container container, string id, PartitionKey partitionKey, RevisionDocument document, string appKey, string fileId) |
|||
{ |
|||
try |
|||
{ |
|||
ItemResponse<RevisionEntry> item = await container.ReadItemAsync<RevisionEntry>(id, partitionKey); |
|||
RevisionEntry revisions = item.Resource; |
|||
revisions.FileRevisions.Add(document); |
|||
ItemRequestOptions requestOptions = new ItemRequestOptions |
|||
{ |
|||
IfMatchEtag = item.ETag |
|||
}; |
|||
var res = await container.UpsertItemAsync<RevisionEntry>(revisions, partitionKey); |
|||
return res.Resource; |
|||
} |
|||
catch (CosmosException ex) |
|||
{ |
|||
if (ex.StatusCode == System.Net.HttpStatusCode.NotFound) |
|||
{ |
|||
List<RevisionDocument> revisions = new List<RevisionDocument> |
|||
{ |
|||
document |
|||
}; |
|||
RevisionEntry entry = new RevisionEntry() |
|||
{ |
|||
AppKey = appKey, |
|||
FileId = fileId, |
|||
FileRevisions = revisions |
|||
}; |
|||
ItemResponse<RevisionEntry> item = await container.CreateItemAsync<RevisionEntry>(entry); |
|||
return item.Resource; |
|||
} |
|||
else if (ex.StatusCode == HttpStatusCode.PreconditionFailed) |
|||
{ |
|||
return await AddOrUpdateRevision(container, id, partitionKey, document, appKey, fileId); |
|||
} |
|||
else |
|||
{ |
|||
throw ex; |
|||
} |
|||
} |
|||
|
|||
} |
|||
|
|||
} |
|||
|
|||
public class GetRevisionsDto |
|||
{ |
|||
public required string AppKey { get; set; } |
|||
public required string FileId { get; set; } |
|||
} |
|||
public class AddRevisionDocumentDto |
|||
{ |
|||
public required string AppKey { get; set; } |
|||
public required string FileId { get; set; } |
|||
public required string RevisionId { get; set; } |
|||
public required string EditedBy { get; set; } |
|||
public required DateTime RevisionDate { get; set; } |
|||
public required string Comments { get; set; } |
|||
} |
|||
|
|||
public class RevisionDocument |
|||
{ |
|||
public required string RevisionId { get; set; } |
|||
public required string EditedBy { get; set; } |
|||
public required DateTime RevisionDate { get; set; } |
|||
public required string Comments { get; set; } |
|||
} |
|||
|
|||
public class RevisionEntry |
|||
{ |
|||
public string id |
|||
{ |
|||
get |
|||
{ |
|||
string s = Helpers.HashAndShortenText(AppKey + FileId); |
|||
return s.Replace('/', '-'); |
|||
} |
|||
} |
|||
public required string AppKey { get; set; } |
|||
public required string FileId { get; set; } |
|||
public List<RevisionDocument> FileRevisions { get; set; } |
|||
} |
|||
} |
@ -0,0 +1,93 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace CDP |
|||
{ |
|||
public class GroupsDocument |
|||
{ |
|||
public string id |
|||
{ |
|||
get |
|||
{ |
|||
return GenPartitionKey(AppKey, UserId, Index); |
|||
} |
|||
} |
|||
public string AppKey { get; set; } |
|||
public string UserId { get; set; } |
|||
public int Index { get; set; } |
|||
public List<GroupsRecord> Records { get; set; } |
|||
|
|||
public GroupsDocument() |
|||
{ |
|||
Index = 0; |
|||
Records = new List<GroupsRecord>(); |
|||
} |
|||
|
|||
public static string GenRootPartitionKey(string id, DateTime dt) |
|||
{ |
|||
return string.Format($"{id}-{dt.Year}-{dt.DayOfYear}"); |
|||
} |
|||
|
|||
public static string GenPartitionKey(string appKey, string userId, int index = 0) |
|||
{ |
|||
return string.Format($"{appKey}-{userId}-{index}"); |
|||
} |
|||
} |
|||
|
|||
public class GroupsRecord |
|||
{ |
|||
public string id { get; set; } |
|||
public string Name { get; set; } |
|||
public string Description { get; set; } |
|||
public List<ContactRecord> Contacts { get; set; } |
|||
public DateTime EventTime { get; set; } |
|||
|
|||
public GroupsRecord() |
|||
{ |
|||
EventTime = DateTime.UtcNow; |
|||
} |
|||
|
|||
public GroupsRecord(string name, string description, List<ContactRecord> contacts) |
|||
{ |
|||
id = Guid.NewGuid().ToString(); |
|||
Name = name; |
|||
Description = description; |
|||
Contacts = contacts != null ? contacts : new List<ContactRecord>(); |
|||
EventTime = DateTime.UtcNow; |
|||
} |
|||
|
|||
public int CalculateRecordSize() |
|||
{ |
|||
int size = 0; |
|||
|
|||
size += 8800; // sizeof(DateTime); 175*50 = 8750 bytes to support at max 50 contacts in a group
|
|||
size += Encoding.UTF8.GetByteCount(Name) == 0 ? 20 : Encoding.UTF8.GetByteCount(Name); // UserId
|
|||
size += sizeof(int); |
|||
return size; |
|||
} |
|||
|
|||
} |
|||
public class MetadataDocumentGroups |
|||
{ |
|||
public string id { get; set; } |
|||
public List<string> PartitionKeys { get; set; } |
|||
|
|||
public MetadataDocumentGroups() |
|||
{ |
|||
PartitionKeys = new List<string>(); |
|||
id = id + "-meta"; |
|||
} |
|||
public string GetLatestKey(string appKey, string userId) |
|||
{ |
|||
PartitionKeys.Sort(); |
|||
if (PartitionKeys.Count == 0) |
|||
return GroupsDocument.GenPartitionKey(appKey, userId); |
|||
|
|||
return PartitionKeys.Last(); |
|||
} |
|||
} |
|||
|
|||
} |
@ -0,0 +1,317 @@ |
|||
using Newtonsoft.Json.Linq; |
|||
using Newtonsoft.Json; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Net; |
|||
using System.Security.Cryptography; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using Azure.Identity; |
|||
using Azure.Security.KeyVault.Secrets; |
|||
|
|||
namespace CDP |
|||
{ |
|||
public class Helpers |
|||
{ |
|||
public static byte[] GenerateAES256Key() |
|||
{ |
|||
using (Aes aesAlg = Aes.Create()) |
|||
{ |
|||
aesAlg.KeySize = 256; |
|||
aesAlg.GenerateKey(); |
|||
return aesAlg.Key; |
|||
} |
|||
} |
|||
|
|||
// hashes a string and removes the dashes (-)
|
|||
public static string HashToHex(string str) |
|||
{ |
|||
using (SHA256 sha256Hash = SHA256.Create()) |
|||
{ |
|||
byte[] bytes = sha256Hash.ComputeHash(Encoding.UTF8.GetBytes(str)); |
|||
return BitConverter.ToString(bytes).Replace("-", string.Empty).ToLower(); |
|||
} |
|||
} |
|||
|
|||
// takes a short hash and converts it to normal hash.
|
|||
// Circle Auth uses hashes stored as hex...use this to convert to that format.
|
|||
public static string ConvertShortHashToHex(string shortHash) |
|||
{ |
|||
// Add padding characters ('=') to the short hash if needed
|
|||
while (shortHash.Length % 4 != 0) |
|||
{ |
|||
shortHash += "="; |
|||
} |
|||
|
|||
// Decode the Base64 short hash to bytes
|
|||
byte[] hashBytes = Convert.FromBase64String(shortHash); |
|||
|
|||
// Convert the bytes to a hexadecimal string
|
|||
string hexHash = BitConverter.ToString(hashBytes).Replace("-", "").ToLower(); |
|||
|
|||
return hexHash; |
|||
} |
|||
|
|||
|
|||
// Generates an AES Key and converts it to base64
|
|||
public static string GenerateAES256KeyToBase64() |
|||
{ |
|||
return Convert.ToBase64String(GenerateAES256Key()); |
|||
} |
|||
|
|||
// Generates a guid, removes the - and then converts it to base64
|
|||
public static string GenerateShortGuid() |
|||
{ |
|||
Guid guid = Guid.NewGuid(); |
|||
byte[] bytes = guid.ToByteArray(); |
|||
|
|||
string shortGuid = Convert.ToBase64String(bytes); |
|||
|
|||
// Remove padding characters from the end of the Base64 string
|
|||
shortGuid = shortGuid.TrimEnd('='); |
|||
|
|||
return shortGuid; |
|||
} |
|||
public static Guid LengthenShortGuid(string shortGuid) |
|||
{ |
|||
// Add padding characters to the end of the Base64 string, if needed
|
|||
while (shortGuid.Length % 4 != 0) |
|||
{ |
|||
shortGuid += "="; |
|||
} |
|||
|
|||
// Convert the Base64 string back to bytes
|
|||
byte[] bytes = Convert.FromBase64String(shortGuid); |
|||
|
|||
// Create a new GUID from the bytes
|
|||
Guid guid = new Guid(bytes); |
|||
|
|||
return guid; |
|||
} |
|||
|
|||
|
|||
public static bool VerifyData(string originalMessage, string signedMessage) |
|||
{ |
|||
bool success = false; |
|||
using (var rsa = new RSACryptoServiceProvider()) |
|||
{ |
|||
var encoder = new UTF8Encoding(); |
|||
byte[] bytesToVerify = encoder.GetBytes(originalMessage); |
|||
byte[] signedBytes = Convert.FromBase64String(signedMessage); |
|||
try |
|||
{ |
|||
rsa.FromXmlString(Constants.PublicKey); |
|||
|
|||
SHA512Managed Hash = new SHA512Managed(); |
|||
|
|||
byte[] hashedData = Hash.ComputeHash(signedBytes); |
|||
|
|||
success = rsa.VerifyData(bytesToVerify, CryptoConfig.MapNameToOID("SHA512"), signedBytes); |
|||
} |
|||
catch (CryptographicException e) |
|||
{ |
|||
Console.WriteLine(e.Message); |
|||
} |
|||
finally |
|||
{ |
|||
rsa.PersistKeyInCsp = false; |
|||
} |
|||
} |
|||
return success; |
|||
} |
|||
public static string ComputeSignature(string stringToSign, string secret) |
|||
{ |
|||
using (var hmacsha256 = new HMACSHA256(System.Text.ASCIIEncoding.UTF8.GetBytes(secret))) |
|||
{ |
|||
var bytes = Encoding.ASCII.GetBytes(stringToSign); |
|||
var hashedBytes = hmacsha256.ComputeHash(bytes); |
|||
return Convert.ToBase64String(hashedBytes); |
|||
} |
|||
} |
|||
|
|||
public static async Task<dynamic> GetSession(string sessionId) |
|||
{ |
|||
string toSign = string.Format($"?s={sessionId}"); |
|||
string sig = Helpers.ComputeSignature(toSign, Constants.CDPWriteKey); |
|||
string URL = string.Format($"https://circleauth.gocircle.ai/api/session/{toSign}&signature={sig}"); |
|||
|
|||
try |
|||
{ |
|||
HttpClient client = new HttpClient(); |
|||
client.Timeout = new TimeSpan(0, 1, 0, 0); |
|||
client.DefaultRequestHeaders.Add("x-ua-appKey", Constants.CDPAppKey); |
|||
var response = await client.GetAsync(URL); |
|||
if (response.StatusCode == System.Net.HttpStatusCode.OK) |
|||
{ |
|||
string responseString = await response.Content.ReadAsStringAsync(); |
|||
dynamic obj = JObject.Parse(responseString); |
|||
dynamic data = obj.data; |
|||
|
|||
return data; |
|||
} |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
Console.WriteLine(e.Message); |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
public static async Task<Boolean> ExpireSession(string sessionId, string userId) |
|||
{ |
|||
try |
|||
{ |
|||
var dataObj = new { sessionID = sessionId, userID = userId }; |
|||
var sig = Helpers.ComputeSignature(JsonConvert.SerializeObject(dataObj), Constants.CDPWriteKey); |
|||
var obj = new { data = dataObj, signature = sig }; |
|||
|
|||
HttpClient client = new HttpClient(); |
|||
client.Timeout = new TimeSpan(0, 1, 0, 0); |
|||
client.DefaultRequestHeaders.Add("x-ua-appKey", Constants.CDPAppKey); |
|||
var content = new StringContent(JsonConvert.SerializeObject(obj), Encoding.UTF8, "application/json"); |
|||
|
|||
var r = client.PostAsync("https://circleaccess.circlesecurity.ai/api/user/session/expire", content).Result; |
|||
return r.StatusCode == HttpStatusCode.OK; |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
Console.WriteLine(e); |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
|
|||
/// <summary>
|
|||
/// this is the HASH that Circle Auth likes, it's just a SHA265 in hex.
|
|||
/// </summary>
|
|||
public static string HashText(string rawData) |
|||
{ |
|||
try |
|||
{ |
|||
// Create a SHA256
|
|||
using SHA256 sha256Hash = SHA256.Create(); |
|||
byte[] bytes = sha256Hash.ComputeHash(Encoding.UTF8.GetBytes(rawData)); |
|||
StringBuilder builder = new(); |
|||
for (int i = 0; i < bytes.Length; i++) |
|||
{ |
|||
builder.Append(bytes[i].ToString("x2")); |
|||
} |
|||
return builder.ToString(); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
return null; |
|||
} |
|||
} |
|||
public static string HashAndShortenText(string text) |
|||
{ |
|||
// Hash the email address using SHA-256
|
|||
using (SHA256 sha256 = SHA256.Create()) |
|||
{ |
|||
byte[] hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(text)); |
|||
|
|||
string shortHash = Convert.ToBase64String(hashBytes); |
|||
|
|||
// make the short hash URL friendly
|
|||
shortHash = shortHash.Replace("+", "-").Replace("/", "_"); |
|||
|
|||
// Remove padding characters from the end of the Base64 string
|
|||
shortHash = shortHash.TrimEnd('='); |
|||
|
|||
return shortHash; |
|||
} |
|||
} |
|||
public static async Task<SingleLogin> CreateSession(string customerAppKey, string returnURL, string payloadJson, string webHook = "") |
|||
{ |
|||
var client = new HttpClient(); |
|||
|
|||
// wacky hack since I can't declare anonymous variables without initializing them
|
|||
var payloadObject = new |
|||
{ |
|||
Test = "123" |
|||
}; |
|||
|
|||
var dataObj = new |
|||
{ |
|||
payload = payloadJson, |
|||
customerAppKey = customerAppKey, |
|||
customID = "blahORama", |
|||
returnUrl = returnURL, |
|||
mobileUrl = returnURL, |
|||
webhookUrl = webHook |
|||
}; |
|||
|
|||
var sig = Helpers.ComputeSignature(JsonConvert.SerializeObject(dataObj), Constants.CDPWriteKey); |
|||
var obj = new { data = dataObj, signature = sig }; |
|||
string json = JsonConvert.SerializeObject(obj); |
|||
|
|||
client.DefaultRequestHeaders.Add("x-ua-appKey", Constants.CDPAppKey); |
|||
HttpContent c = new StringContent(json, Encoding.UTF8, "application/json"); |
|||
var response = await client.PostAsync(new Uri("https://circleaccess.circlesecurity.ai/api/single/create"), c); |
|||
|
|||
SingleLogin sl = new SingleLogin(); |
|||
if (response.StatusCode == System.Net.HttpStatusCode.OK) |
|||
{ |
|||
string body = response.Content.ReadAsStringAsync().Result; //right!
|
|||
dynamic stuff = JObject.Parse(body); |
|||
sl.QRCodeUrl = stuff.data.qrcode.ToString(); |
|||
sl.LoginId = stuff.data.singleLoginID.ToString(); |
|||
return sl; |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
} |
|||
|
|||
public class KeyVaultService |
|||
{ |
|||
private readonly string _keyVaultUrl; |
|||
|
|||
public KeyVaultService(string keyVaultUrl) |
|||
{ |
|||
_keyVaultUrl = keyVaultUrl; |
|||
} |
|||
|
|||
public async Task SetSecretAsync(string secretName, string secretValue) |
|||
{ |
|||
#if DEBUG
|
|||
var client = new SecretClient(new Uri(_keyVaultUrl), new VisualStudioCredential()); |
|||
#else
|
|||
var client = new SecretClient(new Uri(_keyVaultUrl), new DefaultAzureCredential()); |
|||
#endif
|
|||
|
|||
// Set the secret in the Key Vault
|
|||
await client.SetSecretAsync(secretName, secretValue); |
|||
} |
|||
public async Task<string> GetSecretAsync(string secretName) |
|||
{ |
|||
#if DEBUG
|
|||
var client = new SecretClient(new Uri(_keyVaultUrl), new VisualStudioCredential()); |
|||
#else
|
|||
var client = new SecretClient(new Uri(_keyVaultUrl), new DefaultAzureCredential()); |
|||
#endif
|
|||
|
|||
// Set the secret in the Key Vault
|
|||
KeyVaultSecret kvs = await client.GetSecretAsync(secretName); |
|||
return kvs.Value; |
|||
} |
|||
} |
|||
|
|||
|
|||
public class SingleLogin |
|||
{ |
|||
public string QRCodeUrl { get; set; } |
|||
public string LoginId { get; set; } |
|||
public SingleLogin() |
|||
{ |
|||
} |
|||
|
|||
public override string ToString() |
|||
{ |
|||
return QRCodeUrl; |
|||
} |
|||
} |
|||
|
|||
} |
@ -0,0 +1,181 @@ |
|||
using Microsoft.AspNetCore.Http; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Microsoft.Azure.Functions.Worker; |
|||
using Microsoft.Extensions.Logging; |
|||
|
|||
namespace CDP |
|||
{ |
|||
public class MailProcessor |
|||
{ |
|||
private readonly ILogger<MailProcessor> _logger; |
|||
|
|||
public MailProcessor(ILogger<MailProcessor> logger) |
|||
{ |
|||
_logger = logger; |
|||
} |
|||
|
|||
public static async Task ProcessMailEvent(MailRecord mailRecord, ILogger _logger) |
|||
{ |
|||
try |
|||
{ |
|||
for (int i = 0; i < mailRecord.AttachmentDetails.Count; i++) |
|||
{ |
|||
AddRevisionDocumentDto revDoc = new AddRevisionDocumentDto |
|||
{ |
|||
AppKey = mailRecord.AppKey, |
|||
EditedBy = mailRecord.SenderEmail, |
|||
FileId = mailRecord.AttachmentDetails[i].FileId, |
|||
RevisionDate = DateTime.UtcNow, |
|||
RevisionId = mailRecord.AttachmentDetails[i].HeadRevisionId, |
|||
Comments = "original" |
|||
}; |
|||
await FileRevision.AddRevisionInternal(revDoc, _logger); |
|||
|
|||
FileUploadDto fileUploadDto = new FileUploadDto |
|||
{ |
|||
AppKey = mailRecord.AppKey, |
|||
FileContent = mailRecord.Attachments[i], |
|||
FileId = mailRecord.AttachmentDetails[i].FileId, |
|||
VersionId = mailRecord.AttachmentDetails[i].HeadRevisionId |
|||
}; |
|||
|
|||
Task uploadTask = CDPBlobStorage.UploadBlobInternal(fileUploadDto, _logger); |
|||
|
|||
mailRecord.ReceiverEmails.ForEach(async (userToAdd) => |
|||
{ |
|||
AddFileUserDto addFileUserDto = new AddFileUserDto |
|||
{ |
|||
AppKey = mailRecord.AppKey, |
|||
FileId = mailRecord.AttachmentDetails[i].FileId, |
|||
FileName = mailRecord.AttachmentDetails[i].FileName, |
|||
Email = mailRecord.SenderEmail, |
|||
EmailToAdd = userToAdd, |
|||
Policy = "Read" |
|||
}; |
|||
await CDPLite.AddFileUserInternal(addFileUserDto); |
|||
}); |
|||
} |
|||
mailRecord.ReceiverEmails.ForEach(async (userToAdd) => |
|||
{ |
|||
AddFileUserDto addFileUserDto = new AddFileUserDto |
|||
{ |
|||
AppKey = mailRecord.AppKey, |
|||
FileId = mailRecord.MailId, |
|||
FileName = string.Concat(mailRecord.MailId, "-", "MailBody"), |
|||
Email = mailRecord.SenderEmail, |
|||
EmailToAdd = userToAdd, |
|||
Policy = "Read" |
|||
}; |
|||
await CDPLite.AddFileUserInternal(addFileUserDto); |
|||
}); |
|||
} catch (Exception ex) |
|||
{ |
|||
_logger.LogError(ex.ToString()); |
|||
return; |
|||
} |
|||
} |
|||
|
|||
public static async Task ProcessMailEventConcurrent(MailRecord mailRecord, ILogger _logger) |
|||
{ |
|||
List<Task> revisionTasks = new List<Task>(); |
|||
List<Task> uploadTasks = new List<Task>(); |
|||
List<Task> addPolicyTasks = new List<Task>(); |
|||
|
|||
for (int i = 0; i < mailRecord.AttachmentDetails.Count; i++) |
|||
{ |
|||
AddRevisionDocumentDto revDoc = new AddRevisionDocumentDto |
|||
{ |
|||
AppKey = mailRecord.AppKey, |
|||
EditedBy = mailRecord.SenderEmail, |
|||
FileId = mailRecord.AttachmentDetails[i].FileId, |
|||
RevisionDate = DateTime.UtcNow, |
|||
RevisionId = mailRecord.AttachmentDetails[i].HeadRevisionId, |
|||
Comments = "original" |
|||
}; |
|||
Task revTask = FileRevision.AddRevisionInternal(revDoc, _logger); |
|||
|
|||
revisionTasks.Add(revTask); |
|||
|
|||
FileUploadDto fileUploadDto = new FileUploadDto |
|||
{ |
|||
AppKey = mailRecord.AppKey, |
|||
FileContent = mailRecord.Attachments[i], |
|||
FileId = mailRecord.AttachmentDetails[i].FileId, |
|||
VersionId = mailRecord.AttachmentDetails[i].HeadRevisionId |
|||
}; |
|||
|
|||
Task uploadTask = CDPBlobStorage.UploadBlobInternal(fileUploadDto, _logger); |
|||
|
|||
mailRecord.ReceiverEmails.ForEach(userToAdd => |
|||
{ |
|||
AddFileUserDto addFileUserDto = new AddFileUserDto |
|||
{ |
|||
AppKey = mailRecord.AppKey, |
|||
FileId = mailRecord.AttachmentDetails[i].FileId, |
|||
FileName = mailRecord.AttachmentDetails[i].FileName, |
|||
Email = mailRecord.SenderEmail, |
|||
EmailToAdd = userToAdd, |
|||
Policy = "Read" |
|||
}; |
|||
Task addPolicy = CDPLite.AddFileUserInternal(addFileUserDto); |
|||
addPolicyTasks.Add(addPolicy); |
|||
}); |
|||
} |
|||
mailRecord.ReceiverEmails.ForEach(userToAdd => |
|||
{ |
|||
AddFileUserDto addFileUserDto = new AddFileUserDto |
|||
{ |
|||
AppKey = mailRecord.AppKey, |
|||
FileId = mailRecord.MailId, |
|||
FileName = string.Concat(mailRecord.MailId, "-", "MailBody"), |
|||
Email = mailRecord.SenderEmail, |
|||
EmailToAdd = userToAdd, |
|||
Policy = "Read" |
|||
}; |
|||
Task addPolicy = CDPLite.AddFileUserInternal(addFileUserDto); |
|||
}); |
|||
_logger.LogInformation("revTasks: {0} AddFileTasks: {1} uploadTasks: {2}", revisionTasks.Count, addPolicyTasks.Count, uploadTasks.Count); |
|||
try |
|||
{ |
|||
await Task.WhenAll(uploadTasks); |
|||
} |
|||
catch (AggregateException ex) |
|||
{ |
|||
ex.Handle(ex => |
|||
{ |
|||
_logger.LogError("upload failed with message: {0}", ex.ToString()); |
|||
return true; |
|||
}); |
|||
} |
|||
|
|||
try |
|||
{ |
|||
await Task.WhenAll(revisionTasks); |
|||
} |
|||
catch (AggregateException ex) |
|||
{ |
|||
ex.Handle(ex => |
|||
{ |
|||
_logger.LogError("add revision failed with message: {0}", ex.ToString()); |
|||
return true; |
|||
}); |
|||
} |
|||
|
|||
try |
|||
{ |
|||
await Task.WhenAll(addPolicyTasks); |
|||
} |
|||
catch (AggregateException ex) |
|||
{ |
|||
ex.Handle(ex => |
|||
{ |
|||
_logger.LogError("add policy failed with message: {0}", ex.ToString()); |
|||
return true; |
|||
}); |
|||
} |
|||
|
|||
} |
|||
|
|||
|
|||
} |
|||
} |
@ -0,0 +1,14 @@ |
|||
using Microsoft.Azure.Functions.Worker; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Hosting; |
|||
|
|||
var host = new HostBuilder() |
|||
.ConfigureFunctionsWebApplication() |
|||
.ConfigureServices(services => |
|||
{ |
|||
services.AddApplicationInsightsTelemetryWorkerService(); |
|||
services.ConfigureFunctionsApplicationInsights(); |
|||
}) |
|||
.Build(); |
|||
|
|||
host.Run(); |
@ -0,0 +1,113 @@ |
|||
{ |
|||
"$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#", |
|||
"contentVersion": "1.0.0.0", |
|||
"metadata": { |
|||
"_dependencyType": "compute.appService.windows" |
|||
}, |
|||
"parameters": { |
|||
"resourceGroupName": { |
|||
"type": "string", |
|||
"defaultValue": "CDPDev", |
|||
"metadata": { |
|||
"description": "Name of the resource group for the resource. It is recommended to put resources under same resource group for better tracking." |
|||
} |
|||
}, |
|||
"resourceGroupLocation": { |
|||
"type": "string", |
|||
"defaultValue": "centralindia", |
|||
"metadata": { |
|||
"description": "Location of the resource group. Resource groups could have different location than resources, however by default we use API versions from latest hybrid profile which support all locations for resource types we support." |
|||
} |
|||
}, |
|||
"resourceName": { |
|||
"type": "string", |
|||
"defaultValue": "cdpfuncs", |
|||
"metadata": { |
|||
"description": "Name of the main resource to be created by this template." |
|||
} |
|||
}, |
|||
"resourceLocation": { |
|||
"type": "string", |
|||
"defaultValue": "[parameters('resourceGroupLocation')]", |
|||
"metadata": { |
|||
"description": "Location of the resource. By default use resource group's location, unless the resource provider is not supported there." |
|||
} |
|||
} |
|||
}, |
|||
"variables": { |
|||
"appServicePlan_name": "[concat('Plan', uniqueString(concat(parameters('resourceName'), subscription().subscriptionId)))]", |
|||
"appServicePlan_ResourceId": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', parameters('resourceGroupName'), '/providers/Microsoft.Web/serverFarms/', variables('appServicePlan_name'))]" |
|||
}, |
|||
"resources": [ |
|||
{ |
|||
"type": "Microsoft.Resources/resourceGroups", |
|||
"name": "[parameters('resourceGroupName')]", |
|||
"location": "[parameters('resourceGroupLocation')]", |
|||
"apiVersion": "2019-10-01" |
|||
}, |
|||
{ |
|||
"type": "Microsoft.Resources/deployments", |
|||
"name": "[concat(parameters('resourceGroupName'), 'Deployment', uniqueString(concat(parameters('resourceName'), subscription().subscriptionId)))]", |
|||
"resourceGroup": "[parameters('resourceGroupName')]", |
|||
"apiVersion": "2019-10-01", |
|||
"dependsOn": [ |
|||
"[parameters('resourceGroupName')]" |
|||
], |
|||
"properties": { |
|||
"mode": "Incremental", |
|||
"template": { |
|||
"$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", |
|||
"contentVersion": "1.0.0.0", |
|||
"resources": [ |
|||
{ |
|||
"location": "[parameters('resourceLocation')]", |
|||
"name": "[parameters('resourceName')]", |
|||
"type": "Microsoft.Web/sites", |
|||
"apiVersion": "2015-08-01", |
|||
"tags": { |
|||
"[concat('hidden-related:', variables('appServicePlan_ResourceId'))]": "empty" |
|||
}, |
|||
"dependsOn": [ |
|||
"[variables('appServicePlan_ResourceId')]" |
|||
], |
|||
"kind": "app", |
|||
"properties": { |
|||
"name": "[parameters('resourceName')]", |
|||
"kind": "app", |
|||
"httpsOnly": true, |
|||
"reserved": false, |
|||
"serverFarmId": "[variables('appServicePlan_ResourceId')]", |
|||
"siteConfig": { |
|||
"metadata": [ |
|||
{ |
|||
"name": "CURRENT_STACK", |
|||
"value": "dotnetcore" |
|||
} |
|||
] |
|||
} |
|||
}, |
|||
"identity": { |
|||
"type": "SystemAssigned" |
|||
} |
|||
}, |
|||
{ |
|||
"location": "[parameters('resourceLocation')]", |
|||
"name": "[variables('appServicePlan_name')]", |
|||
"type": "Microsoft.Web/serverFarms", |
|||
"apiVersion": "2015-08-01", |
|||
"sku": { |
|||
"name": "S1", |
|||
"tier": "Standard", |
|||
"family": "S", |
|||
"size": "S1" |
|||
}, |
|||
"properties": { |
|||
"name": "[variables('appServicePlan_name')]" |
|||
} |
|||
} |
|||
] |
|||
} |
|||
} |
|||
} |
|||
] |
|||
} |
@ -0,0 +1,174 @@ |
|||
{ |
|||
"$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#", |
|||
"contentVersion": "1.0.0.0", |
|||
"metadata": { |
|||
"_dependencyType": "compute.function.windows.appService" |
|||
}, |
|||
"parameters": { |
|||
"resourceGroupName": { |
|||
"type": "string", |
|||
"defaultValue": "CDPDev", |
|||
"metadata": { |
|||
"description": "Name of the resource group for the resource. It is recommended to put resources under same resource group for better tracking." |
|||
} |
|||
}, |
|||
"resourceGroupLocation": { |
|||
"type": "string", |
|||
"defaultValue": "centralindia", |
|||
"metadata": { |
|||
"description": "Location of the resource group. Resource groups could have different location than resources, however by default we use API versions from latest hybrid profile which support all locations for resource types we support." |
|||
} |
|||
}, |
|||
"resourceName": { |
|||
"type": "string", |
|||
"defaultValue": "cdpfuncs", |
|||
"metadata": { |
|||
"description": "Name of the main resource to be created by this template." |
|||
} |
|||
}, |
|||
"resourceLocation": { |
|||
"type": "string", |
|||
"defaultValue": "[parameters('resourceGroupLocation')]", |
|||
"metadata": { |
|||
"description": "Location of the resource. By default use resource group's location, unless the resource provider is not supported there." |
|||
} |
|||
} |
|||
}, |
|||
"resources": [ |
|||
{ |
|||
"type": "Microsoft.Resources/resourceGroups", |
|||
"name": "[parameters('resourceGroupName')]", |
|||
"location": "[parameters('resourceGroupLocation')]", |
|||
"apiVersion": "2019-10-01" |
|||
}, |
|||
{ |
|||
"type": "Microsoft.Resources/deployments", |
|||
"name": "[concat(parameters('resourceGroupName'), 'Deployment', uniqueString(concat(parameters('resourceName'), subscription().subscriptionId)))]", |
|||
"resourceGroup": "[parameters('resourceGroupName')]", |
|||
"apiVersion": "2019-10-01", |
|||
"dependsOn": [ |
|||
"[parameters('resourceGroupName')]" |
|||
], |
|||
"properties": { |
|||
"mode": "Incremental", |
|||
"expressionEvaluationOptions": { |
|||
"scope": "inner" |
|||
}, |
|||
"parameters": { |
|||
"resourceGroupName": { |
|||
"value": "[parameters('resourceGroupName')]" |
|||
}, |
|||
"resourceGroupLocation": { |
|||
"value": "[parameters('resourceGroupLocation')]" |
|||
}, |
|||
"resourceName": { |
|||
"value": "[parameters('resourceName')]" |
|||
}, |
|||
"resourceLocation": { |
|||
"value": "[parameters('resourceLocation')]" |
|||
} |
|||
}, |
|||
"template": { |
|||
"$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", |
|||
"contentVersion": "1.0.0.0", |
|||
"parameters": { |
|||
"resourceGroupName": { |
|||
"type": "string" |
|||
}, |
|||
"resourceGroupLocation": { |
|||
"type": "string" |
|||
}, |
|||
"resourceName": { |
|||
"type": "string" |
|||
}, |
|||
"resourceLocation": { |
|||
"type": "string" |
|||
} |
|||
}, |
|||
"variables": { |
|||
"storage_name": "[toLower(concat('storage', uniqueString(concat(parameters('resourceName'), subscription().subscriptionId))))]", |
|||
"appServicePlan_name": "[concat('Plan', uniqueString(concat(parameters('resourceName'), subscription().subscriptionId)))]", |
|||
"storage_ResourceId": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', parameters('resourceGroupName'), '/providers/Microsoft.Storage/storageAccounts/', variables('storage_name'))]", |
|||
"appServicePlan_ResourceId": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', parameters('resourceGroupName'), '/providers/Microsoft.Web/serverFarms/', variables('appServicePlan_name'))]", |
|||
"function_ResourceId": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', parameters('resourceGroupName'), '/providers/Microsoft.Web/sites/', parameters('resourceName'))]" |
|||
}, |
|||
"resources": [ |
|||
{ |
|||
"location": "[parameters('resourceLocation')]", |
|||
"name": "[parameters('resourceName')]", |
|||
"type": "Microsoft.Web/sites", |
|||
"apiVersion": "2015-08-01", |
|||
"tags": { |
|||
"[concat('hidden-related:', variables('appServicePlan_ResourceId'))]": "empty" |
|||
}, |
|||
"dependsOn": [ |
|||
"[variables('appServicePlan_ResourceId')]", |
|||
"[variables('storage_ResourceId')]" |
|||
], |
|||
"kind": "functionapp", |
|||
"properties": { |
|||
"name": "[parameters('resourceName')]", |
|||
"kind": "functionapp", |
|||
"httpsOnly": true, |
|||
"reserved": false, |
|||
"serverFarmId": "[variables('appServicePlan_ResourceId')]", |
|||
"siteConfig": { |
|||
"alwaysOn": true |
|||
} |
|||
}, |
|||
"identity": { |
|||
"type": "SystemAssigned" |
|||
}, |
|||
"resources": [ |
|||
{ |
|||
"name": "appsettings", |
|||
"type": "config", |
|||
"apiVersion": "2015-08-01", |
|||
"dependsOn": [ |
|||
"[variables('function_ResourceId')]" |
|||
], |
|||
"properties": { |
|||
"AzureWebJobsStorage": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storage_name'), ';AccountKey=', listKeys(variables('storage_ResourceId'), '2017-10-01').keys[0].value, ';EndpointSuffix=', 'core.windows.net')]", |
|||
"FUNCTIONS_EXTENSION_VERSION": "~3", |
|||
"FUNCTIONS_WORKER_RUNTIME": "dotnet" |
|||
} |
|||
} |
|||
] |
|||
}, |
|||
{ |
|||
"location": "[parameters('resourceGroupLocation')]", |
|||
"name": "[variables('storage_name')]", |
|||
"type": "Microsoft.Storage/storageAccounts", |
|||
"apiVersion": "2017-10-01", |
|||
"tags": { |
|||
"[concat('hidden-related:', concat('/providers/Microsoft.Web/sites/', parameters('resourceName')))]": "empty" |
|||
}, |
|||
"properties": { |
|||
"supportsHttpsTrafficOnly": true |
|||
}, |
|||
"sku": { |
|||
"name": "Standard_LRS" |
|||
}, |
|||
"kind": "Storage" |
|||
}, |
|||
{ |
|||
"location": "[parameters('resourceGroupLocation')]", |
|||
"name": "[variables('appServicePlan_name')]", |
|||
"type": "Microsoft.Web/serverFarms", |
|||
"apiVersion": "2015-08-01", |
|||
"sku": { |
|||
"name": "S1", |
|||
"tier": "Standard", |
|||
"family": "S", |
|||
"size": "S1" |
|||
}, |
|||
"properties": { |
|||
"name": "[variables('appServicePlan_name')]" |
|||
} |
|||
} |
|||
] |
|||
} |
|||
} |
|||
} |
|||
] |
|||
} |
@ -0,0 +1,9 @@ |
|||
{ |
|||
"profiles": { |
|||
"CDP_LTS_80": { |
|||
"commandName": "Project", |
|||
"commandLineArgs": "--port 7082", |
|||
"launchBrowser": false |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,11 @@ |
|||
{ |
|||
"dependencies": { |
|||
"appInsights1": { |
|||
"type": "appInsights" |
|||
}, |
|||
"storage1": { |
|||
"type": "storage", |
|||
"connectionId": "AzureWebJobsStorage" |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,11 @@ |
|||
{ |
|||
"dependencies": { |
|||
"appInsights1": { |
|||
"type": "appInsights.sdk" |
|||
}, |
|||
"storage1": { |
|||
"type": "storage.emulator", |
|||
"connectionId": "AzureWebJobsStorage" |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,12 @@ |
|||
{ |
|||
"version": "2.0", |
|||
"logging": { |
|||
"applicationInsights": { |
|||
"samplingSettings": { |
|||
"isEnabled": true, |
|||
"excludedTypes": "Request" |
|||
}, |
|||
"enableLiveMetricsFilters": true |
|||
} |
|||
} |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue