diff --git a/Controllers/AccountController.cs b/Controllers/AccountController.cs index 2d9c9f5..16dcbf2 100644 --- a/Controllers/AccountController.cs +++ b/Controllers/AccountController.cs @@ -13,12 +13,6 @@ using System.Threading.Tasks; namespace nuget_host.Controllers { - /// - /// This sample controller implements a typical login/logout/provision workflow for local and external accounts. - /// The login service encapsulates the interactions with the user data store. This data store is in-memory only and cannot be used for production! - /// The interaction service provides a way for the UI to communicate with identityserver for validation and context retrieval - /// - [SecurityHeaders] [AllowAnonymous] public class AccountController : Controller { diff --git a/Controllers/ApiKeysController.cs b/Controllers/ApiKeysController.cs new file mode 100644 index 0000000..5622df7 --- /dev/null +++ b/Controllers/ApiKeysController.cs @@ -0,0 +1,146 @@ +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.DataProtection; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Options; +using nuget_host.Data; +using nuget_host.Entities; +using nuget_host.Models; +using nuget_host.Models.ApiKeys; + + +namespace nuget_host.Controllers +{ + [Authorize] + public class ApiKeysController : Controller + { + private readonly ApplicationDbContext dbContext; + private readonly NugetSettings nugetSettings; + private readonly UserManager _userManager; + + private readonly IDataProtector protector; + public ApiKeysController(ApplicationDbContext dbContext, + IOptions nugetSettingsOptions, + IDataProtectionProvider provider, + UserManager userManager) + { + this.dbContext = dbContext; + this.nugetSettings = nugetSettingsOptions.Value; + protector = provider.CreateProtector(nugetSettings.ProtectionTitle); + _userManager = userManager; + } + + [HttpGet] + public async Task Index() + { + string userid = User.FindFirstValue(ClaimTypes.NameIdentifier); + System.Collections.Generic.List index = GetUserKeys().ToList(); + IndexModel model = new IndexModel { ApiKey = index }; + ViewData["Title"] = "Index"; + return View("Index", model); + } + + [HttpGet] + public async Task Create() + { + var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); + var username = User.Identity.Name; + var user = await _userManager.FindByIdAsync(userId); + ViewBag.UserId = new SelectList(new List { user }); + return View(new CreateModel{ }); + } + + [HttpPost] + public async Task Create(CreateModel model) + { + string userid = User.FindFirstValue(ClaimTypes.NameIdentifier); + IQueryable userKeys = GetUserKeys(); + if (userKeys.Count() >= nugetSettings.MaxUserKeyCount) + { + ModelState.AddModelError(null, "Maximum key count reached"); + return View(); + } + ApiKey newKey = new ApiKey { UserId = userid, Name = model.Name }; + _ = dbContext.ApiKeys.Add(newKey); + _ = await dbContext.SaveChangesAsync(); + return View("Details", new DetailModel { Name = newKey.Name, + ProtectedValue = protector.Protect(newKey.Id), + ApiKey = newKey }); + } + + [HttpGet] + public async Task Delete(string id) + { + string userid = User.FindFirstValue(ClaimTypes.NameIdentifier); + ApiKey key = dbContext.ApiKeys.FirstOrDefault(k => k.Id == id && k.UserId == userid); + return View(new DeleteModel { ApiKey = key }); + + } + + [HttpPost] + public async Task Delete(DeleteModel model) + { + string userid = User.FindFirstValue(ClaimTypes.NameIdentifier); + ApiKey key = dbContext.ApiKeys.FirstOrDefault(k => k.Id == model.ApiKey.Id && k.UserId == userid); + if (key == null) + { + ModelState.AddModelError(null, "Key not found"); + return View(); + } + _ = dbContext.ApiKeys.Remove(key); + _ = await dbContext.SaveChangesAsync(); + return View("Index", new IndexModel { ApiKey = GetUserKeys().ToList() } ); + } + + public async Task Details(string id) + { + string userid = User.FindFirstValue(ClaimTypes.NameIdentifier); + ApiKey key = await dbContext.ApiKeys.FirstOrDefaultAsync(k => k.Id == id && k.UserId == userid); + if (key == null) + { + ModelState.AddModelError(null, "Key not found"); + return View(); + } + return View("Details", new DetailModel { ApiKey = key, Name = key.Name, ProtectedValue = protector.Protect(key.Id)}); + + } + + public async Task Edit(string id) + { + + EditModel edit = new EditModel(); + string userId = User.FindFirstValue(ClaimTypes.NameIdentifier); + + var user = await _userManager.FindByIdAsync(userId); + + edit.ApiKey = await GetUserKeys().SingleOrDefaultAsync(k => + k.UserId == userId && k.Id == id); + ViewBag.UserId = new SelectList(new List { user }); + + return View(edit); + } + + [HttpPost] + public async Task Edit(EditModel model) + { + string userId = User.FindFirstValue(ClaimTypes.NameIdentifier); + + var apiKey = await dbContext.ApiKeys.SingleOrDefaultAsync(k => k.UserId == userId && k.Id == model.ApiKey.Id); + apiKey.Name = model.ApiKey.Name; + apiKey.ValidityPeriodInDays = model.ApiKey.ValidityPeriodInDays; + await dbContext.SaveChangesAsync(); + return View("Details", new DetailModel { ApiKey = apiKey }); + } + + public IQueryable GetUserKeys() + { + return dbContext.ApiKeys.Include(k => k.User).Where(k => k.User.UserName == User.Identity.Name); + } + } +} \ No newline at end of file diff --git a/Data/ApplicationDbContext.cs b/Data/ApplicationDbContext.cs index 97a2bf1..a3b8a2d 100644 --- a/Data/ApplicationDbContext.cs +++ b/Data/ApplicationDbContext.cs @@ -4,6 +4,7 @@ using System.Text; using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using nuget_host.Models; +using nuget_host.Models.ApiKeys; namespace nuget_host.Data { @@ -13,5 +14,7 @@ namespace nuget_host.Data : base(options) { } + + public DbSet ApiKeys { get; set; } } } diff --git a/Entities/NugetSettings.cs b/Entities/NugetSettings.cs index 199d594..8ab29fa 100644 --- a/Entities/NugetSettings.cs +++ b/Entities/NugetSettings.cs @@ -4,6 +4,7 @@ namespace nuget_host.Entities { public string ProtectionTitle {get; set;} public string PackagesRootDir {get; set;} + public int MaxUserKeyCount {get; set;} } } \ No newline at end of file diff --git a/Migrations/20210502153508_api-keys.Designer.cs b/Migrations/20210502153508_api-keys.Designer.cs new file mode 100644 index 0000000..24ab13c --- /dev/null +++ b/Migrations/20210502153508_api-keys.Designer.cs @@ -0,0 +1,253 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using nuget_host.Data; + +namespace nugethost.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20210502153508_api-keys")] + partial class apikeys + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn) + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Name") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("RoleId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider"); + + b.Property("ProviderKey"); + + b.Property("ProviderDisplayName"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId"); + + b.Property("RoleId"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId"); + + b.Property("LoginProvider"); + + b.Property("Name"); + + b.Property("Value"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("nuget_host.Models.ApiKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("ApiKeys"); + }); + + modelBuilder.Entity("nuget_host.Models.ApplicationUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessFailedCount"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Email") + .HasMaxLength(256); + + b.Property("EmailConfirmed"); + + b.Property("FullName"); + + b.Property("LockoutEnabled"); + + b.Property("LockoutEnd"); + + b.Property("NormalizedEmail") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasMaxLength(256); + + b.Property("PasswordHash"); + + b.Property("PhoneNumber"); + + b.Property("PhoneNumberConfirmed"); + + b.Property("SecurityStamp"); + + b.Property("TwoFactorEnabled"); + + b.Property("UserName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("nuget_host.Models.ApplicationUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("nuget_host.Models.ApplicationUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("nuget_host.Models.ApplicationUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("nuget_host.Models.ApplicationUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("nuget_host.Models.ApiKey", b => + { + b.HasOne("nuget_host.Models.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Migrations/20210502153508_api-keys.cs b/Migrations/20210502153508_api-keys.cs new file mode 100644 index 0000000..11866e2 --- /dev/null +++ b/Migrations/20210502153508_api-keys.cs @@ -0,0 +1,39 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace nugethost.Migrations +{ + public partial class apikeys : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "ApiKeys", + columns: table => new + { + Id = table.Column(nullable: false), + UserId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ApiKeys", x => x.Id); + table.ForeignKey( + name: "FK_ApiKeys_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_ApiKeys_UserId", + table: "ApiKeys", + column: "UserId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "ApiKeys"); + } + } +} diff --git a/Migrations/20210508012908_ApkiKey.CreationDate.Designer.cs b/Migrations/20210508012908_ApkiKey.CreationDate.Designer.cs new file mode 100644 index 0000000..9e62100 --- /dev/null +++ b/Migrations/20210508012908_ApkiKey.CreationDate.Designer.cs @@ -0,0 +1,259 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using nuget_host.Data; + +namespace nugethost.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20210508012908_ApkiKey.CreationDate")] + partial class ApkiKeyCreationDate + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn) + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Name") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("RoleId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider"); + + b.Property("ProviderKey"); + + b.Property("ProviderDisplayName"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId"); + + b.Property("RoleId"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId"); + + b.Property("LoginProvider"); + + b.Property("Name"); + + b.Property("Value"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("nuget_host.Models.ApiKeys.ApiKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CreationDate"); + + b.Property("Name"); + + b.Property("UserId") + .IsRequired(); + + b.Property("ValidityPeriodInDays"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("ApiKeys"); + }); + + modelBuilder.Entity("nuget_host.Models.ApplicationUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessFailedCount"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Email") + .HasMaxLength(256); + + b.Property("EmailConfirmed"); + + b.Property("FullName"); + + b.Property("LockoutEnabled"); + + b.Property("LockoutEnd"); + + b.Property("NormalizedEmail") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasMaxLength(256); + + b.Property("PasswordHash"); + + b.Property("PhoneNumber"); + + b.Property("PhoneNumberConfirmed"); + + b.Property("SecurityStamp"); + + b.Property("TwoFactorEnabled"); + + b.Property("UserName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("nuget_host.Models.ApplicationUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("nuget_host.Models.ApplicationUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("nuget_host.Models.ApplicationUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("nuget_host.Models.ApplicationUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("nuget_host.Models.ApiKeys.ApiKey", b => + { + b.HasOne("nuget_host.Models.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Migrations/20210508012908_ApkiKey.CreationDate.cs b/Migrations/20210508012908_ApkiKey.CreationDate.cs new file mode 100644 index 0000000..45b6a6c --- /dev/null +++ b/Migrations/20210508012908_ApkiKey.CreationDate.cs @@ -0,0 +1,43 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace nugethost.Migrations +{ + public partial class ApkiKeyCreationDate : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "CreationDate", + table: "ApiKeys", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + + migrationBuilder.AddColumn( + name: "Name", + table: "ApiKeys", + nullable: true); + + migrationBuilder.AddColumn( + name: "ValidityPeriodInDays", + table: "ApiKeys", + nullable: false, + defaultValue: 0); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "CreationDate", + table: "ApiKeys"); + + migrationBuilder.DropColumn( + name: "Name", + table: "ApiKeys"); + + migrationBuilder.DropColumn( + name: "ValidityPeriodInDays", + table: "ApiKeys"); + } + } +} diff --git a/Migrations/ApplicationDbContextModelSnapshot.cs b/Migrations/ApplicationDbContextModelSnapshot.cs index 8b93986..784d232 100644 --- a/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/Migrations/ApplicationDbContextModelSnapshot.cs @@ -126,6 +126,27 @@ namespace nugethost.Migrations b.ToTable("AspNetUserTokens"); }); + modelBuilder.Entity("nuget_host.Models.ApiKeys.ApiKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CreationDate"); + + b.Property("Name"); + + b.Property("UserId") + .IsRequired(); + + b.Property("ValidityPeriodInDays"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("ApiKeys"); + }); + modelBuilder.Entity("nuget_host.Models.ApplicationUser", b => { b.Property("Id") @@ -222,6 +243,14 @@ namespace nugethost.Migrations .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade); }); + + modelBuilder.Entity("nuget_host.Models.ApiKeys.ApiKey", b => + { + b.HasOne("nuget_host.Models.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); #pragma warning restore 612, 618 } } diff --git a/Models/ApiKeys/ApiKey.cs b/Models/ApiKeys/ApiKey.cs new file mode 100644 index 0000000..c385cd2 --- /dev/null +++ b/Models/ApiKeys/ApiKey.cs @@ -0,0 +1,23 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace nuget_host.Models.ApiKeys +{ + public class ApiKey + { + [Required][Key][DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public string Id { get; set; } + + [Required][ForeignKey("User")] + public string UserId { get; set; } + + public virtual ApplicationUser User { get; set; } + + public string Name { get; set; } + + public int ValidityPeriodInDays{ get; set; } + + public DateTime CreationDate { get; set; } + } +} \ No newline at end of file diff --git a/Models/ApiKeys/ApiKeyViewModel.cs b/Models/ApiKeys/ApiKeyViewModel.cs new file mode 100644 index 0000000..b113f21 --- /dev/null +++ b/Models/ApiKeys/ApiKeyViewModel.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations; + +namespace nuget_host.Models.ApiKeys +{ + public class ApiKeyViewModel + { + [Display(Name = "Key Name")] + public string Name { get; set; } + + + [Display(Name = "Key Value")] + public string ProtectedValue { get; set; } + } +} \ No newline at end of file diff --git a/Models/ApiKeys/CreateModel.cs b/Models/ApiKeys/CreateModel.cs new file mode 100644 index 0000000..2ea4fed --- /dev/null +++ b/Models/ApiKeys/CreateModel.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations; + +namespace nuget_host.Models.ApiKeys +{ + public class CreateModel + { + + [Required][StringLength(255)] + [Display(Name = "Key Name")] + public string Name { get; set; } + public string UserId { get; set; } + + } +} \ No newline at end of file diff --git a/Models/ApiKeys/DeleteModel.cs b/Models/ApiKeys/DeleteModel.cs new file mode 100644 index 0000000..89bb34f --- /dev/null +++ b/Models/ApiKeys/DeleteModel.cs @@ -0,0 +1,7 @@ +namespace nuget_host.Models.ApiKeys +{ + public class DeleteModel + { + public ApiKey ApiKey { get; set; } + } +} \ No newline at end of file diff --git a/Models/ApiKeys/DetailModel.cs b/Models/ApiKeys/DetailModel.cs new file mode 100644 index 0000000..d44991b --- /dev/null +++ b/Models/ApiKeys/DetailModel.cs @@ -0,0 +1,8 @@ +namespace nuget_host.Models.ApiKeys +{ + public class DetailModel : ApiKeyViewModel + { + public ApiKey ApiKey { get; set; } + + } +} \ No newline at end of file diff --git a/Models/ApiKeys/EditModel.cs b/Models/ApiKeys/EditModel.cs new file mode 100644 index 0000000..b922d67 --- /dev/null +++ b/Models/ApiKeys/EditModel.cs @@ -0,0 +1,12 @@ +namespace nuget_host.Models.ApiKeys +{ + public class EditModel + { + public EditModel() + { + if (ApiKey==null) ApiKey = new ApiKey(); + } + + public ApiKey ApiKey { get; set; } + } +} \ No newline at end of file diff --git a/Models/ApiKeys/IndexModel.cs b/Models/ApiKeys/IndexModel.cs new file mode 100644 index 0000000..310809b --- /dev/null +++ b/Models/ApiKeys/IndexModel.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace nuget_host.Models.ApiKeys +{ + public class IndexModel + { + public List ApiKey { get; set; } + } +} \ No newline at end of file diff --git a/Models/Grants/GrantsViewModel.cs b/Models/Grants/GrantsViewModel.cs deleted file mode 100644 index 86952fd..0000000 --- a/Models/Grants/GrantsViewModel.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. -// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. - - -using System; -using System.Collections.Generic; - -namespace nuget_host.Models -{ - public class GrantsViewModel - { - public IEnumerable Grants { get; set; } - } - - public class GrantViewModel - { - public string ClientId { get; set; } - public string ClientName { get; set; } - public string ClientUrl { get; set; } - public string ClientLogoUrl { get; set; } - public string Description { get; set; } - public DateTime Created { get; set; } - public DateTime? Expires { get; set; } - public IEnumerable IdentityGrantNames { get; set; } - public IEnumerable ApiGrantNames { get; set; } - } -} \ No newline at end of file diff --git a/Models/SecurityHeadersAttribute.cs b/Models/SecurityHeadersAttribute.cs deleted file mode 100644 index 732330c..0000000 --- a/Models/SecurityHeadersAttribute.cs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. -// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. - - -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Filters; - -namespace nuget_host.Models -{ - public class SecurityHeadersAttribute : ActionFilterAttribute - { - public override void OnResultExecuting(ResultExecutingContext context) - { - var result = context.Result; - if (result is ViewResult) - { - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options - if (!context.HttpContext.Response.Headers.ContainsKey("X-Content-Type-Options")) - { - context.HttpContext.Response.Headers.Add("X-Content-Type-Options", "nosniff"); - } - - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options - if (!context.HttpContext.Response.Headers.ContainsKey("X-Frame-Options")) - { - context.HttpContext.Response.Headers.Add("X-Frame-Options", "SAMEORIGIN"); - } - - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy - var csp = "default-src 'self'; object-src 'none'; frame-ancestors 'none'; sandbox allow-forms allow-same-origin allow-scripts; base-uri 'self';"; - // also consider adding upgrade-insecure-requests once you have HTTPS in place for production - //csp += "upgrade-insecure-requests;"; - // also an example if you need client images to be displayed from twitter - // csp += "img-src 'self' https://pbs.twimg.com;"; - - // once for standards compliant browsers - if (!context.HttpContext.Response.Headers.ContainsKey("Content-Security-Policy")) - { - context.HttpContext.Response.Headers.Add("Content-Security-Policy", csp); - } - // and once again for IE - if (!context.HttpContext.Response.Headers.ContainsKey("X-Content-Security-Policy")) - { - context.HttpContext.Response.Headers.Add("X-Content-Security-Policy", csp); - } - - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy - var referrer_policy = "no-referrer"; - if (!context.HttpContext.Response.Headers.ContainsKey("Referrer-Policy")) - { - context.HttpContext.Response.Headers.Add("Referrer-Policy", referrer_policy); - } - } - } - } -} diff --git a/Startup.cs b/Startup.cs index 6ec9234..a8a9209 100644 --- a/Startup.cs +++ b/Startup.cs @@ -51,9 +51,9 @@ namespace nuget_host services.AddTransient(); services.AddTransient(); - var smtpSettingsconf = Configuration.GetSection("SmtpSettings"); + var smtpSettingsconf = Configuration.GetSection("Smtp"); services.Configure(smtpSettingsconf); - var nugetSettingsconf = Configuration.GetSection("NugetSettings"); + var nugetSettingsconf = Configuration.GetSection("Nuget"); services.Configure(nugetSettingsconf); } diff --git a/Views/ApiKeys/Create.cshtml b/Views/ApiKeys/Create.cshtml new file mode 100644 index 0000000..6294575 --- /dev/null +++ b/Views/ApiKeys/Create.cshtml @@ -0,0 +1,35 @@ + +@model nuget_host.Models.ApiKeys.CreateModel + +@{ + ViewData["Title"] = "Create"; +} + +

Create

+ +

ApiKey

+
+
+
+
+
+
+ + +
+
+ + + +
+
+ +
+
+
+
+ + + diff --git a/Views/ApiKeys/Delete.cshtml b/Views/ApiKeys/Delete.cshtml new file mode 100644 index 0000000..ffc30e3 --- /dev/null +++ b/Views/ApiKeys/Delete.cshtml @@ -0,0 +1,46 @@ + +@model nuget_host.Models.ApiKeys.DeleteModel + +@{ + ViewData["Title"] = "Delete"; +} + +

Delete

+ +

Are you sure you want to delete this?

+
+

ApiKey

+
+
+
+ @Html.DisplayNameFor(model => model.ApiKey.User) +
+
+ @Html.DisplayFor(model => model.ApiKey.User.Id) +
+
+ @Html.DisplayNameFor(model => model.ApiKey.Name) +
+
+ @Html.DisplayFor(model => model.ApiKey.Name) +
+
+ @Html.DisplayNameFor(model => model.ApiKey.CreationDate) +
+
+ @Html.DisplayFor(model => model.ApiKey.CreationDate) +
+
+ @Html.DisplayNameFor(model => model.ApiKey.ValidityPeriodInDays) +
+
+ @Html.DisplayFor(model => model.ApiKey.ValidityPeriodInDays) +
+
+ +
+ + | + Back to List +
+
diff --git a/Views/ApiKeys/Details.cshtml b/Views/ApiKeys/Details.cshtml new file mode 100644 index 0000000..55cf752 --- /dev/null +++ b/Views/ApiKeys/Details.cshtml @@ -0,0 +1,49 @@ + +@model nuget_host.Models.ApiKeys.DetailModel + +@{ + ViewData["Title"] = "Details"; +} + +

Details

+ +
+

ApiKey

+
+
+
+ @Html.DisplayNameFor(model => model.ApiKey.User) +
+
+ @Html.DisplayFor(model => model.ApiKey.User.Id) +
+
+ @Html.DisplayNameFor(model => model.ApiKey.Name) +
+
+ @Html.DisplayFor(model => model.ApiKey.Name) +
+
+ @Html.DisplayNameFor(model => model.ApiKey.CreationDate) +
+
+ @Html.DisplayFor(model => model.ApiKey.CreationDate) +
+
+ @Html.DisplayNameFor(model => model.ApiKey.ValidityPeriodInDays) +
+
+ @Html.DisplayFor(model => model.ApiKey.ValidityPeriodInDays) +
+
+ @Html.DisplayNameFor(model => model.ProtectedValue) +
+
+ @Html.DisplayFor(model => model.ProtectedValue) +
+
+
+ diff --git a/Views/ApiKeys/Edit.cshtml b/Views/ApiKeys/Edit.cshtml new file mode 100644 index 0000000..732b30d --- /dev/null +++ b/Views/ApiKeys/Edit.cshtml @@ -0,0 +1,42 @@ + +@model nuget_host.Models.ApiKeys.EditModel + +@{ + ViewData["Title"] = "Edit"; +} + +

Edit

+ +

ApiKey

+
+
+
+
+
+ +
+ + + +
+
+ + + +
+
+ + + +
+
+ +
+
+
+
+ + + diff --git a/Views/ApiKeys/Index.cshtml b/Views/ApiKeys/Index.cshtml new file mode 100644 index 0000000..d820414 --- /dev/null +++ b/Views/ApiKeys/Index.cshtml @@ -0,0 +1,54 @@ + +@model nuget_host.Models.ApiKeys.IndexModel + +@{ + ViewData["Title"] = "Index"; +} + +

Index

+ +

+ Create New +

+ + + + + + + + + + + +@foreach (var item in Model.ApiKey) { + + + + + + + +} + +
+ @Html.DisplayNameFor(model => model.ApiKey[0].User) + + @Html.DisplayNameFor(model => model.ApiKey[0].Name) + + @Html.DisplayNameFor(model => model.ApiKey[0].CreationDate) + + @Html.DisplayNameFor(model => model.ApiKey[0].ValidityPeriodInDays) +
+ @Html.DisplayFor(modelItem => item.User.Id) + + @Html.DisplayFor(modelItem => item.Name) + + @Html.DisplayFor(modelItem => item.CreationDate) + + @Html.DisplayFor(modelItem => item.ValidityPeriodInDays) + + Edit | + Details | + Delete +
diff --git a/Views/Grants/Index.cshtml b/Views/Grants/Index.cshtml deleted file mode 100644 index a60f226..0000000 --- a/Views/Grants/Index.cshtml +++ /dev/null @@ -1,87 +0,0 @@ -@model GrantsViewModel - -
-
-

Client Application Permissions

-

Below is the list of applications you have given permission to and the resources they have access to.

-
- - @if (Model.Grants.Any() == false) - { -
-
-
- You have not given access to any applications -
-
-
- } - else - { - foreach (var grant in Model.Grants) - { -
-
-
-
- @if (grant.ClientLogoUrl != null) - { - - } - @grant.ClientName -
- -
-
- - -
-
-
-
- -
    - @if (grant.Description != null) - { -
  • - @grant.Description -
  • - } -
  • - @grant.Created.ToString("yyyy-MM-dd") -
  • - @if (grant.Expires.HasValue) - { -
  • - @grant.Expires.Value.ToString("yyyy-MM-dd") -
  • - } - @if (grant.IdentityGrantNames.Any()) - { -
  • - -
      - @foreach (var name in grant.IdentityGrantNames) - { -
    • @name
    • - } -
    -
  • - } - @if (grant.ApiGrantNames.Any()) - { -
  • - -
      - @foreach (var name in grant.ApiGrantNames) - { -
    • @name
    • - } -
    -
  • - } -
-
- } - } -
\ No newline at end of file diff --git a/Views/Home/Index.cshtml b/Views/Home/Index.cshtml index 7ec6551..b46d9cb 100644 --- a/Views/Home/Index.cshtml +++ b/Views/Home/Index.cshtml @@ -1,5 +1,6 @@ @{ - ViewData["Title"] = "Home Page";} + ViewData["Title"] = "Home Page"; +}

Welcome

@@ -8,4 +9,4 @@ Welcome to Nuget host -
+ diff --git a/appsettings.Development.json b/appsettings.Development.json index ffa68ab..0ff6031 100644 --- a/appsettings.Development.json +++ b/appsettings.Development.json @@ -1,7 +1,8 @@ { - "NuGet": { + "Nuget": { "ExternalUrl" : "", - "NuGet": { + "Nuget": { "ExternalUrl" : "", - "ProtectionTitle": "protected-data-v1" + "ProtectionTitle": "protected-data-v1", + "MaxUserKeyCount": 1 }, "ConnectionStrings": { "DefaultConnection": "Server=;Port=;Database=;Username=;Password=;"