Parcourir la source

Uploads - Got encryption working, got file upload working (not encrypted version though), and got upload saving server side.

tags/2.0.3
Teknikode il y a 4 ans
Parent
révision
ebf069e43f

+ 9
- 2
Teknik/Areas/Upload/Controllers/UploadController.cs Voir le fichier

@@ -28,8 +28,9 @@ namespace Teknik.Areas.Upload.Controllers
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Upload()
public ActionResult Upload(string iv)
{
Models.Upload upload = null;
foreach (string fileName in Request.Files)
{
HttpPostedFileBase file = Request.Files[fileName];
@@ -37,9 +38,15 @@ namespace Teknik.Areas.Upload.Controllers
string fName = file.FileName;
if (file != null && file.ContentLength > 0)
{
upload = Uploader.SaveFile(file, iv);
break;
}
}
return Json(new { result = "tempURL.png" });
if (upload != null)
{
return Json(new { result = new { name = upload.Url, url = Url.SubRouteUrl("upload", "Upload.Download", new { file = upload.Url }) } }, "text/plain");
}
return Json(new { error = "Unable to upload file" });
}

[HttpPost]

+ 8
- 2
Teknik/Areas/Upload/Models/Upload.cs Voir le fichier

@@ -18,9 +18,15 @@ namespace Teknik.Areas.Upload.Models

public string Url { get; set; }

public int FileSize { get; set; }
public string FileName { get; set; }

public string Hash { get; set; }
public int ContentLength { get; set; }

public string ContentType { get; set; }

public string Key { get; set; }

public string IV { get; set; }

public string DeleteKey { get; set; }
}

+ 23
- 0
Teknik/Areas/Upload/Scripts/EncryptionWorker.js Voir le fichier

@@ -0,0 +1,23 @@
self.addEventListener('message', function (e) {
var data = e.data;
importScripts(data.script);

switch (data.cmd) {
case 'encrypt':
// encrypt the passed in file data
var encrypted = CryptoJS.AES.encrypt(data.file, data.key, { iv: data.iv });

var cipherText = encrypted.toString();

self.postMessage(cipherText);
break;
case 'decrypt':
// decrypt the passed in file data
var decrypted = CryptoJS.AES.decrypt(data.file, data.key, { iv: data.iv });

var fileText = decrypted.toString();

self.postMessage(fileText);
break;
}
}, false);

+ 106
- 33
Teknik/Areas/Upload/Scripts/Upload.js Voir le fichier

