Skip to content

Commit

Permalink
Fixes a handful of bugs related to user authentication, and configura…
Browse files Browse the repository at this point in the history
…tion validation
  • Loading branch information
replaysMike committed Feb 16, 2025
1 parent ad916e8 commit 12f3b8a
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 14 deletions.
1 change: 0 additions & 1 deletion Binner/Binner.Web/Controllers/PartController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using NPOI.HSSF.Record.Chart;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using System;
Expand Down
34 changes: 27 additions & 7 deletions Binner/Binner.Web/Controllers/StoredFileController.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
using AnyMapper;
using Binner.Common;
using Binner.Common.IO;
using Binner.Common.IO.Printing;
using Binner.Common.Services;
using Binner.Model;
using Binner.Model.Configuration;
using Binner.Model.Requests;
using Binner.Model.Responses;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.Logging;
using SixLabors.ImageSharp;
using System;
using System.Collections.Generic;
using System.IO;
Expand All @@ -27,13 +31,15 @@ public class StoredFileController : ControllerBase
private readonly WebHostServiceConfiguration _config;
private readonly IPartService _partService;
private readonly IStoredFileService _storedFileService;
private readonly IUserService _userService;

public StoredFileController(ILogger<ProjectController> logger, WebHostServiceConfiguration config, IPartService partService, IStoredFileService storedFileService)
public StoredFileController(ILogger<ProjectController> logger, WebHostServiceConfiguration config, IPartService partService, IStoredFileService storedFileService, IUserService userService)
{
_logger = logger;
_config = config;
_partService = partService;
_storedFileService = storedFileService;
_userService = userService;
}

