From 434490160434b7148160ccb616538d748d8576bd Mon Sep 17 00:00:00 2001 From: David Walker Date: Tue, 18 Feb 2020 11:42:15 +0000 Subject: [PATCH] Added user management --- .../DroneFlightLog.Data.InMemory.csproj | 2 +- .../DroneFlightLogDbContext.cs | 2 + .../appsettings.json | 2 +- .../DroneFlightLog.Data.Sqlite.csproj | 4 +- .../DroneFlightLogDbContext.cs | 2 + .../20200214074526_security.Designer.cs | 317 ++++++++++++++++++ .../Migrations/20200214074526_security.cs | 31 ++ .../DroneFlightLogDbContextModelSnapshot.cs | 22 +- .../DroneFlightLog.Data.csproj | 5 +- src/DroneFlightLog.Data/Entities/Drone.cs | 3 +- .../Entities/FlightLogUser.cs | 14 + .../Exceptions/UserExistsException.cs | 21 ++ .../Exceptions/UserNotFoundException.cs | 21 ++ .../Factory/DroneFlightLogFactory.cs | 3 + .../Interfaces/IDroneFlightLogDbContext.cs | 2 + .../Interfaces/IDroneFlightLogFactory.cs | 1 + .../Interfaces/IUserManager.cs | 24 ++ src/DroneFlightLog.Data/Logic/UserManager.cs | 240 +++++++++++++ 18 files changed, 707 insertions(+), 9 deletions(-) create mode 100644 src/DroneFlightLog.Data.Sqlite/Migrations/20200214074526_security.Designer.cs create mode 100644 src/DroneFlightLog.Data.Sqlite/Migrations/20200214074526_security.cs create mode 100644 src/DroneFlightLog.Data/Entities/FlightLogUser.cs create mode 100644 src/DroneFlightLog.Data/Exceptions/UserExistsException.cs create mode 100644 src/DroneFlightLog.Data/Exceptions/UserNotFoundException.cs create mode 100644 src/DroneFlightLog.Data/Interfaces/IUserManager.cs create mode 100644 src/DroneFlightLog.Data/Logic/UserManager.cs diff --git a/src/DroneFlightLog.Data.InMemory/DroneFlightLog.Data.InMemory.csproj b/src/DroneFlightLog.Data.InMemory/DroneFlightLog.Data.InMemory.csproj index 97fa414..5e49405 100644 --- a/src/DroneFlightLog.Data.InMemory/DroneFlightLog.Data.InMemory.csproj +++ b/src/DroneFlightLog.Data.InMemory/DroneFlightLog.Data.InMemory.csproj @@ -3,7 +3,7 @@ netstandard2.1 DroneFlightLog.Data.InMemory - 1.0.0.0 + 1.0.0.1 true Dave Walker Copyright (c) 2020 Dave Walker diff --git a/src/DroneFlightLog.Data.InMemory/DroneFlightLogDbContext.cs b/src/DroneFlightLog.Data.InMemory/DroneFlightLogDbContext.cs index 2cf4873..bd5ef6f 100644 --- a/src/DroneFlightLog.Data.InMemory/DroneFlightLogDbContext.cs +++ b/src/DroneFlightLog.Data.InMemory/DroneFlightLogDbContext.cs @@ -20,6 +20,8 @@ public class DroneFlightLogDbContext : DbContext, IDroneFlightLogDbContext public DbSet FlightPropertyValues { get; set; } public DbSet Locations { get; set; } + public DbSet FlightLogUsers { get; set; } + public DroneFlightLogDbContext(DbContextOptions options) : base(options) { } diff --git a/src/DroneFlightLog.Data.Migrations/appsettings.json b/src/DroneFlightLog.Data.Migrations/appsettings.json index 2719842..704ac4d 100644 --- a/src/DroneFlightLog.Data.Migrations/appsettings.json +++ b/src/DroneFlightLog.Data.Migrations/appsettings.json @@ -1,5 +1,5 @@ { "ConnectionStrings": { - "DroneLogDb": "Data Source=droneflightlog.db" + "DroneLogDb": "Data Source=/Users/dave/droneflightlog.db" } } \ No newline at end of file diff --git a/src/DroneFlightLog.Data.Sqlite/DroneFlightLog.Data.Sqlite.csproj b/src/DroneFlightLog.Data.Sqlite/DroneFlightLog.Data.Sqlite.csproj index b533f52..a80e3d9 100644 --- a/src/DroneFlightLog.Data.Sqlite/DroneFlightLog.Data.Sqlite.csproj +++ b/src/DroneFlightLog.Data.Sqlite/DroneFlightLog.Data.Sqlite.csproj @@ -3,12 +3,12 @@ netstandard2.1 DroneFlightLog.Data.Sqlite - 1.0.0.1 + 1.0.0.3 true Dave Walker Copyright (c) 2020 Dave Walker Dave Walker - Added EF Core Migrations + Added flight log user for security support Drone Flight Logging SQLite Database Layer Drone UAV Log Flight Logbook Drone Flight Logging SQLite Database Layer diff --git a/src/DroneFlightLog.Data.Sqlite/DroneFlightLogDbContext.cs b/src/DroneFlightLog.Data.Sqlite/DroneFlightLogDbContext.cs index 3e8f9f1..65ab6ff 100644 --- a/src/DroneFlightLog.Data.Sqlite/DroneFlightLogDbContext.cs +++ b/src/DroneFlightLog.Data.Sqlite/DroneFlightLogDbContext.cs @@ -20,6 +20,8 @@ public class DroneFlightLogDbContext : DbContext, IDroneFlightLogDbContext public DbSet FlightPropertyValues { get; set; } public DbSet Locations { get; set; } + public DbSet FlightLogUsers { get; set; } + public DroneFlightLogDbContext(DbContextOptions options) : base(options) { } diff --git a/src/DroneFlightLog.Data.Sqlite/Migrations/20200214074526_security.Designer.cs b/src/DroneFlightLog.Data.Sqlite/Migrations/20200214074526_security.Designer.cs new file mode 100644 index 0000000..e846c78 --- /dev/null +++ b/src/DroneFlightLog.Data.Sqlite/Migrations/20200214074526_security.Designer.cs @@ -0,0 +1,317 @@ +// +using System; +using DroneFlightLog.Data.Sqlite; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace DroneFlightLog.Data.Sqlite.Migrations +{ + [DbContext(typeof(DroneFlightLogDbContext))] + [Migration("20200214074526_security")] + partial class security + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.1"); + + modelBuilder.Entity("DroneFlightLog.Data.Entities.Address", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Country") + .HasColumnType("TEXT"); + + b.Property("County") + .HasColumnType("TEXT"); + + b.Property("Number") + .HasColumnType("TEXT"); + + b.Property("Postcode") + .HasColumnType("TEXT"); + + b.Property("Street") + .HasColumnType("TEXT"); + + b.Property("Town") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Addresses"); + }); + + modelBuilder.Entity("DroneFlightLog.Data.Entities.Drone", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ModelId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("SerialNumber") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ModelId"); + + b.ToTable("Drones"); + }); + + modelBuilder.Entity("DroneFlightLog.Data.Entities.Flight", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DroneId") + .HasColumnType("INTEGER"); + + b.Property("End") + .HasColumnType("TEXT"); + + b.Property("LocationId") + .HasColumnType("INTEGER"); + + b.Property("OperatorId") + .HasColumnType("INTEGER"); + + b.Property("Start") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DroneId"); + + b.HasIndex("LocationId"); + + b.HasIndex("OperatorId"); + + b.ToTable("Flights"); + }); + + modelBuilder.Entity("DroneFlightLog.Data.Entities.FlightLogUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Password") + .HasColumnType("TEXT"); + + b.Property("Token") + .HasColumnType("TEXT"); + + b.Property("UserName") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("FlightLogUsers"); + }); + + modelBuilder.Entity("DroneFlightLog.Data.Entities.FlightProperty", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DataType") + .HasColumnType("INTEGER"); + + b.Property("IsSingleInstance") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("FlightProperties"); + }); + + modelBuilder.Entity("DroneFlightLog.Data.Entities.FlightPropertyValue", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateValue") + .HasColumnType("TEXT"); + + b.Property("FlightId") + .HasColumnType("INTEGER"); + + b.Property("NumberValue") + .HasColumnType("TEXT"); + + b.Property("PropertyId") + .HasColumnType("INTEGER"); + + b.Property("StringValue") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("FlightId"); + + b.HasIndex("PropertyId"); + + b.ToTable("FlightPropertyValues"); + }); + + modelBuilder.Entity("DroneFlightLog.Data.Entities.Location", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Locations"); + }); + + modelBuilder.Entity("DroneFlightLog.Data.Entities.Manufacturer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Manufacturers"); + }); + + modelBuilder.Entity("DroneFlightLog.Data.Entities.Model", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ManufacturerId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ManufacturerId"); + + b.ToTable("Models"); + }); + + modelBuilder.Entity("DroneFlightLog.Data.Entities.Operator", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AddressId") + .HasColumnType("INTEGER"); + + b.Property("DoB") + .HasColumnType("TEXT"); + + b.Property("FirstNames") + .HasColumnType("TEXT"); + + b.Property("FlyerNumber") + .HasColumnType("TEXT"); + + b.Property("OperatorNumber") + .HasColumnType("TEXT"); + + b.Property("Surname") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AddressId"); + + b.ToTable("Operators"); + }); + + modelBuilder.Entity("DroneFlightLog.Data.Entities.Drone", b => + { + b.HasOne("DroneFlightLog.Data.Entities.Model", "Model") + .WithMany() + .HasForeignKey("ModelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("DroneFlightLog.Data.Entities.Flight", b => + { + b.HasOne("DroneFlightLog.Data.Entities.Drone", "Drone") + .WithMany() + .HasForeignKey("DroneId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("DroneFlightLog.Data.Entities.Location", "Location") + .WithMany() + .HasForeignKey("LocationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("DroneFlightLog.Data.Entities.Operator", "Operator") + .WithMany() + .HasForeignKey("OperatorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("DroneFlightLog.Data.Entities.FlightPropertyValue", b => + { + b.HasOne("DroneFlightLog.Data.Entities.Flight", "Flight") + .WithMany("Properties") + .HasForeignKey("FlightId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("DroneFlightLog.Data.Entities.FlightProperty", "Property") + .WithMany() + .HasForeignKey("PropertyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("DroneFlightLog.Data.Entities.Model", b => + { + b.HasOne("DroneFlightLog.Data.Entities.Manufacturer", "Manufacturer") + .WithMany() + .HasForeignKey("ManufacturerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("DroneFlightLog.Data.Entities.Operator", b => + { + b.HasOne("DroneFlightLog.Data.Entities.Address", "Address") + .WithMany() + .HasForeignKey("AddressId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/DroneFlightLog.Data.Sqlite/Migrations/20200214074526_security.cs b/src/DroneFlightLog.Data.Sqlite/Migrations/20200214074526_security.cs new file mode 100644 index 0000000..ac4bf89 --- /dev/null +++ b/src/DroneFlightLog.Data.Sqlite/Migrations/20200214074526_security.cs @@ -0,0 +1,31 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace DroneFlightLog.Data.Sqlite.Migrations +{ + public partial class security : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "FlightLogUsers", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + UserName = table.Column(nullable: true), + Password = table.Column(nullable: true), + Token = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_FlightLogUsers", x => x.Id); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "FlightLogUsers"); + } + } +} diff --git a/src/DroneFlightLog.Data.Sqlite/Migrations/DroneFlightLogDbContextModelSnapshot.cs b/src/DroneFlightLog.Data.Sqlite/Migrations/DroneFlightLogDbContextModelSnapshot.cs index ab2841d..d0193f4 100644 --- a/src/DroneFlightLog.Data.Sqlite/Migrations/DroneFlightLogDbContextModelSnapshot.cs +++ b/src/DroneFlightLog.Data.Sqlite/Migrations/DroneFlightLogDbContextModelSnapshot.cs @@ -99,6 +99,26 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Flights"); }); + modelBuilder.Entity("DroneFlightLog.Data.Entities.FlightLogUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Password") + .HasColumnType("TEXT"); + + b.Property("Token") + .HasColumnType("TEXT"); + + b.Property("UserName") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("FlightLogUsers"); + }); + modelBuilder.Entity("DroneFlightLog.Data.Entities.FlightProperty", b => { b.Property("Id") @@ -239,7 +259,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("DroneFlightLog.Data.Entities.Flight", b => { b.HasOne("DroneFlightLog.Data.Entities.Drone", "Drone") - .WithMany("Flights") + .WithMany() .HasForeignKey("DroneId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); diff --git a/src/DroneFlightLog.Data/DroneFlightLog.Data.csproj b/src/DroneFlightLog.Data/DroneFlightLog.Data.csproj index 2f08ee2..14117bc 100644 --- a/src/DroneFlightLog.Data/DroneFlightLog.Data.csproj +++ b/src/DroneFlightLog.Data/DroneFlightLog.Data.csproj @@ -3,12 +3,12 @@ netstandard2.1 DroneFlightLog.Data - 1.0.0.1 + 1.0.0.2 true Dave Walker Copyright (c) 2020 Dave Walker Dave Walker - Added async methods for bulk data retrieval + Added flight log user for security support Drone Flight Logging Database Layer Drone UAV Log Flight Logbook Drone Flight Logging Database Layer @@ -18,6 +18,7 @@ + diff --git a/src/DroneFlightLog.Data/Entities/Drone.cs b/src/DroneFlightLog.Data/Entities/Drone.cs index e1efb1c..6b4d598 100644 --- a/src/DroneFlightLog.Data/Entities/Drone.cs +++ b/src/DroneFlightLog.Data/Entities/Drone.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; namespace DroneFlightLog.Data.Entities diff --git a/src/DroneFlightLog.Data/Entities/FlightLogUser.cs b/src/DroneFlightLog.Data/Entities/FlightLogUser.cs new file mode 100644 index 0000000..71eb247 --- /dev/null +++ b/src/DroneFlightLog.Data/Entities/FlightLogUser.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; + +namespace DroneFlightLog.Data.Entities +{ + [ExcludeFromCodeCoverage] + public class FlightLogUser + { + [Key] + public int Id { get; set; } + public string UserName { get; set; } + public string Password { get; set; } + } +} diff --git a/src/DroneFlightLog.Data/Exceptions/UserExistsException.cs b/src/DroneFlightLog.Data/Exceptions/UserExistsException.cs new file mode 100644 index 0000000..51b0055 --- /dev/null +++ b/src/DroneFlightLog.Data/Exceptions/UserExistsException.cs @@ -0,0 +1,21 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace DroneFlightLog.Data.Exceptions +{ + [ExcludeFromCodeCoverage] + public class UserExistsException : Exception + { + public UserExistsException() + { + } + + public UserExistsException(string message) : base(message) + { + } + + public UserExistsException(string message, Exception inner) : base(message, inner) + { + } + } +} diff --git a/src/DroneFlightLog.Data/Exceptions/UserNotFoundException.cs b/src/DroneFlightLog.Data/Exceptions/UserNotFoundException.cs new file mode 100644 index 0000000..ee428e6 --- /dev/null +++ b/src/DroneFlightLog.Data/Exceptions/UserNotFoundException.cs @@ -0,0 +1,21 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace DroneFlightLog.Data.Exceptions +{ + [ExcludeFromCodeCoverage] + public class UserNotFoundException : Exception + { + public UserNotFoundException() + { + } + + public UserNotFoundException(string message) : base(message) + { + } + + public UserNotFoundException(string message, Exception inner) : base(message, inner) + { + } + } +} diff --git a/src/DroneFlightLog.Data/Factory/DroneFlightLogFactory.cs b/src/DroneFlightLog.Data/Factory/DroneFlightLogFactory.cs index a6eb060..8a66102 100644 --- a/src/DroneFlightLog.Data/Factory/DroneFlightLogFactory.cs +++ b/src/DroneFlightLog.Data/Factory/DroneFlightLogFactory.cs @@ -15,6 +15,7 @@ public class DroneFlightLogFactory : IDroneFlightLogFactory where T : DbCo private Lazy _drones; private Lazy _locations; private Lazy _flights; + private Lazy _users; public DroneFlightLogFactory(T context) { @@ -27,6 +28,7 @@ public DroneFlightLogFactory(T context) _drones = new Lazy(() => new DroneManager(this)); _locations = new Lazy(() => new LocationManager(context)); _flights = new Lazy(() => new FlightManager(this)); + _users = new Lazy(() => new UserManager(context)); } public T Context { get; private set; } @@ -38,5 +40,6 @@ public DroneFlightLogFactory(T context) public IDroneManager Drones { get { return _drones.Value; } } public ILocationManager Locations { get { return _locations.Value; } } public IFlightManager Flights { get { return _flights.Value; } } + public IUserManager Users { get { return _users.Value; } } } } diff --git a/src/DroneFlightLog.Data/Interfaces/IDroneFlightLogDbContext.cs b/src/DroneFlightLog.Data/Interfaces/IDroneFlightLogDbContext.cs index 0a8eaa1..f8eb52f 100644 --- a/src/DroneFlightLog.Data/Interfaces/IDroneFlightLogDbContext.cs +++ b/src/DroneFlightLog.Data/Interfaces/IDroneFlightLogDbContext.cs @@ -16,5 +16,7 @@ public interface IDroneFlightLogDbContext DbSet FlightProperties { get; set; } DbSet FlightPropertyValues { get; set; } DbSet Locations { get; set; } + + DbSet FlightLogUsers { get; set; } } } diff --git a/src/DroneFlightLog.Data/Interfaces/IDroneFlightLogFactory.cs b/src/DroneFlightLog.Data/Interfaces/IDroneFlightLogFactory.cs index 99cb7e9..020facb 100644 --- a/src/DroneFlightLog.Data/Interfaces/IDroneFlightLogFactory.cs +++ b/src/DroneFlightLog.Data/Interfaces/IDroneFlightLogFactory.cs @@ -13,5 +13,6 @@ public interface IDroneFlightLogFactory where T : DbContext, IDroneFlightLogD IDroneManager Drones { get; } IOperatorManager Operators { get; } IFlightManager Flights { get; } + IUserManager Users { get; } } } \ No newline at end of file diff --git a/src/DroneFlightLog.Data/Interfaces/IUserManager.cs b/src/DroneFlightLog.Data/Interfaces/IUserManager.cs new file mode 100644 index 0000000..182d6fb --- /dev/null +++ b/src/DroneFlightLog.Data/Interfaces/IUserManager.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using DroneFlightLog.Data.Entities; + +namespace DroneFlightLog.Data.Interfaces +{ + public interface IUserManager + { + FlightLogUser AddUser(string userName, string password); + Task AddUserAsync(string userName, string password); + bool Authenticate(string userName, string password); + Task AuthenticateAsync(string userName, string password); + void DeleteUser(string userName); + Task DeleteUserAsync(string userName); + FlightLogUser GetUser(int userId); + FlightLogUser GetUser(string userName); + Task GetUserAsync(int userId); + Task GetUserAsync(string userName); + IEnumerable GetUsers(); + IAsyncEnumerable GetUsersAsync(); + void SetPassword(string userName, string password); + Task SetPasswordAsync(string userName, string password); + } +} \ No newline at end of file diff --git a/src/DroneFlightLog.Data/Logic/UserManager.cs b/src/DroneFlightLog.Data/Logic/UserManager.cs new file mode 100644 index 0000000..1ab1d82 --- /dev/null +++ b/src/DroneFlightLog.Data/Logic/UserManager.cs @@ -0,0 +1,240 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading.Tasks; +using DroneFlightLog.Data.Entities; +using DroneFlightLog.Data.Exceptions; +using DroneFlightLog.Data.Interfaces; +using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.Identity; + +namespace DroneFlightLog.Data.Logic +{ + public class UserManager : IUserManager where T : DbContext, IDroneFlightLogDbContext + { + private Lazy> _hasher; + private readonly T _context; + + public UserManager(T context) + { + _hasher = new Lazy>(() => new PasswordHasher()); + _context = context; + } + + /// + /// Return the user with the specified Id + /// + /// + /// + public FlightLogUser GetUser(int userId) + { + FlightLogUser user = _context.FlightLogUsers.FirstOrDefault(u => u.Id == userId); + ThrowIfUserNotFound(user, userId); + return user; + } + + /// + /// Return the user with the specified Id + /// + /// + /// + public async Task GetUserAsync(int userId) + { + FlightLogUser user = await _context.FlightLogUsers.FirstOrDefaultAsync(u => u.Id == userId); + ThrowIfUserNotFound(user, userId); + return user; + } + + /// + /// Return the user with the specified Id + /// + /// + /// + public FlightLogUser GetUser(string userName) + { + FlightLogUser user = _context.FlightLogUsers.FirstOrDefault(u => u.UserName == userName); + ThrowIfUserNotFound(user, userName); + return user; + } + + /// + /// Return the user with the specified Id + /// + /// + /// + public async Task GetUserAsync(string userName) + { + FlightLogUser user = await _context.FlightLogUsers.FirstOrDefaultAsync(u => u.UserName == userName); + ThrowIfUserNotFound(user, userName); + return user; + } + + /// + /// Get all the current user details + /// + public IEnumerable GetUsers() => + _context.FlightLogUsers; + + /// + /// Get all the current user details + /// + public IAsyncEnumerable GetUsersAsync() => + _context.FlightLogUsers.AsAsyncEnumerable(); + + /// + /// Add a new user, given their details + /// + /// + /// + /// + public FlightLogUser AddUser(string userName, string password) + { + FlightLogUser user = _context.FlightLogUsers.FirstOrDefault(u => u.UserName == userName); + ThrowIfUserFound(user, userName); + + user = new FlightLogUser + { + UserName = userName, + Password = _hasher.Value.HashPassword(userName, password) + }; + + _context.FlightLogUsers.Add(user); + _context.SaveChanges(); + return user; + } + + /// + /// Add a new user, given their details + /// + /// + /// + /// + public async Task AddUserAsync(string userName, string password) + { + FlightLogUser user = await _context.FlightLogUsers.FirstOrDefaultAsync(u => u.UserName == userName); + ThrowIfUserFound(user, userName); + + user = new FlightLogUser + { + UserName = userName, + Password = _hasher.Value.HashPassword(userName, password) + }; + + await _context.FlightLogUsers.AddAsync(user); + await _context.SaveChangesAsync(); + return user; + } + + /// + /// Authenticate the specified user + /// + /// + /// + /// + public bool Authenticate(string userName, string password) + { + FlightLogUser user = GetUser(userName); + PasswordVerificationResult result = _hasher.Value.VerifyHashedPassword(userName, user.Password, password); + if (result == PasswordVerificationResult.SuccessRehashNeeded) + { + user.Password = _hasher.Value.HashPassword(userName, password); + _context.SaveChanges(); + } + return result != PasswordVerificationResult.Failed; + } + + /// + /// Authenticate the specified user + /// + /// + /// + /// + public async Task AuthenticateAsync(string userName, string password) + { + FlightLogUser user = await GetUserAsync(userName); + PasswordVerificationResult result = _hasher.Value.VerifyHashedPassword(userName, user.Password, password); + if (result == PasswordVerificationResult.SuccessRehashNeeded) + { + user.Password = _hasher.Value.HashPassword(userName, password); + await _context.SaveChangesAsync(); + } + return result != PasswordVerificationResult.Failed; + } + + /// + /// Set the password for the specified user + /// + /// + /// + public void SetPassword(string userName, string password) + { + FlightLogUser user = GetUser(userName); + user.Password = _hasher.Value.HashPassword(userName, password); + _context.SaveChanges(); + } + + /// + /// Set the password for the specified user + /// + /// + /// + public async Task SetPasswordAsync(string userName, string password) + { + FlightLogUser user = await GetUserAsync(userName); + user.Password = _hasher.Value.HashPassword(userName, password); + await _context.SaveChangesAsync(); + } + + /// + /// Delete the specified user + /// + /// + public void DeleteUser(string userName) + { + FlightLogUser user = GetUser(userName); + _context.FlightLogUsers.Remove(user); + _context.SaveChanges(); + } + + /// + /// Delete the specified user + /// + /// + public async Task DeleteUserAsync(string userName) + { + FlightLogUser user = await GetUserAsync(userName); + _context.FlightLogUsers.Remove(user); + await _context.SaveChangesAsync(); + } + + /// + /// Throw an exception if a user doesn't exist + /// + /// + /// + [ExcludeFromCodeCoverage] + private void ThrowIfUserNotFound(FlightLogUser user, object userId) + { + if (user == null) + { + string message = $"User {userId} not found"; + throw new UserNotFoundException(message); + } + } + + /// + /// Throw an exception if a user already exists + /// + /// + /// + [ExcludeFromCodeCoverage] + private void ThrowIfUserFound(FlightLogUser user, object userId) + { + if (user != null) + { + throw new UserExistsException($"User {userId} already exists"); + } + } + } +}