From af5993e3b26fb250ff74119aae4c0c6773ce908c Mon Sep 17 00:00:00 2001 From: asong91 Date: Sat, 12 Dec 2020 17:06:22 -0600 Subject: [PATCH 1/6] Create dotnet-core.yml --- .github/workflows/dotnet-core.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .github/workflows/dotnet-core.yml diff --git a/.github/workflows/dotnet-core.yml b/.github/workflows/dotnet-core.yml new file mode 100644 index 000000000..8a48bfd51 --- /dev/null +++ b/.github/workflows/dotnet-core.yml @@ -0,0 +1,25 @@ +name: .NET Core + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Setup .NET Core + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 5.0.100 + - name: Install dependencies + run: dotnet restore + - name: Build + run: dotnet build --configuration Release --no-restore + - name: Test + run: dotnet test --no-restore --verbosity normal From 09b5b22328100a967a45771273ff60b1a976530e Mon Sep 17 00:00:00 2001 From: Andrew Song Date: Wed, 23 Dec 2020 01:27:41 -0600 Subject: [PATCH 2/6] adding sonar lint and enabling microsoft code analyzer --- API/API.csproj | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/API/API.csproj b/API/API.csproj index 52664396f..d9bf4fb47 100644 --- a/API/API.csproj +++ b/API/API.csproj @@ -1,7 +1,9 @@ + Default net5.0 + true @@ -15,6 +17,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + From a40bc9e9f7423d58f927e9bbfec569c999a37ede Mon Sep 17 00:00:00 2001 From: Joseph Milazzo Date: Thu, 24 Dec 2020 10:13:22 -0600 Subject: [PATCH 3/6] Added Policy for getUsers and moved some APIs. --- API/Controllers/AdminController.cs | 19 +- API/Controllers/UsersController.cs | 28 +- .../20201224155621_MiscCleanup.Designer.cs | 377 ++++++++++++++++++ .../Migrations/20201224155621_MiscCleanup.cs | 42 ++ 4 files changed, 444 insertions(+), 22 deletions(-) create mode 100644 API/Data/Migrations/20201224155621_MiscCleanup.Designer.cs create mode 100644 API/Data/Migrations/20201224155621_MiscCleanup.cs diff --git a/API/Controllers/AdminController.cs b/API/Controllers/AdminController.cs index 6d427de89..173961a48 100644 --- a/API/Controllers/AdminController.cs +++ b/API/Controllers/AdminController.cs @@ -1,4 +1,6 @@ -using System.Threading.Tasks; +using System.Collections.Generic; +using System.Threading.Tasks; +using API.DTOs; using API.Entities; using API.Interfaces; using Microsoft.AspNetCore.Authorization; @@ -25,20 +27,7 @@ namespace API.Controllers return users.Count > 0; } - [Authorize(Policy = "RequireAdminRole")] - [HttpDelete("delete-user")] - public async Task DeleteUser(string username) - { - var user = await _userRepository.GetUserByUsernameAsync(username); - _userRepository.Delete(user); - - if (await _userRepository.SaveAllAsync()) - { - return Ok(); - } - - return BadRequest("Could not delete the user."); - } + } diff --git a/API/Controllers/UsersController.cs b/API/Controllers/UsersController.cs index 67039cce2..3b546b21f 100644 --- a/API/Controllers/UsersController.cs +++ b/API/Controllers/UsersController.cs @@ -24,12 +24,6 @@ namespace API.Controllers _userRepository = userRepository; _libraryRepository = libraryRepository; } - - [HttpGet] - public async Task>> GetUsers() - { - return Ok(await _userRepository.GetMembersAsync()); - } [HttpPost("add-library")] public async Task AddLibrary(CreateLibraryDto createLibraryDto) @@ -72,7 +66,27 @@ namespace API.Controllers return BadRequest("Not implemented"); } - + [Authorize(Policy = "RequireAdminRole")] + [HttpDelete("delete-user")] + public async Task DeleteUser(string username) + { + var user = await _userRepository.GetUserByUsernameAsync(username); + _userRepository.Delete(user); + + if (await _userRepository.SaveAllAsync()) + { + return Ok(); + } + + return BadRequest("Could not delete the user."); + } + + [Authorize(Policy = "RequireAdminRole")] + [HttpGet] + public async Task>> GetUsers() + { + return Ok(await _userRepository.GetMembersAsync()); + } } } \ No newline at end of file diff --git a/API/Data/Migrations/20201224155621_MiscCleanup.Designer.cs b/API/Data/Migrations/20201224155621_MiscCleanup.Designer.cs new file mode 100644 index 000000000..8ae8c597a --- /dev/null +++ b/API/Data/Migrations/20201224155621_MiscCleanup.Designer.cs @@ -0,0 +1,377 @@ +// +using System; +using API.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace API.Data.Migrations +{ + [DbContext(typeof(DataContext))] + [Migration("20201224155621_MiscCleanup")] + partial class MiscCleanup + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "5.0.1"); + + modelBuilder.Entity("API.Entities.AppRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("API.Entities.AppUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("IsAdmin") + .HasColumnType("INTEGER"); + + b.Property("LastActive") + .HasColumnType("TEXT"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("API.Entities.AppUserRole", b => + { + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.Property("RoleId") + .HasColumnType("INTEGER"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("API.Entities.FolderPath", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LibraryId") + .HasColumnType("INTEGER"); + + b.Property("Path") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LibraryId"); + + b.ToTable("FolderPath"); + }); + + modelBuilder.Entity("API.Entities.Library", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CoverImage") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Library"); + }); + + modelBuilder.Entity("AppUserLibrary", b => + { + b.Property("AppUsersId") + .HasColumnType("INTEGER"); + + b.Property("LibrariesId") + .HasColumnType("INTEGER"); + + b.HasKey("AppUsersId", "LibrariesId"); + + b.HasIndex("LibrariesId"); + + b.ToTable("AppUserLibrary"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("API.Entities.AppUserRole", b => + { + b.HasOne("API.Entities.AppRole", "Role") + .WithMany("UserRoles") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.AppUser", "User") + .WithMany("UserRoles") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("API.Entities.FolderPath", b => + { + b.HasOne("API.Entities.Library", "Library") + .WithMany("Folders") + .HasForeignKey("LibraryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Library"); + }); + + modelBuilder.Entity("AppUserLibrary", b => + { + b.HasOne("API.Entities.AppUser", null) + .WithMany() + .HasForeignKey("AppUsersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Library", null) + .WithMany() + .HasForeignKey("LibrariesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("API.Entities.AppRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("API.Entities.AppUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("API.Entities.AppUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("API.Entities.AppUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("API.Entities.AppRole", b => + { + b.Navigation("UserRoles"); + }); + + modelBuilder.Entity("API.Entities.AppUser", b => + { + b.Navigation("UserRoles"); + }); + + modelBuilder.Entity("API.Entities.Library", b => + { + b.Navigation("Folders"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/API/Data/Migrations/20201224155621_MiscCleanup.cs b/API/Data/Migrations/20201224155621_MiscCleanup.cs new file mode 100644 index 000000000..20e0a4dc9 --- /dev/null +++ b/API/Data/Migrations/20201224155621_MiscCleanup.cs @@ -0,0 +1,42 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace API.Data.Migrations +{ + public partial class MiscCleanup : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "PasswordSalt", + table: "AspNetUsers"); + + migrationBuilder.AlterColumn( + name: "PasswordHash", + table: "AspNetUsers", + type: "TEXT", + nullable: true, + oldClrType: typeof(byte[]), + oldType: "BLOB", + oldNullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "PasswordHash", + table: "AspNetUsers", + type: "BLOB", + nullable: true, + oldClrType: typeof(string), + oldType: "TEXT", + oldNullable: true); + + migrationBuilder.AddColumn( + name: "PasswordSalt", + table: "AspNetUsers", + type: "BLOB", + nullable: true); + } + } +} From 793c13e1e63c6e98da83dcdc9088ff84f287e3fe Mon Sep 17 00:00:00 2001 From: Joseph Milazzo Date: Thu, 24 Dec 2020 10:21:59 -0600 Subject: [PATCH 4/6] Refactored all Policy strings into their own constant file. --- API/Constants/PolicyConstants.cs | 8 ++++++++ API/Controllers/AccountController.cs | 3 ++- API/Data/Seed.cs | 5 +++-- API/Extensions/IdentityServiceExtensions.cs | 3 ++- 4 files changed, 15 insertions(+), 4 deletions(-) create mode 100644 API/Constants/PolicyConstants.cs diff --git a/API/Constants/PolicyConstants.cs b/API/Constants/PolicyConstants.cs new file mode 100644 index 000000000..d64a2bab6 --- /dev/null +++ b/API/Constants/PolicyConstants.cs @@ -0,0 +1,8 @@ +namespace API.Constants +{ + public static class PolicyConstants + { + public static readonly string AdminRole = "Admin"; + public static readonly string PlebRole = "Pleb"; + } +} \ No newline at end of file diff --git a/API/Controllers/AccountController.cs b/API/Controllers/AccountController.cs index 6c8c74a5b..3610925e9 100644 --- a/API/Controllers/AccountController.cs +++ b/API/Controllers/AccountController.cs @@ -1,5 +1,6 @@ using System; using System.Threading.Tasks; +using API.Constants; using API.DTOs; using API.Entities; using API.Interfaces; @@ -50,7 +51,7 @@ namespace API.Controllers // TODO: Need a way to store Roles in enum and configure from there - var role = registerDto.IsAdmin ? "Admin" : "Pleb"; + var role = registerDto.IsAdmin ? PolicyConstants.AdminRole : PolicyConstants.PlebRole; var roleResult = await _userManager.AddToRoleAsync(user, role); if (!roleResult.Succeeded) return BadRequest(result.Errors); diff --git a/API/Data/Seed.cs b/API/Data/Seed.cs index 4bc4ebbc6..c03407e95 100644 --- a/API/Data/Seed.cs +++ b/API/Data/Seed.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Threading.Tasks; +using API.Constants; using API.Entities; using Microsoft.AspNetCore.Identity; @@ -11,8 +12,8 @@ namespace API.Data { var roles = new List { - new AppRole {Name = "Admin"}, - new AppRole {Name = "Pleb"} + new AppRole {Name = PolicyConstants.AdminRole}, + new AppRole {Name = PolicyConstants.PlebRole} }; foreach (var role in roles) diff --git a/API/Extensions/IdentityServiceExtensions.cs b/API/Extensions/IdentityServiceExtensions.cs index 9138ffbb8..2d2a235f5 100644 --- a/API/Extensions/IdentityServiceExtensions.cs +++ b/API/Extensions/IdentityServiceExtensions.cs @@ -1,4 +1,5 @@ using System.Text; +using API.Constants; using API.Data; using API.Entities; using Microsoft.AspNetCore.Authentication.JwtBearer; @@ -37,7 +38,7 @@ namespace API.Extensions }); services.AddAuthorization(opt => { - opt.AddPolicy("RequireAdminRole", policy => policy.RequireRole("Admin")); + opt.AddPolicy("RequireAdminRole", policy => policy.RequireRole(PolicyConstants.AdminRole)); }); return services; From f8c50b40bbd43ada730b1c1a063856b875a099b1 Mon Sep 17 00:00:00 2001 From: Joseph Milazzo Date: Thu, 24 Dec 2020 10:37:49 -0600 Subject: [PATCH 5/6] Ensure only admins can call getDirectories --- API/Controllers/LibraryController.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/API/Controllers/LibraryController.cs b/API/Controllers/LibraryController.cs index 4800a8cbb..42fa27659 100644 --- a/API/Controllers/LibraryController.cs +++ b/API/Controllers/LibraryController.cs @@ -42,12 +42,10 @@ namespace API.Controllers /// /// /// + [Authorize(Policy = "RequireAdminRole")] [HttpGet("list")] public ActionResult> GetDirectories(string path) { - // TODO: We need some sort of validation other than our auth layer - _logger.Log(LogLevel.Debug, "Listing Directories for " + path); - if (string.IsNullOrEmpty(path)) { return Ok(Directory.GetLogicalDrives()); @@ -57,7 +55,7 @@ namespace API.Controllers return Ok(_directoryService.ListDirectory(path)); } - + [HttpGet] public async Task>> GetLibraries() { From 2d066ea36cff4ac91c161756df3c9172fd207a76 Mon Sep 17 00:00:00 2001 From: Andrew Song Date: Fri, 25 Dec 2020 15:08:40 -0600 Subject: [PATCH 6/6] Fixing sonar code quality recommendations --- API/Controllers/LibraryController.cs | 15 +-------------- API/Controllers/UsersController.cs | 1 - API/Data/Seed.cs | 2 +- API/Program.cs | 6 +++++- API/Startup.cs | 1 - 5 files changed, 7 insertions(+), 18 deletions(-) diff --git a/API/Controllers/LibraryController.cs b/API/Controllers/LibraryController.cs index 42fa27659..a20bc1cda 100644 --- a/API/Controllers/LibraryController.cs +++ b/API/Controllers/LibraryController.cs @@ -61,20 +61,7 @@ namespace API.Controllers { return Ok(await _libraryRepository.GetLibrariesAsync()); } - - - // Do I need this method? - // [HttpGet("library/{username}")] - // public async Task>> GetLibrariesForUser(string username) - // { - // _logger.LogDebug("Method hit"); - // var user = await _userRepository.GetUserByUsernameAsync(User.GetUsername()); - // - // if (user == null) return BadRequest("Could not validate user"); - // - // return Ok(await _libraryRepository.GetLibrariesForUserAsync(user)); - // } - + [Authorize(Policy = "RequireAdminRole")] [HttpPut("update-for")] public async Task> UpdateLibrary(UpdateLibraryDto updateLibraryDto) diff --git a/API/Controllers/UsersController.cs b/API/Controllers/UsersController.cs index 3b546b21f..28d1a3916 100644 --- a/API/Controllers/UsersController.cs +++ b/API/Controllers/UsersController.cs @@ -30,7 +30,6 @@ namespace API.Controllers { // NOTE: I think we should move this into library controller because it gets added to all admins - //_logger.Log(LogLevel.Debug, "Creating a new " + createLibraryDto.Type + " library"); var user = await _userRepository.GetUserByUsernameAsync(User.GetUsername()); if (user == null) return BadRequest("Could not validate user"); diff --git a/API/Data/Seed.cs b/API/Data/Seed.cs index c03407e95..cd90243c4 100644 --- a/API/Data/Seed.cs +++ b/API/Data/Seed.cs @@ -6,7 +6,7 @@ using Microsoft.AspNetCore.Identity; namespace API.Data { - public class Seed + public static class Seed { public static async Task SeedRoles(RoleManager roleManager) { diff --git a/API/Program.cs b/API/Program.cs index 7ed8e7175..bafc68d4c 100644 --- a/API/Program.cs +++ b/API/Program.cs @@ -13,6 +13,10 @@ namespace API { public class Program { + protected Program() + { + } + public static async Task Main(string[] args) { var host = CreateHostBuilder(args).Build(); @@ -37,7 +41,7 @@ namespace API await host.RunAsync(); } - public static IHostBuilder CreateHostBuilder(string[] args) => + private static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { diff --git a/API/Startup.cs b/API/Startup.cs index b691a71a4..c98d4064d 100644 --- a/API/Startup.cs +++ b/API/Startup.cs @@ -39,7 +39,6 @@ namespace API if (env.IsDevelopment()) { - //app.UseDeveloperExceptionPage(); app.UseSwagger(); app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "API v1")); }