Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Standalone api with api-key auth for probes #3

Merged
merged 4 commits into from
Aug 8, 2023
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -398,3 +398,4 @@ FodyWeavers.xsd
*.sln.iml
/SEVEN.Arduino/arduino_secrets.h
SEVEN.Pico/Probe/secrets.py
.idea/
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using System.Security.Claims;
using System.Security.Principal;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Options;

namespace SEVEN.MissionControl.Api.AuthenticationSchemes;

public class ApiKeyAuthenticationScheme : AuthenticationHandler<AuthenticationSchemeOptions> {
internal const string SchemeName = "seven-api-key";
internal const string KeyHeaderName = "x-seven-key";
private const string ProbeHeaderName = "x-probe-id";
private readonly string _apiKey;

public ApiKeyAuthenticationScheme(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder,
ISystemClock clock, IConfiguration configuration) : base(options, logger, encoder, clock) {
_apiKey = configuration["API_KEY"] ?? throw new InvalidOperationException("Seven api-key not set in appsettings.json or environment variables");
}

protected override Task<AuthenticateResult> HandleAuthenticateAsync() {
Request.Headers.TryGetValue(KeyHeaderName, out var extractedApiKey);
Request.Headers.TryGetValue(ProbeHeaderName, out var extractedProbeId);

if (!IsPublicEndpoint() && !extractedApiKey.Equals(_apiKey))
return Task.FromResult(AuthenticateResult.Fail("Invalid seven-key"));

var probeId = extractedProbeId.ToString() == string.Empty ? "Generic" : extractedProbeId.ToString();

var identity = new ClaimsIdentity(
claims: new[] {
new Claim("ProbeId", probeId)
},
authenticationType: Scheme.Name);

var principal = new GenericPrincipal(identity, roles: null);
var ticket = new AuthenticationTicket(principal, Scheme.Name);

return Task.FromResult(AuthenticateResult.Success(ticket));
}

private bool IsPublicEndpoint() => Context
.GetEndpoint()?
.Metadata.OfType<AllowAnonymousAttribute>()
.Any() is null or true;
}
33 changes: 33 additions & 0 deletions SEVEN.MissionControl.Api/Data/Contexts/MissionControlContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using Microsoft.EntityFrameworkCore;
using SEVEN.Core.Models;

namespace SEVEN.MissionControl.Api.Data.Contexts;

public class MissionControlContext : DbContext
{
public MissionControlContext(DbContextOptions<MissionControlContext> options)
: base(options)
{
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
AppContext.SetSwitch("Npgsql.DisableDateTimeInfinityConversions", true);
}

public DbSet<Rover> Rovers { get; set; }
public DbSet<RoverTask> RoverTasks { get; set; }
public DbSet<Probe> Probes { get; set; }
public DbSet<Measurement> Measurements { get; set; }

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Rover>()
.HasMany(e => e.Tasks)
.WithOne(e => e.Rover)
.HasForeignKey(e => e.RoverId)
.IsRequired();

modelBuilder.Entity<Probe>()
.HasMany(p => p.Measurements)
.WithOne(m => m.Probe)
.HasForeignKey(m => m.ProbeId);
}
}
77 changes: 77 additions & 0 deletions SEVEN.MissionControl.Api/Data/Generators/RoverGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using Microsoft.EntityFrameworkCore;
using SEVEN.Core.Models;
using SEVEN.MissionControl.Api.Data.Contexts;

namespace SEVEN.MissionControl.Api.Data.Generators;

