From b04344dd173fd8f2cc1213baadcd14bb418087e0 Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" Date: Wed, 12 Jul 2017 16:21:09 +0100 Subject: [PATCH] Moved over to using Identity Server with Asp.Net Core Identity #1456 #865 Still a lot of work to do on the frontend for this. --- src/Ombi.Core/Ombi.Core.csproj | 4 +- .../Ombi.DependencyInjection.csproj | 2 +- src/Ombi.Mapping/Profiles/OmbiProfile.cs | 2 +- src/Ombi.Store/Context/IOmbiContext.cs | 2 +- src/Ombi.Store/Context/OmbiContext.cs | 9 +- src/Ombi.Store/Entities/OmbiUser.cs | 14 + ....cs => 20170712080109_Initial.Designer.cs} | 236 ++++++++++++++++- ...9_Initial.cs => 20170712080109_Initial.cs} | 244 +++++++++++++++++- .../Migrations/OmbiContextModelSnapshot.cs | 234 ++++++++++++++++- src/Ombi.Store/Ombi.Store.csproj | 9 +- src/Ombi.Store/Repository/UserRepository.cs | 10 +- src/Ombi/Auth/CustomJwtDataFormat.cs | 72 ------ src/Ombi/Auth/TokenAuthenticationOptions.cs | 11 - src/Ombi/Auth/TokenProviderMiddleware.cs | 161 ------------ src/Ombi/Auth/TokenProviderOptions.cs | 53 ---- src/Ombi/ClientApp/app/auth/auth.service.ts | 21 +- .../requests/tvrequest-manage.component.html | 2 - src/Ombi/Config/UserSettings.cs | 8 + src/Ombi/Controllers/IdentityController.cs | 110 ++++---- src/Ombi/IdentityConfig.cs | 58 +++++ src/Ombi/Ombi.csproj | 25 +- src/Ombi/Startup.Auth.cs | 75 ------ src/Ombi/Startup.cs | 55 +++- src/Ombi/appsettings.json | 4 + src/Ombi/bower.json | 9 - 25 files changed, 953 insertions(+), 477 deletions(-) create mode 100644 src/Ombi.Store/Entities/OmbiUser.cs rename src/Ombi.Store/Migrations/{20170703134019_Initial.Designer.cs => 20170712080109_Initial.Designer.cs} (60%) rename src/Ombi.Store/Migrations/{20170703134019_Initial.cs => 20170712080109_Initial.cs} (62%) delete mode 100644 src/Ombi/Auth/CustomJwtDataFormat.cs delete mode 100644 src/Ombi/Auth/TokenAuthenticationOptions.cs delete mode 100644 src/Ombi/Auth/TokenProviderMiddleware.cs delete mode 100644 src/Ombi/Auth/TokenProviderOptions.cs create mode 100644 src/Ombi/Config/UserSettings.cs create mode 100644 src/Ombi/IdentityConfig.cs delete mode 100644 src/Ombi/Startup.Auth.cs delete mode 100644 src/Ombi/bower.json diff --git a/src/Ombi.Core/Ombi.Core.csproj b/src/Ombi.Core/Ombi.Core.csproj index 2f2e35861..38caf6f3d 100644 --- a/src/Ombi.Core/Ombi.Core.csproj +++ b/src/Ombi.Core/Ombi.Core.csproj @@ -10,8 +10,8 @@ - - + + diff --git a/src/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj b/src/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj index efbf3dade..fe4befec3 100644 --- a/src/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj +++ b/src/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj @@ -5,7 +5,7 @@ - + diff --git a/src/Ombi.Mapping/Profiles/OmbiProfile.cs b/src/Ombi.Mapping/Profiles/OmbiProfile.cs index 8b6d4f058..8bd03121e 100644 --- a/src/Ombi.Mapping/Profiles/OmbiProfile.cs +++ b/src/Ombi.Mapping/Profiles/OmbiProfile.cs @@ -18,7 +18,7 @@ namespace Ombi.Mapping.Profiles CreateMap().ConvertUsing(); - CreateMap().ForMember(x => x.Password, opt => opt.Ignore()); + CreateMap().ForMember(x => x.Password, opt => opt.Ignore()); CreateMap() .ConstructUsing(checkbox => checkbox.Enabled ? new Claim(ClaimTypes.Role, checkbox.Value) : new Claim(ClaimTypes.Country, "")); diff --git a/src/Ombi.Store/Context/IOmbiContext.cs b/src/Ombi.Store/Context/IOmbiContext.cs index 7bd8279b1..4d71fcaa3 100644 --- a/src/Ombi.Store/Context/IOmbiContext.cs +++ b/src/Ombi.Store/Context/IOmbiContext.cs @@ -17,7 +17,7 @@ namespace Ombi.Store.Context DbSet PlexContent { get; set; } DbSet RadarrCache { get; set; } DatabaseFacade Database { get; } - DbSet Users { get; set; } + DbSet OldUsers { get; set; } EntityEntry Entry(T entry) where T : class; EntityEntry Attach(TEntity entity) where TEntity : class; DbSet Set() where TEntity : class; diff --git a/src/Ombi.Store/Context/OmbiContext.cs b/src/Ombi.Store/Context/OmbiContext.cs index 8d9902290..491732f80 100644 --- a/src/Ombi.Store/Context/OmbiContext.cs +++ b/src/Ombi.Store/Context/OmbiContext.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using Ombi.Helpers; using Ombi.Store.Entities; @@ -7,7 +8,7 @@ using Ombi.Store.Entities.Requests; namespace Ombi.Store.Context { - public sealed class OmbiContext : DbContext, IOmbiContext + public sealed class OmbiContext : IdentityDbContext, IOmbiContext { private static bool _created; public OmbiContext() @@ -19,10 +20,12 @@ namespace Ombi.Store.Context // Add the notifcation templates AddAllTemplates(); + } - + + public DbSet Settings { get; set; } - public DbSet Users { get; set; } + public DbSet OldUsers { get; set; } public DbSet PlexContent { get; set; } public DbSet RadarrCache { get; set; } public DbSet NotificationTemplates { get; set; } diff --git a/src/Ombi.Store/Entities/OmbiUser.cs b/src/Ombi.Store/Entities/OmbiUser.cs new file mode 100644 index 000000000..377e47247 --- /dev/null +++ b/src/Ombi.Store/Entities/OmbiUser.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; + +namespace Ombi.Store.Entities +{ + public class OmbiUser : IdentityUser + { + public string Alias { get; set; } + public UserType UserType { get; set; } + + [NotMapped] + public string UserAlias => string.IsNullOrEmpty(Alias) ? UserName : Alias; + } +} \ No newline at end of file diff --git a/src/Ombi.Store/Migrations/20170703134019_Initial.Designer.cs b/src/Ombi.Store/Migrations/20170712080109_Initial.Designer.cs similarity index 60% rename from src/Ombi.Store/Migrations/20170703134019_Initial.Designer.cs rename to src/Ombi.Store/Migrations/20170712080109_Initial.Designer.cs index 78e6f1172..020a66fdb 100644 --- a/src/Ombi.Store/Migrations/20170703134019_Initial.Designer.cs +++ b/src/Ombi.Store/Migrations/20170712080109_Initial.Designer.cs @@ -10,13 +10,142 @@ using Ombi.Store.Entities; namespace Ombi.Store.Migrations { [DbContext(typeof(OmbiContext))] - [Migration("20170703134019_Initial")] + [Migration("20170712080109_Initial")] partial class Initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) { modelBuilder - .HasAnnotation("ProductVersion", "1.1.1"); + .HasAnnotation("ProductVersion", "1.1.2"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.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.EntityFrameworkCore.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.EntityFrameworkCore.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.EntityFrameworkCore.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.EntityFrameworkCore.IdentityUserRole", b => + { + b.Property("UserId"); + + b.Property("RoleId"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserToken", b => + { + b.Property("UserId"); + + b.Property("LoginProvider"); + + b.Property("Name"); + + b.Property("Value"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmailTokens", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateUsed"); + + b.Property("Token"); + + b.Property("Used"); + + b.Property("UserId"); + + b.Property("ValidUntil"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("EmailTokens"); + }); modelBuilder.Entity("Ombi.Store.Entities.GlobalSettings", b => { @@ -52,6 +181,60 @@ namespace Ombi.Store.Migrations b.ToTable("NotificationTemplates"); }); + modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessFailedCount"); + + b.Property("Alias"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Email") + .HasMaxLength(256); + + b.Property("EmailConfirmed"); + + 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.Property("UserType"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + modelBuilder.Entity("Ombi.Store.Entities.PlexContent", b => { b.Property("Id") @@ -131,6 +314,8 @@ namespace Ombi.Store.Migrations b.Property("RequestedUserId"); + b.Property("Title"); + b.HasKey("Id"); b.HasIndex("ParentRequestId"); @@ -273,7 +458,7 @@ namespace Ombi.Store.Migrations b.HasKey("Id"); - b.ToTable("Users"); + b.ToTable("OldUsers"); }); modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => @@ -320,6 +505,51 @@ namespace Ombi.Store.Migrations b.ToTable("SeasonRequests"); }); + modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRole") + .WithMany("Claims") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserClaim", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany("Claims") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserLogin", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany("Logins") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRole") + .WithMany("Users") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany("Roles") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmailTokens", b => + { + b.HasOne("Ombi.Store.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => { b.HasOne("Ombi.Store.Entities.PlexContent") diff --git a/src/Ombi.Store/Migrations/20170703134019_Initial.cs b/src/Ombi.Store/Migrations/20170712080109_Initial.cs similarity index 62% rename from src/Ombi.Store/Migrations/20170703134019_Initial.cs rename to src/Ombi.Store/Migrations/20170712080109_Initial.cs index edca99333..907e42e21 100644 --- a/src/Ombi.Store/Migrations/20170703134019_Initial.cs +++ b/src/Ombi.Store/Migrations/20170712080109_Initial.cs @@ -8,6 +8,34 @@ namespace Ombi.Store.Migrations { protected override void Up(MigrationBuilder migrationBuilder) { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(nullable: false), + ConcurrencyStamp = table.Column(nullable: true), + Name = table.Column(maxLength: 256, nullable: true), + NormalizedName = table.Column(maxLength: 256, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(nullable: false), + LoginProvider = table.Column(nullable: false), + Name = table.Column(nullable: false), + Value = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + }); + migrationBuilder.CreateTable( name: "GlobalSettings", columns: table => new @@ -39,6 +67,33 @@ namespace Ombi.Store.Migrations table.PrimaryKey("PK_NotificationTemplates", x => x.Id); }); + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(nullable: false), + AccessFailedCount = table.Column(nullable: false), + Alias = table.Column(nullable: true), + ConcurrencyStamp = table.Column(nullable: true), + Email = table.Column(maxLength: 256, nullable: true), + EmailConfirmed = table.Column(nullable: false), + LockoutEnabled = table.Column(nullable: false), + LockoutEnd = table.Column(nullable: true), + NormalizedEmail = table.Column(maxLength: 256, nullable: true), + NormalizedUserName = table.Column(maxLength: 256, nullable: true), + PasswordHash = table.Column(nullable: true), + PhoneNumber = table.Column(nullable: true), + PhoneNumberConfirmed = table.Column(nullable: false), + SecurityStamp = table.Column(nullable: true), + TwoFactorEnabled = table.Column(nullable: false), + UserName = table.Column(maxLength: 256, nullable: true), + UserType = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + migrationBuilder.CreateTable( name: "PlexContent", columns: table => new @@ -92,7 +147,7 @@ namespace Ombi.Store.Migrations }); migrationBuilder.CreateTable( - name: "Users", + name: "OldUsers", columns: table => new { Id = table.Column(nullable: false) @@ -107,7 +162,93 @@ namespace Ombi.Store.Migrations }, constraints: table => { - table.PrimaryKey("PK_Users", x => x.Id); + table.PrimaryKey("PK_OldUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ClaimType = table.Column(nullable: true), + ClaimValue = table.Column(nullable: true), + RoleId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ClaimType = table.Column(nullable: true), + ClaimValue = table.Column(nullable: true), + UserId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(nullable: false), + ProviderKey = table.Column(nullable: false), + ProviderDisplayName = table.Column(nullable: true), + UserId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(nullable: false), + RoleId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); }); migrationBuilder.CreateTable( @@ -132,6 +273,29 @@ namespace Ombi.Store.Migrations onDelete: ReferentialAction.Cascade); }); + migrationBuilder.CreateTable( + name: "EmailTokens", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + DateUsed = table.Column(nullable: false), + Token = table.Column(nullable: false), + Used = table.Column(nullable: false), + UserId = table.Column(nullable: false), + ValidUntil = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_EmailTokens", x => x.Id); + table.ForeignKey( + name: "FK_EmailTokens_OldUsers_UserId", + column: x => x.UserId, + principalTable: "OldUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + migrationBuilder.CreateTable( name: "ChildRequests", columns: table => new @@ -159,9 +323,9 @@ namespace Ombi.Store.Migrations principalColumn: "Id", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "FK_ChildRequests_Users_RequestedUserId", + name: "FK_ChildRequests_OldUsers_RequestedUserId", column: x => x.RequestedUserId, - principalTable: "Users", + principalTable: "OldUsers", principalColumn: "Id", onDelete: ReferentialAction.Cascade); }); @@ -192,9 +356,9 @@ namespace Ombi.Store.Migrations { table.PrimaryKey("PK_MovieRequests", x => x.Id); table.ForeignKey( - name: "FK_MovieRequests_Users_RequestedUserId", + name: "FK_MovieRequests_OldUsers_RequestedUserId", column: x => x.RequestedUserId, - principalTable: "Users", + principalTable: "OldUsers", principalColumn: "Id", onDelete: ReferentialAction.Cascade); }); @@ -301,6 +465,48 @@ namespace Ombi.Store.Migrations onDelete: ReferentialAction.Cascade); }); + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "IX_EmailTokens_UserId", + table: "EmailTokens", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true); + migrationBuilder.CreateIndex( name: "IX_PlexSeasonsContent_PlexContentId", table: "PlexSeasonsContent", @@ -354,6 +560,24 @@ namespace Ombi.Store.Migrations protected override void Down(MigrationBuilder migrationBuilder) { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "EmailTokens"); + migrationBuilder.DropTable( name: "GlobalSettings"); @@ -375,6 +599,12 @@ namespace Ombi.Store.Migrations migrationBuilder.DropTable( name: "EpisodeRequests"); + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + migrationBuilder.DropTable( name: "PlexContent"); @@ -391,7 +621,7 @@ namespace Ombi.Store.Migrations name: "TvRequests"); migrationBuilder.DropTable( - name: "Users"); + name: "OldUsers"); } } } diff --git a/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs b/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs index 7b7237f35..0a5397d0b 100644 --- a/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs +++ b/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs @@ -15,7 +15,136 @@ namespace Ombi.Store.Migrations protected override void BuildModel(ModelBuilder modelBuilder) { modelBuilder - .HasAnnotation("ProductVersion", "1.1.1"); + .HasAnnotation("ProductVersion", "1.1.2"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.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.EntityFrameworkCore.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.EntityFrameworkCore.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.EntityFrameworkCore.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.EntityFrameworkCore.IdentityUserRole", b => + { + b.Property("UserId"); + + b.Property("RoleId"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserToken", b => + { + b.Property("UserId"); + + b.Property("LoginProvider"); + + b.Property("Name"); + + b.Property("Value"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmailTokens", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateUsed"); + + b.Property("Token"); + + b.Property("Used"); + + b.Property("UserId"); + + b.Property("ValidUntil"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("EmailTokens"); + }); modelBuilder.Entity("Ombi.Store.Entities.GlobalSettings", b => { @@ -51,6 +180,60 @@ namespace Ombi.Store.Migrations b.ToTable("NotificationTemplates"); }); + modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessFailedCount"); + + b.Property("Alias"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Email") + .HasMaxLength(256); + + b.Property("EmailConfirmed"); + + 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.Property("UserType"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + modelBuilder.Entity("Ombi.Store.Entities.PlexContent", b => { b.Property("Id") @@ -130,6 +313,8 @@ namespace Ombi.Store.Migrations b.Property("RequestedUserId"); + b.Property("Title"); + b.HasKey("Id"); b.HasIndex("ParentRequestId"); @@ -272,7 +457,7 @@ namespace Ombi.Store.Migrations b.HasKey("Id"); - b.ToTable("Users"); + b.ToTable("OldUsers"); }); modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => @@ -319,6 +504,51 @@ namespace Ombi.Store.Migrations b.ToTable("SeasonRequests"); }); + modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRole") + .WithMany("Claims") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserClaim", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany("Claims") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserLogin", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany("Logins") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRole") + .WithMany("Users") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany("Roles") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmailTokens", b => + { + b.HasOne("Ombi.Store.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => { b.HasOne("Ombi.Store.Entities.PlexContent") diff --git a/src/Ombi.Store/Ombi.Store.csproj b/src/Ombi.Store/Ombi.Store.csproj index 4ebe158cc..c68a68d20 100644 --- a/src/Ombi.Store/Ombi.Store.csproj +++ b/src/Ombi.Store/Ombi.Store.csproj @@ -5,10 +5,11 @@ - - - - + + + + + diff --git a/src/Ombi.Store/Repository/UserRepository.cs b/src/Ombi.Store/Repository/UserRepository.cs index df1ea5574..38f49edc7 100644 --- a/src/Ombi.Store/Repository/UserRepository.cs +++ b/src/Ombi.Store/Repository/UserRepository.cs @@ -45,32 +45,32 @@ namespace Ombi.Store.Repository public async Task GetUser(string username) { - var user = await Db.Users.FirstOrDefaultAsync(x => x.Username.ToLower() == username.ToLower()); + var user = await Db.OldUsers.FirstOrDefaultAsync(x => x.Username.ToLower() == username.ToLower()); Db.Entry(user).Reload(); return user; } public async Task GetUser(int userId) { - var user = await Db.Users.FirstOrDefaultAsync(x => x.Id == userId); + var user = await Db.OldUsers.FirstOrDefaultAsync(x => x.Id == userId); Db.Entry(user).Reload(); return user; } public async Task CreateUser(User user) { - Db.Users.Add(user); + Db.OldUsers.Add(user); await Db.SaveChangesAsync(); } public async Task> GetUsers() { - return await Db.Users.ToListAsync(); + return await Db.OldUsers.ToListAsync(); } public async Task DeleteUser(User user) { - Db.Users.Remove(user); + Db.OldUsers.Remove(user); await Db.SaveChangesAsync(); } diff --git a/src/Ombi/Auth/CustomJwtDataFormat.cs b/src/Ombi/Auth/CustomJwtDataFormat.cs deleted file mode 100644 index de8eccaae..000000000 --- a/src/Ombi/Auth/CustomJwtDataFormat.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using System.IdentityModel.Tokens.Jwt; -using System.Security.Claims; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Http.Authentication; -using Microsoft.IdentityModel.Tokens; - -namespace Ombi.Auth -{ - public class CustomJwtDataFormat : ISecureDataFormat - { - private readonly string algorithm; - private readonly TokenValidationParameters validationParameters; - - public CustomJwtDataFormat(string algorithm, TokenValidationParameters validationParameters) - { - this.algorithm = algorithm; - this.validationParameters = validationParameters; - } - - public AuthenticationTicket Unprotect(string protectedText) - => Unprotect(protectedText, null); - - public AuthenticationTicket Unprotect(string protectedText, string purpose) - { - var handler = new JwtSecurityTokenHandler(); - ClaimsPrincipal principal = null; - SecurityToken validToken = null; - - try - { - principal = handler.ValidateToken(protectedText, this.validationParameters, out validToken); - - var validJwt = validToken as JwtSecurityToken; - - if (validJwt == null) - { - throw new ArgumentException("Invalid JWT"); - } - - if (!validJwt.Header.Alg.Equals(algorithm, StringComparison.Ordinal)) - { - throw new ArgumentException($"Algorithm must be '{algorithm}'"); - } - - // Additional custom validation of JWT claims here (if any) - } - catch (SecurityTokenValidationException) - { - return null; - } - catch (ArgumentException) - { - return null; - } - - // Validation passed. Return a valid AuthenticationTicket: - return new AuthenticationTicket(principal, new AuthenticationProperties(), "Cookie"); - } - - // This ISecureDataFormat implementation is decode-only - public string Protect(AuthenticationTicket data) - { - throw new NotImplementedException(); - } - - public string Protect(AuthenticationTicket data, string purpose) - { - throw new NotImplementedException(); - } - } -} \ No newline at end of file diff --git a/src/Ombi/Auth/TokenAuthenticationOptions.cs b/src/Ombi/Auth/TokenAuthenticationOptions.cs deleted file mode 100644 index 657872e80..000000000 --- a/src/Ombi/Auth/TokenAuthenticationOptions.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Ombi.Auth -{ - public class TokenAuthenticationOptions - { - public string SecretKey { get; set; } - public string Issuer { get; set; } - public string Audience { get; set; } - public string TokenPath { get; set; } - public string CookieName { get; set; } - } -} \ No newline at end of file diff --git a/src/Ombi/Auth/TokenProviderMiddleware.cs b/src/Ombi/Auth/TokenProviderMiddleware.cs deleted file mode 100644 index f928b6049..000000000 --- a/src/Ombi/Auth/TokenProviderMiddleware.cs +++ /dev/null @@ -1,161 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IdentityModel.Tokens.Jwt; -using System.IO; -using System.Linq; -using System.Security.Claims; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Options; -using Newtonsoft.Json; -using Ombi.Core.IdentityResolver; -using Ombi.Models; -using Ombi.Store.Context; -using Ombi.Store.Repository; - -namespace Ombi.Auth -{ - public class TokenProviderMiddleware - { - private readonly RequestDelegate _next; - private readonly TokenProviderOptions _options; - private readonly JsonSerializerSettings _serializerSettings; - private readonly IUserIdentityManager _identityManager; - - public TokenProviderMiddleware( - RequestDelegate next, - IOptions options, IUserIdentityManager manager) - { - _next = next; - _options = options.Value; - ThrowIfInvalidOptions(_options); - - _serializerSettings = new JsonSerializerSettings - { - Formatting = Formatting.Indented - }; - _identityManager = manager; - } - - public Task Invoke(HttpContext context) - { - // If the request path doesn't match, skip - if (!context.Request.Path.Equals(_options.Path, StringComparison.Ordinal)) - { - try - { - - return _next(context); - } - catch (Exception e) - { - - throw; - } - } - - // Request must be POST with Content-Type: application/json - if (!context.Request.Method.Equals("POST") - ) - { - context.Response.StatusCode = 400; - return context.Response.WriteAsync("Bad request."); - } - - - return GenerateToken(context); - } - - private async Task GenerateToken(HttpContext context) - { - var request = context.Request; - UserAuthModel userInfo; - - using (var bodyReader = new StreamReader(request.Body)) - { - string body = await bodyReader.ReadToEndAsync(); - userInfo = JsonConvert.DeserializeObject(body); - } - - var identity = await _options.IdentityResolver(userInfo.Username, userInfo.Password, _identityManager); - if (identity == null) - { - context.Response.StatusCode = 400; - await context.Response.WriteAsync("Invalid username or password."); - return; - } - - var now = DateTime.UtcNow; - - // Specifically add the jti (nonce), iat (issued timestamp), and sub (subject/user) claims. - // You can add other claims here, if you want: - var jwtClaims = new List - { - new Claim(JwtRegisteredClaimNames.Sub, userInfo.Username), - new Claim(JwtRegisteredClaimNames.Jti, await _options.NonceGenerator()), - new Claim(JwtRegisteredClaimNames.Iat, new DateTimeOffset(now).ToUniversalTime().ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64) - }; - - identity.Claims.ToList().AddRange(jwtClaims); - - // Create the JWT and write it to a string - var jwt = new JwtSecurityToken( - issuer: _options.Issuer, - audience: _options.Audience, - claims: identity.Claims, - notBefore: now, - expires: now.Add(_options.Expiration), - signingCredentials: _options.SigningCredentials); - var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt); - - var response = new - { - access_token = encodedJwt, - expires_in = (int)_options.Expiration.TotalSeconds - }; - - // Serialize and return the response - context.Response.ContentType = "application/json"; - await context.Response.WriteAsync(JsonConvert.SerializeObject(response, _serializerSettings)); - } - - private static void ThrowIfInvalidOptions(TokenProviderOptions options) - { - if (string.IsNullOrEmpty(options.Path)) - { - throw new ArgumentNullException(nameof(TokenProviderOptions.Path)); - } - - if (string.IsNullOrEmpty(options.Issuer)) - { - throw new ArgumentNullException(nameof(TokenProviderOptions.Issuer)); - } - - if (string.IsNullOrEmpty(options.Audience)) - { - throw new ArgumentNullException(nameof(TokenProviderOptions.Audience)); - } - - if (options.Expiration == TimeSpan.Zero) - { - throw new ArgumentException("Must be a non-zero TimeSpan.", nameof(TokenProviderOptions.Expiration)); - } - - if (options.IdentityResolver == null) - { - throw new ArgumentNullException(nameof(TokenProviderOptions.IdentityResolver)); - } - - if (options.SigningCredentials == null) - { - throw new ArgumentNullException(nameof(TokenProviderOptions.SigningCredentials)); - } - - if (options.NonceGenerator == null) - { - throw new ArgumentNullException(nameof(TokenProviderOptions.NonceGenerator)); - } - } - - } -} \ No newline at end of file diff --git a/src/Ombi/Auth/TokenProviderOptions.cs b/src/Ombi/Auth/TokenProviderOptions.cs deleted file mode 100644 index d48b23392..000000000 --- a/src/Ombi/Auth/TokenProviderOptions.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Claims; -using System.Security.Cryptography; -using System.Threading.Tasks; -using Microsoft.IdentityModel.Tokens; -using Ombi.Core.IdentityResolver; - -namespace Ombi.Auth -{ - public class TokenProviderOptions - { - /// - /// The relative request path to listen on. - /// - /// The default path is /token. - public string Path { get; set; } = "/token/"; - - /// - /// The Issuer (iss) claim for generated tokens. - /// - public string Issuer { get; set; } - - /// - /// The Audience (aud) claim for the generated tokens. - /// - public string Audience { get; set; } - - /// - /// The expiration time for the generated tokens. - /// - /// The default is 1 Days. - public TimeSpan Expiration { get; set; } = TimeSpan.FromDays(1); - - /// - /// The signing key to use when generating tokens. - /// - public SigningCredentials SigningCredentials { get; set; } - - /// - /// Resolves a user identity given a username and password. - /// - public Func> IdentityResolver { get; set; } - - /// - /// Generates a random value (nonce) for each generated token. - /// - /// The default nonce is a random GUID. - public Func> NonceGenerator { get; set; } - = () => Task.FromResult(Guid.NewGuid().ToString()); - } -} diff --git a/src/Ombi/ClientApp/app/auth/auth.service.ts b/src/Ombi/ClientApp/app/auth/auth.service.ts index 645e74cf4..0f81ca289 100644 --- a/src/Ombi/ClientApp/app/auth/auth.service.ts +++ b/src/Ombi/ClientApp/app/auth/auth.service.ts @@ -1,5 +1,4 @@ - -import { Injectable } from '@angular/core'; +import { Injectable } from '@angular/core'; import { Observable } from 'rxjs/Rx'; import { ServiceHelpers } from '../services/service.helpers'; @@ -8,18 +7,28 @@ import { IUserLogin, ILocalUser } from './IUserLogin'; import { tokenNotExpired, JwtHelper } from 'angular2-jwt'; -import { Http } from '@angular/http'; +import { Http, Headers, URLSearchParams } from '@angular/http'; @Injectable() export class AuthService extends ServiceHelpers { constructor(http: Http) { - super(http, '/api/v1/token'); + super(http, '/connect/token'); } jwtHelper: JwtHelper = new JwtHelper(); - login(login:IUserLogin) : Observable { - return this.http.post(`${this.url}/`, JSON.stringify(login), { headers: this.headers }) + login(login: IUserLogin): Observable { + this.headers = new Headers(); + this.headers.append('Content-Type', 'application/x-www-form-urlencoded'); + let data = new URLSearchParams(); + data.append('client_id', 'frontend'); + data.append('scope', 'api'); + data.append('client_secret', 'secret'); + data.append('grant_type', 'password'); + data.append('username', login.username); + data.append('password', login.password); + + return this.http.post(`${this.url}/`, data.toString(), { headers: this.headers }) .map(this.extractData); } diff --git a/src/Ombi/ClientApp/app/requests/tvrequest-manage.component.html b/src/Ombi/ClientApp/app/requests/tvrequest-manage.component.html index 73b4be136..ba8049ff9 100644 --- a/src/Ombi/ClientApp/app/requests/tvrequest-manage.component.html +++ b/src/Ombi/ClientApp/app/requests/tvrequest-manage.component.html @@ -1,9 +1,7 @@ 
-
-
\ No newline at end of file diff --git a/src/Ombi/Config/UserSettings.cs b/src/Ombi/Config/UserSettings.cs new file mode 100644 index 000000000..49ccadb95 --- /dev/null +++ b/src/Ombi/Config/UserSettings.cs @@ -0,0 +1,8 @@ +namespace Ombi.Config +{ + public class UserSettings + { + public string WebsiteUrl { get; set; } + public bool UseHttps { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi/Controllers/IdentityController.cs b/src/Ombi/Controllers/IdentityController.cs index 9ca9319d7..3e3d4302d 100644 --- a/src/Ombi/Controllers/IdentityController.cs +++ b/src/Ombi/Controllers/IdentityController.cs @@ -1,18 +1,18 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Reflection; -using System.Security.Claims; using System.Threading.Tasks; using AutoMapper; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; using Ombi.Attributes; using Ombi.Core.Claims; -using Ombi.Core.IdentityResolver; -using Ombi.Core.Models; using Ombi.Core.Models.UI; using Ombi.Models; +using Ombi.Store.Entities; namespace Ombi.Controllers { @@ -23,13 +23,15 @@ namespace Ombi.Controllers [PowerUser] public class IdentityController : BaseV1ApiController { - public IdentityController(IUserIdentityManager identity, IMapper mapper) + public IdentityController(UserManager user, IMapper mapper, RoleManager rm) { - IdentityManager = identity; + UserManager = user; Mapper = mapper; + RoleManager = rm; } - private IUserIdentityManager IdentityManager { get; } + private UserManager UserManager { get; } + private RoleManager RoleManager { get; } private IMapper Mapper { get; } /// @@ -39,10 +41,9 @@ namespace Ombi.Controllers [HttpGet] public async Task GetUser() { - return Mapper.Map(await IdentityManager.GetUser(this.HttpContext.User.Identity.Name)); + return Mapper.Map(await UserManager.GetUserAsync(User)); } - /// /// This is what the Wizard will call when creating the user for the very first time. /// This should never be called after this. @@ -57,20 +58,39 @@ namespace Ombi.Controllers [AllowAnonymous] public async Task CreateWizardUser([FromBody] UserAuthModel user) { - var users = await IdentityManager.GetUsers(); + var users = UserManager.Users; if (users.Any()) { // No one should be calling this. Only the wizard return false; } - await IdentityManager.CreateUser(new UserDto + var userToCreate = new OmbiUser + { + UserName = user.Username, + + }; + + var result = await UserManager.CreateAsync(userToCreate, user.Password); + if (result.Succeeded) { - Username = user.Username, - UserType = UserType.LocalUser, - Claims = new List() { new Claim(ClaimTypes.Role, OmbiClaims.Admin) }, - Password = user.Password, - }); + if (!(await RoleManager.RoleExistsAsync("Admin"))) + { + var r = await RoleManager.CreateAsync(new IdentityRole("Admin")); + } + var re = await UserManager.AddToRoleAsync(userToCreate, "Admin"); + + var v = User.IsInRole("Admin"); + + + } + //await UserManager.CreateUser(new UserDto + //{ + // Username = user.Username, + // UserType = UserType.LocalUser, + // Claims = new List() { new Claim(ClaimTypes.Role, OmbiClaims.Admin) }, + // Password = user.Password, + //}); return true; } @@ -88,7 +108,7 @@ namespace Ombi.Controllers var fields = fieldInfos.Where(fi => fi.IsLiteral && !fi.IsInitOnly).ToList(); var allClaims = fields.Select(x => x.Name).ToList(); - var users = Mapper.Map>(await IdentityManager.GetUsers()).ToList(); + var users = Mapper.Map>(UserManager.Users).ToList(); foreach (var user in users) { @@ -121,7 +141,7 @@ namespace Ombi.Controllers var fields = fieldInfos.Where(fi => fi.IsLiteral && !fi.IsInitOnly).ToList(); var allClaims = fields.Select(x => x.Name).ToList(); - var user = Mapper.Map(await IdentityManager.GetUser(id)); + var user = Mapper.Map(await UserManager.Users.FirstOrDefaultAsync(x => x.Id == id.ToString())); var userClaims = user.Claims.Select(x => x.Value); @@ -145,37 +165,37 @@ namespace Ombi.Controllers /// /// The user. /// - [HttpPost] - public async Task CreateUser([FromBody] UserViewModel user) - { - user.Id = null; - var userResult = await IdentityManager.CreateUser(Mapper.Map(user)); - return Mapper.Map(userResult); - } - - /// - /// Updates the user. - /// - /// The user. - /// - [HttpPut] - public async Task UpdateUser([FromBody] UserViewModel user) - { - var userResult = await IdentityManager.UpdateUser(Mapper.Map(user)); - return Mapper.Map(userResult); - } + //[HttpPost] + //public async Task CreateUser([FromBody] UserViewModel user) + //{ + // user.Id = null; + // var userResult = await UserManager.CreateUser(Mapper.Map(user)); + // return Mapper.Map(userResult); + //} /// - /// Deletes the user. + /// Updates the user. /// /// The user. /// - [HttpDelete] - public async Task DeleteUser([FromBody] UserViewModel user) - { - await IdentityManager.DeleteUser(Mapper.Map(user)); - return Ok(); - } + //[HttpPut] + //public async Task UpdateUser([FromBody] UserViewModel user) + //{ + // var userResult = await UserManager.UpdateUser(Mapper.Map(user)); + // return Mapper.Map(userResult); + //} + + ///// + ///// Deletes the user. + ///// + ///// The user. + ///// + //[HttpDelete] + //public async Task DeleteUser([FromBody] UserViewModel user) + //{ + // await UserManager.DeleteUser(Mapper.Map(user)); + // return Ok(); + //} /// /// Gets all available claims in the system. diff --git a/src/Ombi/IdentityConfig.cs b/src/Ombi/IdentityConfig.cs new file mode 100644 index 000000000..f943e6f83 --- /dev/null +++ b/src/Ombi/IdentityConfig.cs @@ -0,0 +1,58 @@ +using System.Collections.Generic; +using IdentityServer4.Models; + +namespace Ombi +{ + public class IdentityConfig + { + // scopes define the resources in your system + public static IEnumerable GetIdentityResources() + { + return new List + { + new IdentityResources.OpenId(), + new IdentityResources.Profile(), + new IdentityResource { + Name = "role", + UserClaims = new List {"role"} + } + }; + } + + public static IEnumerable GetApiResources() + { + return new List + { + new ApiResource("api", "API") + { + UserClaims = {"role", "name"}, + + } + }; + } + + // clients want to access resources (aka scopes) + public static IEnumerable GetClients() + { + // client credentials client + return new List + { + new Client + { + ClientId = "frontend", + AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, + + ClientSecrets = + { + new Secret("secret".Sha256()) // TODO read up on what this actually is + }, + AllowedScopes = + { + "api", + }, + AccessTokenType = AccessTokenType.Jwt + } + }; + } + } +} \ No newline at end of file diff --git a/src/Ombi/Ombi.csproj b/src/Ombi/Ombi.csproj index d8ef4e8c4..fb9ddf459 100644 --- a/src/Ombi/Ombi.csproj +++ b/src/Ombi/Ombi.csproj @@ -45,18 +45,21 @@ - - - + + + + + + - - - - - - - - + + + + + + + + diff --git a/src/Ombi/Startup.Auth.cs b/src/Ombi/Startup.Auth.cs deleted file mode 100644 index 0531e0130..000000000 --- a/src/Ombi/Startup.Auth.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System; -using System.Security.Claims; -using System.Security.Principal; -using System.Text; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.Options; -using Microsoft.IdentityModel.Tokens; -using Ombi.Auth; -using Ombi.Core.IdentityResolver; - -namespace Ombi -{ - public partial class Startup - { - /// - /// A key... - /// - public SymmetricSecurityKey SigningKey; - private void ConfigureAuth(IApplicationBuilder app, IOptions options) - { - - var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(options.Value.SecretKey)); - - var tokenProviderOptions = new TokenProviderOptions - { - Path = options.Value.TokenPath, - Audience = options.Value.Audience, - Issuer = options.Value.Issuer, - SigningCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256), - IdentityResolver = GetIdentity - }; - - var tokenValidationParameters = new TokenValidationParameters - { - // The signing key must match! - ValidateIssuerSigningKey = true, - IssuerSigningKey = signingKey, - // Validate the JWT Issuer (iss) claim - ValidateIssuer = true, - ValidIssuer = options.Value.Issuer, - // Validate the JWT Audience (aud) claim - ValidateAudience = true, - ValidAudience = options.Value.Audience, - // Validate the token expiry - ValidateLifetime = true, - // If you want to allow a certain amount of clock drift, set that here: - ClockSkew = TimeSpan.Zero, - }; - - app.UseJwtBearerAuthentication(new JwtBearerOptions - { - AutomaticAuthenticate = true, - AutomaticChallenge = true, - TokenValidationParameters = tokenValidationParameters, - }); - - app.UseMiddleware(Options.Create(tokenProviderOptions)); - } - - - private async Task GetIdentity(string username, string password, IUserIdentityManager userIdentityManager) - { - var validLogin = await userIdentityManager.CredentialsValid(username, password); - if (!validLogin) - { - return null; - } - - var user = await userIdentityManager.GetUser(username); - var claim = new ClaimsIdentity(new GenericIdentity(user.Username, "Token"), user.Claims); - return claim; - } - } -} \ No newline at end of file diff --git a/src/Ombi/Startup.cs b/src/Ombi/Startup.cs index 45768cea0..5c1f6fa71 100644 --- a/src/Ombi/Startup.cs +++ b/src/Ombi/Startup.cs @@ -9,19 +9,23 @@ using Hangfire.SQLite; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.AspNetCore.SpaServices.Webpack; using Microsoft.AspNetCore.StaticFiles; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.Extensions.PlatformAbstractions; -using Ombi.Auth; +using Microsoft.IdentityModel.Tokens; using Ombi.Config; using Ombi.DependencyInjection; using Ombi.Mapping; using Ombi.Schedule; +using Ombi.Store.Context; +using Ombi.Store.Entities; using Serilog; using Serilog.Events; using StackExchange.Profiling; @@ -66,9 +70,35 @@ namespace Ombi public void ConfigureServices(IServiceCollection services) { // Add framework services. + services.AddDbContext(options => + options.UseSqlite("Data Source=Ombi.db")); + + services.AddIdentity() + .AddEntityFrameworkStores() + .AddDefaultTokenProviders(); + + services.AddIdentityServer() + .AddTemporarySigningCredential() + .AddInMemoryPersistedGrants() + .AddInMemoryIdentityResources(IdentityConfig.GetIdentityResources()) + .AddInMemoryApiResources(IdentityConfig.GetApiResources()) + .AddInMemoryClients(IdentityConfig.GetClients()) + .AddAspNetIdentity(); + + services.Configure(options => + { + options.Password.RequireDigit = false; + options.Password.RequiredLength = 1; + options.Password.RequireLowercase = false; + options.Password.RequireNonAlphanumeric = false; + options.Password.RequireUppercase = false; + }); + services.AddMemoryCache(); + services.AddMvc() .AddJsonOptions(x => x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore); + services.AddOmbiMappingProfile(); services.AddAutoMapper(expression => { @@ -114,8 +144,9 @@ namespace Ombi services.AddScoped(sp => sp.GetService().HttpContext.User); - services.Configure(Configuration.GetSection("TokenAuthentication")); + //services.Configure(Configuration.GetSection("TokenAuthentication")); services.Configure(Configuration.GetSection("ApplicationSettings")); + services.Configure(Configuration.GetSection("UserSettings")); services.AddHangfire(x => { @@ -140,6 +171,24 @@ namespace Ombi { //loggerFactory.AddConsole(Configuration.GetSection("Logging")); //loggerFactory.AddDebug(); + var options = (IOptions) app.ApplicationServices.GetService( + typeof(IOptions)); + + app.UseIdentity(); + app.UseIdentityServer(); + app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions + { + Authority = options.Value.WebsiteUrl, + ApiName = "api", + ApiSecret = "secret", + + EnableCaching = true, + CacheDuration = TimeSpan.FromMinutes(10), // that's the default + RequireHttpsMetadata = options.Value.UseHttps, // FOR DEV set to false + AutomaticAuthenticate = true, + AutomaticChallenge = true + + }); loggerFactory.AddSerilog(); @@ -180,7 +229,7 @@ namespace Ombi var jobSetup = (IJobSetup)app.ApplicationServices.GetService(typeof(IJobSetup)); jobSetup.Setup(); - ConfigureAuth(app, (IOptions)app.ApplicationServices.GetService(typeof(IOptions))); + //ConfigureAuth(app, (IOptions)app.ApplicationServices.GetService(typeof(IOptions))); var provider = new FileExtensionContentTypeProvider(); provider.Mappings[".map"] = "application/octet-stream"; diff --git a/src/Ombi/appsettings.json b/src/Ombi/appsettings.json index 4d21149d6..0c4cfe68f 100644 --- a/src/Ombi/appsettings.json +++ b/src/Ombi/appsettings.json @@ -8,6 +8,10 @@ "ApplicationSettings": { "Verison": "{{VERSIONNUMBER}}" }, + "UserSettings": { + "WebsiteUrl": "http://localhost:52038", + "UseHttps": false + }, "TokenAuthentication": { "SecretKey": "secretkey_secretkey123!", "Issuer": "OmbiIssuer", diff --git a/src/Ombi/bower.json b/src/Ombi/bower.json deleted file mode 100644 index c7183c60e..000000000 --- a/src/Ombi/bower.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "ombi", - "private": true, - "dependencies": { - "PACE": "pace#^1.0.2", - "font-awesome": "^4.7.0" - } -} - \ No newline at end of file