From 8c9ad9b414fdc6c88bdb911d6057ae5d38783b98 Mon Sep 17 00:00:00 2001 From: tidusjar Date: Fri, 30 Sep 2022 21:52:21 +0100 Subject: [PATCH] =?UTF-8?q?fix(importer):=20=F0=9F=90=9B=20Allow=20you=20t?= =?UTF-8?q?o=20only=20import=20Plex=20Admins=20without=20the=20Plex=20User?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PlexUserImporterTests.cs | 328 ++++++++++++++++++ .../Jobs/Plex/PlexUserImporter.cs | 158 +++++---- 2 files changed, 414 insertions(+), 72 deletions(-) create mode 100644 src/Ombi.Schedule.Tests/PlexUserImporterTests.cs diff --git a/src/Ombi.Schedule.Tests/PlexUserImporterTests.cs b/src/Ombi.Schedule.Tests/PlexUserImporterTests.cs new file mode 100644 index 000000000..ae29f1ddf --- /dev/null +++ b/src/Ombi.Schedule.Tests/PlexUserImporterTests.cs @@ -0,0 +1,328 @@ +using Microsoft.AspNetCore.Identity; +using Moq; +using Moq.AutoMock; +using NUnit.Framework; +using Ombi.Api.Plex; +using Ombi.Api.Plex.Models; +using Ombi.Api.Plex.Models.Friends; +using Ombi.Core.Authentication; +using Ombi.Core.Settings; +using Ombi.Core.Settings.Models.External; +using Ombi.Helpers; +using Ombi.Hubs; +using Ombi.Schedule.Jobs.Plex; +using Ombi.Settings.Settings.Models; +using Ombi.Store.Entities; +using Ombi.Test.Common; +using Ombi.Tests; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ombi.Schedule.Tests +{ + [TestFixture] + public class PlexUserImporterTests + { + private List _users = new List + { + new OmbiUser { Id = Guid.NewGuid().ToString("N"), UserName="abc", NormalizedUserName = "ABC", UserType = UserType.LocalUser}, + new OmbiUser { Id = Guid.NewGuid().ToString("N"), UserName="sys", NormalizedUserName = "SYS", UserType = UserType.SystemUser}, + new OmbiUser { Id = Guid.NewGuid().ToString("N"), UserName="plex", NormalizedUserName = "PLEX", UserType = UserType.PlexUser, ProviderUserId = "PLEX_ID", Email = "dupe"}, + }; + private AutoMocker _mocker; + private PlexUserImporter _subject; + + [SetUp] + public void SetUp() + { + _mocker = new AutoMocker(); + + var um = MockHelper.MockUserManager(_users); + var hub = SignalRHelper.MockHub(); + + _mocker.Use(um); + _mocker.Use(hub); + + _mocker.Setup, Task>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings + { + Enable = true, + Servers = new List + { + new PlexServers { Name = "Test", MachineIdentifier = "123", PlexAuthToken = "abc" } + } + }); + _subject = _mocker.CreateInstance(); + } + + [Test] + public async Task Import_Exits_WhenNot_Enabled() + { + _mocker.Setup, Task>(x => x.GetSettingsAsync()) + .ReturnsAsync(new UserManagementSettings { ImportPlexAdmin = false, ImportPlexUsers = false }); + + await _subject.Execute(null); + + _mocker.Verify(x => x.CreateAsync(It.IsAny()), Times.Never); + } + + [Test] + public async Task Import_Exits_When_Plex_Not_Enabled() + { + _mocker.Setup, Task>(x => x.GetSettingsAsync()) + .ReturnsAsync(new UserManagementSettings { ImportPlexAdmin = true, ImportPlexUsers = true }); + + _mocker.Setup, Task>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = false }); + + await _subject.Execute(null); + + _mocker.Verify(x => x.CreateAsync(It.IsAny()), Times.Never); + } + + [Test] + public async Task Import_Exits_When_Plex_No_AuthToken() + { + _mocker.Setup, Task>(x => x.GetSettingsAsync()) + .ReturnsAsync(new UserManagementSettings { ImportPlexAdmin = true, ImportPlexUsers = true }); + _mocker.Setup, Task>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings + { + Enable = true, + Servers = new List + { + new PlexServers { Name = "Test", MachineIdentifier = "123", PlexAuthToken = null } + } + }); + + await _subject.Execute(null); + + _mocker.Verify(x => x.CreateAsync(It.IsAny()), Times.Never); + } + + [Test] + public async Task Import_Only_Imports_Plex_Admin() + { + _mocker.Setup, Task>(x => x.GetSettingsAsync()) + .ReturnsAsync(new UserManagementSettings { ImportPlexAdmin = true, ImportPlexUsers = false }); + _mocker.Setup>(x => x.GetAccount(It.IsAny())).ReturnsAsync(new PlexAccount + { + user = new User + { + email = "email", + authentication_token = "user_token", + title = "user_title", + username = "user_username", + id = "user_id", + } + }); + _mocker.Setup>(x => x.CreateAsync(It.Is(x => x.UserName == "user_username" && x.Email == "email" && x.ProviderUserId == "user_id" && x.UserType == UserType.PlexUser))) + .ReturnsAsync(IdentityResult.Success); + _mocker.Setup>(x => x.AddToRoleAsync(It.Is(x => x.UserName == "user_username"), It.Is(x => x == OmbiRoles.Admin))) + .ReturnsAsync(IdentityResult.Success); + + await _subject.Execute(null); + + _mocker.Verify(x => x.CreateAsync(It.IsAny()), Times.Once); + } + + [Test] + public async Task Import_Only_Imports_Plex_Admin_Already_Exists() + { + _mocker.Setup, Task>(x => x.GetSettingsAsync()) + .ReturnsAsync(new UserManagementSettings { ImportPlexAdmin = true, ImportPlexUsers = false }); + _mocker.Setup>(x => x.GetAccount(It.IsAny())).ReturnsAsync(new PlexAccount + { + user = new User + { + email = "email", + authentication_token = "user_token", + title = "user_title", + username = "newUsername", + id = "PLEX_ID", + } + }); + _mocker.Setup>(x => x.CreateAsync(It.Is(x => x.UserName == "user_username" && x.Email == "email" && x.ProviderUserId == "user_id" && x.UserType == UserType.PlexUser))) + .ReturnsAsync(IdentityResult.Success); + _mocker.Setup>(x => x.AddToRoleAsync(It.Is(x => x.UserName == "user_username"), It.Is(x => x == OmbiRoles.Admin))) + .ReturnsAsync(IdentityResult.Success); + + await _subject.Execute(null); + + _mocker.Verify(x => x.CreateAsync(It.IsAny()), Times.Never); + _mocker.Verify(x => x.UpdateAsync(It.Is(x => x.Email == "email" && x.UserName == "newUsername")), Times.Once); + } + + [Test] + public async Task Import_Only_Imports_Plex_Admin_Username_Clash() + { + _mocker.Setup, Task>(x => x.GetSettingsAsync()) + .ReturnsAsync(new UserManagementSettings { ImportPlexAdmin = true, ImportPlexUsers = false }); + _mocker.Setup>(x => x.GetAccount(It.IsAny())).ReturnsAsync(new PlexAccount + { + user = new User + { + email = "email", + authentication_token = "user_token", + title = "user_title", + username = "abc", + id = "nah", + } + }); + _mocker.Setup>(x => x.CreateAsync(It.Is(x => x.UserName == "user_username" && x.Email == "email" && x.ProviderUserId == "user_id" && x.UserType == UserType.PlexUser))) + .ReturnsAsync(IdentityResult.Success); + _mocker.Setup>(x => x.AddToRoleAsync(It.Is(x => x.UserName == "user_username"), It.Is(x => x == OmbiRoles.Admin))) + .ReturnsAsync(IdentityResult.Success); + + await _subject.Execute(null); + + _mocker.Verify(x => x.CreateAsync(It.IsAny()), Times.Never); + _mocker.Verify(x => x.UpdateAsync(It.IsAny()), Times.Never); + } + + + + [Test] + public async Task Import_Doesnt_Import_Banned_Users() + { + _mocker.Setup, Task>(x => x.GetSettingsAsync()) + .ReturnsAsync(new UserManagementSettings { ImportPlexAdmin = false, ImportPlexUsers = true, BannedPlexUserIds = new List { "Banned" } }); + _mocker.Setup>(x => x.GetUsers(It.IsAny())).ReturnsAsync(new PlexFriends + { + User = new UserFriends[] + { + new UserFriends + { + Email = "email", + Id = "Banned", + Title = "title", + Username = "username" + } + } + }); + + await _subject.Execute(null); + + _mocker.Verify(x => x.CreateAsync(It.IsAny()), Times.Never); + } + + [Test] + public async Task Import_Doesnt_Import_Managed_User() + { + _mocker.Setup, Task>(x => x.GetSettingsAsync()) + .ReturnsAsync(new UserManagementSettings { ImportPlexAdmin = false, ImportPlexUsers = true }); + _mocker.Setup>(x => x.GetUsers(It.IsAny())).ReturnsAsync(new PlexFriends + { + User = new UserFriends[] + { + new UserFriends + { + Email = "email", + Id = "id", + Title = "title", + } + } + }); + + await _subject.Execute(null); + + _mocker.Verify(x => x.CreateAsync(It.IsAny()), Times.Never); + } + + [Test] + public async Task Import_Doesnt_Import_DuplicateEmail() + { + _mocker.Setup, Task>(x => x.GetSettingsAsync()) + .ReturnsAsync(new UserManagementSettings { ImportPlexAdmin = false, ImportPlexUsers = true }); + _mocker.Setup>(x => x.GetUsers(It.IsAny())).ReturnsAsync(new PlexFriends + { + User = new UserFriends[] + { + new UserFriends + { + Email = "dupe", + Id = "id", + Title = "title", + Username = "username" + } + } + }); + + _mocker.Setup>(x => x.FindByEmailAsync("dupe")).ReturnsAsync(new OmbiUser()); + + await _subject.Execute(null); + + _mocker.Verify(x => x.CreateAsync(It.IsAny()), Times.Never); + } + + [Test] + public async Task Import_Created_Plex_User() + { + _mocker.Setup, Task>(x => x.GetSettingsAsync()) + .ReturnsAsync(new UserManagementSettings { ImportPlexAdmin = false, ImportPlexUsers = true, DefaultRoles = new List + { + OmbiRoles.RequestMovie + } + }); + _mocker.Setup>(x => x.GetUsers(It.IsAny())).ReturnsAsync(new PlexFriends + { + User = new UserFriends[] + { + new UserFriends + { + Email = "email", + Id = "id", + Username = "plex" + } + } + }); + + _mocker.Setup>(x => x.CreateAsync(It.Is(x => x.UserName == "plex" && x.Email == "email" && x.ProviderUserId == "id" && x.UserType == UserType.PlexUser))) + .ReturnsAsync(IdentityResult.Success); + + _mocker.Setup>(x => x.AddToRoleAsync(It.Is(x => x.UserName == "plex"), OmbiRoles.RequestMovie)) + .ReturnsAsync(IdentityResult.Success); + + await _subject.Execute(null); + + _mocker.Verify(x => x.CreateAsync(It.IsAny()), Times.Once); + } + + [Test] + public async Task Import_Update_Plex_User() + { + _mocker.Setup, Task>(x => x.GetSettingsAsync()) + .ReturnsAsync(new UserManagementSettings + { + ImportPlexAdmin = false, + ImportPlexUsers = true, + DefaultRoles = new List + { + OmbiRoles.RequestMovie + } + }); + _mocker.Setup>(x => x.GetUsers(It.IsAny())).ReturnsAsync(new PlexFriends + { + User = new UserFriends[] + { + new UserFriends + { + Email = "email", + Id = "PLEX_ID", + Username = "user" + } + } + }); + + _mocker.Setup>(x => x.CreateAsync(It.Is(x => x.UserName == "plex" && x.Email == "email" && x.ProviderUserId == "id" && x.UserType == UserType.PlexUser))) + .ReturnsAsync(IdentityResult.Success); + + _mocker.Setup>(x => x.AddToRoleAsync(It.Is(x => x.UserName == "plex"), OmbiRoles.RequestMovie)) + .ReturnsAsync(IdentityResult.Success); + + await _subject.Execute(null); + + _mocker.Verify(x => x.UpdateAsync(It.Is(x => x.ProviderUserId == "PLEX_ID" && x.Email == "email" && x.UserName == "user")), Times.Once); + } + } +} diff --git a/src/Ombi.Schedule/Jobs/Plex/PlexUserImporter.cs b/src/Ombi.Schedule/Jobs/Plex/PlexUserImporter.cs index 56872c1a8..4e9df54fc 100644 --- a/src/Ombi.Schedule/Jobs/Plex/PlexUserImporter.cs +++ b/src/Ombi.Schedule/Jobs/Plex/PlexUserImporter.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.SignalR; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Ombi.Api.Plex; +using Ombi.Core.Authentication; using Ombi.Core.Settings; using Ombi.Core.Settings.Models.External; using Ombi.Helpers; @@ -19,7 +20,7 @@ namespace Ombi.Schedule.Jobs.Plex { public class PlexUserImporter : IPlexUserImporter { - public PlexUserImporter(IPlexApi api, UserManager um, ILogger log, + public PlexUserImporter(IPlexApi api, OmbiUserManager um, ILogger log, ISettingsService plexSettings, ISettingsService ums, IHubContext hub) { _api = api; @@ -33,7 +34,7 @@ namespace Ombi.Schedule.Jobs.Plex } private readonly IPlexApi _api; - private readonly UserManager _userManager; + private readonly OmbiUserManager _userManager; private readonly ILogger _log; private readonly ISettingsService _plexSettings; private readonly ISettingsService _userManagementSettings; @@ -43,17 +44,17 @@ namespace Ombi.Schedule.Jobs.Plex public async Task Execute(IJobExecutionContext job) { var userManagementSettings = await _userManagementSettings.GetSettingsAsync(); - if (!userManagementSettings.ImportPlexUsers) + if (!userManagementSettings.ImportPlexUsers && !userManagementSettings.ImportPlexAdmin) { return; } + var settings = await _plexSettings.GetSettingsAsync(); if (!settings.Enable) { return; } - await _notification.Clients.Clients(NotificationHub.AdminConnectionIds) .SendAsync(NotificationHub.NotificationEvent, "Plex User Importer Started"); var allUsers = await _userManager.Users.Where(x => x.UserType == UserType.PlexUser).ToListAsync(); @@ -64,92 +65,97 @@ namespace Ombi.Schedule.Jobs.Plex continue; } - await ImportAdmin(userManagementSettings, server, allUsers); + if (userManagementSettings.ImportPlexAdmin) + { + await ImportAdmin(userManagementSettings, server, allUsers); + } + if (userManagementSettings.ImportPlexUsers) + { + await ImportPlexUsers(userManagementSettings, allUsers, server); + } + } - var users = await _api.GetUsers(server.PlexAuthToken); + await _notification.Clients.Clients(NotificationHub.AdminConnectionIds) + .SendAsync(NotificationHub.NotificationEvent, "Plex User Importer Finished"); + } - foreach (var plexUser in users.User) + private async Task ImportPlexUsers(UserManagementSettings userManagementSettings, List allUsers, PlexServers server) + { + var users = await _api.GetUsers(server.PlexAuthToken); + + foreach (var plexUser in users.User) + { + // Check if we should import this user + if (userManagementSettings.BannedPlexUserIds.Contains(plexUser.Id)) { - // Check if we should import this user - if (userManagementSettings.BannedPlexUserIds.Contains(plexUser.Id)) + // Do not import these, they are not allowed into the country. + continue; + } + + // 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 == plexUser.Id); + if (existingPlexUser == null) + { + + if (!plexUser.Username.HasValue()) { - // Do not import these, they are not allowed into the country. + _log.LogInformation("Could not create Plex user since the have no username, PlexUserId: {0}", plexUser.Id); continue; } - // 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 == plexUser.Id); - if (existingPlexUser == null) + if ((plexUser.Email.HasValue()) && await _userManager.FindByEmailAsync(plexUser.Email) != null) { - - if (!plexUser.Username.HasValue()) - { - _log.LogInformation("Could not create Plex user since the have no username, PlexUserId: {0}", plexUser.Id); - continue; - } - - if ((plexUser.Email.HasValue()) && await _userManager.FindByEmailAsync(plexUser.Email) != null) - { - _log.LogWarning($"Cannot add user {plexUser.Username} because their email address is already in Ombi, skipping this user"); - continue; - } - // Create this users - // We do not store a password against the user since they will authenticate via Plex - var newUser = new OmbiUser - { - UserType = UserType.PlexUser, - UserName = plexUser?.Username ?? plexUser.Id, - ProviderUserId = plexUser.Id, - Email = plexUser?.Email ?? string.Empty, - Alias = string.Empty, - MovieRequestLimit = userManagementSettings.MovieRequestLimit, - MovieRequestLimitType = userManagementSettings.MovieRequestLimitType, - EpisodeRequestLimit = userManagementSettings.EpisodeRequestLimit, - EpisodeRequestLimitType = userManagementSettings.EpisodeRequestLimitType, - MusicRequestLimit = userManagementSettings.MusicRequestLimit, - MusicRequestLimitType = userManagementSettings.MusicRequestLimitType, - StreamingCountry = userManagementSettings.DefaultStreamingCountry - }; - _log.LogInformation("Creating Plex user {0}", newUser.UserName); - var result = await _userManager.CreateAsync(newUser); - if (!LogResult(result)) - { - continue; - } - if (userManagementSettings.DefaultRoles.Any()) + _log.LogWarning($"Cannot add user {plexUser.Username} because their email address is already in Ombi, skipping this user"); + continue; + } + // Create this users + // We do not store a password against the user since they will authenticate via Plex + var newUser = new OmbiUser + { + UserType = UserType.PlexUser, + UserName = plexUser?.Username ?? plexUser.Id, + ProviderUserId = plexUser.Id, + Email = plexUser?.Email ?? string.Empty, + Alias = string.Empty, + MovieRequestLimit = userManagementSettings.MovieRequestLimit, + MovieRequestLimitType = userManagementSettings.MovieRequestLimitType, + EpisodeRequestLimit = userManagementSettings.EpisodeRequestLimit, + EpisodeRequestLimitType = userManagementSettings.EpisodeRequestLimitType, + MusicRequestLimit = userManagementSettings.MusicRequestLimit, + MusicRequestLimitType = userManagementSettings.MusicRequestLimitType, + StreamingCountry = userManagementSettings.DefaultStreamingCountry + }; + _log.LogInformation("Creating Plex user {0}", newUser.UserName); + var result = await _userManager.CreateAsync(newUser); + if (!LogResult(result)) + { + continue; + } + if (userManagementSettings.DefaultRoles.Any()) + { + // Get the new user object to avoid any concurrency failures + var dbUser = + await _userManager.Users.FirstOrDefaultAsync(x => x.UserName == newUser.UserName); + foreach (var defaultRole in userManagementSettings.DefaultRoles) { - // Get the new user object to avoid any concurrency failures - var dbUser = - await _userManager.Users.FirstOrDefaultAsync(x => x.UserName == newUser.UserName); - foreach (var defaultRole in userManagementSettings.DefaultRoles) - { - await _userManager.AddToRoleAsync(dbUser, defaultRole); - } + await _userManager.AddToRoleAsync(dbUser, defaultRole); } } - else - { - // Do we need to update this user? - existingPlexUser.Email = plexUser.Email; - existingPlexUser.UserName = plexUser.Username; + } + else + { + // Do we need to update this user? + existingPlexUser.Email = plexUser.Email; + existingPlexUser.UserName = plexUser.Username; - await _userManager.UpdateAsync(existingPlexUser); - } + await _userManager.UpdateAsync(existingPlexUser); } } - - await _notification.Clients.Clients(NotificationHub.AdminConnectionIds) - .SendAsync(NotificationHub.NotificationEvent, "Plex User Importer Finished"); } private async Task ImportAdmin(UserManagementSettings settings, PlexServers server, List allUsers) { - if (!settings.ImportPlexAdmin) - { - return; - } - var plexAdmin = (await _api.GetAccount(server.PlexAuthToken)).user; // Check if the admin is already in the DB @@ -166,6 +172,14 @@ namespace Ombi.Schedule.Jobs.Plex return; } + // Ensure we don't have a user with the same username + var normalUsername = plexAdmin.username.ToUpperInvariant(); + if (await _userManager.Users.AnyAsync(x => x.NormalizedUserName == normalUsername)) + { + _log.LogWarning($"Cannot add user {plexAdmin.username} because their username is already in Ombi, skipping this user"); + return; + } + var newUser = new OmbiUser { UserType = UserType.PlexUser,