Move APIKeys to file and allow read vs read/write
This commit is contained in:
@@ -7,13 +7,13 @@ namespace TodoApi.Controllers
|
||||
{
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
[ApiKey]
|
||||
public class TodoItemsController(TodoContext context) : ControllerBase
|
||||
{
|
||||
private readonly TodoContext _context = context;
|
||||
|
||||
// GET: api/TodoItems
|
||||
[HttpGet]
|
||||
[ApiKeyCanRead]
|
||||
public async Task<ActionResult<IEnumerable<TodoItem>>> GetTodoItems()
|
||||
{
|
||||
return await _context.TodoItems.ToListAsync();
|
||||
@@ -21,6 +21,7 @@ namespace TodoApi.Controllers
|
||||
|
||||
// GET: api/TodoItems/5
|
||||
[HttpGet("{id}")]
|
||||
[ApiKeyCanRead]
|
||||
public async Task<ActionResult<TodoItem>> GetTodoItem(long id)
|
||||
{
|
||||
var todoItem = await _context.TodoItems.FindAsync(id);
|
||||
@@ -36,6 +37,7 @@ namespace TodoApi.Controllers
|
||||
// PUT: api/TodoItems/5
|
||||
// To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
|
||||
[HttpPut("{id}")]
|
||||
[ApiKeyCanWrite]
|
||||
public async Task<IActionResult> PutTodoItem(long id, TodoItem todoItem)
|
||||
{
|
||||
if (id != todoItem.Id)
|
||||
@@ -67,6 +69,7 @@ namespace TodoApi.Controllers
|
||||
// POST: api/TodoItems
|
||||
// To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
|
||||
[HttpPost]
|
||||
[ApiKeyCanWrite]
|
||||
public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem todoItem)
|
||||
{
|
||||
_context.TodoItems.Add(todoItem);
|
||||
@@ -77,6 +80,7 @@ namespace TodoApi.Controllers
|
||||
|
||||
// DELETE: api/TodoItems/5
|
||||
[HttpDelete("{id}")]
|
||||
[ApiKeyCanWrite]
|
||||
public async Task<IActionResult> DeleteTodoItem(long id)
|
||||
{
|
||||
var todoItem = await _context.TodoItems.FindAsync(id);
|
||||
|
||||
@@ -3,7 +3,7 @@ using Microsoft.AspNetCore.Mvc.Filters;
|
||||
|
||||
namespace TodoApi.Helpers;
|
||||
|
||||
public class ApiKeyAttribute : ActionFilterAttribute
|
||||
public class ApiKeyCanReadAttribute : ActionFilterAttribute
|
||||
{
|
||||
public override void OnActionExecuting(ActionExecutingContext context)
|
||||
{
|
||||
@@ -14,7 +14,7 @@ public class ApiKeyAttribute : ActionFilterAttribute
|
||||
var apiKey = context.HttpContext.Request.Headers["X-API-KEY"];
|
||||
|
||||
// Validate the API key using the IApiKeyValidator service
|
||||
if (string.IsNullOrEmpty(apiKey) || !apiKeyValidator.Validate(apiKey))
|
||||
if (string.IsNullOrEmpty(apiKey) || !apiKeyValidator.CanRead(apiKey))
|
||||
{
|
||||
// If the API key is invalid, set the response status code to 401 Unauthorized
|
||||
context.Result = new UnauthorizedResult();
|
||||
27
Helpers/ApiKeyCanWriteAttribute.cs
Normal file
27
Helpers/ApiKeyCanWriteAttribute.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
|
||||
namespace TodoApi.Helpers;
|
||||
|
||||
public class ApiKeyCanWriteAttribute : ActionFilterAttribute
|
||||
{
|
||||
public override void OnActionExecuting(ActionExecutingContext context)
|
||||
{
|
||||
// Get the required service to validate the API key
|
||||
var apiKeyValidator = context.HttpContext.RequestServices.GetRequiredService<IApiKeyValidator>();
|
||||
|
||||
// Get the API key from the X-API-KEY header
|
||||
var apiKey = context.HttpContext.Request.Headers["X-API-KEY"];
|
||||
|
||||
// Validate the API key using the IApiKeyValidator service
|
||||
if (string.IsNullOrEmpty(apiKey) || !apiKeyValidator.CanWrite(apiKey))
|
||||
{
|
||||
// If the API key is invalid, set the response status code to 401 Unauthorized
|
||||
context.Result = new UnauthorizedResult();
|
||||
return;
|
||||
}
|
||||
|
||||
// If the API key is valid, continue with the action execution
|
||||
base.OnActionExecuting(context);
|
||||
}
|
||||
}
|
||||
@@ -2,19 +2,29 @@ namespace TodoApi.Helpers
|
||||
{
|
||||
public interface IApiKeyValidator
|
||||
{
|
||||
bool Validate(string? apiKey);
|
||||
bool CanRead(string? apiKey);
|
||||
bool CanWrite(string? apiKey);
|
||||
}
|
||||
|
||||
public class ApiKeyValidator(List<string>? apiKeys) : IApiKeyValidator
|
||||
public class ApiKeyValidator(ApiKeys? apiKeys) : IApiKeyValidator
|
||||
{
|
||||
private readonly List<string>? _apiKeys = apiKeys;
|
||||
private readonly ApiKeys _apiKeys = apiKeys ?? new ApiKeys();
|
||||
|
||||
public bool Validate(string? apiKey)
|
||||
public bool CanRead(string? apiKey)
|
||||
{
|
||||
if (_apiKeys == null) return false;
|
||||
if (apiKey == null) return false;
|
||||
|
||||
// Verify the provided apiKey is in our configuration
|
||||
return _apiKeys.Contains(apiKey!.ToLower());
|
||||
return _apiKeys.ReadOnly.Contains(apiKey, StringComparison.OrdinalIgnoreCase) ||
|
||||
_apiKeys.ReadWrite.Contains(apiKey, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public bool CanWrite(string? apiKey)
|
||||
{
|
||||
if (apiKey == null) return false;
|
||||
|
||||
// Verify the provided apiKey is in our configuration
|
||||
return _apiKeys.ReadWrite.Contains(apiKey, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
12
Helpers/ApiKeys.cs
Normal file
12
Helpers/ApiKeys.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace TodoApi.Helpers;
|
||||
|
||||
public class ApiKeys
|
||||
{
|
||||
[JsonProperty("read_only")]
|
||||
public List<string> ReadOnly { get; set; } = [];
|
||||
|
||||
[JsonProperty("read_write")]
|
||||
public List<string> ReadWrite { get; set; } = [];
|
||||
}
|
||||
12
Helpers/ExtensionMethods.cs
Normal file
12
Helpers/ExtensionMethods.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace TodoApi;
|
||||
|
||||
public static class ExtensionMethods
|
||||
{
|
||||
public static bool Contains(this IEnumerable<string> source, string toCheck, StringComparison comp)
|
||||
{
|
||||
return
|
||||
source != null &&
|
||||
!string.IsNullOrEmpty(toCheck) &&
|
||||
source.Any(x => string.Compare(x, toCheck, comp) == 0);
|
||||
}
|
||||
}
|
||||
17
Program.cs
17
Program.cs
@@ -3,6 +3,7 @@ using Microsoft.Data.Sqlite;
|
||||
using TodoApi.Helpers;
|
||||
using TodoApi.Models;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
@@ -16,8 +17,20 @@ var connectionString = new SqliteConnectionStringBuilder(builder.Configuration.G
|
||||
}.ToString();
|
||||
builder.Services.AddDbContext<TodoContext>(options => options.UseSqlite(connectionString));
|
||||
|
||||
//setup APIKey validation using APIKeys from config file
|
||||
var apiKeys = builder.Configuration.GetSection("Authentication").GetValue<List<string>>("APIKeys");
|
||||
//setup APIKey validation using apikey.json file
|
||||
ApiKeys apiKeys = new();
|
||||
try
|
||||
{
|
||||
string? apiKeyFilename = builder.Configuration.GetValue<string>("APIKeyFile");
|
||||
if (File.Exists(apiKeyFilename))
|
||||
{
|
||||
string jsonText = File.ReadAllText(apiKeyFilename);
|
||||
|
||||
ApiKeys? apiKeysTemp = JsonConvert.DeserializeObject<ApiKeys>(jsonText);
|
||||
if (apiKeysTemp != null) apiKeys = apiKeysTemp;
|
||||
}
|
||||
}
|
||||
catch {}
|
||||
builder.Services.AddSingleton<IApiKeyValidator, ApiKeyValidator>(_ => new ApiKeyValidator(apiKeys));
|
||||
|
||||
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
|
||||
|
||||
@@ -20,11 +20,12 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="8.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="data\*.*">
|
||||
<Content Include="data\todo.db*">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
@@ -2,11 +2,7 @@
|
||||
"ConnectionStrings": {
|
||||
"TodoDatabase": "Data Source=data/todo.db"
|
||||
},
|
||||
"Authentication": {
|
||||
"APIKeys": [
|
||||
"0a7eefbb-17f5-4299-882b-94719461a896"
|
||||
]
|
||||
},
|
||||
"APIKeyFile": "data/apikeys.json",
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
|
||||
8
data/apikeys.json
Normal file
8
data/apikeys.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"read_only": [
|
||||
"0a7eefbb-17f5-4299-882b-94719461a896"
|
||||
],
|
||||
"read_write": [
|
||||
"8aa58ea6-0743-4598-b5ee-24a6842852ba"
|
||||
]
|
||||
}
|
||||
BIN
data/todo.db-shm
Normal file
BIN
data/todo.db-shm
Normal file
Binary file not shown.
BIN
data/todo.db-wal
Normal file
BIN
data/todo.db-wal
Normal file
Binary file not shown.
Reference in New Issue
Block a user