public class RoverGenerator
{
public static void Initialize(IServiceCollection services)
{
using (var context = new MissionControlContext(services.BuildServiceProvider()
.GetRequiredService<DbContextOptions<MissionControlContext>>()))
{
context.Database.EnsureCreated();

// Look for any board games.
if (context.Rovers.Any()) return; // Data was already seeded

var roverOne = new Rover
{
Id = Guid.Parse("7A73F8AE-0000-0000-AAAA-7AB5A00A9C1D"),
Name = "Sandberg1",
//GeoCoordinates = new GeoCoordinates("50.85953679509189", "11.19558185972183")
};

var probeOne = new Probe
{
Id = Guid.Parse("7A73F8AE-0000-0000-BBBB-7AB5A00A9C1D"),
Name = "Probe1",
MeasurementsType = MeasurementType.Temperature | MeasurementType.Humidity,
SendingIntervalMinutes = 5
};

context.Probes.Add(probeOne);
context.Rovers.Add(roverOne);

context.RoverTasks.AddRange(
new RoverTask
{
Id = Guid.NewGuid(),
Position = 1,
RoverId = roverOne.Id,
Rover = roverOne,
Command = RoverTaskCommands.CommandHeadlightsOn,
StatusUpdate = DateTime.Now.AddDays(-3),
Status = RoverTaskStatus.Ready,
StatusInfo = null
}
,
new RoverTask
{
Id = Guid.NewGuid(),
Position = 2,
RoverId = roverOne.Id,
Rover = roverOne,
Command = RoverTaskCommands.CommandCameraTakefoto,
StatusUpdate = DateTime.Now.AddDays(-2),
Status = RoverTaskStatus.Success,
StatusInfo = null
},
new RoverTask
{
Id = Guid.NewGuid(),
Position = 3,
RoverId = roverOne.Id,
Rover = roverOne,
Command = RoverTaskCommands.CommandHeadlightsOff,
StatusUpdate = DateTime.Now.AddDays(-1),
Status = RoverTaskStatus.Success,
StatusInfo = null
}
);

context.SaveChanges();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using SEVEN.Core.Models;

namespace SEVEN.MissionControl.Api.Data.Repositories.Interfaces;

public interface IMeasurementRepository
{
Task<IEnumerable<Measurement>> GetMeasurements();
Task<Measurement?> GetLastMeasurement(Guid probeId);
Task<Measurement?> CreateMeasurement(Measurement measurement);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using SEVEN.Core.Models;

namespace SEVEN.MissionControl.Api.Data.Repositories.Interfaces;

public interface IProbeRepository
{
Task<Probe> CreateProbe(Probe probe);
Task<Probe?> UpdateProbe(Probe probe);
Task<bool> RemoveProbe(Guid id);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using SEVEN.Core.Models;

namespace SEVEN.MissionControl.Api.Data.Repositories.Interfaces;

public interface IRoverTaskRepository
{
Task<RoverTask?> CreateRoverTask(RoverTask roverTask);
Task<RoverTask?> CreateRoverTask(Guid roverId, RoverTaskCommands command);
Task<IEnumerable<RoverTask>> GetReadyRoverTasks(Guid roverId);
Task<RoverTask?> GetRoverTask(Guid roverTaskId);
Task<RoverTask?> UpdateRoverTask(RoverTask inputRoverTask);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using Microsoft.EntityFrameworkCore;
using SEVEN.Core.Models;
using SEVEN.MissionControl.Api.Data.Contexts;
using SEVEN.MissionControl.Api.Data.Repositories.Interfaces;

namespace SEVEN.MissionControl.Api.Data.Repositories;

public class MeasurementRepository : IMeasurementRepository
{
private readonly MissionControlContext _context;

public MeasurementRepository(MissionControlContext context)
{
_context = context;
}

public async Task<IEnumerable<Measurement>> GetMeasurements()
{
return await _context.Measurements.Include(_ => _.Probe).AsNoTracking().ToListAsync();
}

public async Task<Measurement?> GetLastMeasurement(Guid probeId)
{
var measurements = await GetMeasurements();
var measurement = measurements.Where(_ => _.ProbeId == probeId).OrderByDescending(_ => _.Time)?.FirstOrDefault();
return measurement;
}


public async Task<Measurement?> CreateMeasurement(Measurement measurement)
{
var probe = await _context.Probes.FindAsync(measurement.ProbeId);
if (probe is null) return null;

measurement.Id = Guid.NewGuid();
measurement.Time = DateTime.UtcNow;

await _context.Measurements.AddAsync(measurement);
await _context.SaveChangesAsync();
return measurement;
}
}
54 changes: 54 additions & 0 deletions SEVEN.MissionControl.Api/Data/Repositories/ProbeRepository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using SEVEN.Core.Models;
using SEVEN.MissionControl.Api.Data.Contexts;
using SEVEN.MissionControl.Api.Data.Repositories.Interfaces;

namespace SEVEN.MissionControl.Api.Data.Repositories;

public class ProbeRepository : IProbeRepository
{
private readonly MissionControlContext _context;

public ProbeRepository(MissionControlContext context)
{
_context = context;
}

public async Task<Probe> CreateProbe(Probe probe)
{
probe.Id = Guid.NewGuid();
await _context.AddAsync(probe);
await _context.SaveChangesAsync();
return probe;
}

public async Task<Probe?> UpdateProbe(Probe probe)
{
var dbProbe = await _context.Probes.FindAsync(probe.Id);
if (dbProbe != null)
{
dbProbe.Name = probe.Name;
dbProbe.MeasurementsType = probe.MeasurementsType;

await _context.SaveChangesAsync();
}
else
{
probe = null;
}

return probe;
}

public async Task<bool> RemoveProbe(Guid id)
{
var dbProbe = await _context.Probes.FindAsync(id);
if (dbProbe != null)
{
_context.Probes.Remove(dbProbe);
await _context.SaveChangesAsync();
return true;
}

return false;
}
}
70 changes: 70 additions & 0 deletions SEVEN.MissionControl.Api/Data/Repositories/RoverTaskRepository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using Microsoft.EntityFrameworkCore;
using SEVEN.Core.Models;
using SEVEN.MissionControl.Api.Data.Contexts;
using SEVEN.MissionControl.Api.Data.Repositories.Interfaces;

namespace SEVEN.MissionControl.Api.Data.Repositories;

public class RoverTaskRepository : IRoverTaskRepository
{
private readonly MissionControlContext _context;

public RoverTaskRepository(MissionControlContext context)
{
_context = context;
}


public async Task<RoverTask?> GetRoverTask(Guid roverTaskId)
{
return await _context.RoverTasks.FindAsync(roverTaskId);
}

public async Task<IEnumerable<RoverTask>> GetReadyRoverTasks(Guid roverId)
{
var tasks = await _context.RoverTasks.Where(_ => _.RoverId == roverId && _.Status == RoverTaskStatus.Ready)
.OrderBy(_ => _.Position).ToListAsync();
return tasks;
}

public async Task<RoverTask?> CreateRoverTask(Guid roverId, RoverTaskCommands command)
{
var roverTask = new RoverTask
{
Id = Guid.NewGuid(),
Command = command,
RoverId = roverId,
Status = RoverTaskStatus.Ready
};
return await CreateRoverTask(roverTask);
}

public async Task<RoverTask?> CreateRoverTask(RoverTask roverTask)
{
var rover = await _context.Rovers.FindAsync(roverTask.RoverId);

if (rover == null) return null;

var positoin = await _context.RoverTasks.Where(_ => _.RoverId == rover.Id).MaxAsync(_ => _.Position);
roverTask.StatusUpdate = DateTime.Now;
roverTask.Position = ++positoin;
_context.RoverTasks.Add(roverTask);
await _context.SaveChangesAsync();
return roverTask;
}

public async Task<RoverTask?> UpdateRoverTask(RoverTask inputRoverTask)
{
var roverTask = await _context.RoverTasks.FindAsync(inputRoverTask.Id);

if (roverTask is null) return null;

roverTask.StatusUpdate = DateTime.Now;
roverTask.Status = inputRoverTask.Status;
roverTask.StatusInfo = inputRoverTask?.StatusInfo;

await _context.SaveChangesAsync();

return roverTask;
}
}
Loading