03-11-2023

This commit is contained in:
Jonathan Jenne 2023-11-03 14:12:24 +01:00
parent b08f13a7fd
commit 0b6eecc534
12 changed files with 133 additions and 404 deletions

View File

@ -1,4 +1,4 @@
Public Class EnvelopeResponse
Public Property Envelope As Envelope
Public Property ReceiverId As Integer
Public Property Receiver As EnvelopeReceiver
End Class

View File

@ -0,0 +1,12 @@
namespace EnvelopeGenerator.Web
{
public class Constants
{
public enum ErrorType
{
None,
ServerError,
FilesystemError
}
}
}

View File

@ -1,9 +1,9 @@
using DigitalData.Modules.Logging;
using Microsoft.AspNetCore.Mvc;
using DigitalData.Modules.Logging;
using EnvelopeGenerator.Common;
using EnvelopeGenerator.Web.Services;
using Microsoft.AspNetCore.Mvc;
using System.Reflection.Metadata;
using static EnvelopeGenerator.Web.Handler.FileHandler;
using static EnvelopeGenerator.Common.Constants;
using static EnvelopeGenerator.Web.Constants;
namespace EnvelopeGenerator.Web.Controllers
{
@ -24,15 +24,16 @@ namespace EnvelopeGenerator.Web.Controllers
[HttpGet]
[Route("api/document/{envelopeKey}")]
[IgnoreAntiforgeryToken]
public async Task<IActionResult> Get(string envelopeKey)
{
try
{
logger.Info("Handling file download.");
// Validate Envelope Key
EnvelopeResponse r = api.EnsureValidEnvelopeKey(envelopeKey);
// Load document info
var Request = ControllerContext.HttpContext.Request;
var document = api.GetDocument(Request, envelopeKey);
@ -43,6 +44,15 @@ namespace EnvelopeGenerator.Web.Controllers
// Return the document as bytes
return File(bytes, "application/octet-stream");
}
catch (IOException e)
{
logger.Error(e);
return Problem(
statusCode: 500,
detail: e.Message,
type: ErrorType.ServerError.ToString());
}
catch (Exception e)
{
// Better error handling & reporting
@ -57,25 +67,46 @@ namespace EnvelopeGenerator.Web.Controllers
[HttpPost]
[Route("api/document/{envelopeKey}")]
[IgnoreAntiforgeryToken]
public async Task<IActionResult> Update(string envelopeKey)
{
try
{
logger.Info("Handling file update.");
api.EnsureValidEnvelopeKey(envelopeKey);
// Validate Envelope Key
EnvelopeResponse r = api.EnsureValidEnvelopeKey(envelopeKey);
// Load Document info
var Request = ControllerContext.HttpContext.Request;
var document = api.GetDocument(Request, envelopeKey);
// Try to update the document with new data
if (!await api.UpdateDocument(Request.Body, document.Filepath))
{
throw new IOException("Document could not be saved to disk!");
}
// Add history entry
database.InsertHistoryEntry(new EnvelopeHistoryEntry()
{
ActionDescription = "Dokument wurde signiert",
ActionDate = DateTime.Now,
ActionType = EnvelopeHistoryActionType.Signed,
EnvelopeId = r.Envelope.Id,
UserReference = r.Receiver.Email
});
return Ok();
}
catch (IOException e)
{
logger.Error(e);
return Problem(
statusCode: 500,
detail: e.Message,
type: ErrorType.ServerError.ToString());
}
catch (Exception e)
{
// Better error handling & reporting

View File

@ -1,8 +1,9 @@
using EnvelopeGenerator.Common;
using DigitalData.Modules.Logging;
using EnvelopeGenerator.Common;
using EnvelopeGenerator.Web.Services;
using Microsoft.AspNetCore.Mvc;
using NLog;
using static EnvelopeGenerator.Web.Handler.FileHandler;
using static EnvelopeGenerator.Web.Constants;
namespace EnvelopeGenerator.Web.Controllers
{
@ -23,7 +24,6 @@ namespace EnvelopeGenerator.Web.Controllers
[HttpGet]
[Route("api/envelope/{envelopeKey}")]
[IgnoreAntiforgeryToken]
public IActionResult Get(string envelopeKey)
{
try
@ -50,16 +50,13 @@ namespace EnvelopeGenerator.Web.Controllers
[HttpPost]
[Route("api/envelope/{envelopeKey}")]
[IgnoreAntiforgeryToken]
public async Task<IActionResult> Update(string envelopeKey)
{
try
{
logger.Info("Handling envelope saving.");
api.EnsureValidEnvelopeKey(envelopeKey);
EnvelopeResponse r = database.LoadEnvelope(envelopeKey);
EnvelopeResponse r = api.EnsureValidEnvelopeKey(envelopeKey);
var Request = ControllerContext.HttpContext.Request;
var document = api.GetDocument(Request, envelopeKey);
@ -71,13 +68,10 @@ namespace EnvelopeGenerator.Web.Controllers
throw new ArgumentNullException("AnnotationData");
}
State state = api.GetState(logging.LogConfig, database.MSSQL);
DocumentStatusModel model = new(state);
model.InsertOrUpdate(new DocumentStatus()
database.InsertDocumentStatus(new DocumentStatus()
{
EnvelopeId = r.Envelope.Id,
ReceiverId = r.ReceiverId,
ReceiverId = r.Receiver.Id,
Value = annotationData,
Status = Common.Constants.DocumentStatus.Signed
});

View File

@ -1,75 +0,0 @@
using DigitalData.Modules.Logging;
using EnvelopeGenerator.Common;
using EnvelopeGenerator.Web.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using static EnvelopeGenerator.Common.Constants;
namespace EnvelopeGenerator.Web.Controllers
{
public class HistoryController : Controller
{
private readonly DatabaseService database;
private readonly LoggingService logging;
private readonly Logger logger;
private readonly ApiService api;
public HistoryController(DatabaseService database, LoggingService logging, ApiService api)
{
this.database = database;
this.logging = logging;
this.logger = logging.LogConfig.GetLoggerFor(GetType().Name);
this.api = api;
}
[HttpPost]
[Route("api/history/{envelopeKey}")]
[IgnoreAntiforgeryToken]
public IActionResult Create(string envelopeKey, [FromBody] ActionObject action)
{
try
{
var Request = ControllerContext.HttpContext.Request;
api.EnsureValidEnvelopeKey(envelopeKey);
EnvelopeResponse r = database.LoadEnvelope(envelopeKey);
var receiver = r.Envelope.Receivers.Where(receiver => receiver.Id == r.ReceiverId).SingleOrDefault();
if (receiver == null)
{
return BadRequest();
}
string actionTypeString = action.actionType;
string actionDescription = action.actionDescription;
if (!Enum.TryParse<EnvelopeHistoryActionType>(actionTypeString, out var actionType))
{
return BadRequest();
};
database.InsertHistoryEntry(new EnvelopeHistoryEntry()
{
ActionDescription = actionDescription,
ActionDate = DateTime.Now,
ActionType = actionType,
EnvelopeId = r.Envelope.Id,
UserReference = receiver.Email
});
return Ok();
}
catch (Exception e)
{
logger.Error(e);
return Problem(statusCode: 500);
}
}
public class ActionObject
{
public string actionType { get; set; }
public string actionDescription { get; set; }
}
}
}

View File

@ -1,281 +0,0 @@
using DigitalData.Modules.Database;
using DigitalData.Modules.Logging;
using EnvelopeGenerator.Common;
using EnvelopeGenerator.Common.My.Resources;
using EnvelopeGenerator.Web.Services;
using Microsoft.Extensions.Primitives;
using System.IO.Pipelines;
using System.Reflection.Metadata.Ecma335;
using System.Text;
namespace EnvelopeGenerator.Web.Handler
{
public class FileHandler
{
public class PostResult
{
readonly bool Ok = true;
readonly string ErrorMessage = "";
readonly ErrorType ErrorType = ErrorType.None;
public PostResult()
{
Ok = true;
}
public PostResult(ErrorType errorType, string errorMessage)
{
Ok = false;
ErrorType = errorType;
ErrorMessage = errorMessage;
}
}
public enum ErrorType
{
None,
ServerError,
FilesystemError
}
/// <summary>
/// URL: GET /api/envelope/{envelopeKey}
///
/// Returns a
/// </summary>
/// <returns></returns>
public IResult HandleGetEnvelope(HttpContext ctx, DatabaseService database, LoggingService logging)
{
var logger = logging.LogConfig.GetLogger("FileHandler");
try
{
logger.Info("Handling envelope loading.");
// Load Envelope from EnvelopeKey
string envelopeKey = EnsureValidEnvelopeKey(logger, ctx.Request);
EnvelopeResponse r = database.LoadEnvelope(envelopeKey);
// Return the envelope and additional data as json
return Results.Json(r);
}
catch (Exception e)
{
// Better error handling & reporting
logger.Error(e);
return Results.Problem(e.Message);
}
}
/// <summary>
/// URL: GET /api/document/{envelopeKey}?index={documentIndex}
///
/// Returns a document for the supplied EnvelopeKey and Document Id / Index
/// </summary>
/// <returns>file buffer of the requested document</returns>
public async Task<IResult> HandleGetDocument(HttpContext ctx, DatabaseService database, LoggingService logging)
{
var logger = logging.LogConfig.GetLogger("FileHandler");
try
{
logger.Info("Handling file download.");
// Load Envelope from EnvelopeKey
string envelopeKey = EnsureValidEnvelopeKey(logger, ctx.Request);
EnvelopeResponse r = database.LoadEnvelope(envelopeKey);
// Get the document Index
int documentId = EnsureValidDocumentIndex(logger, ctx.Request);
var document = GetDocument(r.Envelope, documentId);
// Load the document from disk
var bytes = await File.ReadAllBytesAsync(document.Filepath);
logger.Info("Serving file, size: [{0}]", bytes.Length);
// Return the document as bytes
return Results.File(bytes);
}
catch (Exception e)
{
// Better error handling & reporting
logger.Error(e);
return Results.Problem(
statusCode: 500,
detail: e.Message,
type: ErrorType.ServerError.ToString());
}
}
public async Task<IResult> HandlePostDocument(HttpContext ctx, DatabaseService database, LoggingService logging)
{
var logger = logging.LogConfig.GetLogger("FileHandler");
try
{
logger.Info("Handling file upload.");
// Load Envelope from EnvelopeKey
string envelopeKey = EnsureValidEnvelopeKey(logger, ctx.Request);
EnvelopeResponse response = database.LoadEnvelope(envelopeKey);
// Get the document Index
int documentId = EnsureValidDocumentIndex(logger, ctx.Request);
var document = GetDocument(response.Envelope, documentId);
using FileStream fs = new(document.Filepath, FileMode.Open);
await ctx.Request.Body.CopyToAsync(fs);
fs.Flush();
return Results.Ok();
}
catch (Exception e)
{
// Better error handling & reporting
logger.Error(e);
return Results.Problem(
statusCode: 500,
detail: e.Message,
type: ErrorType.ServerError.ToString());
}
}
public async Task<IResult> HandlePostEnvelope(HttpContext ctx, DatabaseService database, LoggingService logging)
{
var logger = logging.LogConfig.GetLogger("FileHandler");
try
{
logger.Info("Handling envelope saving.");
// Load Envelope from EnvelopeKey
string envelopeKey = EnsureValidEnvelopeKey(logger, ctx.Request);
EnvelopeResponse r = database.LoadEnvelope(envelopeKey);
// Get the document Index
int documentId = EnsureValidDocumentIndex(logger, ctx.Request);
var document = GetDocument(r.Envelope, documentId);
string? annotationData = await EnsureValidAnnotationData(logger, ctx.Request);
if (annotationData == null)
{
throw new ArgumentNullException("AnnotationData");
}
State state = GetState(logging.LogConfig, database.MSSQL);
DocumentStatusModel model = new(state);
model.InsertOrUpdate(new DocumentStatus()
{
EnvelopeId = r.Envelope.Id,
ReceiverId = r.ReceiverId,
Value = annotationData,
Status = Common.Constants.DocumentStatus.Signed
});
return Results.Ok();
}
catch (Exception e)
{
// Better error handling & reporting
logger.Error(e);
return Results.Problem(
statusCode: 500,
detail: e.Message,
type: ErrorType.ServerError.ToString());
}
}
#region Private
private State GetState(LogConfig LogConfig, MSSQLServer Database)
{
return new State
{
LogConfig = LogConfig,
Database = Database,
};
}
private int EnsureValidDocumentIndex(Logger logger, HttpRequest request)
{
if (request.Query.TryGetValue("index", out StringValues documentIndexString))
{
try
{
return int.Parse(documentIndexString.First());
}
catch (Exception e)
{
throw new ArgumentNullException("DocumentIndex", e);
}
}
else
{
throw new ArgumentNullException("DocumentIndex");
}
}
private string EnsureValidEnvelopeKey(Logger logger, HttpRequest request)
{
logger.Debug("Parsing EnvelopeKey..");
var envelopeKey = request.RouteValues["envelopeKey"] as string;
if (string.IsNullOrEmpty(envelopeKey))
throw new ArgumentNullException("EnvelopeKey");
Tuple<string, string> result = Helpers.DecodeEnvelopeReceiverId(envelopeKey);
logger.Debug("EnvelopeUUID: [{0}]", result.Item1);
logger.Debug("ReceiverSignature: [{0}]", result.Item2);
if (string.IsNullOrEmpty(result.Item1))
throw new ArgumentNullException("EnvelopeUUID");
if (string.IsNullOrEmpty(result.Item2))
throw new ArgumentNullException("ReceiverSignature");
return envelopeKey;
}
private async Task<string?> EnsureValidAnnotationData(Logger logger, HttpRequest request)
{
logger.Debug("Parsing AnnotationData..");
try
{
using MemoryStream ms = new();
await request.BodyReader.CopyToAsync(ms);
var bytes = ms.ToArray();
return Encoding.UTF8.GetString(bytes);
}
catch (Exception e)
{
logger.Error(e);
return null;
}
}
private EnvelopeDocument GetDocument(Common.Envelope envelope, int documentId)
{
var document = envelope.Documents.
Where(d => d.Id == documentId).
FirstOrDefault();
if (document == null)
throw new ArgumentException("DocumentId");
return document;
}
#endregion
}
}

View File

@ -7,8 +7,9 @@
<head>
<meta charset="utf-8" />
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<base href="~/" />
<link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
<link href="css/site.css" rel="stylesheet" />
@ -30,5 +31,7 @@
</div>
<script src="_framework/blazor.server.js"></script>
@Html.AntiForgeryToken()
</body>
</html>

View File

@ -254,12 +254,12 @@ class Annotation {
class Network {
public getEnvelope(envelopeKey: string): Promise<any> {
return fetch(`/api/envelope/${envelopeKey}`, { credentials: "include" })
return fetch(`/api/envelope/${envelopeKey}`, this.withCSRFToken({ credentials: "include" }))
.then(res => res.json());
}
public getDocument(envelopeKey: string, documentId: number): Promise<ArrayBuffer> {
return fetch(`/api/document/${envelopeKey}?index=${documentId}`, { credentials: "include" })
return fetch(`/api/document/${envelopeKey}?index=${documentId}`, this.withCSRFToken({ credentials: "include" }))
.then(res => res.arrayBuffer());
}
@ -272,7 +272,7 @@ class Network {
}
console.debug("PostDocument/Calling url: " + url)
return fetch(url, options)
return fetch(url, this.withCSRFToken(options))
.then(this.handleResponse)
.then((res: Response) => {
if (!res.ok) {
@ -291,7 +291,7 @@ class Network {
}
console.debug("PostEnvelope/Calling url: " + url)
return fetch(url, options)
return fetch(url, this.withCSRFToken(options))
.then(this.handleResponse)
.then((res: Response) => {
if (!res.ok) {
@ -319,7 +319,7 @@ class Network {
}
console.debug("PostHistory/Calling url: " + url)
return fetch(url, options)
return fetch(url, this.withCSRFToken(options))
.then(this.handleResponse)
.then((res: Response) => {
if (!res.ok) {
@ -329,6 +329,14 @@ class Network {
});
}
private withCSRFToken(options: RequestInit): RequestInit {
const token = (document.getElementsByName("__RequestVerificationToken")[0] as any).value;
let headers = options.headers;
options.headers = { ...headers, 'X-XSRF-TOKEN': token };
return options;
}
private handleResponse(res: Response) {
if (!res.ok) {
console.log(`Request failed with status ${res.status}`)

View File

@ -2,6 +2,7 @@
using DigitalData.Modules.Logging;
using EnvelopeGenerator.Common;
using Microsoft.Extensions.Primitives;
using System.Reflection;
using System.Reflection.Metadata;
using System.Text;
@ -39,9 +40,9 @@ namespace EnvelopeGenerator.Web.Services
if (string.IsNullOrEmpty(result.Item2))
throw new ArgumentNullException("ReceiverSignature");
EnvelopeResponse r = database.LoadEnvelope(envelopeKey);
EnvelopeResponse response = database.LoadEnvelope(envelopeKey);
return r;
return response;
}
public async Task<string?> EnsureValidAnnotationData(HttpRequest request)
@ -118,10 +119,6 @@ namespace EnvelopeGenerator.Web.Services
}
public State GetState(LogConfig LogConfig, MSSQLServer Database)
{
return new State

View File

@ -13,7 +13,7 @@ namespace EnvelopeGenerator.Web.Services
private ReceiverModel receiverModel;
private ElementModel elementModel;
private HistoryModel historyModel;
private DocumentStatusModel documentStatusModel;
private readonly LogConfig _logConfig;
private readonly Logger _logger;
@ -56,6 +56,7 @@ namespace EnvelopeGenerator.Web.Services
receiverModel = new(state);
elementModel = new(state);
historyModel = new(state);
documentStatusModel = new(state);
}
public EnvelopeResponse LoadEnvelope(string pEnvelopeKey)
@ -65,11 +66,28 @@ namespace EnvelopeGenerator.Web.Services
var receiverSignature = result.Item2;
var receiverId = receiverModel.GetReceiverIdBySignature(receiverSignature);
Envelope envelope = envelopeModel.GetByUuid(envelopeUuid);
Envelope? envelope = envelopeModel.GetByUuid(envelopeUuid);
if (envelope == null)
{
throw new NullReferenceException("Envelope not found");
}
if (envelope.Receivers == null)
{
throw new NullReferenceException("Receivers for envelope not loaded");
}
EnvelopeReceiver? receiver = envelope.Receivers.Where(r => r.Id == receiverId).SingleOrDefault();
if (receiver == null)
{
throw new NullReferenceException("Receiver not found");
}
return new()
{
ReceiverId = receiverId,
Receiver = receiver,
Envelope = envelope
};
}
@ -89,5 +107,10 @@ namespace EnvelopeGenerator.Web.Services
return historyModel.Insert(historyEntry);
}
public bool InsertDocumentStatus(DocumentStatus documentStatus)
{
return documentStatusModel.InsertOrUpdate(documentStatus);
}
}
}

View File

@ -1,3 +1,14 @@
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
@ -101,7 +112,7 @@ var App = /** @class */ (function () {
return [4 /*yield*/, App.Instance.create(annotations)];
case 7:
createdAnnotations = _b.sent();
description = "Umschlag wurde geöffnet";
description = "Umschlag wurde ge<EFBFBD>ffnet";
return [4 /*yield*/, App.Network.postHistory(App.envelopeKey, ActionType.Seen, description)];
case 8:
_b.sent();
@ -127,10 +138,10 @@ var App = /** @class */ (function () {
case 2:
result = _b.sent();
if (result == true) {
alert("Dokument zurückgesetzt!");
alert("Dokument zur<EFBFBD>ckgesetzt!");
}
else {
alert("Fehler beim Zurücksetzen des Dokuments!");
alert("Fehler beim Zur<EFBFBD>cksetzen des Dokuments!");
}
return [3 /*break*/, 5];
case 3: return [4 /*yield*/, App.handleFinish(null)];
@ -141,7 +152,7 @@ var App = /** @class */ (function () {
alert("Dokument erfolgreich signiert!");
}
else {
alert("Fehler beim Abschließen des Dokuments!");
alert("Fehler beim Abschlie<EFBFBD>en des Dokuments!");
}
return [3 /*break*/, 5];
case 5: return [2 /*return*/];
@ -216,7 +227,7 @@ var App = /** @class */ (function () {
return __awaiter(this, void 0, void 0, function () {
var result;
return __generator(this, function (_a) {
if (confirm("Wollen Sie das Dokument und alle erstellten Signaturen zurücksetzen?")) {
if (confirm("Wollen Sie das Dokument und alle erstellten Signaturen zur<EFBFBD>cksetzen?")) {
result = App.Annotation.deleteAnnotations(App.Instance);
return [2 /*return*/, true];
}
@ -336,11 +347,11 @@ var Network = /** @class */ (function () {
function Network() {
}
Network.prototype.getEnvelope = function (envelopeKey) {
return fetch("/api/envelope/".concat(envelopeKey), { credentials: "include" })
return fetch("/api/envelope/".concat(envelopeKey), this.withCSRFToken({ credentials: "include" }))
.then(function (res) { return res.json(); });
};
Network.prototype.getDocument = function (envelopeKey, documentId) {
return fetch("/api/document/".concat(envelopeKey, "?index=").concat(documentId), { credentials: "include" })
return fetch("/api/document/".concat(envelopeKey, "?index=").concat(documentId), this.withCSRFToken({ credentials: "include" }))
.then(function (res) { return res.arrayBuffer(); });
};
Network.prototype.postDocument = function (envelopeKey, documentId, buffer) {
@ -351,7 +362,7 @@ var Network = /** @class */ (function () {
body: buffer
};
console.debug("PostDocument/Calling url: " + url);
return fetch(url, options)
return fetch(url, this.withCSRFToken(options))
.then(this.handleResponse)
.then(function (res) {
if (!res.ok) {
@ -369,7 +380,7 @@ var Network = /** @class */ (function () {
body: jsonString
};
console.debug("PostEnvelope/Calling url: " + url);
return fetch(url, options)
return fetch(url, this.withCSRFToken(options))
.then(this.handleResponse)
.then(function (res) {
if (!res.ok) {
@ -394,7 +405,7 @@ var Network = /** @class */ (function () {
body: JSON.stringify(data)
};
console.debug("PostHistory/Calling url: " + url);
return fetch(url, options)
return fetch(url, this.withCSRFToken(options))
.then(this.handleResponse)
.then(function (res) {
if (!res.ok) {
@ -404,6 +415,12 @@ var Network = /** @class */ (function () {
return true;
});
};
Network.prototype.withCSRFToken = function (options) {
var token = document.getElementsByName("__RequestVerificationToken")[0].value;
var headers = options.headers;
options.headers = __assign(__assign({}, headers), { 'X-XSRF-TOKEN': token });
return options;
};
Network.prototype.handleResponse = function (res) {
if (!res.ok) {
console.log("Request failed with status ".concat(res.status));
@ -435,7 +452,7 @@ var UI = /** @class */ (function () {
type: "custom",
id: "button-reset",
className: "button-reset",
title: "Zurücksetzen",
title: "Zur<EFBFBD>cksetzen",
onPress: function () {
callback("RESET");
},
@ -445,7 +462,7 @@ var UI = /** @class */ (function () {
type: "custom",
id: "button-finish",
className: "button-finish",
title: "Abschließen",
title: "Abschlie<EFBFBD>en",
onPress: function () {
callback("FINISH");
},

File diff suppressed because one or more lines are too long