@@ -32,18 +32,19 @@ Dropzone.options.TeknikUpload = {
paramName: "file", // The name that will be used to transfer the file
maxFilesize: maxUploadSize, // MB
addRemoveLinks: true,
autoProcessQueue: false,
autoProcessQueue: true,
clickable: true,
accept: function (file, done) {
file.done = done;
encryptFile(file);
encryptFile(file, done);
},
init: function() {
this.on("addedfile", function(file, responseText) {
$("#upload_message").css('display', 'none', 'important');
});
this.on("success", function(file, response) {
var name = response.result;
this.on("success", function (file, response) {
obj = JSON.parse(response);
var name = obj.result.name;
var fullName = obj.result.url;
var short_name = file.name.split(".")[0].hashCode();
$("#upload-links").css('display', 'inline', 'important');
$("#upload-links").prepend(' \
@@ -52,7 +53,7 @@ Dropzone.options.TeknikUpload = {
'+file.name+' \
</div> \
<div class="col-sm-3"> \
<a href='+uploadURL+'/'+name+'" target="_blank" class="alert-link">'+uploadURL+'/'+name+'</a> \
<a href="' + fullName + '" target="_blank" class="alert-link">' + fullName + '</a> \
</div> \
<div class="col-sm-3"> \
<button type="button" class="btn btn-default btn-xs generate-delete-link-'+short_name+'" id="'+name+'">Generate Deletion URL</button> \
@@ -76,8 +77,8 @@ Dropzone.options.TeknikUpload = {
$("#top_msg").html('<div class="alert alert-danger alert-dismissable"><button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>'+errorMessage+'</div>');
});
this.on("totaluploadprogress", function(progress, totalBytes, totalBytesSent) {
$(".progress").children('.progress-bar').css('width', progress.toFixed(2)+'%');
$(".progress").children('.progress-bar').html(progress.toFixed(2)+'%');
$(".progress").children('.progress-bar').css('width', (progress.toFixed(2) * (3/5)) + 40 +'%');
$(".progress").children('.progress-bar').html(progress.toFixed(2)+'% Uploaded');
});
this.on("queuecomplete", function() {
$(".progress").children('.progress-bar').html('Complete');
@@ -85,6 +86,91 @@ Dropzone.options.TeknikUpload = {
}
};

// Function to encrypt a file, overide the file's data attribute with the encrypted value, and then call a callback function if supplied
function encryptFile(file, callback) {
// Start the file reader
var reader = new FileReader();

// When the file has been loaded, encrypt it
reader.onload = (function (theFile, callback) {
return function (e) {
// Create random key and iv
var keyStr = randomString(16, '#aA');
var ivStr = randomString(16, '#aA');
var key = CryptoJS.enc.Utf8.parse(keyStr);
var iv = CryptoJS.enc.Utf8.parse(ivStr);

// Display encryption message
$(".progress").children('.progress-bar').css('width', '20%');
$(".progress").children('.progress-bar').html('Encrypting...');

var worker = new Worker(encScriptSrc);

worker.addEventListener('message', function (e) {
// create the blob from e.data.encrypted
theFile.data = e.data;
theFile.key = keyStr;
theFile.iv = ivStr;

$("#iv").val(ivStr);

if (callback != null) {
// Finish
callback();
}
});

// Execute worker with data
worker.postMessage({
cmd: 'encrypt',
script: aesScriptSrc,
key: key,
iv: iv,
file: e.target.result,
chunkSize: 1024
});
};
})(file, callback);

// While reading, display the current progress
reader.onprogress = function (data) {
if (data.lengthComputable) {
var progress = parseInt(((data.loaded / data.total) * 100), 10);
$(".progress").children('.progress-bar').css('width', (progress.toFixed(2) / 5) + '%');
$(".progress").children('.progress-bar').html(progress.toFixed(2) + '% Loaded');
}
}

// Start async read
reader.readAsDataURL(file);
}

function encryptData(data, file, callback) {

// 1) create the jQuery Deferred object that will be used
var deferred = $.Deferred();

// Create random key and iv
var keyStr = randomString(16, '#aA');
var ivStr = randomString(16, '#aA');
var key = CryptoJS.enc.Utf8.parse(keyStr);
var iv = CryptoJS.enc.Utf8.parse(ivStr);

// Ecrypt the file
var encData = CryptoJS.AES.encrypt(data, key, { iv: iv });

// Save Data
file.data = encData;
file.key = keyStr;
file.iv = ivStr;

// Finish
callback();

// 2) return the promise of this deferred
return deferred.promise();
}

function readBlob(file, opt_startByte, opt_stopByte)
{
var start = parseInt(opt_startByte) || 0;
@@ -93,10 +179,19 @@ function readBlob(file, opt_startByte, opt_stopByte)
var reader = new FileReader();

reader.onload = (function (theFile) {
var callback = theFile.callback;
var callback = theFile.done;
window.processedSize += theFile.size;
return function (e) {
// Ecrypt the blog
window.bits.push(window.aesEncryptor.process(evt.target.result));
callback();
// Add the current size to the processed variable
window.processedSize += evt.target.result.size;
// If we have processed the entire file, let's do a finalize and call the callback
if (window.processedSize > window.totalSize - 1) {
window.bits.push(aesEncryptor.finalize());

callback();
}
};
})(file);

@@ -109,29 +204,7 @@ function readBlob(file, opt_startByte, opt_stopByte)
}

var blob = file.slice(start, stop + 1);
reader.readAsArrayBuffer(blob);
}

function encryptFile(file)
{
// INITIALIZE PROGRESSIVE ENCRYPTION
window.keyStr = randomString(16, '#aA');
window.ivStr = randomString(16, '#aA');
var key = CryptoJS.enc.Utf8.parse(keyStr);
var iv = CryptoJS.enc.Utf8.parse(ivStr);
window.aesEncryptor = CryptoJS.algo.AES.createEncryptor(key, { iv: iv });

// LOOP THROUGH BYTES AND PROGRESSIVELY ENCRYPT

window.bits = []

var startByte = 0;
var endByte = 0;
while (startByte <= file.size - 1) {
endByte = startByte + 1024;
readBlob(file, startByte, endByte);
startByte = endByte;
}
reader.readAsDataURL(blob);
}

function fileDataEncrypted(done)

+ 6
- 1
Teknik/Areas/Upload/UploadAreaRegistration.cs Voir le fichier

@@ -25,7 +25,7 @@ namespace Teknik.Areas.Upload
context.MapSubdomainRoute(
"Upload.Download",
"dev",
"Upload/{url}",
"Upload/{file}",
new { controller = "Upload", action = "Download", url = string.Empty },
new[] { typeof(Controllers.UploadController).Namespace }
);
@@ -106,6 +106,11 @@ namespace Teknik.Areas.Upload
"~/Areas/Upload/Scripts/Upload.js",
"~/Scripts/bootbox/bootbox.min.js",
"~/Areas/Upload/Scripts/aes.js"));
BundleTable.Bundles.Add(new ScriptBundle("~/bundles/cryptoWorker").Include(
"~/Areas/Upload/Scripts/EncryptionWorker.js"));
BundleTable.Bundles.Add(new ScriptBundle("~/bundles/crypto").Include(
"~/Areas/Upload/Scripts/aes.js"));

// Register Style Bundles
BundleTable.Bundles.Add(new StyleBundle("~/Content/upload").Include(
"~/Content/dropzone.css",

+ 56
- 0
Teknik/Areas/Upload/Uploader.cs Voir le fichier

@@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Teknik.Configuration;
using Teknik.Models;

namespace Teknik.Areas.Upload
{
public static class Uploader
{
public static Models.Upload SaveFile(HttpPostedFileBase file)
{
return SaveFile(file, null, null);
}

public static Models.Upload SaveFile(HttpPostedFileBase file, string iv)
{
return SaveFile(file, iv, null);
}

public static Models.Upload SaveFile(HttpPostedFileBase file, string iv, string key)
{
Config config = Config.Load();
TeknikEntities db = new TeknikEntities();

// Generate a unique file name that does not currently exist
string fileName = Utility.GenerateUniqueFileName(config.UploadConfig.UploadDirectory, config.UploadConfig.FileExtension, 10);

// once we have the filename, lets save the file
file.SaveAs(fileName);

// Generate a unique url
string extension = (config.UploadConfig.IncludeExtension) ? Utility.GetDefaultExtension(file.ContentType) : string.Empty;
string url = Utility.RandomString(config.UploadConfig.UrlLength) + extension;
while (db.Uploads.Where(u => u.Url == url).FirstOrDefault() != null)
{
url = Utility.RandomString(config.UploadConfig.UrlLength) + extension;
}

// Now we need to update the database with the new upload information
Models.Upload upload = db.Uploads.Create();
upload.DateUploaded = DateTime.Now;
upload.Url = url;
upload.FileName = fileName;
upload.ContentLength = file.ContentLength;
upload.ContentType = file.ContentType;
upload.Key = key;
upload.IV = iv;

db.Uploads.Add(upload);

return upload;
}
}
}

+ 1
- 0
Teknik/Areas/Upload/Views/Upload/Download.cshtml Voir le fichier

@@ -0,0 +1 @@
@model Teknik.Areas.Upload.ViewModels.UploadViewModel

+ 4
- 1
Teknik/Areas/Upload/Views/Upload/Index.cshtml Voir le fichier

@@ -1,6 +1,8 @@
@model Teknik.Areas.Upload.ViewModels.UploadViewModel

<script>
var encScriptSrc = '@Scripts.Url("~/bundles/cryptoWorker")';
var aesScriptSrc = '@Scripts.Url("~/bundles/crypto")';
var generateDeleteKeyURL = '@Url.SubRouteUrl("upload", "Upload.Action", new { action= "GenerateDeleteKey" })';
var uploadURL = '@Url.SubRouteUrl("upload", "Upload.Download")';
var maxUploadSize = @(Model.Config.UploadConfig.MaxUploadSize / 100000);
@@ -10,8 +12,9 @@

<div class="container">
<div class="row text-center">
<form action="@Url.SubRouteUrl("upload", "Upload.Upload")" class="dropzone" id="TeknikUpload" name="TeknikUpload" enctype="multipart/form-data">
<form action="@Url.SubRouteUrl("upload", "Upload.Action", new { action = "Upload" })" class="dropzone" id="TeknikUpload" name="TeknikUpload" enctype="multipart/form-data">
@Html.AntiForgeryToken()
<input name="iv" id="iv" type="hidden" />
<div class="dz-message text-center" id="upload_message">
<div class="row">
<div class="col-sm-12">

+ 10
- 0
Teknik/Configuration/UploadConfig.cs Voir le fichier

@@ -9,6 +9,12 @@ namespace Teknik.Configuration
{
// Max upload size in bytes
public int MaxUploadSize { get; set; }
// Location of the upload directory
public string UploadDirectory { get; set; }
// File Extension for saved files
public string FileExtension { get; set; }
public int UrlLength { get; set; }
public bool IncludeExtension { get; set; }

public UploadConfig()
{
@@ -18,6 +24,10 @@ namespace Teknik.Configuration
public void SetDefaults()
{
MaxUploadSize = 100000000;
UploadDirectory = string.Empty;
FileExtension = "enc";
UrlLength = 6;
IncludeExtension = true;
}
}
}

+ 64
- 1
Teknik/Helpers/Utility.cs Voir le fichier

@@ -1,7 +1,10 @@
using System;
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.IO;
using System.Linq;
using System.Text;
using System.Web;

namespace Teknik
@@ -25,5 +28,65 @@ namespace Teknik
}
return result;
}

public static string RandomString(int length, string allowedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
{
const int byteSize = 0x100;
var allowedCharSet = new HashSet<char>(allowedChars).ToArray();
if (byteSize < allowedCharSet.Length) throw new ArgumentException(String.Format("allowedChars may contain no more than {0} characters.", byteSize));

// Guid.NewGuid and System.Random are not particularly random. By using a
// cryptographically-secure random number generator, the caller is always
// protected, regardless of use.
using (var rng = new System.Security.Cryptography.RNGCryptoServiceProvider())
{
var result = new StringBuilder();
var buf = new byte[128];
while (result.Length < length)
{
rng.GetBytes(buf);
for (var i = 0; i < buf.Length && result.Length < length; ++i)
{
// Divide the byte into allowedCharSet-sized groups. If the
// random value falls into the last group and the last group is
// too small to choose from the entire allowedCharSet, ignore
// the value in order to avoid biasing the result.
var outOfRangeStart = byteSize - (byteSize % allowedCharSet.Length);
if (outOfRangeStart <= buf[i]) continue;
result.Append(allowedCharSet[buf[i] % allowedCharSet.Length]);
}
}
return result.ToString();
}
}

public static string GenerateUniqueFileName(string path, string extension, int length)
{
if (Directory.Exists(path))
{
string filename = RandomString(length);
while (File.Exists(Path.Combine(path, string.Format("{0}.{1}", filename, extension))))
{
filename = RandomString(length);
}

return Path.Combine(path, string.Format("{0}.{1}", filename, extension));
}

return string.Empty;
}

public static string GetDefaultExtension(string mimeType)
{
string result;
RegistryKey key;
object value;

key = Registry.ClassesRoot.OpenSubKey(@"MIME\Database\Content Type\" + mimeType, false);
value = key != null ? key.GetValue("Extension", null) : null;
result = value != null ? value.ToString() : string.Empty;

return result;
}
}
}

+ 3
- 0
Teknik/Models/TeknikEntities.cs Voir le fichier

@@ -5,6 +5,7 @@ using Teknik.Areas.Blog.Models;
using Teknik.Areas.Profile.Models;
using Teknik.Areas.Contact.Models;
using Teknik.Migrations;
using Teknik.Areas.Upload.Models;

namespace Teknik.Models
{
@@ -17,6 +18,7 @@ namespace Teknik.Models
public DbSet<Post> Posts { get; set; }
public DbSet<Comment> BlogComments { get; set; }
public DbSet<Contact> Contact { get; set; }
public DbSet<Upload> Uploads { get; set; }

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
@@ -27,6 +29,7 @@ namespace Teknik.Models
modelBuilder.Entity<Post>().ToTable("Posts");
modelBuilder.Entity<Comment>().ToTable("BlogComments");
modelBuilder.Entity<Contact>().ToTable("Contact");
modelBuilder.Entity<Upload>().ToTable("Uploads");

base.OnModelCreating(modelBuilder);
}

BIN
Teknik/Scripts/_references.js Voir le fichier


+ 3
- 0
Teknik/Teknik.csproj Voir le fichier

@@ -168,6 +168,7 @@
<Compile Include="Areas\Upload\Controllers\UploadController.cs" />
<Compile Include="Areas\Upload\Models\Upload.cs" />
<Compile Include="Areas\Upload\UploadAreaRegistration.cs" />
<Compile Include="Areas\Upload\Uploader.cs" />
<Compile Include="Areas\Upload\ViewModels\UploadViewModel.cs" />
<Compile Include="Configuration\BlogConfig.cs" />
<Compile Include="Configuration\Config.cs" />
@@ -208,6 +209,7 @@
<Content Include="Areas\Home\Scripts\Home.js" />
<Content Include="Areas\Profile\Scripts\Profile.js" />
<Content Include="Areas\Upload\Scripts\aes.js" />
<Content Include="Areas\Upload\Scripts\EncryptionWorker.js" />
<Content Include="Areas\Upload\Scripts\Upload.js" />
<Content Include="Content\bootstrap-markdown.min.css" />
<Content Include="Content\bootstrap-theme.css" />
@@ -278,6 +280,7 @@
<Content Include="Areas\Error\Views\Error\Exception.cshtml" />
<Content Include="Areas\Error\Views\Error\General.cshtml" />
<Content Include="Areas\Error\Views\Error\Http500.cshtml" />
<Content Include="Areas\Upload\Views\Upload\Download.cshtml" />
<None Include="Properties\PublishProfiles\Teknik Dev.pubxml" />
<None Include="Scripts\jquery-2.1.4.intellisense.js" />
<Content Include="Scripts\bootbox\bootbox.min.js" />

Chargement…
Annuler
Enregistrer