diff --git a/src/Ombi.Api.Discord/DiscordApi.cs b/src/Ombi.Api.Discord/DiscordApi.cs index 0300ee2e2..7bbb9350c 100644 --- a/src/Ombi.Api.Discord/DiscordApi.cs +++ b/src/Ombi.Api.Discord/DiscordApi.cs @@ -11,12 +11,12 @@ namespace Ombi.Api.Discord Api = api; } - private string Endpoint => "https://discordapp.com/api/"; + private const string BaseUrl = "https://discordapp.com/api/"; private IApi Api { get; } public async Task SendMessage(DiscordWebhookBody body, string webhookId, string webhookToken) { - var request = new Request(Endpoint, $"webhooks/{webhookId}/{webhookToken}", HttpMethod.Post); + var request = new Request($"webhooks/{webhookId}/{webhookToken}", BaseUrl, HttpMethod.Post); request.AddJsonBody(body); diff --git a/src/Ombi.Core/Authentication/OmbiUserManager.cs b/src/Ombi.Core/Authentication/OmbiUserManager.cs new file mode 100644 index 000000000..6a51cca6e --- /dev/null +++ b/src/Ombi.Core/Authentication/OmbiUserManager.cs @@ -0,0 +1,124 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: OmbiUserManager.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Ombi.Api.Emby; +using Ombi.Api.Plex; +using Ombi.Api.Plex.Models; +using Ombi.Core.Settings; +using Ombi.Core.Settings.Models.External; +using Ombi.Store.Entities; + +namespace Ombi.Core.Authentication +{ + public class OmbiUserManager : UserManager + { + public OmbiUserManager(IUserStore store, IOptions optionsAccessor, + IPasswordHasher passwordHasher, IEnumerable> userValidators, + IEnumerable> passwordValidators, ILookupNormalizer keyNormalizer, + IdentityErrorDescriber errors, IServiceProvider services, ILogger> logger, IPlexApi plexApi, + IEmbyApi embyApi, ISettingsService embySettings) + : base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger) + { + _plexApi = plexApi; + _embyApi = embyApi; + _embySettings = embySettings; + } + + private readonly IPlexApi _plexApi; + private readonly IEmbyApi _embyApi; + private readonly ISettingsService _embySettings; + + public override async Task CheckPasswordAsync(OmbiUser user, string password) + { + if (user.UserType == UserType.LocalUser) + { + return await base.CheckPasswordAsync(user, password); + } + if (user.UserType == UserType.PlexUser) + { + return await CheckPlexPasswordAsync(user, password); + } + if (user.UserType == UserType.EmbyUser) + { + return await CheckEmbyPasswordAsync(user, password); + } + return false; + } + + /// + /// Sign the user into plex and make sure we can get the authentication token. + /// We do not check if the user is in the owners "friends" since they must have a local user account to get this far + /// + /// + /// + /// + private async Task CheckPlexPasswordAsync(OmbiUser user, string password) + { + var result = await _plexApi.SignIn(new UserRequest { password = password, login = user.UserName }); + if (result.user?.authentication_token != null) + { + return true; + } + return false; + } + + /// + /// Sign the user into Emby + /// We do not check if the user is in the owners "friends" since they must have a local user account to get this far. + /// We also have to try and authenticate them with every server, the first server that work we just say it was a success + /// + /// + /// + /// + private async Task CheckEmbyPasswordAsync(OmbiUser user, string password) + { + var embySettings = await _embySettings.GetSettingsAsync(); + foreach (var server in embySettings.Servers) + { + try + { + var result = await _embyApi.LogIn(user.UserName, password, server.ApiKey, server.FullUri); + if (result != null) + { + return true; + } + } + catch (Exception e) + { + Logger.LogError(e, "Emby Login Failed"); + } + } + return false; + } + } +} \ No newline at end of file diff --git a/src/Ombi.Core/Engine/BaseMediaEngine.cs b/src/Ombi.Core/Engine/BaseMediaEngine.cs index b04cf9e5a..7cd252304 100644 --- a/src/Ombi.Core/Engine/BaseMediaEngine.cs +++ b/src/Ombi.Core/Engine/BaseMediaEngine.cs @@ -13,6 +13,7 @@ using Ombi.Store.Repository; using Ombi.Store.Repository.Requests; using Ombi.Store.Entities; using Microsoft.AspNetCore.Identity; +using Ombi.Core.Authentication; namespace Ombi.Core.Engine { @@ -23,7 +24,7 @@ namespace Ombi.Core.Engine private Dictionary _dbTv; protected BaseMediaEngine(IPrincipal identity, IRequestServiceMain requestService, - IRuleEvaluator rules, UserManager um) : base(identity, um, rules) + IRuleEvaluator rules, OmbiUserManager um) : base(identity, um, rules) { RequestService = requestService; } diff --git a/src/Ombi.Core/Engine/Interfaces/BaseEngine.cs b/src/Ombi.Core/Engine/Interfaces/BaseEngine.cs index 6a67b951f..32f08e2b1 100644 --- a/src/Ombi.Core/Engine/Interfaces/BaseEngine.cs +++ b/src/Ombi.Core/Engine/Interfaces/BaseEngine.cs @@ -9,12 +9,13 @@ using Ombi.Store.Entities; using Microsoft.AspNetCore.Identity; using System.Linq; using Microsoft.EntityFrameworkCore; +using Ombi.Core.Authentication; namespace Ombi.Core.Engine.Interfaces { public abstract class BaseEngine { - protected BaseEngine(IPrincipal user, UserManager um, IRuleEvaluator rules) + protected BaseEngine(IPrincipal user, OmbiUserManager um, IRuleEvaluator rules) { UserPrinciple = user; Rules = rules; @@ -23,7 +24,7 @@ namespace Ombi.Core.Engine.Interfaces protected IPrincipal UserPrinciple { get; } protected IRuleEvaluator Rules { get; } - protected UserManager UserManager { get; } + protected OmbiUserManager UserManager { get; } protected string Username => UserPrinciple.Identity.Name; private OmbiUser _user; diff --git a/src/Ombi.Core/Engine/MovieRequestEngine.cs b/src/Ombi.Core/Engine/MovieRequestEngine.cs index fe31bdf19..e2652edeb 100644 --- a/src/Ombi.Core/Engine/MovieRequestEngine.cs +++ b/src/Ombi.Core/Engine/MovieRequestEngine.cs @@ -12,6 +12,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; +using Ombi.Core.Authentication; using Ombi.Core.Engine.Interfaces; using Ombi.Core.Rule.Interfaces; using Ombi.Store.Entities.Requests; @@ -21,8 +22,8 @@ namespace Ombi.Core.Engine public class MovieRequestEngine : BaseMediaEngine, IMovieRequestEngine { public MovieRequestEngine(IMovieDbApi movieApi, IRequestServiceMain requestService, IPrincipal user, - INotificationHelper helper, IRuleEvaluator r, IMovieSender sender, ILogger log, - UserManager manager) : base(user, requestService, r, manager) + INotificationHelper helper, IRuleEvaluator r, IMovieSender sender, ILogger log, + OmbiUserManager manager) : base(user, requestService, r, manager) { MovieApi = movieApi; NotificationHelper = helper; diff --git a/src/Ombi.Core/Engine/MovieSearchEngine.cs b/src/Ombi.Core/Engine/MovieSearchEngine.cs index 033efcaf9..ce986a5f0 100644 --- a/src/Ombi.Core/Engine/MovieSearchEngine.cs +++ b/src/Ombi.Core/Engine/MovieSearchEngine.cs @@ -15,13 +15,14 @@ using Ombi.Core.Rule.Interfaces; using StackExchange.Profiling; using Ombi.Store.Entities; using Microsoft.AspNetCore.Identity; +using Ombi.Core.Authentication; namespace Ombi.Core.Engine { public class MovieSearchEngine : BaseMediaEngine, IMovieEngine { public MovieSearchEngine(IPrincipal identity, IRequestServiceMain service, IMovieDbApi movApi, IMapper mapper, - ILogger logger, IRuleEvaluator r, UserManager um) + ILogger logger, IRuleEvaluator r, OmbiUserManager um) : base(identity, service, r, um) { MovieApi = movApi; diff --git a/src/Ombi.Core/Engine/TvRequestEngine.cs b/src/Ombi.Core/Engine/TvRequestEngine.cs index 0f5502f27..1c25bdec4 100644 --- a/src/Ombi.Core/Engine/TvRequestEngine.cs +++ b/src/Ombi.Core/Engine/TvRequestEngine.cs @@ -11,6 +11,7 @@ using System.Security.Principal; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; +using Ombi.Core.Authentication; using Ombi.Core.Engine.Interfaces; using Ombi.Core.Helpers; using Ombi.Core.Rule; @@ -24,7 +25,7 @@ namespace Ombi.Core.Engine { public TvRequestEngine(ITvMazeApi tvApi, IRequestServiceMain requestService, IPrincipal user, INotificationHelper helper, IMapper map, - IRuleEvaluator rule, UserManager manager, + IRuleEvaluator rule, OmbiUserManager manager, ITvSender sender, IAuditRepository audit) : base(user, requestService, rule, manager) { TvApi = tvApi; diff --git a/src/Ombi.Core/Engine/TvSearchEngine.cs b/src/Ombi.Core/Engine/TvSearchEngine.cs index 4eee6a74f..44559cbf6 100644 --- a/src/Ombi.Core/Engine/TvSearchEngine.cs +++ b/src/Ombi.Core/Engine/TvSearchEngine.cs @@ -20,13 +20,14 @@ using Ombi.Store.Repository.Requests; using Ombi.Store.Entities; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; +using Ombi.Core.Authentication; namespace Ombi.Core.Engine { public class TvSearchEngine : BaseMediaEngine, ITvSearchEngine { public TvSearchEngine(IPrincipal identity, IRequestServiceMain service, ITvMazeApi tvMaze, IMapper mapper, ISettingsService plexSettings, - ISettingsService embySettings, IPlexContentRepository repo, IEmbyContentRepository embyRepo, ITraktApi trakt, IRuleEvaluator r, UserManager um) + ISettingsService embySettings, IPlexContentRepository repo, IEmbyContentRepository embyRepo, ITraktApi trakt, IRuleEvaluator r, OmbiUserManager um) : base(identity, service, r, um) { TvMazeApi = tvMaze; diff --git a/src/Ombi.Core/Requests/Models/IRequestServiceMain.cs b/src/Ombi.Core/Models/Requests/IRequestServiceMain.cs similarity index 100% rename from src/Ombi.Core/Requests/Models/IRequestServiceMain.cs rename to src/Ombi.Core/Models/Requests/IRequestServiceMain.cs diff --git a/src/Ombi.Core/Models/UI/UserViewModel.cs b/src/Ombi.Core/Models/UI/UserViewModel.cs index 705332e4a..87ad7a97f 100644 --- a/src/Ombi.Core/Models/UI/UserViewModel.cs +++ b/src/Ombi.Core/Models/UI/UserViewModel.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace Ombi.Core.Models.UI { @@ -10,7 +11,8 @@ namespace Ombi.Core.Models.UI public List Claims { get; set; } public string EmailAddress { get; set; } public string Password { get; set; } - public bool IsSetup { get; set; } + public DateTime? LastLoggedIn { get; set; } + public bool HasLoggedIn { get; set; } public UserType UserType { get; set; } } diff --git a/src/Ombi.Schedule/Jobs/Plex/PlexUserImporter.cs b/src/Ombi.Schedule/Jobs/Plex/PlexUserImporter.cs index 52f79332f..64de7fb67 100644 --- a/src/Ombi.Schedule/Jobs/Plex/PlexUserImporter.cs +++ b/src/Ombi.Schedule/Jobs/Plex/PlexUserImporter.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System; +using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; @@ -53,10 +54,10 @@ namespace Ombi.Schedule.Jobs.Plex var users = await _api.GetUsers(server.PlexAuthToken); - foreach (var plexUsers in users.User) + foreach (var plexUser in users.User) { // Check if we should import this user - if (userManagementSettings.BannedPlexUserIds.Contains(plexUsers.Id)) + if (userManagementSettings.BannedPlexUserIds.Contains(plexUser.Id)) { // Do not import these, they are not allowed into the country. continue; @@ -64,7 +65,7 @@ namespace Ombi.Schedule.Jobs.Plex // Check if this Plex User already exists // We are using the Plex USERNAME and Not the TITLE, the Title is for HOME USERS - var existingPlexUser = allUsers.FirstOrDefault(x => x.ProviderUserId == plexUsers.Id); + var existingPlexUser = allUsers.FirstOrDefault(x => x.ProviderUserId == plexUser.Id); if (existingPlexUser == null) { // Create this users @@ -72,9 +73,9 @@ namespace Ombi.Schedule.Jobs.Plex var newUser = new OmbiUser { UserType = UserType.PlexUser, - UserName = plexUsers.Username, - ProviderUserId = plexUsers.Id, - Email = plexUsers.Email, + UserName = plexUser.Username, + ProviderUserId = plexUser.Id, + Email = plexUser.Email, Alias = string.Empty }; var result = await _userManager.CreateAsync(newUser); @@ -97,6 +98,10 @@ namespace Ombi.Schedule.Jobs.Plex else { // Do we need to update this user? + existingPlexUser.Email = plexUser.Email; + existingPlexUser.UserName = plexUser.Username; + + await _userManager.UpdateAsync(existingPlexUser); } } } diff --git a/src/Ombi.Store/Context/OmbiContext.cs b/src/Ombi.Store/Context/OmbiContext.cs index 68539aa42..257e6a3c8 100644 --- a/src/Ombi.Store/Context/OmbiContext.cs +++ b/src/Ombi.Store/Context/OmbiContext.cs @@ -100,13 +100,13 @@ namespace Ombi.Store.Context foreach (var agent in allAgents) { - if (templates.Any(x => x.Agent == agent)) - { - // We have all the templates for this notification agent - continue; - } foreach (var notificationType in allTypes) { + if (templates.Any(x => x.Agent == agent && x.NotificationType == notificationType)) + { + // We already have this + continue; + } NotificationTemplates notificationToAdd; switch (notificationType) { diff --git a/src/Ombi.Store/Entities/OmbiUser.cs b/src/Ombi.Store/Entities/OmbiUser.cs index 24353ecfc..42dce2fe7 100644 --- a/src/Ombi.Store/Entities/OmbiUser.cs +++ b/src/Ombi.Store/Entities/OmbiUser.cs @@ -1,4 +1,5 @@ -using System.ComponentModel.DataAnnotations.Schema; +using System; +using System.ComponentModel.DataAnnotations.Schema; using Microsoft.AspNetCore.Identity; namespace Ombi.Store.Entities @@ -7,11 +8,14 @@ namespace Ombi.Store.Entities { public string Alias { get; set; } public UserType UserType { get; set; } + /// /// This will be the unique Plex/Emby user id reference /// public string ProviderUserId { get; set; } + public DateTime? LastLoggedIn { get; set; } + [NotMapped] public string UserAlias => string.IsNullOrEmpty(Alias) ? UserName : Alias; } diff --git a/src/Ombi.Store/Migrations/20170928150420_LastLoggedIn.Designer.cs b/src/Ombi.Store/Migrations/20170928150420_LastLoggedIn.Designer.cs new file mode 100644 index 000000000..2cb6dbcc5 --- /dev/null +++ b/src/Ombi.Store/Migrations/20170928150420_LastLoggedIn.Designer.cs @@ -0,0 +1,731 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.Storage.Internal; +using Ombi.Helpers; +using Ombi.Store.Context; +using Ombi.Store.Entities; +using System; + +namespace Ombi.Store.Migrations +{ + [DbContext(typeof(OmbiContext))] + [Migration("20170928150420_LastLoggedIn")] + partial class LastLoggedIn + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); + + 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("Ombi.Store.Entities.ApplicationConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Type"); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("ApplicationConfiguration"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Audit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuditArea"); + + b.Property("AuditType"); + + b.Property("DateTime"); + + b.Property("Description"); + + b.Property("User"); + + b.HasKey("Id"); + + b.ToTable("Audit"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId") + .IsRequired(); + + b.Property("ProviderId"); + + b.Property("Title"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.ToTable("EmbyContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId"); + + b.Property("EpisodeNumber"); + + b.Property("ParentId"); + + b.Property("ProviderId"); + + b.Property("SeasonNumber"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("EmbyEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.GlobalSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Content"); + + b.Property("SettingsName"); + + b.HasKey("Id"); + + b.ToTable("GlobalSettings"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Agent"); + + b.Property("Enabled"); + + b.Property("Message"); + + b.Property("NotificationType"); + + b.Property("Subject"); + + b.HasKey("Id"); + + 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("LastLoggedIn"); + + 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("ProviderUserId"); + + 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") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("Key"); + + b.Property("ProviderId"); + + b.Property("Quality"); + + b.Property("ReleaseYear"); + + b.Property("Title"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("PlexContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("GrandparentKey"); + + b.Property("Key"); + + b.Property("ParentKey"); + + b.Property("SeasonNumber"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("GrandparentKey"); + + b.ToTable("PlexEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ParentKey"); + + b.Property("PlexContentId"); + + b.Property("SeasonKey"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("PlexContentId"); + + b.ToTable("PlexSeasonsContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("RadarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("IssueId"); + + b.Property("ParentRequestId"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("ParentRequestId"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("ChildRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieIssues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Description"); + + b.Property("IssueId"); + + b.Property("MovieId"); + + b.Property("Subect"); + + b.HasKey("Id"); + + b.HasIndex("IssueId"); + + b.HasIndex("MovieId"); + + b.ToTable("MovieIssues"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("ImdbId"); + + b.Property("IssueId"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("ReleaseDate"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("Status"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("MovieRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.TvIssues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Description"); + + b.Property("IssueId"); + + b.Property("Subect"); + + b.Property("TvId"); + + b.HasKey("Id"); + + b.HasIndex("IssueId"); + + b.HasIndex("TvId"); + + b.ToTable("TvIssues"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ImdbId"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("ReleaseDate"); + + b.Property("RootFolder"); + + b.Property("Status"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("TvRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Token"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AirDate"); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("EpisodeNumber"); + + b.Property("Requested"); + + b.Property("SeasonId"); + + b.Property("Title"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.HasIndex("SeasonId"); + + b.ToTable("EpisodeRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChildRequestId"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("ChildRequestId"); + + b.ToTable("SeasonRequests"); + }); + + 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("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .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("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.HasOne("Ombi.Store.Entities.EmbyContent", "Series") + .WithMany("Episodes") + .HasForeignKey("ParentId") + .HasPrincipalKey("EmbyId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.HasOne("Ombi.Store.Entities.PlexContent", "Series") + .WithMany("Episodes") + .HasForeignKey("GrandparentKey") + .HasPrincipalKey("Key") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.HasOne("Ombi.Store.Entities.PlexContent") + .WithMany("Seasons") + .HasForeignKey("PlexContentId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.TvRequests", "ParentRequest") + .WithMany("ChildRequests") + .HasForeignKey("ParentRequestId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieIssues", b => + { + b.HasOne("Ombi.Store.Entities.Requests.MovieRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.Requests.MovieRequests", "Movie") + .WithMany() + .HasForeignKey("MovieId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.TvIssues", b => + { + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", "Child") + .WithMany() + .HasForeignKey("TvId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.HasOne("Ombi.Store.Repository.Requests.SeasonRequests", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", "ChildRequest") + .WithMany("SeasonRequests") + .HasForeignKey("ChildRequestId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Migrations/20170928150420_LastLoggedIn.cs b/src/Ombi.Store/Migrations/20170928150420_LastLoggedIn.cs new file mode 100644 index 000000000..cf393c7a5 --- /dev/null +++ b/src/Ombi.Store/Migrations/20170928150420_LastLoggedIn.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System; +using System.Collections.Generic; + +namespace Ombi.Store.Migrations +{ + public partial class LastLoggedIn : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "LastLoggedIn", + table: "AspNetUsers", + type: "TEXT", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "LastLoggedIn", + table: "AspNetUsers"); + } + } +} diff --git a/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs b/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs index 9c913fa59..1a46eb954 100644 --- a/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs +++ b/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs @@ -260,6 +260,8 @@ namespace Ombi.Store.Migrations b.Property("EmailConfirmed"); + b.Property("LastLoggedIn"); + b.Property("LockoutEnabled"); b.Property("LockoutEnd"); diff --git a/src/Ombi/ClientApp/app/interfaces/IUser.ts b/src/Ombi/ClientApp/app/interfaces/IUser.ts index 5e42afafe..fae6a4bc1 100644 --- a/src/Ombi/ClientApp/app/interfaces/IUser.ts +++ b/src/Ombi/ClientApp/app/interfaces/IUser.ts @@ -8,7 +8,8 @@ export interface IUser { emailAddress: string; password: string; userType: UserType; - isSetup: boolean; + lastLoggedIn: Date; + hasLoggedIn: boolean; // FOR UI checked: boolean; } diff --git a/src/Ombi/ClientApp/app/search/moviesearch.component.html b/src/Ombi/ClientApp/app/search/moviesearch.component.html index 8851b09b9..663b5bb34 100644 --- a/src/Ombi/ClientApp/app/search/moviesearch.component.html +++ b/src/Ombi/ClientApp/app/search/moviesearch.component.html @@ -54,7 +54,7 @@ Available Processing Request Pending Approval - Not Requested + Not Requested diff --git a/src/Ombi/ClientApp/app/settings/about/about.component.html b/src/Ombi/ClientApp/app/settings/about/about.component.html index 5893a39d5..9ac67c87b 100644 --- a/src/Ombi/ClientApp/app/settings/about/about.component.html +++ b/src/Ombi/ClientApp/app/settings/about/about.component.html @@ -14,7 +14,7 @@ Version - {{about.version}} + {{about.version}} (New Update Available) diff --git a/src/Ombi/ClientApp/app/settings/about/about.component.ts b/src/Ombi/ClientApp/app/settings/about/about.component.ts index cd4cc83af..8061af057 100644 --- a/src/Ombi/ClientApp/app/settings/about/about.component.ts +++ b/src/Ombi/ClientApp/app/settings/about/about.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit } from "@angular/core"; import { IAbout } from "../../interfaces/ISettings"; -import { SettingsService } from "../../services"; +import { JobService, SettingsService } from "../../services"; @Component({ templateUrl: "./about.component.html", @@ -8,10 +8,18 @@ import { SettingsService } from "../../services"; export class AboutComponent implements OnInit { public about: IAbout; + public newUpdate: boolean; - constructor(private settingsService: SettingsService) { } + constructor(private readonly settingsService: SettingsService, + private readonly jobService: JobService) { } public ngOnInit() { this.settingsService.about().subscribe(x => this.about = x); + this.jobService.checkForNewUpdate().subscribe(x => { + if (x === true) { + this.newUpdate = true; + } + }); + } } diff --git a/src/Ombi/ClientApp/app/settings/radarr/radarr.component.ts b/src/Ombi/ClientApp/app/settings/radarr/radarr.component.ts index 8487cf80a..198b6e060 100644 --- a/src/Ombi/ClientApp/app/settings/radarr/radarr.component.ts +++ b/src/Ombi/ClientApp/app/settings/radarr/radarr.component.ts @@ -93,7 +93,7 @@ export class RadarrComponent implements OnInit { } const settings = form.value; this.testerService.radarrTest(settings).subscribe(x => { - if (x) { + if (x === true) { this.notificationService.success("Connected", "Successfully connected to Radarr!"); } else { this.notificationService.error("Connected", "We could not connect to Radarr!"); diff --git a/src/Ombi/ClientApp/app/settings/usermanagement/usermanagement.component.html b/src/Ombi/ClientApp/app/settings/usermanagement/usermanagement.component.html index b1b2b9572..0dc11ca92 100644 --- a/src/Ombi/ClientApp/app/settings/usermanagement/usermanagement.component.html +++ b/src/Ombi/ClientApp/app/settings/usermanagement/usermanagement.component.html @@ -23,7 +23,7 @@
- +
diff --git a/src/Ombi/ClientApp/app/usermanagement/usermanagement-add.component.ts b/src/Ombi/ClientApp/app/usermanagement/usermanagement-add.component.ts index 3f7b4a3af..5100f675e 100644 --- a/src/Ombi/ClientApp/app/usermanagement/usermanagement-add.component.ts +++ b/src/Ombi/ClientApp/app/usermanagement/usermanagement-add.component.ts @@ -27,7 +27,8 @@ export class UserManagementAddComponent implements OnInit { username: "", userType: UserType.LocalUser, checked:false, - isSetup:false, + hasLoggedIn: false, + lastLoggedIn:new Date(), }; } diff --git a/src/Ombi/ClientApp/app/usermanagement/usermanagement-edit.component.html b/src/Ombi/ClientApp/app/usermanagement/usermanagement-edit.component.html index 31cf6cd2f..a4fec0fd0 100644 --- a/src/Ombi/ClientApp/app/usermanagement/usermanagement-edit.component.html +++ b/src/Ombi/ClientApp/app/usermanagement/usermanagement-edit.component.html @@ -27,7 +27,7 @@
- +
diff --git a/src/Ombi/ClientApp/app/usermanagement/usermanagement-edit.component.ts b/src/Ombi/ClientApp/app/usermanagement/usermanagement-edit.component.ts index 715f17569..e16d1bb06 100644 --- a/src/Ombi/ClientApp/app/usermanagement/usermanagement-edit.component.ts +++ b/src/Ombi/ClientApp/app/usermanagement/usermanagement-edit.component.ts @@ -18,7 +18,7 @@ export class UserManagementEditComponent { private notificationSerivce: NotificationService, private router: Router) { this.route.params - .subscribe(params => { + .subscribe((params: any) => { this.userId = params.id; this.identityService.getUserById(this.userId).subscribe(x => { diff --git a/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.html b/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.html index bb5df3647..265548636 100644 --- a/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.html +++ b/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.html @@ -47,6 +47,9 @@ Roles + + Last Logged In + User Type @@ -74,6 +77,9 @@ + + {{u.lastLoggedIn | date: 'short'}} + Local User Plex User @@ -83,7 +89,7 @@ Details/Edit - Send Welcome Email + diff --git a/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.ts b/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.ts index 2149884be..144dc42be 100644 --- a/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.ts +++ b/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit } from "@angular/core"; -import { IEmailNotificationSettings, IUser } from "../interfaces"; +import { ICustomizationSettings, IEmailNotificationSettings, IUser } from "../interfaces"; import { IdentityService, NotificationService, SettingsService } from "../services"; @Component({ @@ -11,10 +11,11 @@ export class UserManagementComponent implements OnInit { public users: IUser[]; public checkAll = false; public emailSettings: IEmailNotificationSettings; + public customizationSettings: ICustomizationSettings; - constructor(private identityService: IdentityService, - private settingsService: SettingsService, - private notificationService: NotificationService) { } + constructor(private readonly identityService: IdentityService, + private readonly settingsService: SettingsService, + private readonly notificationService: NotificationService) { } public ngOnInit() { this.users = []; @@ -22,6 +23,7 @@ export class UserManagementComponent implements OnInit { this.users = x; }); + this.settingsService.getCustomization().subscribe(x => this.customizationSettings = x); this.settingsService.getEmailNotificationSettings().subscribe(x => this.emailSettings = x); } diff --git a/src/Ombi/ClientApp/styles/base.scss b/src/Ombi/ClientApp/styles/base.scss index ea3c6f02b..e1491e0b0 100644 --- a/src/Ombi/ClientApp/styles/base.scss +++ b/src/Ombi/ClientApp/styles/base.scss @@ -26,7 +26,7 @@ $i:!important; } } -@media (max-width: 48em) { +@media (max-width: 48em) { .home { padding-top: 1rem; } @@ -97,20 +97,20 @@ label { margin-bottom: .5rem $i; font-size: 16px $i; } + .small-label { display: inline-block $i; margin-bottom: .5rem $i; font-size: 11px $i; } -.small-checkbox{ - min-height:0 $i; - +.small-checkbox { + min-height: 0 $i; } .round-checkbox { - border-radius:8px; + border-radius: 8px; } .nav-tabs > li { @@ -428,7 +428,7 @@ $border-radius: 10px; bottom: 1px; border: 2px solid #eee; border-radius: 8px; - min-height:0px $i; + min-height: 0px $i; } .small-checkbox input[type=checkbox] { @@ -444,11 +444,11 @@ $border-radius: 10px; } .small-checkbox label { - min-height: 0 $i; - padding-left: 20px; - margin-bottom: 0; - font-weight: normal; - cursor: pointer; + min-height: 0 $i; + padding-left: 20px; + margin-bottom: 0; + font-weight: normal; + cursor: pointer; } .input-group-sm { @@ -517,6 +517,7 @@ $border-radius: 10px; -webkit-box-shadow: 3px 3px 5px 6px #191919; box-shadow: 3px 3px 5px 6px #191919; } + .img-circle { border-radius: 50%; } @@ -542,7 +543,7 @@ $border-radius: 10px; margin-right: -250px; overflow-y: auto; background: #4e5d6c; - padding-left:0; + padding-left: 0; -webkit-transition: all 0.5s ease; -moz-transition: all 0.5s ease; -o-transition: all 0.5s ease; @@ -641,61 +642,60 @@ $border-radius: 10px; } #lightbox { - background-color: grey; - filter:alpha(opacity=50); /* IE */ + filter: alpha(opacity=50); /* IE */ opacity: 0.5; /* Safari, Opera */ - -moz-opacity:0.50; /* FireFox */ + -moz-opacity: 0.50; /* FireFox */ top: 0px; left: 0px; z-index: 20; height: 100%; width: 100%; - background-repeat:no-repeat; - background-position:center; - position:absolute; + background-repeat: no-repeat; + background-position: center; + position: absolute; } .list-group-item-dropdown { - position: relative; - display: block; - padding: 10px 15px; - margin-bottom: -1px; - background-color: #3e3e3e; - border: 1px solid transparent; + position: relative; + display: block; + padding: 10px 15px; + margin-bottom: -1px; + background-color: #3e3e3e; + border: 1px solid transparent; } -.wizard-heading{ +.wizard-heading { text-align: center; } -.wizard-img{ + +.wizard-img { width: 300px; display: block $i; margin: 0 auto $i; } .pace { - -webkit-pointer-events: none; - pointer-events: none; - - -webkit-user-select: none; - -moz-user-select: none; - user-select: none; + -webkit-pointer-events: none; + pointer-events: none; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; } .pace-inactive { - display: none; + display: none; } .pace .pace-progress { - background: $primary-colour; - position: fixed; - z-index: 2000; - top: 0; - right: 100%; - width: 100%; - height: 5px; + background: $primary-colour; + position: fixed; + z-index: 2000; + top: 0; + right: 100%; + width: 100%; + height: 5px; } .navbar-brand { @@ -705,8 +705,8 @@ $border-radius: 10px; height: 40px; } -.gravatar{ - border-radius:1em; +.gravatar { + border-radius: 1em; } @@ -716,6 +716,7 @@ html { font-size: 16px; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); } + body { font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 14px; @@ -727,6 +728,7 @@ body { .ui-datatable-odd { background-color: $form-color $i; } + .ui-datatable-even { background-color: $form-color-lighter $i; } @@ -742,8 +744,9 @@ body { border-bottom: 1px solid transparent; background: $form-color; } -.card-header > a{ - color:white; + +.card-header > a { + color: white; } @@ -758,7 +761,7 @@ textarea { .poster { box-shadow: 5px 5px 30px #000000; - border-radius: 30px; + border-radius: 30px; } @@ -776,9 +779,10 @@ textarea { box-shadow: none; } -.ui-state-default.ui-unselectable-text{ - display:none; +.ui-state-default.ui-unselectable-text { + display: none; } + .ui-treetable-toggler.fa.fa-fw.ui-clickable.fa-caret-right, .ui-treetable-toggler.fa.fa-fw.ui-clickable.fa-caret-down { display: none; @@ -795,6 +799,20 @@ textarea { .ui-state-default { border: 1px solid $form-color-lighter; } + .ui-treetable tbody td { - white-space:inherit; -} \ No newline at end of file + white-space: inherit; +} + +table a:not(.btn) { + text-decoration: none; +} + +a > h4 { + color: #df691a; + text-decoration: none; +} + +a > h4:hover { + text-decoration: underline; +} diff --git a/src/Ombi/Controllers/IdentityController.cs b/src/Ombi/Controllers/IdentityController.cs index d94f7bd4e..b4321c703 100644 --- a/src/Ombi/Controllers/IdentityController.cs +++ b/src/Ombi/Controllers/IdentityController.cs @@ -15,6 +15,7 @@ using Microsoft.Extensions.Options; using Ombi.Attributes; using Ombi.Config; +using Ombi.Core.Authentication; using Ombi.Core.Claims; using Ombi.Core.Helpers; using Ombi.Core.Models.UI; @@ -41,7 +42,7 @@ namespace Ombi.Controllers [Produces("application/json")] public class IdentityController : Controller { - public IdentityController(UserManager user, IMapper mapper, RoleManager rm, IEmailProvider prov, + public IdentityController(OmbiUserManager user, IMapper mapper, RoleManager rm, IEmailProvider prov, ISettingsService s, ISettingsService c, IOptions userSettings, @@ -57,7 +58,7 @@ namespace Ombi.Controllers WelcomeEmail = welcome; } - private UserManager UserManager { get; } + private OmbiUserManager UserManager { get; } private RoleManager RoleManager { get; } private IMapper Mapper { get; } private IEmailProvider EmailProvider { get; } @@ -178,7 +179,8 @@ namespace Ombi.Controllers EmailAddress = user.Email, UserType = (Core.Models.UserType)(int)user.UserType, Claims = new List(), - IsSetup = !string.IsNullOrEmpty(user.PasswordHash) + LastLoggedIn = user.LastLoggedIn, + HasLoggedIn = user.LastLoggedIn.HasValue }; foreach (var role in userRoles) diff --git a/src/Ombi/Controllers/TokenController.cs b/src/Ombi/Controllers/TokenController.cs index bbb2379da..1a20af0c9 100644 --- a/src/Ombi/Controllers/TokenController.cs +++ b/src/Ombi/Controllers/TokenController.cs @@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; +using Ombi.Core.Authentication; using Ombi.Core.Claims; using Ombi.Models; using Ombi.Models.Identity; @@ -21,7 +22,7 @@ namespace Ombi.Controllers [Produces("application/json")] public class TokenController { - public TokenController(UserManager um, IOptions ta, + public TokenController(OmbiUserManager um, IOptions ta, IApplicationConfigRepository config, IAuditRepository audit, ITokenRepository token) { _userManager = um; @@ -35,7 +36,7 @@ namespace Ombi.Controllers private IApplicationConfigRepository _config; private readonly IAuditRepository _audit; private readonly ITokenRepository _token; - private readonly UserManager _userManager; + private readonly OmbiUserManager _userManager; /// /// Gets the token. @@ -65,6 +66,9 @@ namespace Ombi.Controllers return new UnauthorizedResult(); } + user.LastLoggedIn = DateTime.UtcNow; + await _userManager.UpdateAsync(user); + var claims = new List { new Claim(JwtRegisteredClaimNames.Sub, user.UserName), diff --git a/src/Ombi/Program.cs b/src/Ombi/Program.cs index b41975950..2567d4c78 100644 --- a/src/Ombi/Program.cs +++ b/src/Ombi/Program.cs @@ -15,7 +15,6 @@ namespace Ombi public class Program { private static string UrlArgs { get; set; } - private static string WebRoot { get; set; } public static void Main(string[] args) { Console.Title = "Ombi"; @@ -26,16 +25,11 @@ namespace Ombi .WithParsed(o => { host = o.Host; - WebRoot = Path.Combine(o.WebRoot, "wwwroot"); storagePath = o.StoragePath; }); Console.WriteLine(HelpOutput(result)); - if (string.IsNullOrEmpty(WebRoot)) - { - WebRoot = Path.Combine(WebHost.CreateDefaultBuilder().GetSetting("contentRoot"), "wwwroot"); - } UrlArgs = host; var urlValue = string.Empty; @@ -73,7 +67,6 @@ namespace Ombi .UseStartup() .UseUrls(UrlArgs) .PreferHostingUrls(true) - .UseWebRoot(WebRoot) .Build(); private static string HelpOutput(ParserResult args) @@ -100,11 +93,6 @@ namespace Ombi [Option('s', "storage", Required = false, HelpText = "Storage path, where we save the logs and database")] public string StoragePath { get; set; } - - [Option('w', "webroot", Required = false, - HelpText = "(Root Path for Reverse Proxies) If not specified, the default is \"(Working Directory)\", if the path exists. If the path doesn\'t exist, then a no-op file provider is used." - ,Default = "")] - public string WebRoot { get; set; } - + } } diff --git a/src/Ombi/Startup.cs b/src/Ombi/Startup.cs index 87600a3ca..8558f91ee 100644 --- a/src/Ombi/Startup.cs +++ b/src/Ombi/Startup.cs @@ -29,6 +29,7 @@ using Microsoft.Extensions.Options; using Microsoft.Extensions.PlatformAbstractions; using Microsoft.IdentityModel.Tokens; using Ombi.Config; +using Ombi.Core.Authentication; using Ombi.Core.Claims; using Ombi.Core.Settings; using Ombi.DependencyInjection; @@ -85,7 +86,8 @@ namespace Ombi services.AddIdentity() .AddEntityFrameworkStores() - .AddDefaultTokenProviders(); + .AddDefaultTokenProviders() + .AddUserManager(); services.Configure(options => {