using Microsoft.AspNetCore.Mvc; using Microsoft.Azure.Cosmos; using Microsoft.Azure.Functions.Worker.Http; using Microsoft.Azure.Functions.Worker; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace CDP { public class CDPLite { private readonly ILogger _logger; public static string FileAuditContainer = "FileAudits"; public static string UserAuditContainer = "UserAudits"; public static string GroupAuditContainer = "GroupAudits"; public static string TenantAuditContainer = "TenantAudits"; public CDPLite(ILogger log) { _logger = log; } /*internal async Task AddFilesBatchedInternal(AddFileBatchedDto dto) { string userId = Helpers.HashAndShortenText(dto.Email.ToLower()); string jobId = Guid.NewGuid().ToString(); List vaultEvents = new List(); //List auditEvents = new List(); List fileRecords = new List(); for (int i = 0; i < dto.Count; i++) { string fileId = Guid.NewGuid().ToString(); string fileName = dto.FileNames[i]; string aesKey = Helpers.GenerateAES256KeyToBase64(); //string message = string.Format($"{dto.Email} protected {fileName} file having {fileId} id."); KeyVaultEvent vaultEvent = new KeyVaultEvent { AESKey = aesKey, FileId = fileId }; //AuditEventMetadata auditEvent = new AuditEventMetadata { Action = "Addded", FileId = fileId, FileName = dto.FileNames[i], Message = message, UserId = userId }; //auditEvents.Add(auditEvent); vaultEvents.Add(vaultEvent); AccessPolicy ac = new AccessPolicy() { Access = "Owner", Key = aesKey, Email = dto.Email }; FileRecord fr = await CDPDB.UpsertFile(dto.AppKey, fileId, fileName, userId, "", ac); fileRecords.Add(fr); } List jobs = new List(); Job vaultJob = await AddKeyVaultBatchedEvent(vaultEvents, dto.AppKey); //Job auditJob = await AddAuditsBatchedEvent(auditEvents, dto.AppKey); //jobs.Add(auditJob); jobs.Add(vaultJob); await MetaProcessor.PublishBatchJobs(jobs); return new OkObjectResult(fileRecords); }*/ internal async Task AddFileInternal(AddFileDto dto, bool useKeyVaultEvent = false) { string fileId = Guid.NewGuid().ToString(); string userId = Helpers.HashAndShortenText(dto.Email.ToLower()); string fileName = dto.FileName; string aesKey = Helpers.GenerateAES256KeyToBase64(); // the KeyVault is slow for some reason and while it's dangerous to return a key // before we're sure it got added to the database...I'm going to do it anyway. if (useKeyVaultEvent) { await AddKeyVaultEvent(fileId, aesKey, dto.AppKey); } else { await Task.Run(async () => { try { KeyVaultService kvs = new KeyVaultService(Constants.KeyVaultURI); await kvs.SetSecretAsync(fileId, aesKey); } catch (Exception e) { Console.WriteLine(e); } }); } // when you add a file, you have rights to manage access to it. AccessPolicy ac = new AccessPolicy() { Access = "Owner", Key = aesKey, Email = dto.Email }; // since we're generating a new file id, a new entry will always be created. FileRecord fr = await CDPDB.UpsertFile(dto.AppKey, fileId, fileName, userId, "", ac); string message = string.Format($"{dto.Email} protected {fileName} file having {fileId} id."); string action = "Added"; await AuditFunctions.AddAuditsEvent(dto.AppKey, fileId, fileName, userId, dto.GroupId, action, message); //commenting for speed test return new OkObjectResult(fr); } internal static async Task 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 AuditFunctions.AddAuditsEvent(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 AuditFunctions.AddAuditsEvent(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 AuditFunctions.AddAuditsEvent(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 AuditFunctions.AddAuditsEvent(dto.AppKey, fileId, fileName, "", dto.Group.id, action, message); } return new OkObjectResult(fr); } #region CDP File Functions [Function("AddFile")] public async Task AddFile([HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequestData req) { _logger.LogInformation("AddFile invoked"); // Convert the JSON payload to a string string requestBody = await new StreamReader(req.Body).ReadToEndAsync(); AddFileDto dto = JsonConvert.DeserializeObject(requestBody); if (dto == null) return new BadRequestObjectResult(new { error = true, message = "Parse error." }); return await AddFileInternal(dto, true); } [Function("AddFileUser")] public async Task AddFileUser([HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequestData req) { _logger.LogInformation("AddFileUser invoked"); // Convert the JSON payload to a string string requestBody = await new StreamReader(req.Body).ReadToEndAsync(); AddFileUserDto dto = JsonConvert.DeserializeObject(requestBody); if (dto == null) return new BadRequestObjectResult(new { error = true, message = "Parse error." }); return await AddFileUserInternal(dto); } [Function("GetFileForUser")] public async Task GetFileForUser([HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequestData req) { _logger.LogInformation("GetFile invoked"); // Convert the JSON payload to a string string requestBody = await new StreamReader(req.Body).ReadToEndAsync(); AddFileUserDto dto = JsonConvert.DeserializeObject(requestBody); if (dto == null) return new BadRequestObjectResult(new { error = true, message = "Parse error." }); // check to see if the email has the power to add a user // string a = Helpers.HashAndShortenText(dto.Email.ToLower()); // string b = Helpers.HashToHex(dto.Email.ToLower()); // string c = Helpers.ConvertShortHashToHex(a); string userId = Helpers.HashAndShortenText(dto.Email.ToLower()); // userId = "user-" + Helpers.HashToHex(dto.Email.ToLower()); FileRecord fr = await CDPDB.GetFile(dto.AppKey, dto.FileId, userId); if (fr == null) { string AuditMessage = string.Format($"File not found for user {dto.Email} having fileId {dto.FileId}"); string AuditAction = "Decrypt failed"; await AuditFunctions.AddAuditsEvent(dto.AppKey, dto.FileId, dto.FileName, userId, "", AuditAction, AuditMessage); return new BadRequestObjectResult(new { error = true, message = "File not found for user " + dto.Email }); } if (fr.Policy.CheckAccess("None")) { string AuditMessage = string.Format($"{dto.Email} don't have the rights to decrypt {fr.FileName} file having {dto.FileId} id"); string AuditAction = "Decrypt failed"; await AuditFunctions.AddAuditsEvent(dto.AppKey, dto.FileId, fr.FileName, userId, "", AuditAction, AuditMessage); return new BadRequestObjectResult(new { error = true, message = "Access is denied for user " + dto.Email }); } KeyVaultService kvs = new KeyVaultService(Constants.KeyVaultURI); fr.Policy.Key = await kvs.GetSecretAsync(fr.FileId); string message = string.Format($"{dto.Email} decrypted {fr.FileName} file having {dto.FileId} id"); string action = "Decrypted"; await AuditFunctions.AddAuditsEvent(dto.AppKey, dto.FileId, dto.FileName, userId, "", action, message); return new OkObjectResult(fr); } [Function("GetPoliciesForFile")] public async Task GetPoliciesForFile([HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequestData req) { _logger.LogInformation("GetFile invoked"); // Convert the JSON payload to a string string requestBody = await new StreamReader(req.Body).ReadToEndAsync(); GetPoliciesForFileDto dto = JsonConvert.DeserializeObject(requestBody); if (dto == null) return new BadRequestObjectResult(new { error = true, message = "Parse error." }); List fr = await CDPDB.GetPoliciesForFile(dto.AppKey, dto.FileId); if (fr == null) { return new BadRequestObjectResult(new { error = true, message = "File not found " + dto.FileId }); } KeyVaultService kvs = new KeyVaultService(Constants.KeyVaultURI); string aesKey = await kvs.GetSecretAsync(dto.FileId); foreach (var f in fr) { f.Policy.Key = aesKey; } return new OkObjectResult(fr); } [Function("DeleteRegisteredUserPolicies")] public async Task DeleteRegisteredUserPolicies([HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequestData req) { _logger.LogInformation("Deleting Registered User invoked"); // Convert the JSON payload to a string string requestBody = await new StreamReader(req.Body).ReadToEndAsync(); DeleteRegisteredUserDto deleteContactdto = JsonConvert.DeserializeObject(requestBody); if (deleteContactdto == null) return new BadRequestObjectResult(new { error = true, message = "Parse error." }); string userId = Helpers.HashAndShortenText(deleteContactdto.UserEmail.ToLower()); await CDPDB.revokeRegisteredUserPolicies(deleteContactdto.AppKey, deleteContactdto.UserEmail.ToLower(), deleteContactdto.ContactEmail.ToLower(), deleteContactdto.AdminEmail.ToLower()); return new OkObjectResult(true); } #endregion #region Background Processing Support public static async Task AddAuditsBatchedEvent(List auditEvents, string appKey) { using (var mt = new MethodTimer("AddAuditsBatchedEvent")) { string jobMeta = JsonConvert.SerializeObject(auditEvents); string jobId = Guid.NewGuid().ToString(); Job job = new Job { AppKey = appKey, EventType = JobType.AddAuditsBatch, Id = jobId, JobMetadata = jobMeta }; return job; } } public static async Task AddKeyVaultBatchedEvent(List vaultEvents, string appKey) { using (var mt = new MethodTimer("AddKeyVaultBatchedEvent")) { string jobMeta = JsonConvert.SerializeObject(vaultEvents); string jobId = Guid.NewGuid().ToString(); Job job = new Job { AppKey = appKey, EventType = JobType.KeyVaultInsertionBatch, Id = jobId, JobMetadata = jobMeta }; //await MetaProcessor.PublishJob(job); return job; } } public static async Task AddKeyVaultEvent(string fileId, string aesKey, string appKey) { using (var mt = new MethodTimer("AddKeyVaultEvent")) { KeyVaultEvent vaultEvent = new KeyVaultEvent { AESKey = aesKey, FileId = fileId }; string jobMeta = JsonConvert.SerializeObject(vaultEvent); string jobId = Guid.NewGuid().ToString(); Job keyVaultJob = new Job { AppKey = appKey, EventType = JobType.KeyVaultInsertion, Id = jobId, JobMetadata = jobMeta }; await MetaProcessor.PublishJob(keyVaultJob); return jobId; } } #endregion } }