Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions GpioController/Authorization/ConditionalJwtAuthorization.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using Microsoft.AspNetCore.Authorization;

namespace GpioController.Authorization;

public class ConditionalAuthRequirement : IAuthorizationRequirement { }

public class ConditionalJwtAuthorization(IConfiguration config) : AuthorizationHandler<ConditionalAuthRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
ConditionalAuthRequirement requirement)
{
var requireAuth = config.GetValue<bool>("Authorization:Enabled");

if (!requireAuth || (context.User.Identity?.IsAuthenticated ?? false))
{
context.Succeed(requirement);
}

return Task.CompletedTask;
}
}



2 changes: 2 additions & 0 deletions GpioController/Commands/GpioSetCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ protected override void RunOptionalPostCommandLogic(GpioSetRequest request, Gpio

for (var timesRepeated = 1; timesRepeated < request.Options?.RepeatTimes*2; timesRepeated++)
{
if (request.CancellationToken.IsCancellationRequested)
break;
Thread.Sleep(sleepTime);
RunOpposite(request);
}
Expand Down
1 change: 1 addition & 0 deletions GpioController/Commands/Request/GpioSetRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ public class GpioSetRequest : Request
public required IEnumerable<int> Gpios { get; set; }
public required string State { get; set; }
public OptionalSettings? Options { get; set; }
public CancellationToken CancellationToken { get; set; }
}

public class OptionalSettings
Expand Down
2 changes: 1 addition & 1 deletion GpioController/Commands/Results/GpioInfoResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ namespace GpioController.Commands.Results;

public class GpioInfoResult : Result
{
public IEnumerable<Gpio> Result { get; set; }
public required IEnumerable<Gpio> Result { get; set; }
}
2 changes: 1 addition & 1 deletion GpioController/Commands/Results/GpioSetResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ namespace GpioController.Commands.Results;

public class GpioSetResult : Result
{
public string Result { get; set; }
public required string Result { get; set; }
}
13 changes: 10 additions & 3 deletions GpioController/Controllers/GpioController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,29 @@ namespace GpioController.Controllers;

