Webapp/SDAF/Controllers/FileController.cs (403 lines of code) (raw):
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using SDAFWebApp.Models;
using SDAFWebApp.Services;
using SDAFWebApp.Controllers;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using System.IO;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Net.Http.Headers;
using Newtonsoft.Json;
using JsonSerializer = System.Text.Json.JsonSerializer;
using System.Drawing.Drawing2D;
using System.Collections.Concurrent;
namespace SDAFWebApp.Controllers
{
public class FileController : Controller
{
private readonly ITableStorageService<AppFile> _appFileService;
private readonly ITableStorageService<LandscapeEntity> _landscapeService;
private readonly ITableStorageService<SystemEntity> _systemService;
private readonly RestHelper restHelper;
public FileController(ITableStorageService<AppFile> appFileService, ITableStorageService<LandscapeEntity> landscapeService,
ITableStorageService<SystemEntity> systemService, IConfiguration configuration)
{
_appFileService = appFileService;
_landscapeService = landscapeService;
_systemService = systemService;
restHelper = new RestHelper(configuration, "GIT");
}
[ActionName("Index")]
public async Task<IActionResult> Index()
{
return View(await _appFileService.GetAllAsync());
}
[ActionName("Templates")]
public ActionResult Templates(string sourceController)
{
string[] landscapeFilePaths = restHelper.GetTemplateFileNames("Terraform/WORKSPACES/LANDSCAPE").Result;
string[] systemFilePaths = restHelper.GetTemplateFileNames("Terraform/WORKSPACES/SYSTEM").Result;
Dictionary<string, string[]> filePaths = new()
{
{ "landscapes", landscapeFilePaths },
{ "systems", systemFilePaths }
};
ViewBag.SourceController = sourceController;
return View(filePaths);
}
[ActionName("UseTemplate")]
public IActionResult UseTemplate(string fileName, string sourceController)
{
string content = restHelper.GetTemplateFile(fileName).Result;
ViewBag.Message = content;
ViewBag.TemplateName = fileName[(fileName.LastIndexOf('/') + 1)..];
ViewBag.SourceController = sourceController;
return View("Create");
}
[ActionName("Upload")]
public IActionResult UploadAsync(string sourceController)
{
ViewBag.SourceController = sourceController;
return View();
}
[HttpPost]
[ActionName("Upload")]
public async Task<IActionResult> UploadAsync(FileUploadModel fileUpload, string sourceController)
{
// Perform an initial check to catch FileUpload class
// attribute violations.
if (ModelState.IsValid)
{
try
{
string[] permittedExtensions = { ".tfvars" };
long fileSizeLimit = 2097152;
foreach (var formFile in fileUpload.FormFiles)
{
byte[] formFileContent =
await Helper.ProcessFormFile(formFile, ModelState, permittedExtensions, fileSizeLimit);
// Perform a second check to catch ProcessFormFile method
// violations. If any validation check fails, return to the
// page.
if (!ModelState.IsValid)
{
ViewBag.SourceController = sourceController;
return View();
}
// **WARNING!**
// In the following example, the file is saved without
// scanning the file's contents. In most production
// scenarios, an anti-virus/anti-malware scanner API
// is used on the file before making the file available
// for download or for use by other systems.
// For more information, see the topic that accompanies
// this sample.
AppFile file = new()
{
Content = formFileContent,
UntrustedName = formFile.FileName,
Size = formFile.Length,
UploadDT = DateTime.UtcNow,
Id = WebUtility.HtmlEncode(formFile.FileName)
};
await _appFileService.CreateAsync(file);
TempData["success"] = "Successfully uploaded file(s)";
}
}
catch (Exception e)
{
TempData["error"] = "Error uploading files: " + e.Message;
}
return RedirectToAction("Index", sourceController);
}
ViewBag.SourceController = sourceController;
return View();
}
[ActionName("Convert")]
public async Task<IActionResult> ConvertFileToObject(string id, string sourceController)
{
try
{
// Convert a file to a landscape or system object
AppFile file = await _appFileService.GetByIdAsync(id, GetPartitionKey(id));
if (file == null) return NotFound();
id = id[..id.IndexOf('.')];
byte[] bytes = file.Content;
string bitString = Encoding.UTF8.GetString(bytes);
string jsonString = Helper.TfvarToJson(bitString);
if (file.Id.EndsWith("INFRASTRUCTURE.tfvars"))
{
LandscapeModel landscape = JsonSerializer.Deserialize<LandscapeModel>(jsonString);
landscape.Id = id;
await _landscapeService.CreateAsync(new LandscapeEntity(landscape));
TempData["success"] = "Successfully converted file " + id + " to a workload zone object";
}
else
{
SystemModel system = JsonSerializer.Deserialize<SystemModel>(jsonString);
system.Id = id;
await _systemService.CreateAsync(new SystemEntity(system));
TempData["success"] = "Successfully converted file " + id + " to a system object";
}
}
catch (Exception e)
{
TempData["error"] = "Error converting file: " + e.Message;
}
return RedirectToAction("Index", sourceController);
}
[ActionName("Details")]
public async Task<IActionResult> DetailsAsync(string id, string sourceController)
{
AppFile file = await _appFileService.GetByIdAsync(id, GetPartitionKey(id));
if (file == null) return NotFound();
byte[] bytes = file.Content;
string bitString = Encoding.UTF8.GetString(bytes);
ViewBag.Message = bitString;
ViewBag.SourceController = sourceController;
return View(file);
}
[ActionName("Create")]
public IActionResult Create(string sourceController)
{
ViewBag.SourceController = sourceController;
return View();
}
[HttpPost]
[ActionName("Create")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> CreateAsync(string id, string fileContent, string templateName, string sourceController)
{
try
{
byte[] bytes = Encoding.UTF8.GetBytes(fileContent);
AppFile file = new()
{
Id = WebUtility.HtmlEncode(id),
Content = bytes,
UntrustedName = id,
Size = bytes.Length,
UploadDT = DateTime.UtcNow
};
await _appFileService.CreateAsync(file);
TempData["success"] = "Successfully created file " + id;
return RedirectToAction("Index", sourceController);
}
catch (Exception e)
{
ModelState.AddModelError("FileId", "Error creating file: " + e.Message);
}
ViewBag.TemplateName = templateName;
ViewBag.Message = fileContent;
ViewBag.SourceController = sourceController;
return View();
}
[ActionName("Edit")]
public async Task<IActionResult> EditAsync(string id, string sourceController, string fileName, int type = 0)
{
AppFile file = null;
ViewBag.IsImagesFile = false;
switch (type)
{
case 0:
file = await GetImagesFile(fileName, type, "VM");
ViewBag.IsImagesFile = true;
file.FileType = 0;
break;
case 1:
case 2:
file = await GetImagesFile(id + "_" + fileName, type, GetPartitionKey(id));
ViewBag.IsImagesFile = true;
ViewBag.FilePattern = id + "_" + fileName;
file.FileType = type;
break;
default:
file = await _appFileService.GetByIdAsync(id, GetPartitionKey(id));
break;
}
if (file == null) return NotFound();
byte[] bytes = file.Content;
string bitString = Encoding.UTF8.GetString(bytes);
ViewBag.Message = bitString;
ViewBag.SourceController = sourceController;
ViewBag.Type = type;
return View(file);
}
[HttpPost]
[ActionName("Edit")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> EditAsync(string id, string newId, string fileContent, string sourceController, int type)
{
AppFile file = null;
ViewBag.IsImagesFile = false;
int newType = type;
if (newId.EndsWith("_custom_naming.json"))
{
newType = 2;
}
else if (newId.EndsWith("_custom_sizes.json"))
{
newType = 1;
}
switch (newType)
{
case 0:
file = await GetImagesFile(newId, newType, "VM");
ViewBag.IsImagesFile = true;
break;
case 1:
case 2:
file = await GetImagesFile(newId, newType, GetPartitionKey(id));
ViewBag.IsImagesFile = true;
ViewBag.FilePattern = newId;
break;
default:
file = await _appFileService.GetByIdAsync(id, GetPartitionKey(id));
break;
}
if (file == null) return NotFound();
try
{
byte[] bytes = Encoding.UTF8.GetBytes(fileContent);
file.Content = bytes;
if (id != newId)
{
file.Id = newId;
await _appFileService.CreateAsync(file);
await _appFileService.DeleteAsync(id, GetPartitionKey(id));
}
else
{
await _appFileService.UpdateAsync(file);
}
TempData["success"] = "Successfully updated file " + id;
if (newType == 0)
{
return RedirectToAction("Index", sourceController);
}
else
{
string newName = id[..id.IndexOf("_custom")];
return RedirectToAction("Edit", sourceController, new { @id = newName, @partitionKey = GetPartitionKey(id) });
}
}
catch (Exception e)
{
ModelState.AddModelError("FileId", "Error updating file: " + e.Message);
}
ViewBag.Message = fileContent;
ViewBag.SourceController = sourceController;
ViewBag.Type = type;
return View(file);
}
[HttpPost]
[ActionName("SubmitNew")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> SubmitNewAsync(string id, string newId, string fileContent, string sourceController)
{
AppFile file = await _appFileService.GetByIdAsync(id, GetPartitionKey(id));
if (file == null) return NotFound();
file.Id = newId;
byte[] bytes = Encoding.UTF8.GetBytes(fileContent);
file.Content = bytes;
try
{
await _appFileService.CreateAsync(file);
TempData["success"] = "Successfully created file " + id;
return RedirectToAction("Index", sourceController);
}
catch (Exception e)
{
ModelState.AddModelError("FileId", "Error creating file: " + e.Message);
}
ViewBag.Message = fileContent;
ViewBag.SourceController = sourceController;
return View("Edit", file);
}
[ActionName("Delete")]
public async Task<IActionResult> DeleteAsync(string id, string sourceController)
{
if (id == null)
{
return BadRequest();
}
AppFile file = await _appFileService.GetByIdAsync(id, GetPartitionKey(id));
if (file == null)
{
return NotFound();
}
byte[] bytes = file.Content;
string bitString = Encoding.UTF8.GetString(bytes);
ViewBag.Message = bitString;
ViewBag.SourceController = sourceController;
return View(file);
}
[HttpPost]
[ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmedAsync(string id, string sourceController)
{
await _appFileService.DeleteAsync(id, GetPartitionKey(id));
TempData["success"] = "Successfully deleted file " + id;
return RedirectToAction("Index", sourceController);
}
[ActionName("Download")]
public async Task<ActionResult> DownloadFile(string id, string sourceController, string fileName, bool isImagesFile = false)
{
try
{
AppFile file = (isImagesFile) ? await GetImagesFile(fileName, 0, GetPartitionKey(id)) : await _appFileService.GetByIdAsync(id, GetPartitionKey(id));
if (file == null) return NotFound();
var stream = new MemoryStream(file.Content);
return new FileStreamResult(stream, new MediaTypeHeaderValue("text/plain"))
{
FileDownloadName = id
};
}
catch (Exception e)
{
TempData["error"] = "Something went wrong downloading file " + id + ": " + e.Message;
return RedirectToAction("Index", sourceController);
}
}
private string GetPartitionKey(string id)
{
return id[..id.IndexOf('-')];
}
public async Task<AppFile> GetImagesFile(string filename, int type, string partitionKey)
{
AppFile file = null;
try
{
file = await _appFileService.GetByIdAsync(filename, partitionKey);
}
catch
{
string newName = filename;
if (filename.EndsWith("_custom_sizes.json"))
{
newName = filename[(filename.IndexOf("_custom_sizes.json") + 1)..];
type = 1;
}
if (filename.EndsWith("_custom_naming.json"))
{
newName = filename[(filename.IndexOf("_custom_naming.json") + 1)..];
type = 2;
}
if (newName.Contains("..") || newName.Contains("/") || newName.Contains("\\"))
{
throw new Exception("Invalid filename");
}
else
{
byte[] byteContent = System.IO.File.ReadAllBytes("ParameterDetails/" + newName);
using (MemoryStream memory = new(byteContent))
{
file = new AppFile()
{
Id = WebUtility.HtmlEncode(filename),
Content = byteContent,
UntrustedName = filename,
Size = memory.Length,
UploadDT = DateTime.UtcNow,
FileType = type
};
}
}
}
return file ?? new AppFile();
}
}
}