/// <summary>
Expand Down Expand Up @@ -104,17 +110,22 @@ public async Task<IActionResult> GetStoredFileContentAsync([FromQuery] string fi
/// </summary>
/// <param name="fileName"></param>
/// <returns></returns>
[AllowAnonymous]
[HttpGet("preview")]
public async Task<IActionResult> GetStoredFilePreviewAsync([FromQuery] string fileName)
public async Task<IActionResult> GetStoredFilePreviewAsync([FromQuery] StoredFilePreviewRequest request)
{
if (string.IsNullOrEmpty(fileName)) return NotFound();
var storedFile = await _storedFileService.GetStoredFileAsync(fileName);
var userContext = await _userService.ValidateUserImageToken(request.Token ?? string.Empty);
if (userContext == null) return GetInvalidTokenImage();
System.Threading.Thread.CurrentPrincipal = new TokenPrincipal(userContext, request.Token);

if (string.IsNullOrEmpty(request.Filename)) return NotFound();
var storedFile = await _storedFileService.GetStoredFileAsync(request.Filename);
if (storedFile == null) return NotFound();

// read the file contents
new FileExtensionContentTypeProvider().TryGetContentType(fileName, out var contentType);
new FileExtensionContentTypeProvider().TryGetContentType(request.Filename, out var contentType);
if (string.IsNullOrEmpty(contentType))
return BadRequest($"Could not determine content type for file '{fileName}'");
return BadRequest($"Could not determine content type for file '{request.Filename}'");
// return a default cover image for the following formats
switch (contentType)
{
Expand All @@ -134,7 +145,7 @@ public async Task<IActionResult> GetStoredFilePreviewAsync([FromQuery] string fi

// return the actual file if it's an image type
var path = _storedFileService.GetStoredFilePath(storedFile.StoredFileType);
var pathToFile = Path.Combine(path, fileName);
var pathToFile = Path.Combine(path, request.Filename);
if (System.IO.File.Exists(pathToFile))
{
var bytes = System.IO.File.ReadAllBytes(pathToFile);
Expand Down Expand Up @@ -221,5 +232,14 @@ public async Task<IActionResult> DeletePartAsync(DeleteStoredFileRequest request
});
return Ok(isDeleted);
}

private FileStreamResult GetInvalidTokenImage()
{
var image = new BlankImage(300, 100, Color.White, Color.Red, "Invalid Image Token!\nYou may need to re-login.");
var stream = new MemoryStream();
image.Image.SaveAsPng(stream);
stream.Seek(0, SeekOrigin.Begin);
return new FileStreamResult(stream, "image/png");
}
}
}
16 changes: 10 additions & 6 deletions Binner/Library/Binner.Common/Integrations/ArrowApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,20 @@ public ArrowApi(ArrowConfiguration configuration, IHttpContextAccessor httpConte
_client = new HttpClient();
}

private void ValidateConfiguration()
{
if (string.IsNullOrEmpty(_configuration.Username)) throw new BinnerConfigurationException("ArrowConfiguration must specify a Username!");
if (string.IsNullOrEmpty(_configuration.ApiKey)) throw new BinnerConfigurationException("ArrowConfiguration must specify a ApiKey!");
if (string.IsNullOrEmpty(_configuration.ApiUrl)) throw new BinnerConfigurationException("ArrowConfiguration must specify a ApiUrl!");
}

public Task<IApiResponse> SearchAsync(string keyword, int recordCount = 25, Dictionary<string, string>? additionalOptions = null) => SearchAsync(keyword, string.Empty, string.Empty, recordCount, additionalOptions);

public Task<IApiResponse> SearchAsync(string keyword, string partType, int recordCount = 25, Dictionary<string, string>? additionalOptions = null) => SearchAsync(keyword, partType, string.Empty, recordCount, additionalOptions);

public async Task<IApiResponse> SearchAsync(string keyword, string partType, string mountingType, int recordCount = 25, Dictionary<string, string>? additionalOptions = null)
{
if (string.IsNullOrEmpty(_configuration.Username)) throw new BinnerConfigurationException("ArrowConfiguration must specify a Username!");
if (string.IsNullOrEmpty(_configuration.ApiKey)) throw new BinnerConfigurationException("ArrowConfiguration must specify a ApiKey!");
if (string.IsNullOrEmpty(_configuration.ApiUrl)) throw new BinnerConfigurationException("ArrowConfiguration must specify a ApiUrl!");
ValidateConfiguration();

if (!(recordCount > 0)) throw new ArgumentOutOfRangeException(nameof(recordCount));

Expand All @@ -74,9 +79,8 @@ public async Task<IApiResponse> SearchAsync(string keyword, string partType, str

public async Task<IApiResponse> GetOrderAsync(string orderId, Dictionary<string, string>? additionalOptions = null)
{
if (string.IsNullOrEmpty(_configuration.Username)) throw new BinnerConfigurationException($"{nameof(ArrowConfiguration)} must specify a Username!");
if (string.IsNullOrEmpty(_configuration.ApiKey)) throw new BinnerConfigurationException($"{nameof(ArrowConfiguration)} must specify a ApiKey!");
if (string.IsNullOrEmpty(_configuration.ApiUrl)) throw new BinnerConfigurationException($"{nameof(ArrowConfiguration)} must specify a ApiUrl!");
ValidateConfiguration();

if (additionalOptions == null) throw new ArgumentNullException(nameof(additionalOptions));
if (!additionalOptions.ContainsKey("password") || string.IsNullOrEmpty(additionalOptions["password"])) throw new ArgumentNullException(nameof(additionalOptions), "User password value is required!");
var username = _configuration.Username;
Expand Down
21 changes: 21 additions & 0 deletions Binner/Library/Binner.Common/Integrations/DigikeyApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,17 @@ public DigikeyApi(DigikeyConfiguration configuration, LocaleConfiguration locale
_requestContext = requestContext;
}

private void ValidateConfiguration()
{
if (_configuration.Enabled)
{
if (string.IsNullOrWhiteSpace(_configuration.ClientId)) throw new BinnerConfigurationException("DigiKey API ClientId cannot be empty!");
if (string.IsNullOrWhiteSpace(_configuration.ClientSecret)) throw new BinnerConfigurationException("DigiKey API ClientSecret cannot be empty!");
if (string.IsNullOrWhiteSpace(_configuration.oAuthPostbackUrl)) throw new BinnerConfigurationException("DigiKey API oAuthPostbackUrl cannot be empty!");
if (string.IsNullOrWhiteSpace(_configuration.ApiUrl)) throw new BinnerConfigurationException("DigiKey API ApiUrl cannot be empty!");
}
}

public enum MountingTypes
{
None = 0,
Expand All @@ -85,6 +96,8 @@ public enum MountingTypes

public async Task<IApiResponse> GetOrderAsync(string orderId, Dictionary<string, string>? additionalOptions = null)
{
ValidateConfiguration();

var authResponse = await AuthorizeAsync();
if (!authResponse.IsAuthorized)
return ApiResponse.Create(true, authResponse.AuthorizationUrl, $"User must authorize", nameof(DigikeyApi));
Expand Down Expand Up @@ -116,6 +129,8 @@ public async Task<IApiResponse> GetOrderAsync(string orderId, Dictionary<string,

public async Task<IApiResponse> GetProductDetailsAsync(string partNumber, Dictionary<string, string>? additionalOptions = null)
{
ValidateConfiguration();

var authResponse = await AuthorizeAsync();
if (!authResponse.IsAuthorized)
return ApiResponse.Create(true, authResponse.AuthorizationUrl, $"User must authorize", nameof(DigikeyApi));
Expand Down Expand Up @@ -153,6 +168,8 @@ public async Task<IApiResponse> GetProductDetailsAsync(string partNumber, Dictio
/// <returns></returns>
public async Task<IApiResponse> GetBarcodeDetailsAsync(string barcode, ScannedBarcodeType barcodeType)
{
ValidateConfiguration();

var authResponse = await AuthorizeAsync();
if (!authResponse.IsAuthorized)
return ApiResponse.Create(true, authResponse.AuthorizationUrl, $"User must authorize", nameof(DigikeyApi));
Expand Down Expand Up @@ -222,6 +239,8 @@ public async Task<IApiResponse> GetBarcodeDetailsAsync(string barcode, ScannedBa

public async Task<IApiResponse> GetCategoriesAsync()
{
ValidateConfiguration();

var authResponse = await AuthorizeAsync();
if (!authResponse.IsAuthorized)
return ApiResponse.Create(true, authResponse.AuthorizationUrl, $"User must authorize", nameof(DigikeyApi));
Expand Down Expand Up @@ -257,6 +276,8 @@ public async Task<IApiResponse> GetCategoriesAsync()

public async Task<IApiResponse> SearchAsync(string partNumber, string? partType, string? mountingType, int recordCount = 25, Dictionary<string, string>? additionalOptions = null)
{
ValidateConfiguration();

if (!(recordCount > 0)) throw new ArgumentOutOfRangeException(nameof(recordCount));
var authResponse = await AuthorizeAsync();
if (!authResponse.IsAuthorized)
Expand Down
19 changes: 19 additions & 0 deletions Binner/Library/Binner.Common/Integrations/MouserApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,22 @@ public MouserApi(MouserConfiguration configuration, IHttpContextAccessor httpCon
_client = new HttpClient();
}

private void ValidateOrderConfiguration()
{
if (string.IsNullOrWhiteSpace(_configuration.ApiKeys.OrderApiKey)) throw new BinnerConfigurationException("Mouser API OrderApiKey cannot be empty!");
if (string.IsNullOrWhiteSpace(_configuration.ApiUrl)) throw new BinnerConfigurationException("Mouser API ApiUrl cannot be empty!");
}

private void ValidateSearchConfiguration()
{
if (string.IsNullOrWhiteSpace(_configuration.ApiKeys.SearchApiKey)) throw new BinnerConfigurationException("Mouser API SearchApiKey cannot be empty!");
if (string.IsNullOrWhiteSpace(_configuration.ApiUrl)) throw new BinnerConfigurationException("Mouser API ApiUrl cannot be empty!");
}

public async Task<IApiResponse> GetOrderAsync(string orderId, Dictionary<string, string>? additionalOptions = null)
{
ValidateOrderConfiguration();

// use the newer order history api, slightly different data format
var uri = Url.Combine(_configuration.ApiUrl, BasePath, $"/orderhistory/webOrderNumber?webOrderNumber={orderId}&apiKey={_configuration.ApiKeys.OrderApiKey}");
var requestMessage = CreateRequest(HttpMethod.Get, uri);
Expand All @@ -62,6 +76,8 @@ public async Task<IApiResponse> GetOrderAsync(string orderId, Dictionary<string,

public async Task<IApiResponse> GetOldOrderAsync(string orderId, Dictionary<string, string>? additionalOptions = null)
{
ValidateOrderConfiguration();

var uri = Url.Combine(_configuration.ApiUrl, BasePath, $"/order/{orderId}?apiKey={_configuration.ApiKeys.OrderApiKey}");
var requestMessage = CreateRequest(HttpMethod.Get, uri);
var response = await _client.SendAsync(requestMessage);
Expand All @@ -85,6 +101,8 @@ public async Task<IApiResponse> GetOldOrderAsync(string orderId, Dictionary<stri
/// <returns></returns>
public async Task<IApiResponse> GetProductDetailsAsync(string partNumber, Dictionary<string, string>? additionalOptions = null)
{
ValidateSearchConfiguration();

var uri = Url.Combine(_configuration.ApiUrl, BasePath, $"/search/partnumber?apiKey={_configuration.ApiKeys.SearchApiKey}");
var requestMessage = CreateRequest(HttpMethod.Post, uri);
var request = new
Expand Down Expand Up @@ -119,6 +137,7 @@ public Task<IApiResponse> SearchAsync(string keyword, int recordCount = 25, Dict

public async Task<IApiResponse> SearchAsync(string keyword, string partType, string mountingType, int recordCount = 25, Dictionary<string, string>? additionalOptions = null)
{
ValidateSearchConfiguration();
if (!(recordCount > 0)) throw new ArgumentOutOfRangeException(nameof(recordCount));
var uri = Url.Combine(_configuration.ApiUrl, BasePath, $"/search/keyword?apiKey={_configuration.ApiKeys.SearchApiKey}");
var requestMessage = CreateRequest(HttpMethod.Post, uri);
Expand Down
15 changes: 15 additions & 0 deletions Binner/Library/Binner.Model/Requests/StoredFilePreviewRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace Binner.Model.Requests
{
public class StoredFilePreviewRequest
{
/// <summary>
/// The stored file to load
/// </summary>
public string? Filename { get; set; }

/// <summary>
/// The image token to validate the insecure request with
/// </summary>
public string? Token { get; set; }
}
}

0 comments on commit 12f3b8a

Please sign in to comment.