[ApiController]
[Route("sbc")]
[Authorize(Policy = "ConditionalPolicy")]
public class GpioController(IOptions<AuthorizationSettings> authorizationSettings, IGpioService gpioService) : SecureController(authorizationSettings)
{
[AllowAnonymous]
[HttpGet]
[Route("chipsets/gpios")]
public IActionResult Get()
{
if (!IsAuthorized())
return Unauthorized();

var gpios = gpioService.GetGpios();
return Ok(gpios);
var filteredResults = gpioService.OrderResultsByFilter(gpios);

return Ok(filteredResults);
}

[AllowAnonymous]
[HttpGet]
[Route("chipsets/{chipsetId}/gpios/{gpioId}")]
public IActionResult GetById([FromRoute] int chipsetId, [FromRoute] int gpioId)
{
if (!IsAuthorized())
return Unauthorized();

var gpio = gpioService.GetGpioById(chipsetId, gpioId);
return Ok(gpio);
}
Expand Down
16 changes: 10 additions & 6 deletions GpioController/Controllers/SecureController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,26 @@

namespace GpioController.Controllers;

public class SecureController : ControllerBase
public class SecureController(IOptions<AuthorizationSettings> authorizationSettings) : ControllerBase
{
private readonly AuthorizationSettings authorizationSettings;
private readonly AuthorizationSettings authorizationSettings = authorizationSettings.Value;

public SecureController(IOptions<AuthorizationSettings> authorizationSettings)
protected bool IsAuthorized()
{
this.authorizationSettings = authorizationSettings.Value;
return !authorizationSettings.Enabled || IsEmailClaimAuthorized();
}

protected bool IsAuthorized()
private bool IsEmailClaimAuthorized()
{
const string validationType = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress";

var userIdentity = HttpContext.User.Identity as ClaimsIdentity;

var emailCliam =
userIdentity?.Claims?.FirstOrDefault(claim => claim.Type == authorizationSettings?.ValidationType);
userIdentity?.Claims?.FirstOrDefault(claim => claim.Type == validationType);

var currentUserEmail = emailCliam?.Value ?? string.Empty;

var isAuthorized = authorizationSettings?.AuthorizedEmails?.Contains(currentUserEmail) ?? false;

return isAuthorized;
Expand Down
22 changes: 9 additions & 13 deletions GpioController/Controllers/StateController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,26 @@ namespace GpioController.Controllers;

[ApiController]
[Route("sbc")]
public class StateController(IOptions<AuthorizationSettings> authorizationSettings, IStateService stateService) : SecureController(authorizationSettings)
[Authorize(Policy = "ConditionalPolicy")]
public class StateController(IOptions<AuthorizationSettings> authorizationSettings, IStateService stateService, ITokenManagementService tokenManagementService) : SecureController(authorizationSettings)
{
[AllowAnonymous]
// [Authorize]
[HttpGet]
[Route("chipsets/{chipsetId}/gpios/{gpioId}/state")]
public IActionResult GetById([FromRoute] int chipsetId, [FromRoute] int gpioId)
{
// if (!IsAuthorized())
// return Unauthorized();
if (!IsAuthorized())
return Unauthorized();

var result = stateService.GetStateByGpioId(chipsetId, gpioId);
return Ok(result);
}

[AllowAnonymous]
// [Authorize]
[HttpPost]
[Route("chipsets/{chipsetId}/gpios/{gpioId}/state/{state}")]
public IActionResult UpdateSingleStateById([FromRoute] int chipsetId, [FromRoute] int gpioId, [FromRoute] string state)
{
// if (!IsAuthorized())
// return Unauthorized();
if (!IsAuthorized())
return Unauthorized();

stateService.UpdateSingleState(new GpioSetRequest
{
Expand All @@ -43,15 +40,14 @@ public IActionResult UpdateSingleStateById([FromRoute] int chipsetId, [FromRoute
return NoContent();
}

[AllowAnonymous]
// [Authorize]
[HttpPost]
[Route("chipsets/gpios/state")]
public IActionResult UpdateMultipleStatesByRequest([FromRoute] int chipsetId, [FromRoute] int gpioId, [FromBody] IEnumerable<GpioSetRequest> updateRequests)
{
// if (!IsAuthorized())
// return Unauthorized();
if (!IsAuthorized())
return Unauthorized();

tokenManagementService.CancelAll();
stateService.UpdateMultipleStates(updateRequests);

return NoContent();
Expand Down
9 changes: 9 additions & 0 deletions GpioController/Models/ActiveTask.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using GpioController.Commands.Request;

namespace GpioController.Models;

public class ActiveTask
{
public required CancellationTokenSource TokenSource { get; set; }
public required GpioSetRequest ActiveRequest { get; set; }
}
2 changes: 1 addition & 1 deletion GpioController/Models/AuthorizationSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ namespace GpioController.Models;

public class AuthorizationSettings
{
public required string ValidationType { get; set; }
public required bool Enabled { get; set; }
public required List<string> AuthorizedEmails { get; set; }
}
7 changes: 7 additions & 0 deletions GpioController/Models/FilterSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace GpioController.Models;

public class FilterSettings
{
public required List<int> AllowOnlyTheseChipsets { get; set; }
public required List<int> AllowOnlyTheseGpios { get; set; }
}
2 changes: 1 addition & 1 deletion GpioController/Models/Gpio.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ public class Gpio
{
public int Chipset { get; set; }
public int Id { get; set; }
public string Name { get; set; }
public string? Name { get; set; }
}
69 changes: 58 additions & 11 deletions GpioController/Program.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using GpioController.Authorization;
using GpioController.Commands;
using GpioController.Commands.Request;
using GpioController.Commands.Results;
Expand All @@ -7,6 +8,7 @@
using GpioController.Parsers;
using GpioController.Services;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;

namespace GpioController;

Expand All @@ -19,18 +21,49 @@ public static void Main(string[] args)
builder.Services.Configure<AuthorizationSettings>(
builder.Configuration.GetSection("Authorization")
);

builder.Services.Configure<FilterSettings>(
builder.Configuration.GetSection("Filters")
);

var requireAuth = builder.Configuration.GetValue<bool>("Authorization:Enabled");

builder.Services.AddTransient<ITerminalService, TerminalService>();
builder.Services.AddTransient<IGpioService, GpioService>();
builder.Services.AddTransient<IStateService, StateService>();
builder.Services.AddTransient<IParser<GpioInfoResult>, InfoParser>();
builder.Services.AddTransient<IParser<GpioSetResult>, UpdateParser>();
builder.Services.AddTransient<IParser<GpioReadResult>, ReadParser>();
builder.Services.AddTransient<ICommand<GpioInfoRequest, GpioInfoResult>, GpioInfoCommand>();
builder.Services.AddTransient<ICommand<GpioReadRequest, GpioReadResult>, GpioReadCommand>();
builder.Services.AddTransient<ICommand<GpioSetRequest, GpioSetResult>, GpioSetCommand>();
builder.Services.AddSingleton<ICommandFactory>(provider => new CommandFactory(provider));
if (requireAuth)
{
var authorizedCorsOrigin = builder.Configuration
.GetSection("Authorization:AuthorizedCorsOrigins")
.Get<string[]>();

builder.Services.AddCors(options =>
{
options.AddPolicy("AuthorizedCorsPolicy", policy =>
{
policy
.WithOrigins(authorizedCorsOrigin ?? [])
.AllowCredentials()
.AllowAnyHeader()
.WithMethods("GET", "POST", "OPTIONS");
});
});
}
else
{
builder.Services.AddCors(options =>
{
options.AddPolicy("AuthorizedCorsPolicy", policy =>
{
policy
.AllowAnyOrigin()
.AllowAnyHeader()
.WithMethods("GET", "POST", "OPTIONS");
});
});
}

builder.Services.AddAuthorizationBuilder()
.AddPolicy("ConditionalPolicy", policy =>
policy.Requirements.Add(new ConditionalAuthRequirement()));

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
Expand All @@ -44,10 +77,24 @@ public static void Main(string[] args)
};
});

builder.Services.AddSingleton<IAuthorizationHandler, ConditionalJwtAuthorization>();
builder.Services.AddTransient<ITerminalService, TerminalService>();
builder.Services.AddTransient<IGpioService, GpioService>();
builder.Services.AddSingleton<ITokenManagementService, TokenManagementService>();
builder.Services.AddTransient<IStateService, StateService>();
builder.Services.AddTransient<IParser<GpioInfoResult>, InfoParser>();
builder.Services.AddTransient<IParser<GpioSetResult>, UpdateParser>();
builder.Services.AddTransient<IParser<GpioReadResult>, ReadParser>();
builder.Services.AddTransient<ICommand<GpioInfoRequest, GpioInfoResult>, GpioInfoCommand>();
builder.Services.AddTransient<ICommand<GpioReadRequest, GpioReadResult>, GpioReadCommand>();
builder.Services.AddTransient<ICommand<GpioSetRequest, GpioSetResult>, GpioSetCommand>();
builder.Services.AddSingleton<ICommandFactory>(provider => new CommandFactory(provider));

builder.Services.AddControllers();

var app = builder.Build();


app.UseCors("AuthorizedCorsPolicy");
app.UseMiddleware<ExceptionMiddleware>();
app.UseAuthorization();
app.UseHttpsRedirection();
Expand Down
27 changes: 24 additions & 3 deletions GpioController/Services/GpioService.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,44 @@
using GpioController.Commands.Request;
using GpioController.Commands.Results;
using GpioController.Exceptions;
using GpioController.Extensions;
using GpioController.Factories;
using GpioController.Models;
using Microsoft.Extensions.Options;

namespace GpioController.Services;

public class GpioService(ICommandFactory commandFactory) : IGpioService
public class GpioService(ICommandFactory commandFactory, IOptions<FilterSettings> filterSettings) : IGpioService
{
public IEnumerable<Gpio> GetGpios()
{
var gpioFilter = filterSettings.Value;
var command = commandFactory.GetCommand<GpioInfoRequest, GpioInfoResult>();
var gpios = command.Execute(new GpioInfoRequest());
var results = gpios?.Result ?? [];

if (!gpios?.Result.Any() ?? false)
throw new NoGpiosFoundException();

if (results.Any() && gpioFilter.AllowOnlyTheseChipsets.Any())
results = results.Where(gpio => gpioFilter.AllowOnlyTheseChipsets.Contains(gpio.Chipset));

if (results.Any() && gpioFilter.AllowOnlyTheseGpios.Any())
results = results.Where(gpio => gpioFilter.AllowOnlyTheseGpios.Contains(gpio.Id));

return gpios.Result;
return results;
}

public IEnumerable<Gpio?> OrderResultsByFilter(IEnumerable<Gpio> results)
{
var filter = filterSettings?.Value?.AllowOnlyTheseGpios ?? [];

if (results.Any() && filter.Any())
return filter
.Select(id => results.FirstOrDefault(p => p.Id == id))
.Where(p => p != null)
.ToList();

return results;
}

public Gpio GetGpioById(int chipsetId, int gpioId)
Expand Down
1 change: 1 addition & 0 deletions GpioController/Services/IGpioService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ public interface IGpioService
{
IEnumerable<Gpio> GetGpios();
Gpio GetGpioById(int chipsetId, int gpioId);
IEnumerable<Gpio?> OrderResultsByFilter(IEnumerable<Gpio> results);
}
9 changes: 9 additions & 0 deletions GpioController/Services/ITokenManagementService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using GpioController.Commands.Request;

namespace GpioController.Services;

public interface ITokenManagementService
{
public void CancelAll();
public CancellationToken CreateToken(GpioSetRequest request);
}
5 changes: 3 additions & 2 deletions GpioController/Services/StateService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

namespace GpioController.Services;

public class StateService(ICommandFactory commandFactory, IGpioService gpioService) : IStateService
public class StateService(ICommandFactory commandFactory, IGpioService gpioService, ITokenManagementService tokenManagementService) : IStateService
{
public GpioReadResult GetStateByGpioId(int chipsetId, int gpioId)
{
Expand Down Expand Up @@ -38,7 +38,8 @@ public void UpdateMultipleStates(IEnumerable<GpioSetRequest> updateRequests)
new Action(() =>
{
foreach (var request in updateRequests)
{
{
request.CancellationToken = tokenManagementService.CreateToken(request);
var command = commandFactory.GetCommand<GpioSetRequest, GpioSetResult>();
command.Execute(request);
}
Expand Down
Loading