diff --git a/server/ConfigureSwaggerOptions.cs b/server/ConfigureSwaggerOptions.cs
new file mode 100644
index 0000000..28e7200
--- /dev/null
+++ b/server/ConfigureSwaggerOptions.cs
@@ -0,0 +1,54 @@
+namespace Sockeye
+{
+ using Microsoft.AspNetCore.Mvc.ApiExplorer;
+ using Microsoft.Extensions.DependencyInjection;
+ using Microsoft.Extensions.Options;
+ using Microsoft.OpenApi.Models;
+ using Swashbuckle.AspNetCore.SwaggerGen;
+ using System;
+
+ ///
+ /// Configures the Swagger generation options.
+ ///
+ /// This allows API versioning to define a Swagger document per API version after the
+ /// service has been resolved from the service container.
+ public class ConfigureSwaggerOptions : IConfigureOptions
+ {
+ readonly IApiVersionDescriptionProvider provider;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The provider used to generate Swagger documents.
+ public ConfigureSwaggerOptions( IApiVersionDescriptionProvider provider ) => this.provider = provider;
+
+ ///
+ public void Configure( SwaggerGenOptions options )
+ {
+ // add a swagger document for each discovered API version
+ // note: you might choose to skip or document deprecated API versions differently
+ foreach ( var description in provider.ApiVersionDescriptions )
+ {
+ options.SwaggerDoc( description.GroupName, CreateInfoForApiVersion( description ) );
+ }
+ }
+
+ static OpenApiInfo CreateInfoForApiVersion( ApiVersionDescription description )
+ {
+ var info = new OpenApiInfo()
+ {
+ Title = "Sockeye API",
+ Version = description.ApiVersion.ToString(),
+ Description = "Sockeye service management software API",
+
+ };
+
+ if ( description.IsDeprecated )
+ {
+ info.Description += " This API version has been deprecated.";
+ }
+
+ return info;
+ }
+ }
+}
\ No newline at end of file
diff --git a/server/ControllerHelpers/ApiCreatedResponse.cs b/server/ControllerHelpers/ApiCreatedResponse.cs
new file mode 100644
index 0000000..974e8a3
--- /dev/null
+++ b/server/ControllerHelpers/ApiCreatedResponse.cs
@@ -0,0 +1,19 @@
+
+namespace Sockeye.Api.ControllerHelpers
+{
+
+
+
+ public class ApiCreatedResponse
+ {
+
+ public object Data { get; }
+
+ public ApiCreatedResponse(object result)
+ {
+ Data = result;
+ }
+ }//eoc
+
+
+}//eons
\ No newline at end of file
diff --git a/server/ControllerHelpers/ApiCustomExceptionFilter.cs b/server/ControllerHelpers/ApiCustomExceptionFilter.cs
new file mode 100644
index 0000000..ed6205e
--- /dev/null
+++ b/server/ControllerHelpers/ApiCustomExceptionFilter.cs
@@ -0,0 +1,106 @@
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.Filters;
+using Microsoft.AspNetCore.Http;
+using System;
+using System.Net;
+using System.Net.Http;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+using Microsoft.Extensions.Logging;
+using Sockeye.Util;
+using Sockeye.Biz;
+
+namespace Sockeye.Api.ControllerHelpers
+{
+
+
+ ///
+ /// This is essentially an unhandled exception handler
+ ///
+ public class ApiCustomExceptionFilter : IExceptionFilter
+ {
+ private readonly ILogger log;
+
+ // public ApiCustomExceptionFilter(ILoggerFactory logger)
+ // {
+ // if (logger == null)
+ // {
+ // throw new ArgumentNullException(nameof(logger));
+ // }
+
+ // this.log = logger.CreateLogger("Server Exception");
+ // }
+
+ public ApiCustomExceptionFilter(ILogger logger)
+ {
+ if (logger == null)
+ {
+ throw new ArgumentNullException(nameof(logger));
+ }
+
+ this.log = logger;
+ }
+
+
+ public void OnException(ExceptionContext context)
+ {
+ HttpStatusCode status = HttpStatusCode.InternalServerError;
+ String message = String.Empty;
+ #region If need to refine this further and deal with specific types
+ // var exceptionType = context.Exception.GetType();
+ // if (exceptionType == typeof(UnauthorizedAccessException))
+ // {
+ // message = "Unauthorized Access";
+ // status = HttpStatusCode.Unauthorized;
+ // }
+ // else if (exceptionType == typeof(NotImplementedException))
+ // {
+ // message = "A server error occurred.";
+ // status = HttpStatusCode.NotImplemented;
+ // }
+ // // else if (exceptionType == typeof(MyAppException))
+ // // {
+ // // message = context.Exception.ToString();
+ // // status = HttpStatusCode.InternalServerError;
+ // // }
+ // else
+ // {
+ #endregion
+ message = context.Exception.Message;
+ status = HttpStatusCode.InternalServerError;
+ //}
+
+ //No need to log test exceptions to check and filter out
+ bool loggableError = true;
+
+ if (message.StartsWith("Test exception"))
+ loggableError = false;
+
+ //LOG IT
+ if (loggableError)
+ log.LogError(context.Exception, "Error");
+
+ //Notify ops notification issue
+ NotifyEventHelper.AddOpsProblemEvent("Server API internal error, see log for more details", context.Exception).Forget();//.Wait();
+
+ HttpResponse response = context.HttpContext.Response;
+ response.StatusCode = (int)status;
+ response.ContentType = "application/json; charset=utf-8";
+
+ //This line is critical, without it the response is not proper and fails in various clients (postman, xunit tests with httpclient)
+ context.ExceptionHandled = true;
+
+
+ response.WriteAsync(JsonConvert.SerializeObject(
+ new ApiErrorResponse(ApiErrorCode.API_SERVER_ERROR, "generalerror", "Server internal error; see server log for details"),
+ new JsonSerializerSettings
+ {
+ ContractResolver = new CamelCasePropertyNamesContractResolver()
+ }
+ ));
+ }
+
+
+ }//eoc
+
+}//eons
\ No newline at end of file
diff --git a/server/ControllerHelpers/ApiDetailError.cs b/server/ControllerHelpers/ApiDetailError.cs
new file mode 100644
index 0000000..649d422
--- /dev/null
+++ b/server/ControllerHelpers/ApiDetailError.cs
@@ -0,0 +1,30 @@
+using Newtonsoft.Json;
+using Sockeye.Biz;
+
+namespace Sockeye.Api.ControllerHelpers
+{
+
+
+ ///
+ /// Detail error for inner part of error response
+ ///
+ public class ApiDetailError
+ {
+ /* WAIT, why does this have CODE AND Error??! */
+ // [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
+ // public string Code { get; internal set; }
+
+ [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
+ public string Message { get; internal set; }
+
+ [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
+ public string Target { get; internal set; }
+
+ [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
+ public string Error { get; internal set; }
+
+
+ }//eoc
+
+
+}//eons
\ No newline at end of file
diff --git a/server/ControllerHelpers/ApiError.cs b/server/ControllerHelpers/ApiError.cs
new file mode 100644
index 0000000..815e2e9
--- /dev/null
+++ b/server/ControllerHelpers/ApiError.cs
@@ -0,0 +1,37 @@
+using Newtonsoft.Json;
+using System.Collections.Generic;
+using Sockeye.Biz;
+
+namespace Sockeye.Api.ControllerHelpers
+{
+
+
+
+
+ ///
+ ///
+ ///
+ public class ApiError
+ {
+ public string Code { get; }
+
+ [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
+ public List Details { get; internal set; }
+
+ [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
+ public string Message { get; }
+
+ [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
+ public string Target { get; }
+
+ public ApiError(ApiErrorCode apiCode, string message = null, string target = null)
+ {
+ Code = ((int)apiCode).ToString();
+ Target = target;
+ Message=message;
+ }
+
+ }//eoc
+
+
+}//eons
\ No newline at end of file
diff --git a/server/ControllerHelpers/ApiErrorResponse.cs b/server/ControllerHelpers/ApiErrorResponse.cs
new file mode 100644
index 0000000..449a09b
--- /dev/null
+++ b/server/ControllerHelpers/ApiErrorResponse.cs
@@ -0,0 +1,122 @@
+using System;
+using System.Linq;
+using System.Collections.Generic;
+using Microsoft.AspNetCore.Mvc.ModelBinding;
+using Microsoft.Extensions.Logging;
+using Newtonsoft.Json;
+using Sockeye.Biz;
+
+namespace Sockeye.Api.ControllerHelpers
+{
+
+
+
+ public class ApiErrorResponse
+ {
+
+ [JsonIgnore]
+ private ILogger log = Sockeye.Util.ApplicationLogging.CreateLogger();
+
+ //Mandatory properties
+ public ApiError Error { get; }
+
+
+
+
+ //Generic error
+ public ApiErrorResponse(ApiErrorCode apiCode, string target = null, string message = null)
+ {
+
+ //try to get a stock message if nothing specified
+ if (message == null)
+ {
+ message = ApiErrorCodeStockMessage.GetTranslationCodeForApiErrorCode(apiCode);
+ }
+
+ Error = new ApiError(apiCode, message, target);
+
+ log.LogDebug("apiCode={0}, target={1}, message={2}", apiCode, target, message);
+ }
+
+
+ //Bad request (MODELSTATE ISSUE) error response handling
+
+ public ApiErrorResponse(ModelStateDictionary modelState)
+ {
+
+ if (modelState.IsValid)
+ {
+ throw new ArgumentException("ModelState must be invalid", nameof(modelState));
+ }
+
+
+ //Set outer error and then put validation in details
+
+ Error = new ApiError(ApiErrorCode.VALIDATION_FAILED, ApiErrorCodeStockMessage.GetTranslationCodeForApiErrorCode(ApiErrorCode.VALIDATION_FAILED));
+
+
+ //https://www.jerriepelser.com/blog/validation-response-aspnet-core-webapi/
+ //Message = "Validation Failed";
+ Error.Details = new List();
+
+
+ foreach (var key in modelState.Keys)
+ {
+ var vErrors = modelState[key].Errors;
+ foreach (ModelError m in vErrors)
+ {
+ string msg = "";
+ if (!string.IsNullOrWhiteSpace(m.ErrorMessage))
+ {
+ msg += m.ErrorMessage + ". ";
+ }
+ if (m.Exception != null && !string.IsNullOrWhiteSpace(m.Exception.Message))
+ {
+ msg += "Exception: " + m.Exception.Message;
+ }
+ //example this produces
+ //
+ Error.Details.Add(new ApiDetailError() { Target = key, Message = msg, Error = ((int)ApiErrorCode.VALIDATION_INVALID_VALUE).ToString() });
+ }
+ }
+
+
+ log.LogDebug("BadRequest - Validation error");
+ }
+
+
+ //Business rule validation error response
+ public ApiErrorResponse(List errors)
+ {
+ Error = new ApiError(ApiErrorCode.VALIDATION_FAILED, ApiErrorCodeStockMessage.GetTranslationCodeForApiErrorCode(ApiErrorCode.VALIDATION_FAILED));
+ Error.Details = new List();
+ foreach (ValidationError v in errors)
+ {
+ Error.Details.Add(new ApiDetailError() { Target = v.Target, Message = v.Message, Error = ((int)v.Code).ToString() });
+ }
+ log.LogDebug("BadRequest - Validation error");
+ }
+
+
+
+ // public void AddDetailError(ApiErrorCode apiCode, string target = null, string message = null)
+ // {
+ // if (Error.Details == null)
+ // {
+ // Error.Details = new List();
+ // }
+
+ // //try to get a stock message if nothing specified
+ // if (message == null)
+ // {
+ // message = ApiErrorCodeStockMessage.GetMessage(apiCode);
+ // }
+
+ // Error.Details.Add(new ApiDetailError() { Code = ((int)apiCode).ToString(), Target = target, Message = message });
+ // }
+
+
+ }//eoc
+
+
+}//eons
\ No newline at end of file
diff --git a/server/ControllerHelpers/ApiNotAuthorizedResponse.cs b/server/ControllerHelpers/ApiNotAuthorizedResponse.cs
new file mode 100644
index 0000000..ad5a371
--- /dev/null
+++ b/server/ControllerHelpers/ApiNotAuthorizedResponse.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Linq;
+using System.Collections.Generic;
+using Microsoft.AspNetCore.Mvc.ModelBinding;
+using Microsoft.Extensions.Logging;
+using Newtonsoft.Json;
+using Sockeye.Biz;
+
+namespace Sockeye.Api.ControllerHelpers
+{
+
+
+
+ public class ApiNotAuthorizedResponse
+ {
+
+ [JsonIgnore]
+ private ILogger log = Sockeye.Util.ApplicationLogging.CreateLogger();
+
+ //Mandatory properties
+ public ApiError Error { get; }
+
+ //Generic error
+ public ApiNotAuthorizedResponse()
+ {
+ Error = new ApiError(ApiErrorCode.NOT_AUTHORIZED, ApiErrorCodeStockMessage.GetTranslationCodeForApiErrorCode(ApiErrorCode.NOT_AUTHORIZED));
+
+ log.LogDebug("ApiErrorCode={0}, message={1}", (int)ApiErrorCode.NOT_AUTHORIZED, Error.Message);
+ }
+
+ }//eoc
+
+
+}//eons
\ No newline at end of file
diff --git a/server/ControllerHelpers/ApiOkResponse.cs b/server/ControllerHelpers/ApiOkResponse.cs
new file mode 100644
index 0000000..460ed98
--- /dev/null
+++ b/server/ControllerHelpers/ApiOkResponse.cs
@@ -0,0 +1,10 @@
+namespace Sockeye.Api.ControllerHelpers
+{
+ public static class ApiOkResponse
+ {
+ public static object Response(object result)
+ {
+ return new { Data = result };
+ }
+ }//eoc
+}//eons
\ No newline at end of file
diff --git a/server/ControllerHelpers/ApiServerState.cs b/server/ControllerHelpers/ApiServerState.cs
new file mode 100644
index 0000000..fd03fb9
--- /dev/null
+++ b/server/ControllerHelpers/ApiServerState.cs
@@ -0,0 +1,220 @@
+using Sockeye.Biz;
+namespace Sockeye.Api.ControllerHelpers
+{
+
+
+
+
+ ///
+ /// Contains the current status of the server
+ /// is injected everywhere for routes and others to check
+ ///
+ public class ApiServerState
+ {
+
+ //private ILogger log = Sockeye.Util.ApplicationLogging.CreateLogger();
+ public enum ServerState
+ {
+ ///Unknown state, used for parsing
+ UNKNOWN = 0,
+ ///No access for anyone API completely locked down. Not set by user but rather by internal server operations like importing or backup.
+ Closed = 1,
+
+ ///Access only to API Operations routes. Can be set by Ops user
+ OpsOnly = 3,
+ ///Open for all users (default). Can be set by Ops user
+ Open = 4
+ }
+
+ private ServerState _currentState = ServerState.Closed;
+ private ServerState _priorState = ServerState.Closed;
+ private string _reason = string.Empty;
+ private string _priorReason = string.Empty;
+ private bool SYSTEM_LOCK = false;//really this is a license lock but not called that
+
+ public ApiServerState()
+ {
+
+ }
+
+
+
+ internal void SetSystemLock(string reason)
+ {
+ //Lock down the server for license related issue
+ //Only SuperUser account (id=1) can login or do anything, treats as if server was set to closed even if they change it to open
+ //only way to reset it is to fetch a valid license
+ // This is set to locked in TWO ways:
+
+ // 1) By CoreJobSweeper *if* the user count is mismatched to the license likely due to Users messing directly with the DB
+ //trying to circumvent licensing
+ // 2) By License::InitializeAsync upon finding user count mismatch or expired license key
+ // and initializeasync is called on boot up or when a new key is downloaded and installed only
+ //
+ var msg = $"{reason}\r\nLogin as SuperUser to start evaluation / install license";
+ SetState(ServerState.OpsOnly, msg);
+ SYSTEM_LOCK = true;
+ }
+
+
+ //WARNING: if in future this is used for anything other than a license then it will need to see if locked for another reason before unlocking
+ //recommend putting a code number in the reason then looking to see if it has the matching code
+ internal void ClearSystemLock()
+ {
+ SYSTEM_LOCK = false;
+ SetState(ServerState.Open, "");
+ }
+
+ ///
+ /// Set the server state
+ ///
+ ///
+ ///
+ public void SetState(ServerState newState, string reason)
+ {
+ //No changes allowed during a system lock
+ if (SYSTEM_LOCK) return;
+
+ _reason = reason;//keep the reason even if the state doesn't change
+ if (newState == _currentState) return;
+
+ //Here we will likely need to trigger a notification to users if the state is going to be shutting down or is shut down
+ _priorState = _currentState;//keep the prior state so it can be resumed easily
+ _priorReason = _reason;//keep the original reason
+
+ _currentState = newState;
+ }
+
+ ///
+ /// Get the current state of the server
+ ///
+ ///
+ public ServerState GetState()
+ {
+ return _currentState;
+ }
+
+ ///
+ /// Get the current reason for the state of the server
+ ///
+ ///
+ public string Reason
+ {
+ get
+ {
+ if (_currentState == ServerState.Open)
+ {
+ return "";
+ }
+ else
+ {
+ return $"\"{_reason}\"";
+ // if (_currentState == ServerState.Closed)
+ // return $"{ Sockeye.Biz.TranslationBiz.GetDefaultTranslationAsync("ErrorAPI2000").Result}\r\n{_reason}";
+ // else //opsonly
+ // return $"{ Sockeye.Biz.TranslationBiz.GetDefaultTranslationAsync("ErrorAPI2001").Result}\r\n{_reason}";
+ }
+ }
+ }
+
+ //get the api error code associated with the server state
+ public ApiErrorCode ApiErrorCode
+ {
+ get
+ {
+ switch (_currentState)
+ {
+ case ServerState.Open:
+ throw new System.NotSupportedException("ApiServerState:ApiErrorCode - No error code is associated with server state OPEN");
+ case ServerState.OpsOnly:
+ return ApiErrorCode.API_OPS_ONLY;
+
+ case ServerState.Closed:
+ return ApiErrorCode.API_CLOSED;
+
+ }
+ throw new System.NotSupportedException("ApiServerState:ApiErrorCode - No error code is associated with server state UNKNOWN");
+ }
+
+ }
+
+
+ public void SetOpsOnly(string reason)
+ {
+ //No changes allowed during a system lock
+ if (SYSTEM_LOCK) return;
+ SetState(ServerState.OpsOnly, reason);
+ }
+
+
+ public void SetClosed(string reason)
+ {
+ //No changes allowed during a system lock
+ if (SYSTEM_LOCK) return;
+ SetState(ServerState.Closed, reason);
+ }
+
+ public void SetOpen()
+ {
+ //No changes allowed during a system lock
+ if (SYSTEM_LOCK) return;
+ SetState(ServerState.Open, string.Empty);
+ }
+
+ public void ResumePriorState()
+ {
+ //No changes allowed during a system lock
+ if (SYSTEM_LOCK) return;
+ SetState(_priorState, _priorReason);
+ }
+
+
+ public bool IsOpsOnly
+ {
+ get
+ {
+ return _currentState == ServerState.OpsOnly && !SYSTEM_LOCK;
+ }
+ }
+
+
+ public bool IsOpen
+ {
+ get
+ {
+ return _currentState != ServerState.Closed && _currentState != ServerState.OpsOnly && !SYSTEM_LOCK;
+ }
+ }
+
+
+ public bool IsClosed
+ {
+ get
+ {
+ return _currentState == ServerState.Closed || SYSTEM_LOCK;
+ }
+ }
+
+ // public bool IsOpenOrOpsOnly
+ // {
+ // get
+ // {
+ // return (IsOpen || IsOpsOnly) && !SYSTEM_LOCK;
+ // }
+ // }
+
+ public bool IsSystemLocked
+ {
+ get
+ {
+ return SYSTEM_LOCK;
+ }
+ }
+
+
+
+
+ }//eoc
+
+
+}//eons
\ No newline at end of file
diff --git a/server/ControllerHelpers/ApiUploadProcessor.cs b/server/ControllerHelpers/ApiUploadProcessor.cs
new file mode 100644
index 0000000..6280636
--- /dev/null
+++ b/server/ControllerHelpers/ApiUploadProcessor.cs
@@ -0,0 +1,180 @@
+using System;
+using System.Threading.Tasks;
+using System.IO;
+using System.Text;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.AspNetCore.WebUtilities;
+using Microsoft.Net.Http.Headers;
+using System.Collections.Generic;
+using Sockeye.Models;
+using Sockeye.Util;
+
+
+namespace Sockeye.Api.ControllerHelpers
+{
+
+
+ //Adapted from the example found here: https://docs.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads#uploading-large-files-with-streaming
+ //See AttachmentController at bottom of class for example of form that works with this code
+
+ ///
+ /// Handle processing uplod form with potentially huge files being uploaded (which means can't use simplest built in upload handler method)
+ ///
+ internal static class ApiUploadProcessor
+ {
+
+
+ ///
+ /// handle upload
+ ///
+ ///
+ /// list of files and form field data (if present)
+ internal static async Task ProcessUploadAsync(Microsoft.AspNetCore.Http.HttpContext httpContext)
+ {
+
+ ApiUploadedFilesResult result = new ApiUploadedFilesResult();
+ FormOptions _defaultFormOptions = new FormOptions();
+ try
+ {
+ // Used to accumulate all the form url encoded key value pairs in the
+ // request.
+ var formAccumulator = new KeyValueAccumulator();
+
+ var boundary = MultipartRequestHelper.GetBoundary(
+ MediaTypeHeaderValue.Parse(httpContext.Request.ContentType),
+ _defaultFormOptions.MultipartBoundaryLengthLimit);
+ var reader = new MultipartReader(boundary, httpContext.Request.Body);
+
+ var section = await reader.ReadNextSectionAsync();
+
+ while (section != null)
+ {
+ ContentDispositionHeaderValue contentDisposition;
+ var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out contentDisposition);
+
+ if (hasContentDispositionHeader)
+ {
+ if (MultipartRequestHelper.HasFileContentDisposition(contentDisposition))
+ {
+
+ string filePathAndName = string.Empty;
+ var CleanedUploadFileName = contentDisposition.FileName.Value.Replace("\"", "");
+
+ //get temp file path and temp file name
+ filePathAndName = FileUtil.NewRandomAttachmentFilesFolderFileName;
+
+ //save to disk
+ using (var stream = new FileStream(filePathAndName, FileMode.Create))
+ {
+ await section.Body.CopyToAsync(stream);
+ }
+ result.UploadedFiles.Add(new UploadedFileInfo()
+ {
+ InitialUploadedPathName = filePathAndName,
+ OriginalFileName = CleanedUploadFileName,
+ MimeType = section.ContentType
+
+ });
+ }
+ else if (MultipartRequestHelper.HasFormDataContentDisposition(contentDisposition))
+ {
+ // Content-Disposition: form-data; name="key"
+ //
+ // value
+
+ // Do not limit the key name length here because the
+ // multipart headers length limit is already in effect.
+ var key = HeaderUtilities.RemoveQuotes(contentDisposition.Name);
+ var encoding = GetEncoding(section);
+ using (var streamReader = new StreamReader(
+ section.Body,
+ encoding,
+ detectEncodingFromByteOrderMarks: true,
+ bufferSize: 1024,
+ leaveOpen: true))
+ {
+ // The value length limit is enforced by MultipartBodyLengthLimit
+ var value = await streamReader.ReadToEndAsync();
+ if (String.Equals(value, "undefined", StringComparison.OrdinalIgnoreCase))
+ {
+ value = String.Empty;
+ }
+ formAccumulator.Append(key.Value, value);
+
+ if (formAccumulator.ValueCount > _defaultFormOptions.ValueCountLimit)
+ {
+ throw new InvalidDataException($"Form key count limit {_defaultFormOptions.ValueCountLimit} exceeded.");
+ }
+ }
+ }
+ }
+
+ // Drains any remaining section body that has not been consumed and
+ // reads the headers for the next section.
+ section = await reader.ReadNextSectionAsync();
+ }
+
+ //Get any extra form fields and return them
+ result.FormFieldData = formAccumulator.GetResults();
+ return result;
+ }
+ catch (Microsoft.AspNetCore.Http.BadHttpRequestException ex)
+ {
+ //most commonly here due to file too large
+ result.Error = $"Code:{ex.StatusCode}, Error: {ex.Message}";
+ return result;
+
+ }
+
+ }
+
+ public static void DeleteTempUploadFile(ApiUploadedFilesResult uploadFormData)
+ {
+ if (uploadFormData.UploadedFiles.Count > 0)
+ {
+ foreach (UploadedFileInfo a in uploadFormData.UploadedFiles)
+ {
+ System.IO.File.Delete(a.InitialUploadedPathName);
+ }
+ }
+ }
+
+ private static Encoding GetEncoding(MultipartSection section)
+ {
+ MediaTypeHeaderValue mediaType;
+ var hasMediaTypeHeader = MediaTypeHeaderValue.TryParse(section.ContentType, out mediaType);
+ // UTF-7 is insecure and should not be honored. UTF-8 will succeed in
+ // most cases.
+ if (!hasMediaTypeHeader || Encoding.UTF8.Equals(mediaType.Encoding))
+ {
+ return Encoding.UTF8;
+ }
+ return mediaType.Encoding;
+ }
+
+
+ ///
+ /// Contains result of upload form processor
+ ///
+ public class ApiUploadedFilesResult
+ {
+ public Dictionary FormFieldData { get; set; }
+ public List UploadedFiles { get; set; }
+ public string Error { get; set; }
+
+ public ApiUploadedFilesResult()
+ {
+ FormFieldData = new Dictionary();
+ UploadedFiles = new List();
+ Error = null;
+ }
+
+ }
+
+
+
+
+ }//eoc
+
+
+}//eons
diff --git a/server/ControllerHelpers/Authorized.cs b/server/ControllerHelpers/Authorized.cs
new file mode 100644
index 0000000..4fc5523
--- /dev/null
+++ b/server/ControllerHelpers/Authorized.cs
@@ -0,0 +1,228 @@
+using EnumsNET;
+using System.Collections.Generic;
+using Sockeye.Biz;
+
+
+namespace Sockeye.Api.ControllerHelpers
+{
+
+ //AUTHORIZATION ROLES: NOTE - this is only 'stage1' of generally checking rights, individual objects can also have business rules that affect access exactly as these roles do
+ //Most objects won't need more than this but some specialized ones will have further checks depending on biz rules
+
+ internal static class Authorized
+ {
+
+ ///
+ /// User has any role restricted or full
+ ///
+ ///
+ ///
+ ///
+ internal static bool HasAnyRole(IDictionary