From 92266c7a586030153d16b4fbc399a225674317b0 Mon Sep 17 00:00:00 2001 From: tidusjar Date: Thu, 6 Apr 2017 21:31:54 +0100 Subject: [PATCH 001/725] Upgrade to .Net Standard 1.6 --- Ombi/Ombi.Api/Ombi.Api.csproj | 2 +- Ombi/Ombi.Core/Ombi.Core.csproj | 2 +- Ombi/Ombi.Core/Settings/ISettingsService.cs | 4 +- .../Ombi.DependencyInjection.csproj | 2 +- .../Ombi.Helpers.Tests.csproj | 11 ++ Ombi/Ombi.Helpers/Ombi.Helpers.csproj | 2 +- Ombi/Ombi.Helpers/StringCipher.cs | 107 ++++++------------ Ombi/Ombi.Store/Ombi.Store.csproj | 2 +- .../Ombi.TheMovieDbApi.csproj | 2 +- Ombi/Ombi.sln | 11 +- 10 files changed, 65 insertions(+), 80 deletions(-) create mode 100644 Ombi/Ombi.Helpers.Tests/Ombi.Helpers.Tests.csproj diff --git a/Ombi/Ombi.Api/Ombi.Api.csproj b/Ombi/Ombi.Api/Ombi.Api.csproj index 80457725a..b3d86c156 100644 --- a/Ombi/Ombi.Api/Ombi.Api.csproj +++ b/Ombi/Ombi.Api/Ombi.Api.csproj @@ -1,7 +1,7 @@  - netstandard1.4 + netstandard1.6 diff --git a/Ombi/Ombi.Core/Ombi.Core.csproj b/Ombi/Ombi.Core/Ombi.Core.csproj index 078f56b95..ed719e074 100644 --- a/Ombi/Ombi.Core/Ombi.Core.csproj +++ b/Ombi/Ombi.Core/Ombi.Core.csproj @@ -1,7 +1,7 @@  - netstandard1.4 + netstandard1.6 diff --git a/Ombi/Ombi.Core/Settings/ISettingsService.cs b/Ombi/Ombi.Core/Settings/ISettingsService.cs index f781676a0..7fe6b4f6f 100644 --- a/Ombi/Ombi.Core/Settings/ISettingsService.cs +++ b/Ombi/Ombi.Core/Settings/ISettingsService.cs @@ -8,7 +8,7 @@ namespace Ombi.Core.Settings Task GetSettingsAsync(); bool SaveSettings(T model); Task SaveSettingsAsync(T model); - bool Delete(T model); - Task DeleteAsync(T model); + void Delete(T model); + Task DeleteAsync(T model); } } \ No newline at end of file diff --git a/Ombi/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj b/Ombi/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj index 0be698b01..828d87026 100644 --- a/Ombi/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj +++ b/Ombi/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj @@ -1,7 +1,7 @@  - netstandard1.4 + netstandard1.6 diff --git a/Ombi/Ombi.Helpers.Tests/Ombi.Helpers.Tests.csproj b/Ombi/Ombi.Helpers.Tests/Ombi.Helpers.Tests.csproj new file mode 100644 index 000000000..3aabbe61a --- /dev/null +++ b/Ombi/Ombi.Helpers.Tests/Ombi.Helpers.Tests.csproj @@ -0,0 +1,11 @@ + + + + netstandard1.6 + + + + + + + \ No newline at end of file diff --git a/Ombi/Ombi.Helpers/Ombi.Helpers.csproj b/Ombi/Ombi.Helpers/Ombi.Helpers.csproj index 498e443c1..2a9ec6330 100644 --- a/Ombi/Ombi.Helpers/Ombi.Helpers.csproj +++ b/Ombi/Ombi.Helpers/Ombi.Helpers.csproj @@ -1,7 +1,7 @@  - netstandard1.4 + netstandard1.6 diff --git a/Ombi/Ombi.Helpers/StringCipher.cs b/Ombi/Ombi.Helpers/StringCipher.cs index 152dc3f0d..aaa33d289 100644 --- a/Ombi/Ombi.Helpers/StringCipher.cs +++ b/Ombi/Ombi.Helpers/StringCipher.cs @@ -1,6 +1,5 @@ using System; using System.IO; -using System.Linq; using System.Security.Cryptography; using System.Text; @@ -8,12 +7,6 @@ namespace Ombi.Helpers { public class StringCipher { - // This constant determines the number of iterations for the password bytes generation function. - private const int DerivationIterations = 1000; - // This constant is used to determine the keysize of the encryption algorithm in bits. - // We divide this by 8 within the code below to get the equivalent number of bytes. - private const int Keysize = 256; - /// /// Decrypts the specified cipher text. /// @@ -22,39 +15,32 @@ namespace Ombi.Helpers /// public static string Decrypt(string cipherText, string passPhrase) { - // Get the complete stream of bytes that represent: - // [32 bytes of Salt] + [32 bytes of IV] + [n bytes of CipherText] - var cipherTextBytesWithSaltAndIv = Convert.FromBase64String(cipherText); - // Get the saltbytes by extracting the first 32 bytes from the supplied cipherText bytes. - var saltStringBytes = cipherTextBytesWithSaltAndIv.Take(Keysize / 8).ToArray(); - // Get the IV bytes by extracting the next 32 bytes from the supplied cipherText bytes. - var ivStringBytes = cipherTextBytesWithSaltAndIv.Skip(Keysize / 8).Take(Keysize / 8).ToArray(); - // Get the actual cipher text bytes by removing the first 64 bytes from the cipherText string. - var cipherTextBytes = cipherTextBytesWithSaltAndIv.Skip((Keysize / 8) * 2).Take(cipherTextBytesWithSaltAndIv.Length - ((Keysize / 8) * 2)).ToArray(); + var fullCipher = Convert.FromBase64String(cipherText); + + var iv = new byte[16]; + var cipher = new byte[16]; + + Buffer.BlockCopy(fullCipher, 0, iv, 0, iv.Length); + Buffer.BlockCopy(fullCipher, iv.Length, cipher, 0, iv.Length); + var key = Encoding.UTF8.GetBytes(passPhrase); - using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations)) + using (var aesAlg = Aes.Create()) { - var keyBytes = password.GetBytes(Keysize / 8); - var aes = Aes.Create(); - using (var symmetricKey = new RijndaelManaged()) + using (var decryptor = aesAlg.CreateDecryptor(key, iv)) { - symmetricKey.BlockSize = 256; - symmetricKey.Mode = CipherMode.CBC; - symmetricKey.Padding = PaddingMode.PKCS7; - using (var decryptor = symmetricKey.CreateDecryptor(keyBytes, ivStringBytes)) + string result; + using (var msDecrypt = new MemoryStream(cipher)) { - using (var memoryStream = new MemoryStream(cipherTextBytes)) + using (var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read)) { - using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read)) + using (var srDecrypt = new StreamReader(csDecrypt)) { - var plainTextBytes = new byte[cipherTextBytes.Length]; - var decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length); - memoryStream.Close(); - cryptoStream.Close(); - return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount); + result = srDecrypt.ReadToEnd(); } } } + + return result; } } } @@ -67,54 +53,33 @@ namespace Ombi.Helpers /// public static string Encrypt(string plainText, string passPhrase) { - // Salt and IV is randomly generated each time, but is preprended to encrypted cipher text - // so that the same Salt and IV values can be used when decrypting. - var saltStringBytes = Generate256BitsOfRandomEntropy(); - var ivStringBytes = Generate256BitsOfRandomEntropy(); - var plainTextBytes = Encoding.UTF8.GetBytes(plainText); - using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations)) + var key = Encoding.UTF8.GetBytes(passPhrase); + + using (var aesAlg = Aes.Create()) { - var keyBytes = password.GetBytes(Keysize / 8); - using (var symmetricKey = new RijndaelManaged()) + using (var encryptor = aesAlg.CreateEncryptor(key, aesAlg.IV)) { - symmetricKey.BlockSize = 256; - symmetricKey.Mode = CipherMode.CBC; - symmetricKey.Padding = PaddingMode.PKCS7; - using (var encryptor = symmetricKey.CreateEncryptor(keyBytes, ivStringBytes)) + using (var msEncrypt = new MemoryStream()) { - using (var memoryStream = new MemoryStream()) + using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write)) + using (var swEncrypt = new StreamWriter(csEncrypt)) { - using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write)) - { - cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length); - cryptoStream.FlushFinalBlock(); - // Create the final bytes as a concatenation of the random salt bytes, the random iv bytes and the cipher bytes. - var cipherTextBytes = saltStringBytes; - cipherTextBytes = cipherTextBytes.Concat(ivStringBytes).ToArray(); - cipherTextBytes = cipherTextBytes.Concat(memoryStream.ToArray()).ToArray(); - memoryStream.Close(); - cryptoStream.Close(); - return Convert.ToBase64String(cipherTextBytes); - } + swEncrypt.Write(plainText); } + + var iv = aesAlg.IV; + + var decryptedContent = msEncrypt.ToArray(); + + var result = new byte[iv.Length + decryptedContent.Length]; + + Buffer.BlockCopy(iv, 0, result, 0, iv.Length); + Buffer.BlockCopy(decryptedContent, 0, result, iv.Length, decryptedContent.Length); + + return Convert.ToBase64String(result); } } } } - - /// - /// Generate256s the bits of random entropy. - /// - /// - private static byte[] Generate256BitsOfRandomEntropy() - { - var randomBytes = new byte[32]; // 32 Bytes will give us 256 bits. - using (var rngCsp = new RNGCryptoServiceProvider()) - { - // Fill the array with cryptographically secure random bytes. - rngCsp.GetBytes(randomBytes); - } - return randomBytes; - } } } \ No newline at end of file diff --git a/Ombi/Ombi.Store/Ombi.Store.csproj b/Ombi/Ombi.Store/Ombi.Store.csproj index 7d9da619e..6dac2a1d5 100644 --- a/Ombi/Ombi.Store/Ombi.Store.csproj +++ b/Ombi/Ombi.Store/Ombi.Store.csproj @@ -1,7 +1,7 @@  - netstandard1.4 + netstandard1.6 diff --git a/Ombi/Ombi.TheMovieDbApi/Ombi.TheMovieDbApi.csproj b/Ombi/Ombi.TheMovieDbApi/Ombi.TheMovieDbApi.csproj index 16516740e..07b575cc8 100644 --- a/Ombi/Ombi.TheMovieDbApi/Ombi.TheMovieDbApi.csproj +++ b/Ombi/Ombi.TheMovieDbApi/Ombi.TheMovieDbApi.csproj @@ -1,7 +1,7 @@  - netstandard1.4 + netstandard1.6 diff --git a/Ombi/Ombi.sln b/Ombi/Ombi.sln index bd42c7e32..cfe27f8fb 100644 --- a/Ombi/Ombi.sln +++ b/Ombi/Ombi.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26228.10 +VisualStudioVersion = 15.0.26228.9 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi", "Ombi\Ombi.csproj", "{C987AA67-AFE1-468F-ACD3-EAD5A48E1F6A}" EndProject @@ -26,6 +26,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Store", "Ombi.Store\Om EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.DependencyInjection", "Ombi.DependencyInjection\Ombi.DependencyInjection.csproj", "{B39E4558-C557-48E7-AA74-19C5CD809617}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Helpers.Tests", "Ombi.Helpers.Tests\Ombi.Helpers.Tests.csproj", "{0E74C637-F5DE-42CF-AF5C-98440676D9F6}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{2DA84300-4B25-42E0-BB9C-4A32A984C87E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -60,6 +64,10 @@ Global {B39E4558-C557-48E7-AA74-19C5CD809617}.Debug|Any CPU.Build.0 = Debug|Any CPU {B39E4558-C557-48E7-AA74-19C5CD809617}.Release|Any CPU.ActiveCfg = Release|Any CPU {B39E4558-C557-48E7-AA74-19C5CD809617}.Release|Any CPU.Build.0 = Release|Any CPU + {0E74C637-F5DE-42CF-AF5C-98440676D9F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0E74C637-F5DE-42CF-AF5C-98440676D9F6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0E74C637-F5DE-42CF-AF5C-98440676D9F6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0E74C637-F5DE-42CF-AF5C-98440676D9F6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -67,5 +75,6 @@ Global GlobalSection(NestedProjects) = preSolution {132DA282-5894-4570-8916-D8C18ED2CE84} = {9293CA11-360A-4C20-A674-B9E794431BF5} {EA31F915-31F9-4318-B521-1500CDF40DDF} = {9293CA11-360A-4C20-A674-B9E794431BF5} + {0E74C637-F5DE-42CF-AF5C-98440676D9F6} = {2DA84300-4B25-42E0-BB9C-4A32A984C87E} EndGlobalSection EndGlobal From bb2e5a4c1c95d8bb5a4ffa4808bad4b7fac64f79 Mon Sep 17 00:00:00 2001 From: tidusjar Date: Thu, 6 Apr 2017 21:47:09 +0100 Subject: [PATCH 002/725] small tweaks --- .../Ombi.Helpers.Tests.csproj | 5 ++ Ombi/Ombi.Helpers.Tests/StringCipherTests.cs | 53 +++++++++++++++++++ Ombi/Ombi.Helpers/StringCipher.cs | 2 +- .../Ombi.TheMovieDbApi.csproj | 1 + 4 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 Ombi/Ombi.Helpers.Tests/StringCipherTests.cs diff --git a/Ombi/Ombi.Helpers.Tests/Ombi.Helpers.Tests.csproj b/Ombi/Ombi.Helpers.Tests/Ombi.Helpers.Tests.csproj index 3aabbe61a..c8047a6b6 100644 --- a/Ombi/Ombi.Helpers.Tests/Ombi.Helpers.Tests.csproj +++ b/Ombi/Ombi.Helpers.Tests/Ombi.Helpers.Tests.csproj @@ -6,6 +6,11 @@ + + + + + \ No newline at end of file diff --git a/Ombi/Ombi.Helpers.Tests/StringCipherTests.cs b/Ombi/Ombi.Helpers.Tests/StringCipherTests.cs new file mode 100644 index 000000000..5866fbc88 --- /dev/null +++ b/Ombi/Ombi.Helpers.Tests/StringCipherTests.cs @@ -0,0 +1,53 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: StringCipherTests.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 NUnit.Framework; +using System.Collections.Generic; + +namespace Ombi.Helpers.Tests +{ + [TestFixture] + public class StringCipherTests + { + [TestCaseSource(nameof(StringData))] + public string AddPrefix(string text, string key) + { + return StringCipher.Encrypt(text, key); + } + + private static IEnumerable StringData + { + get + { + yield return new TestCaseData("password1","key1").Returns("AbcCba").SetName("pascalCase"); + yield return new TestCaseData("").Returns("").SetName("Empty"); + yield return new TestCaseData("12DSAda").Returns("12DSAda").SetName("With numbers"); + } + } + + } +} \ No newline at end of file diff --git a/Ombi/Ombi.Helpers/StringCipher.cs b/Ombi/Ombi.Helpers/StringCipher.cs index aaa33d289..c8e75c588 100644 --- a/Ombi/Ombi.Helpers/StringCipher.cs +++ b/Ombi/Ombi.Helpers/StringCipher.cs @@ -5,7 +5,7 @@ using System.Text; namespace Ombi.Helpers { - public class StringCipher + public static class StringCipher { /// /// Decrypts the specified cipher text. diff --git a/Ombi/Ombi.TheMovieDbApi/Ombi.TheMovieDbApi.csproj b/Ombi/Ombi.TheMovieDbApi/Ombi.TheMovieDbApi.csproj index 07b575cc8..19b57c95c 100644 --- a/Ombi/Ombi.TheMovieDbApi/Ombi.TheMovieDbApi.csproj +++ b/Ombi/Ombi.TheMovieDbApi/Ombi.TheMovieDbApi.csproj @@ -2,6 +2,7 @@ netstandard1.6 + False From 1e4f0d9e1b01c66817e6898832263d4c3c15eb63 Mon Sep 17 00:00:00 2001 From: tidusjar Date: Thu, 6 Apr 2017 21:49:44 +0100 Subject: [PATCH 003/725] revert --- Ombi.Core.Migration/Migrations/Version2210.cs | 2 +- Ombi.Services/Jobs/PlexContentCacher.cs | 9 +- .../EmbyRecentlyAddedNewsletter.cs | 4 +- .../PlexRecentlyAddedNewsletter.cs | 4 +- Ombi/Ombi.Api/Ombi.Api.csproj | 2 +- Ombi/Ombi.Core/Ombi.Core.csproj | 2 +- Ombi/Ombi.Core/Settings/ISettingsService.cs | 4 +- .../Ombi.DependencyInjection.csproj | 2 +- .../Ombi.Helpers.Tests.csproj | 16 --- Ombi/Ombi.Helpers.Tests/StringCipherTests.cs | 53 --------- Ombi/Ombi.Helpers/Ombi.Helpers.csproj | 2 +- Ombi/Ombi.Helpers/StringCipher.cs | 109 ++++++++++++------ Ombi/Ombi.Store/Ombi.Store.csproj | 2 +- .../Ombi.TheMovieDbApi.csproj | 3 +- Ombi/Ombi.sln | 11 +- 15 files changed, 92 insertions(+), 133 deletions(-) delete mode 100644 Ombi/Ombi.Helpers.Tests/Ombi.Helpers.Tests.csproj delete mode 100644 Ombi/Ombi.Helpers.Tests/StringCipherTests.cs diff --git a/Ombi.Core.Migration/Migrations/Version2210.cs b/Ombi.Core.Migration/Migrations/Version2210.cs index 258e4da46..94592174a 100644 --- a/Ombi.Core.Migration/Migrations/Version2210.cs +++ b/Ombi.Core.Migration/Migrations/Version2210.cs @@ -53,7 +53,7 @@ namespace Ombi.Core.Migration.Migrations EmbyEpisodes = embyEp; } - public int Version => 22000; + public int Version => 22100; private IRepository Log { get; } private IRepository PlexContent { get; } private IRepository PlexEpisodes { get; } diff --git a/Ombi.Services/Jobs/PlexContentCacher.cs b/Ombi.Services/Jobs/PlexContentCacher.cs index 5b6dc55d4..d4f872fc2 100644 --- a/Ombi.Services/Jobs/PlexContentCacher.cs +++ b/Ombi.Services/Jobs/PlexContentCacher.cs @@ -276,7 +276,8 @@ namespace Ombi.Services.Jobs Title = m.Title, Type = Store.Models.Plex.PlexMediaType.Movie, Url = m.Url, - ItemId = m.ItemId + ItemId = m.ItemId, + AddedAt = DateTime.UtcNow, }); } } @@ -318,7 +319,8 @@ namespace Ombi.Services.Jobs Type = Store.Models.Plex.PlexMediaType.Show, Url = t.Url, Seasons = ByteConverterHelper.ReturnBytes(t.Seasons), - ItemId = t.ItemId + ItemId = t.ItemId, + AddedAt = DateTime.UtcNow, }); } } @@ -360,7 +362,8 @@ namespace Ombi.Services.Jobs Title = a.Title, Type = Store.Models.Plex.PlexMediaType.Artist, Url = a.Url, - ItemId = "album" + ItemId = "album", + AddedAt = DateTime.UtcNow, }); } } diff --git a/Ombi.Services/Jobs/RecentlyAddedNewsletter/EmbyRecentlyAddedNewsletter.cs b/Ombi.Services/Jobs/RecentlyAddedNewsletter/EmbyRecentlyAddedNewsletter.cs index c4506e843..158d77f0d 100644 --- a/Ombi.Services/Jobs/RecentlyAddedNewsletter/EmbyRecentlyAddedNewsletter.cs +++ b/Ombi.Services/Jobs/RecentlyAddedNewsletter/EmbyRecentlyAddedNewsletter.cs @@ -120,7 +120,7 @@ namespace Ombi.Services.Jobs.RecentlyAddedNewsletter var filteredSeries = series.Where(m => recentlyAdded.All(x => x.ProviderId != m.EmbyId)).ToList(); var info = new List(); - foreach (var m in filteredMovies) + foreach (var m in filteredMovies.OrderByDescending(x => x.AddedAt)) { var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) => Log.Error(exception, "Exception thrown when processing an emby movie for the newsletter, Retrying {0}", timespan)); @@ -210,7 +210,7 @@ namespace Ombi.Services.Jobs.RecentlyAddedNewsletter } else { - foreach (var t in filteredSeries) + foreach (var t in filteredSeries.OrderByDescending(x => x.AddedAt)) { diff --git a/Ombi.Services/Jobs/RecentlyAddedNewsletter/PlexRecentlyAddedNewsletter.cs b/Ombi.Services/Jobs/RecentlyAddedNewsletter/PlexRecentlyAddedNewsletter.cs index 6eda44bec..ca88b4c2b 100644 --- a/Ombi.Services/Jobs/RecentlyAddedNewsletter/PlexRecentlyAddedNewsletter.cs +++ b/Ombi.Services/Jobs/RecentlyAddedNewsletter/PlexRecentlyAddedNewsletter.cs @@ -127,7 +127,7 @@ namespace Ombi.Services.Jobs.RecentlyAddedNewsletter // if this is a test make sure we show something filteredMovies = movie.Take(5).ToList(); } - foreach (var m in filteredMovies) + foreach (var m in filteredMovies.OrderByDescending(x => x.AddedAt)) { var i = Api.GetMetadata(plexSettings.PlexAuthToken, plexSettings.FullUri, m.ItemId); if (i.Video == null) @@ -194,7 +194,7 @@ namespace Ombi.Services.Jobs.RecentlyAddedNewsletter // if this is a test make sure we show something filteredSeries = series.Take(5).ToList(); } - foreach (var t in filteredSeries) + foreach (var t in filteredSeries.OrderByDescending(x => x.AddedAt)) { var i = Api.GetMetadata(plexSettings.PlexAuthToken, plexSettings.FullUri, t.ItemId); if (i.Directory == null) diff --git a/Ombi/Ombi.Api/Ombi.Api.csproj b/Ombi/Ombi.Api/Ombi.Api.csproj index b3d86c156..80457725a 100644 --- a/Ombi/Ombi.Api/Ombi.Api.csproj +++ b/Ombi/Ombi.Api/Ombi.Api.csproj @@ -1,7 +1,7 @@  - netstandard1.6 + netstandard1.4 diff --git a/Ombi/Ombi.Core/Ombi.Core.csproj b/Ombi/Ombi.Core/Ombi.Core.csproj index ed719e074..078f56b95 100644 --- a/Ombi/Ombi.Core/Ombi.Core.csproj +++ b/Ombi/Ombi.Core/Ombi.Core.csproj @@ -1,7 +1,7 @@  - netstandard1.6 + netstandard1.4 diff --git a/Ombi/Ombi.Core/Settings/ISettingsService.cs b/Ombi/Ombi.Core/Settings/ISettingsService.cs index 7fe6b4f6f..f781676a0 100644 --- a/Ombi/Ombi.Core/Settings/ISettingsService.cs +++ b/Ombi/Ombi.Core/Settings/ISettingsService.cs @@ -8,7 +8,7 @@ namespace Ombi.Core.Settings Task GetSettingsAsync(); bool SaveSettings(T model); Task SaveSettingsAsync(T model); - void Delete(T model); - Task DeleteAsync(T model); + bool Delete(T model); + Task DeleteAsync(T model); } } \ No newline at end of file diff --git a/Ombi/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj b/Ombi/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj index 828d87026..0be698b01 100644 --- a/Ombi/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj +++ b/Ombi/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj @@ -1,7 +1,7 @@  - netstandard1.6 + netstandard1.4 diff --git a/Ombi/Ombi.Helpers.Tests/Ombi.Helpers.Tests.csproj b/Ombi/Ombi.Helpers.Tests/Ombi.Helpers.Tests.csproj deleted file mode 100644 index c8047a6b6..000000000 --- a/Ombi/Ombi.Helpers.Tests/Ombi.Helpers.Tests.csproj +++ /dev/null @@ -1,16 +0,0 @@ - - - - netstandard1.6 - - - - - - - - - - - - \ No newline at end of file diff --git a/Ombi/Ombi.Helpers.Tests/StringCipherTests.cs b/Ombi/Ombi.Helpers.Tests/StringCipherTests.cs deleted file mode 100644 index 5866fbc88..000000000 --- a/Ombi/Ombi.Helpers.Tests/StringCipherTests.cs +++ /dev/null @@ -1,53 +0,0 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2017 Jamie Rees -// File: StringCipherTests.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 NUnit.Framework; -using System.Collections.Generic; - -namespace Ombi.Helpers.Tests -{ - [TestFixture] - public class StringCipherTests - { - [TestCaseSource(nameof(StringData))] - public string AddPrefix(string text, string key) - { - return StringCipher.Encrypt(text, key); - } - - private static IEnumerable StringData - { - get - { - yield return new TestCaseData("password1","key1").Returns("AbcCba").SetName("pascalCase"); - yield return new TestCaseData("").Returns("").SetName("Empty"); - yield return new TestCaseData("12DSAda").Returns("12DSAda").SetName("With numbers"); - } - } - - } -} \ No newline at end of file diff --git a/Ombi/Ombi.Helpers/Ombi.Helpers.csproj b/Ombi/Ombi.Helpers/Ombi.Helpers.csproj index 2a9ec6330..498e443c1 100644 --- a/Ombi/Ombi.Helpers/Ombi.Helpers.csproj +++ b/Ombi/Ombi.Helpers/Ombi.Helpers.csproj @@ -1,7 +1,7 @@  - netstandard1.6 + netstandard1.4 diff --git a/Ombi/Ombi.Helpers/StringCipher.cs b/Ombi/Ombi.Helpers/StringCipher.cs index c8e75c588..152dc3f0d 100644 --- a/Ombi/Ombi.Helpers/StringCipher.cs +++ b/Ombi/Ombi.Helpers/StringCipher.cs @@ -1,12 +1,19 @@ using System; using System.IO; +using System.Linq; using System.Security.Cryptography; using System.Text; namespace Ombi.Helpers { - public static class StringCipher + public class StringCipher { + // This constant determines the number of iterations for the password bytes generation function. + private const int DerivationIterations = 1000; + // This constant is used to determine the keysize of the encryption algorithm in bits. + // We divide this by 8 within the code below to get the equivalent number of bytes. + private const int Keysize = 256; + /// /// Decrypts the specified cipher text. /// @@ -15,32 +22,39 @@ namespace Ombi.Helpers /// public static string Decrypt(string cipherText, string passPhrase) { - var fullCipher = Convert.FromBase64String(cipherText); - - var iv = new byte[16]; - var cipher = new byte[16]; - - Buffer.BlockCopy(fullCipher, 0, iv, 0, iv.Length); - Buffer.BlockCopy(fullCipher, iv.Length, cipher, 0, iv.Length); - var key = Encoding.UTF8.GetBytes(passPhrase); + // Get the complete stream of bytes that represent: + // [32 bytes of Salt] + [32 bytes of IV] + [n bytes of CipherText] + var cipherTextBytesWithSaltAndIv = Convert.FromBase64String(cipherText); + // Get the saltbytes by extracting the first 32 bytes from the supplied cipherText bytes. + var saltStringBytes = cipherTextBytesWithSaltAndIv.Take(Keysize / 8).ToArray(); + // Get the IV bytes by extracting the next 32 bytes from the supplied cipherText bytes. + var ivStringBytes = cipherTextBytesWithSaltAndIv.Skip(Keysize / 8).Take(Keysize / 8).ToArray(); + // Get the actual cipher text bytes by removing the first 64 bytes from the cipherText string. + var cipherTextBytes = cipherTextBytesWithSaltAndIv.Skip((Keysize / 8) * 2).Take(cipherTextBytesWithSaltAndIv.Length - ((Keysize / 8) * 2)).ToArray(); - using (var aesAlg = Aes.Create()) + using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations)) { - using (var decryptor = aesAlg.CreateDecryptor(key, iv)) + var keyBytes = password.GetBytes(Keysize / 8); + var aes = Aes.Create(); + using (var symmetricKey = new RijndaelManaged()) { - string result; - using (var msDecrypt = new MemoryStream(cipher)) + symmetricKey.BlockSize = 256; + symmetricKey.Mode = CipherMode.CBC; + symmetricKey.Padding = PaddingMode.PKCS7; + using (var decryptor = symmetricKey.CreateDecryptor(keyBytes, ivStringBytes)) { - using (var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read)) + using (var memoryStream = new MemoryStream(cipherTextBytes)) { - using (var srDecrypt = new StreamReader(csDecrypt)) + using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read)) { - result = srDecrypt.ReadToEnd(); + var plainTextBytes = new byte[cipherTextBytes.Length]; + var decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length); + memoryStream.Close(); + cryptoStream.Close(); + return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount); } } } - - return result; } } } @@ -53,33 +67,54 @@ namespace Ombi.Helpers /// public static string Encrypt(string plainText, string passPhrase) { - var key = Encoding.UTF8.GetBytes(passPhrase); - - using (var aesAlg = Aes.Create()) + // Salt and IV is randomly generated each time, but is preprended to encrypted cipher text + // so that the same Salt and IV values can be used when decrypting. + var saltStringBytes = Generate256BitsOfRandomEntropy(); + var ivStringBytes = Generate256BitsOfRandomEntropy(); + var plainTextBytes = Encoding.UTF8.GetBytes(plainText); + using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations)) { - using (var encryptor = aesAlg.CreateEncryptor(key, aesAlg.IV)) + var keyBytes = password.GetBytes(Keysize / 8); + using (var symmetricKey = new RijndaelManaged()) { - using (var msEncrypt = new MemoryStream()) + symmetricKey.BlockSize = 256; + symmetricKey.Mode = CipherMode.CBC; + symmetricKey.Padding = PaddingMode.PKCS7; + using (var encryptor = symmetricKey.CreateEncryptor(keyBytes, ivStringBytes)) { - using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write)) - using (var swEncrypt = new StreamWriter(csEncrypt)) + using (var memoryStream = new MemoryStream()) { - swEncrypt.Write(plainText); + using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write)) + { + cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length); + cryptoStream.FlushFinalBlock(); + // Create the final bytes as a concatenation of the random salt bytes, the random iv bytes and the cipher bytes. + var cipherTextBytes = saltStringBytes; + cipherTextBytes = cipherTextBytes.Concat(ivStringBytes).ToArray(); + cipherTextBytes = cipherTextBytes.Concat(memoryStream.ToArray()).ToArray(); + memoryStream.Close(); + cryptoStream.Close(); + return Convert.ToBase64String(cipherTextBytes); + } } - - var iv = aesAlg.IV; - - var decryptedContent = msEncrypt.ToArray(); - - var result = new byte[iv.Length + decryptedContent.Length]; - - Buffer.BlockCopy(iv, 0, result, 0, iv.Length); - Buffer.BlockCopy(decryptedContent, 0, result, iv.Length, decryptedContent.Length); - - return Convert.ToBase64String(result); } } } } + + /// + /// Generate256s the bits of random entropy. + /// + /// + private static byte[] Generate256BitsOfRandomEntropy() + { + var randomBytes = new byte[32]; // 32 Bytes will give us 256 bits. + using (var rngCsp = new RNGCryptoServiceProvider()) + { + // Fill the array with cryptographically secure random bytes. + rngCsp.GetBytes(randomBytes); + } + return randomBytes; + } } } \ No newline at end of file diff --git a/Ombi/Ombi.Store/Ombi.Store.csproj b/Ombi/Ombi.Store/Ombi.Store.csproj index 6dac2a1d5..7d9da619e 100644 --- a/Ombi/Ombi.Store/Ombi.Store.csproj +++ b/Ombi/Ombi.Store/Ombi.Store.csproj @@ -1,7 +1,7 @@  - netstandard1.6 + netstandard1.4 diff --git a/Ombi/Ombi.TheMovieDbApi/Ombi.TheMovieDbApi.csproj b/Ombi/Ombi.TheMovieDbApi/Ombi.TheMovieDbApi.csproj index 19b57c95c..16516740e 100644 --- a/Ombi/Ombi.TheMovieDbApi/Ombi.TheMovieDbApi.csproj +++ b/Ombi/Ombi.TheMovieDbApi/Ombi.TheMovieDbApi.csproj @@ -1,8 +1,7 @@  - netstandard1.6 - False + netstandard1.4 diff --git a/Ombi/Ombi.sln b/Ombi/Ombi.sln index cfe27f8fb..bd42c7e32 100644 --- a/Ombi/Ombi.sln +++ b/Ombi/Ombi.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26228.9 +VisualStudioVersion = 15.0.26228.10 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi", "Ombi\Ombi.csproj", "{C987AA67-AFE1-468F-ACD3-EAD5A48E1F6A}" EndProject @@ -26,10 +26,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Store", "Ombi.Store\Om EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.DependencyInjection", "Ombi.DependencyInjection\Ombi.DependencyInjection.csproj", "{B39E4558-C557-48E7-AA74-19C5CD809617}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Helpers.Tests", "Ombi.Helpers.Tests\Ombi.Helpers.Tests.csproj", "{0E74C637-F5DE-42CF-AF5C-98440676D9F6}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{2DA84300-4B25-42E0-BB9C-4A32A984C87E}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -64,10 +60,6 @@ Global {B39E4558-C557-48E7-AA74-19C5CD809617}.Debug|Any CPU.Build.0 = Debug|Any CPU {B39E4558-C557-48E7-AA74-19C5CD809617}.Release|Any CPU.ActiveCfg = Release|Any CPU {B39E4558-C557-48E7-AA74-19C5CD809617}.Release|Any CPU.Build.0 = Release|Any CPU - {0E74C637-F5DE-42CF-AF5C-98440676D9F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0E74C637-F5DE-42CF-AF5C-98440676D9F6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0E74C637-F5DE-42CF-AF5C-98440676D9F6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0E74C637-F5DE-42CF-AF5C-98440676D9F6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -75,6 +67,5 @@ Global GlobalSection(NestedProjects) = preSolution {132DA282-5894-4570-8916-D8C18ED2CE84} = {9293CA11-360A-4C20-A674-B9E794431BF5} {EA31F915-31F9-4318-B521-1500CDF40DDF} = {9293CA11-360A-4C20-A674-B9E794431BF5} - {0E74C637-F5DE-42CF-AF5C-98440676D9F6} = {2DA84300-4B25-42E0-BB9C-4A32A984C87E} EndGlobalSection EndGlobal From 8ce7ff07fcef7ee9ee80cb184f080c0e3167591f Mon Sep 17 00:00:00 2001 From: Jamie Date: Thu, 6 Apr 2017 21:55:44 +0100 Subject: [PATCH 004/725] Update StringCipher.cs --- Ombi/Ombi.Helpers/StringCipher.cs | 113 +++++++++++------------------- 1 file changed, 39 insertions(+), 74 deletions(-) diff --git a/Ombi/Ombi.Helpers/StringCipher.cs b/Ombi/Ombi.Helpers/StringCipher.cs index 152dc3f0d..df10fcb6a 100644 --- a/Ombi/Ombi.Helpers/StringCipher.cs +++ b/Ombi/Ombi.Helpers/StringCipher.cs @@ -1,19 +1,12 @@ -using System; +using System; using System.IO; -using System.Linq; using System.Security.Cryptography; using System.Text; namespace Ombi.Helpers { - public class StringCipher + public static class StringCipher { - // This constant determines the number of iterations for the password bytes generation function. - private const int DerivationIterations = 1000; - // This constant is used to determine the keysize of the encryption algorithm in bits. - // We divide this by 8 within the code below to get the equivalent number of bytes. - private const int Keysize = 256; - /// /// Decrypts the specified cipher text. /// @@ -22,39 +15,32 @@ namespace Ombi.Helpers /// public static string Decrypt(string cipherText, string passPhrase) { - // Get the complete stream of bytes that represent: - // [32 bytes of Salt] + [32 bytes of IV] + [n bytes of CipherText] - var cipherTextBytesWithSaltAndIv = Convert.FromBase64String(cipherText); - // Get the saltbytes by extracting the first 32 bytes from the supplied cipherText bytes. - var saltStringBytes = cipherTextBytesWithSaltAndIv.Take(Keysize / 8).ToArray(); - // Get the IV bytes by extracting the next 32 bytes from the supplied cipherText bytes. - var ivStringBytes = cipherTextBytesWithSaltAndIv.Skip(Keysize / 8).Take(Keysize / 8).ToArray(); - // Get the actual cipher text bytes by removing the first 64 bytes from the cipherText string. - var cipherTextBytes = cipherTextBytesWithSaltAndIv.Skip((Keysize / 8) * 2).Take(cipherTextBytesWithSaltAndIv.Length - ((Keysize / 8) * 2)).ToArray(); + var fullCipher = Convert.FromBase64String(cipherText); + + var iv = new byte[16]; + var cipher = new byte[16]; + + Buffer.BlockCopy(fullCipher, 0, iv, 0, iv.Length); + Buffer.BlockCopy(fullCipher, iv.Length, cipher, 0, iv.Length); + var key = Encoding.UTF8.GetBytes(passPhrase); - using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations)) + using (var aesAlg = Aes.Create()) { - var keyBytes = password.GetBytes(Keysize / 8); - var aes = Aes.Create(); - using (var symmetricKey = new RijndaelManaged()) + using (var decryptor = aesAlg.CreateDecryptor(key, iv)) { - symmetricKey.BlockSize = 256; - symmetricKey.Mode = CipherMode.CBC; - symmetricKey.Padding = PaddingMode.PKCS7; - using (var decryptor = symmetricKey.CreateDecryptor(keyBytes, ivStringBytes)) + string result; + using (var msDecrypt = new MemoryStream(cipher)) { - using (var memoryStream = new MemoryStream(cipherTextBytes)) + using (var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read)) { - using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read)) + using (var srDecrypt = new StreamReader(csDecrypt)) { - var plainTextBytes = new byte[cipherTextBytes.Length]; - var decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length); - memoryStream.Close(); - cryptoStream.Close(); - return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount); + result = srDecrypt.ReadToEnd(); } } } + + return result; } } } @@ -67,54 +53,33 @@ namespace Ombi.Helpers /// public static string Encrypt(string plainText, string passPhrase) { - // Salt and IV is randomly generated each time, but is preprended to encrypted cipher text - // so that the same Salt and IV values can be used when decrypting. - var saltStringBytes = Generate256BitsOfRandomEntropy(); - var ivStringBytes = Generate256BitsOfRandomEntropy(); - var plainTextBytes = Encoding.UTF8.GetBytes(plainText); - using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations)) + var key = Encoding.UTF8.GetBytes(passPhrase); + + using (var aesAlg = Aes.Create()) { - var keyBytes = password.GetBytes(Keysize / 8); - using (var symmetricKey = new RijndaelManaged()) + using (var encryptor = aesAlg.CreateEncryptor(key, aesAlg.IV)) { - symmetricKey.BlockSize = 256; - symmetricKey.Mode = CipherMode.CBC; - symmetricKey.Padding = PaddingMode.PKCS7; - using (var encryptor = symmetricKey.CreateEncryptor(keyBytes, ivStringBytes)) + using (var msEncrypt = new MemoryStream()) { - using (var memoryStream = new MemoryStream()) + using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write)) + using (var swEncrypt = new StreamWriter(csEncrypt)) { - using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write)) - { - cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length); - cryptoStream.FlushFinalBlock(); - // Create the final bytes as a concatenation of the random salt bytes, the random iv bytes and the cipher bytes. - var cipherTextBytes = saltStringBytes; - cipherTextBytes = cipherTextBytes.Concat(ivStringBytes).ToArray(); - cipherTextBytes = cipherTextBytes.Concat(memoryStream.ToArray()).ToArray(); - memoryStream.Close(); - cryptoStream.Close(); - return Convert.ToBase64String(cipherTextBytes); - } + swEncrypt.Write(plainText); } + + var iv = aesAlg.IV; + + var decryptedContent = msEncrypt.ToArray(); + + var result = new byte[iv.Length + decryptedContent.Length]; + + Buffer.BlockCopy(iv, 0, result, 0, iv.Length); + Buffer.BlockCopy(decryptedContent, 0, result, iv.Length, decryptedContent.Length); + + return Convert.ToBase64String(result); } } } } - - /// - /// Generate256s the bits of random entropy. - /// - /// - private static byte[] Generate256BitsOfRandomEntropy() - { - var randomBytes = new byte[32]; // 32 Bytes will give us 256 bits. - using (var rngCsp = new RNGCryptoServiceProvider()) - { - // Fill the array with cryptographically secure random bytes. - rngCsp.GetBytes(randomBytes); - } - return randomBytes; - } } -} \ No newline at end of file +} From d040d06976b112b42d0da35bb5c235921a101d15 Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" Date: Fri, 7 Apr 2017 13:27:13 +0100 Subject: [PATCH 005/725] changes --- Ombi/Ombi.Api/Api.cs | 2 + .../Engine/Interfaces/IRequestEngine.cs | 5 +- Ombi/Ombi.Core/Engine/MovieEngine.cs | 37 ++++++++------- Ombi/Ombi.Core/Engine/RequestEngine.cs | 34 ++++++++++++++ Ombi/Ombi.Core/Models/QualityModel.cs | 8 ++++ .../Ombi.Core/Models/Requests/RequestModel.cs | 5 -- .../Models/Requests/RequestViewModel.cs | 33 +++++++++++++ Ombi/Ombi.Core/Models/RootFolderModel.cs | 9 ++++ Ombi/Ombi.Core/Settings/ISettingsService.cs | 4 +- Ombi/Ombi.Store/Entities/RequestBlobs.cs | 5 +- Ombi/Ombi.TheMovieDbApi/IMovieDbApi.cs | 1 + Ombi/Ombi.TheMovieDbApi/TheMovieDbApi.cs | 8 ++++ Ombi/Ombi/Controllers/RequestController.cs | 10 +++- Ombi/Ombi/Styles/base.scss | 12 +++++ Ombi/Ombi/app/app.component.html | 11 +++-- Ombi/Ombi/app/app.component.ts | 2 + Ombi/Ombi/app/app.module.ts | 16 +++++-- Ombi/Ombi/app/interfaces/IRequestModel.ts | 27 +++++++++++ .../Ombi/app/interfaces/ISearchMovieResult.ts | 6 ++- Ombi/Ombi/app/requests/request.component.html | 46 +++++++++++++++++++ Ombi/Ombi/app/requests/request.component.ts | 24 ++++++++++ Ombi/Ombi/app/search/search.component.html | 6 +-- Ombi/Ombi/app/search/search.component.ts | 16 +++++-- .../Ombi/app/services/notification.service.ts | 30 ++++++++++++ Ombi/Ombi/app/services/request.service.ts | 5 ++ appveyor.yml | 4 +- 26 files changed, 321 insertions(+), 45 deletions(-) create mode 100644 Ombi/Ombi.Core/Models/QualityModel.cs create mode 100644 Ombi/Ombi.Core/Models/Requests/RequestViewModel.cs create mode 100644 Ombi/Ombi.Core/Models/RootFolderModel.cs create mode 100644 Ombi/Ombi/app/interfaces/IRequestModel.ts create mode 100644 Ombi/Ombi/app/requests/request.component.html create mode 100644 Ombi/Ombi/app/requests/request.component.ts create mode 100644 Ombi/Ombi/app/services/notification.service.ts diff --git a/Ombi/Ombi.Api/Api.cs b/Ombi/Ombi.Api/Api.cs index adeb34bfc..931598b50 100644 --- a/Ombi/Ombi.Api/Api.cs +++ b/Ombi/Ombi.Api/Api.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Net.Http; +using System.Net.Http.Headers; using System.Text; using System.Threading.Tasks; using Newtonsoft.Json; @@ -16,6 +17,7 @@ namespace Ombi.Api public async Task Get(Uri uri) { var h = new HttpClient(); + //h.DefaultRequestHeaders.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json")); var response = await h.GetAsync(uri); if (!response.IsSuccessStatusCode) diff --git a/Ombi/Ombi.Core/Engine/Interfaces/IRequestEngine.cs b/Ombi/Ombi.Core/Engine/Interfaces/IRequestEngine.cs index f23604468..ace321444 100644 --- a/Ombi/Ombi.Core/Engine/Interfaces/IRequestEngine.cs +++ b/Ombi/Ombi.Core/Engine/Interfaces/IRequestEngine.cs @@ -1,4 +1,6 @@ -using System.Threading.Tasks; +using System.Collections.Generic; +using System.Threading.Tasks; +using Ombi.Core.Models.Requests; using Ombi.Core.Models.Search; using Ombi.Store.Entities; @@ -8,5 +10,6 @@ namespace Ombi.Core.Engine { Task RequestMovie(SearchMovieViewModel model); bool ShouldAutoApprove(RequestType requestType); + Task> GetRequests(); } } \ No newline at end of file diff --git a/Ombi/Ombi.Core/Engine/MovieEngine.cs b/Ombi/Ombi.Core/Engine/MovieEngine.cs index 219f65d11..fa6eae68a 100644 --- a/Ombi/Ombi.Core/Engine/MovieEngine.cs +++ b/Ombi/Ombi.Core/Engine/MovieEngine.cs @@ -7,6 +7,7 @@ using Ombi.Core.Models.Search; using Ombi.Core.Requests.Models; using Ombi.Helpers; using Ombi.Store.Entities; +using Ombi.TheMovieDbApi; using Ombi.TheMovieDbApi.Models; namespace Ombi.Core.Engine @@ -14,11 +15,14 @@ namespace Ombi.Core.Engine public class MovieEngine : IMovieEngine { - public MovieEngine(IRequestService service) + public MovieEngine(IRequestService service, IMovieDbApi movApi) { RequestService = service; + MovieApi = movApi; } private IRequestService RequestService { get; } + private IMovieDbApi MovieApi { get; } + public async Task> ProcessMovieSearch(string search) { var api = new TheMovieDbApi.TheMovieDbApi(); @@ -99,24 +103,25 @@ namespace Ombi.Core.Engine }; viewMovies.Add(viewMovie); - //if (counter <= 5) // Let's only do it for the first 5 items - //{ - // var movieInfo = MovieApi.GetMovieInformationWithVideos(movie.Id); + var counter = 0; + if (counter <= 5) // Let's only do it for the first 5 items + { + var movieInfo = await MovieApi.GetMovieInformationWithVideo(movie.id); - // // TODO needs to be careful about this, it's adding extra time to search... - // // https://www.themoviedb.org/talk/5807f4cdc3a36812160041f2 - // viewMovie.ImdbId = movieInfo?.imdb_id; - // viewMovie.Homepage = movieInfo?.homepage; - // var videoId = movieInfo?.video ?? false - // ? movieInfo?.videos?.results?.FirstOrDefault()?.key - // : string.Empty; + // TODO needs to be careful about this, it's adding extra time to search... + // https://www.themoviedb.org/talk/5807f4cdc3a36812160041f2 + viewMovie.ImdbId = movieInfo?.imdb_id; + viewMovie.Homepage = movieInfo?.homepage; + //var videoId = movieInfo?.video ?? false + // ? movieInfo?.videos?.results?.FirstOrDefault()?.key + // : string.Empty; - // viewMovie.Trailer = string.IsNullOrEmpty(videoId) - // ? string.Empty - // : $"https://www.youtube.com/watch?v={videoId}"; + //viewMovie.Trailer = string.IsNullOrEmpty(videoId) + // ? string.Empty + // : $"https://www.youtube.com/watch?v={videoId}"; - // counter++; - //} + counter++; + } // var canSee = CanUserSeeThisRequest(viewMovie.Id, Security.HasPermissions(User, Permissions.UsersCanViewOnlyOwnRequests), dbMovies); diff --git a/Ombi/Ombi.Core/Engine/RequestEngine.cs b/Ombi/Ombi.Core/Engine/RequestEngine.cs index eeaecfa86..f4f02e2f7 100644 --- a/Ombi/Ombi.Core/Engine/RequestEngine.cs +++ b/Ombi/Ombi.Core/Engine/RequestEngine.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Ombi.Core.Models.Requests; using Ombi.Core.Models.Search; @@ -228,5 +229,38 @@ namespace Ombi.Core.Engine return new RequestEngineResult{RequestAdded = true}; } + + public async Task> GetRequests() + { + var allRequests = await RequestService.GetAllAsync(); + var viewModel = allRequests.Select(movie => new RequestViewModel + { + ProviderId = movie.ProviderId, + Type = movie.Type, + Status = movie.Status, + ImdbId = movie.ImdbId, + Id = movie.Id, + PosterPath = movie.PosterPath, + ReleaseDate = movie.ReleaseDate, + RequestedDate = movie.RequestedDate, + Released = DateTime.Now > movie.ReleaseDate, + Approved = movie.Available || movie.Approved, + Title = movie.Title, + Overview = movie.Overview, + RequestedUsers = movie.AllUsers.ToArray(), + ReleaseYear = movie.ReleaseDate.Year.ToString(), + Available = movie.Available, + Admin = false, + IssueId = movie.IssueId, + Denied = movie.Denied, + DeniedReason = movie.DeniedReason, + //Qualities = qualities.ToArray(), + //HasRootFolders = rootFolders.Any(), + //RootFolders = rootFolders.ToArray(), + //CurrentRootPath = radarr.Enabled ? GetRootPath(movie.RootFolderSelected, radarr).Result : null + }).ToList(); + return viewModel; + } + } } \ No newline at end of file diff --git a/Ombi/Ombi.Core/Models/QualityModel.cs b/Ombi/Ombi.Core/Models/QualityModel.cs new file mode 100644 index 000000000..0e6be341e --- /dev/null +++ b/Ombi/Ombi.Core/Models/QualityModel.cs @@ -0,0 +1,8 @@ +namespace Ombi.Core.Models +{ + public class QualityModel + { + public string Id { get; set; } + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/Ombi/Ombi.Core/Models/Requests/RequestModel.cs b/Ombi/Ombi.Core/Models/Requests/RequestModel.cs index 1eb9442e6..92ca73a68 100644 --- a/Ombi/Ombi.Core/Models/Requests/RequestModel.cs +++ b/Ombi/Ombi.Core/Models/Requests/RequestModel.cs @@ -33,10 +33,7 @@ namespace Ombi.Core.Models.Requests public int[] SeasonList { get; set; } public int SeasonCount { get; set; } public string SeasonsRequested { get; set; } - public string MusicBrainzId { get; set; } public List RequestedUsers { get; set; } - public string ArtistName { get; set; } - public string ArtistId { get; set; } public int IssueId { get; set; } public List Episodes { get; set; } public bool Denied { get; set; } @@ -86,8 +83,6 @@ namespace Ombi.Core.Models.Requests return "Movie"; case RequestType.TvShow: return "TV Show"; - case RequestType.Album: - return "Album"; default: return string.Empty; } diff --git a/Ombi/Ombi.Core/Models/Requests/RequestViewModel.cs b/Ombi/Ombi.Core/Models/Requests/RequestViewModel.cs new file mode 100644 index 000000000..a7816f777 --- /dev/null +++ b/Ombi/Ombi.Core/Models/Requests/RequestViewModel.cs @@ -0,0 +1,33 @@ +using System; +using Ombi.Store.Entities; + +namespace Ombi.Core.Models.Requests +{ + public class RequestViewModel + { + public int Id { get; set; } + public int ProviderId { get; set; } + public string ImdbId { get; set; } + public string Overview { get; set; } + public string Title { get; set; } + public string PosterPath { get; set; } + public DateTime ReleaseDate { get; set; } + public bool Released { get; set; } + public RequestType Type { get; set; } + public string Status { get; set; } + public bool Approved { get; set; } + public string[] RequestedUsers { get; set; } + public DateTime RequestedDate { get; set; } + public string ReleaseYear { get; set; } + public bool Available { get; set; } + public bool Admin { get; set; } + public int IssueId { get; set; } + public QualityModel[] Qualities { get; set; } + public EpisodesModel[] Episodes { get; set; } + public bool Denied { get; set; } + public string DeniedReason { get; set; } + public RootFolderModel[] RootFolders { get; set; } + public bool HasRootFolders { get; set; } + public string CurrentRootPath { get; set; } + } +} \ No newline at end of file diff --git a/Ombi/Ombi.Core/Models/RootFolderModel.cs b/Ombi/Ombi.Core/Models/RootFolderModel.cs new file mode 100644 index 000000000..209d301b8 --- /dev/null +++ b/Ombi/Ombi.Core/Models/RootFolderModel.cs @@ -0,0 +1,9 @@ +namespace Ombi.Core.Models.Requests +{ + public class RootFolderModel + { + public string Id { get; set; } + public string Path { get; set; } + public long FreeSpace { get; set; } + } +} \ No newline at end of file diff --git a/Ombi/Ombi.Core/Settings/ISettingsService.cs b/Ombi/Ombi.Core/Settings/ISettingsService.cs index f781676a0..7fe6b4f6f 100644 --- a/Ombi/Ombi.Core/Settings/ISettingsService.cs +++ b/Ombi/Ombi.Core/Settings/ISettingsService.cs @@ -8,7 +8,7 @@ namespace Ombi.Core.Settings Task GetSettingsAsync(); bool SaveSettings(T model); Task SaveSettingsAsync(T model); - bool Delete(T model); - Task DeleteAsync(T model); + void Delete(T model); + Task DeleteAsync(T model); } } \ No newline at end of file diff --git a/Ombi/Ombi.Store/Entities/RequestBlobs.cs b/Ombi/Ombi.Store/Entities/RequestBlobs.cs index 16ecff989..21c60a13e 100644 --- a/Ombi/Ombi.Store/Entities/RequestBlobs.cs +++ b/Ombi/Ombi.Store/Entities/RequestBlobs.cs @@ -13,8 +13,7 @@ namespace Ombi.Store.Entities } public enum RequestType { - Movie, - TvShow, - Album + Movie = 1, + TvShow = 2 } } \ No newline at end of file diff --git a/Ombi/Ombi.TheMovieDbApi/IMovieDbApi.cs b/Ombi/Ombi.TheMovieDbApi/IMovieDbApi.cs index 219bc5a41..13f4a65f6 100644 --- a/Ombi/Ombi.TheMovieDbApi/IMovieDbApi.cs +++ b/Ombi/Ombi.TheMovieDbApi/IMovieDbApi.cs @@ -7,6 +7,7 @@ namespace Ombi.TheMovieDbApi public interface IMovieDbApi { Task GetMovieInformation(int movieId); + Task GetMovieInformationWithVideo(int movieId); Task> NowPlaying(); Task> PopularMovies(); Task> SearchMovie(string searchTerm); diff --git a/Ombi/Ombi.TheMovieDbApi/TheMovieDbApi.cs b/Ombi/Ombi.TheMovieDbApi/TheMovieDbApi.cs index dc68c5f52..33787f911 100644 --- a/Ombi/Ombi.TheMovieDbApi/TheMovieDbApi.cs +++ b/Ombi/Ombi.TheMovieDbApi/TheMovieDbApi.cs @@ -23,6 +23,14 @@ namespace Ombi.TheMovieDbApi return await Api.Get(url); } + public async Task GetMovieInformationWithVideo(int movieId) + { + var url = BaseUri.ChangePath("movie/{0}", movieId.ToString()); + url = AddHeaders(url); + url = url.AddQueryParameter("append_to_response", "videos"); + return await Api.Get(url); + } + public async Task> SearchMovie(string searchTerm) { var url = BaseUri.ChangePath("search/movie/"); diff --git a/Ombi/Ombi/Controllers/RequestController.cs b/Ombi/Ombi/Controllers/RequestController.cs index 34e4465d2..6fa16fca6 100644 --- a/Ombi/Ombi/Controllers/RequestController.cs +++ b/Ombi/Ombi/Controllers/RequestController.cs @@ -1,6 +1,8 @@ -using System.Threading.Tasks; +using System.Collections.Generic; +using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Ombi.Core.Engine; +using Ombi.Core.Models.Requests; using Ombi.Core.Models.Search; namespace Ombi.Controllers @@ -14,6 +16,12 @@ namespace Ombi.Controllers private IRequestEngine RequestEngine { get; } + [HttpGet] + public async Task> GetRequests() + { + return await RequestEngine.GetRequests(); + } + [HttpPost("movie")] public async Task SearchMovie([FromBody]SearchMovieViewModel movie) { diff --git a/Ombi/Ombi/Styles/base.scss b/Ombi/Ombi/Styles/base.scss index c750dc353..095d7d2ab 100644 --- a/Ombi/Ombi/Styles/base.scss +++ b/Ombi/Ombi/Styles/base.scss @@ -701,4 +701,16 @@ body { line-height: 1.42857143; color: #333; background-color: #fff; +} + +.ui-datatable-odd { + background-color: $form-color $i; +} +.ui-datatable-even { + background-color: $form-color-lighter $i; +} + +.ui-widget-content { + border: 1px solid $form-color-lighter $i; + background: $form-color $i; } \ No newline at end of file diff --git a/Ombi/Ombi/app/app.component.html b/Ombi/Ombi/app/app.component.html index 4311d459d..d75a0c200 100644 --- a/Ombi/Ombi/app/app.component.html +++ b/Ombi/Ombi/app/app.component.html @@ -1,4 +1,5 @@ - + + -
diff --git a/Ombi/Ombi/app/app.component.ts b/Ombi/Ombi/app/app.component.ts index af6818c55..36b29087e 100644 --- a/Ombi/Ombi/app/app.component.ts +++ b/Ombi/Ombi/app/app.component.ts @@ -1,4 +1,5 @@ import { Component } from '@angular/core'; +import { NotificationService } from './services/notification.service'; @Component({ selector: 'ombi', @@ -7,4 +8,5 @@ }) export class AppComponent { + constructor(public notificationService: NotificationService) { }; } \ No newline at end of file diff --git a/Ombi/Ombi/app/app.module.ts b/Ombi/Ombi/app/app.module.ts index ea5d2123c..c7a1dee52 100644 --- a/Ombi/Ombi/app/app.module.ts +++ b/Ombi/Ombi/app/app.module.ts @@ -9,21 +9,25 @@ import { RouterModule, Routes } from '@angular/router'; import { HttpModule } from '@angular/http'; import { SearchComponent } from './search/search.component'; +import { RequestComponent } from './requests/request.component'; import { PageNotFoundComponent } from './errors/not-found.component'; // Services import { SearchService } from './services/search.service'; import { RequestService } from './services/request.service'; +import { NotificationService } from './services/notification.service'; // Modules import { SettingsModule } from './settings/settings.module'; import { ButtonModule } from 'primeng/primeng'; import { GrowlModule } from 'primeng/components/growl/growl'; +import { DataTableModule, SharedModule } from 'primeng/primeng'; const routes: Routes = [ { path: '*', component: PageNotFoundComponent }, - { path: 'search', component: SearchComponent } + { path: 'search', component: SearchComponent }, + { path: 'requests', component: RequestComponent }, ]; @NgModule({ @@ -35,16 +39,20 @@ const routes: Routes = [ GrowlModule, ButtonModule, FormsModule, - SettingsModule + SettingsModule, + DataTableModule, + SharedModule ], declarations: [ AppComponent, PageNotFoundComponent, - SearchComponent + SearchComponent, + RequestComponent ], providers: [ SearchService, - RequestService + RequestService, + NotificationService ], bootstrap: [AppComponent] }) diff --git a/Ombi/Ombi/app/interfaces/IRequestModel.ts b/Ombi/Ombi/app/interfaces/IRequestModel.ts new file mode 100644 index 000000000..31db39bf7 --- /dev/null +++ b/Ombi/Ombi/app/interfaces/IRequestModel.ts @@ -0,0 +1,27 @@ +export interface IRequestModel { + id: number, + providerId: number, + imdbId: string, + overview: string, + title: string, + posterPath: string, + releaseDate: Date, + released: boolean, + type: RequestType, + status: string, + approved: boolean, + requestedUsers: string[], + requestedDate: Date, + releaseYear: string, + available: boolean, + issueId: number, + denied: boolean, + deniedReason: string, + + +} + +export enum RequestType { + movie = 1, + tvShow = 2 +} \ No newline at end of file diff --git a/Ombi/Ombi/app/interfaces/ISearchMovieResult.ts b/Ombi/Ombi/app/interfaces/ISearchMovieResult.ts index 4a308438f..99af3b533 100644 --- a/Ombi/Ombi/app/interfaces/ISearchMovieResult.ts +++ b/Ombi/Ombi/app/interfaces/ISearchMovieResult.ts @@ -16,5 +16,9 @@ alreadyInCp: boolean, trailer: string, homepage: string, - imdbId:string + imdbId: string, + approved: boolean, + requested: boolean, + available: boolean, + plexUrl: string } \ No newline at end of file diff --git a/Ombi/Ombi/app/requests/request.component.html b/Ombi/Ombi/app/requests/request.component.html new file mode 100644 index 000000000..b2654cbdc --- /dev/null +++ b/Ombi/Ombi/app/requests/request.component.html @@ -0,0 +1,46 @@ +

Requests

+ + + + + + + + + + + + + + +
+
+
+ + + +
+
+
+
+
Type:
+
{{request.type}}
+
+
+
Status:
+
{{request.status}}
+
+
+
Approved:
+
{{request.approved}}
+
+
+
Available:
+
{{request.available}}
+
+
+
+
+
+
+
diff --git a/Ombi/Ombi/app/requests/request.component.ts b/Ombi/Ombi/app/requests/request.component.ts new file mode 100644 index 000000000..65a733b77 --- /dev/null +++ b/Ombi/Ombi/app/requests/request.component.ts @@ -0,0 +1,24 @@ +import { Component, OnInit } from '@angular/core'; +import 'rxjs/add/operator/debounceTime'; +import 'rxjs/add/operator/distinctUntilChanged'; +import 'rxjs/add/operator/map'; + +import { RequestService } from '../services/request.service'; + +import { IRequestModel } from '../interfaces/IRequestModel'; + +@Component({ + selector: 'ombi', + moduleId: module.id, + templateUrl: './request.component.html', + providers: [RequestService] +}) +export class RequestComponent implements OnInit { + constructor(private requestService: RequestService) { } + + requests: IRequestModel[]; + + ngOnInit() { + this.requestService.getAllRequests().subscribe(x => this.requests = x); + } +} \ No newline at end of file diff --git a/Ombi/Ombi/app/search/search.component.html b/Ombi/Ombi/app/search/search.component.html index 5178a9f71..1ef35faf1 100644 --- a/Ombi/Ombi/app/search/search.component.html +++ b/Ombi/Ombi/app/search/search.component.html @@ -128,14 +128,14 @@
-

{{result.title}} ({{result.releaseDate}})

+

{{result.title}} ({{result.releaseDate | date: 'yyyy'}})

- Air Date: {{result.firstAired}} + Air Date: {{result.firstAired | date: 'dd/MM/yyyy'}} - Release Date: {{result.releaseDate}} + Release Date: {{result.releaseDate | date: 'dd/MM/yyyy'}} @UI.Search_Available diff --git a/Ombi/Ombi/app/search/search.component.ts b/Ombi/Ombi/app/search/search.component.ts index c7152dcea..20a5a6a7a 100644 --- a/Ombi/Ombi/app/search/search.component.ts +++ b/Ombi/Ombi/app/search/search.component.ts @@ -6,6 +6,7 @@ import 'rxjs/add/operator/map'; import { SearchService } from '../services/search.service'; import { RequestService } from '../services/request.service'; +import { NotificationService } from '../services/notification.service'; import { ISearchMovieResult } from '../interfaces/ISearchMovieResult'; import { IRequestEngineResult } from '../interfaces/IRequestEngineResult'; @@ -14,7 +15,6 @@ import { IRequestEngineResult } from '../interfaces/IRequestEngineResult'; selector: 'ombi', moduleId: module.id, templateUrl: './search.component.html', - providers: [SearchService, RequestService] }) export class SearchComponent implements OnInit { @@ -23,7 +23,7 @@ export class SearchComponent implements OnInit { movieResults: ISearchMovieResult[]; result: IRequestEngineResult; - constructor(private searchService: SearchService, private requestService: RequestService) { + constructor(private searchService: SearchService, private requestService: RequestService, private notificationService : NotificationService) { this.searchChanged .debounceTime(600) // Wait Xms afterthe last event before emitting last event .distinctUntilChanged() // only emit if value is different from previous value @@ -51,7 +51,17 @@ export class SearchComponent implements OnInit { } request(searchResult: ISearchMovieResult) { - this.requestService.requestMovie(searchResult).subscribe(x => this.result = x); + searchResult.requested = true; + this.requestService.requestMovie(searchResult).subscribe(x => { + this.result = x; + + if (this.result.requestAdded) { + this.notificationService.success("Request Added", + `Request for ${searchResult.title} has been added successfully`); + } else { + this.notificationService.warning("Request Added", this.result.message); + } + }); } popularMovies() { diff --git a/Ombi/Ombi/app/services/notification.service.ts b/Ombi/Ombi/app/services/notification.service.ts new file mode 100644 index 000000000..edf17ae44 --- /dev/null +++ b/Ombi/Ombi/app/services/notification.service.ts @@ -0,0 +1,30 @@ +import { Injectable } from '@angular/core'; +import { Message } from 'primeng/components/common/api'; + +@Injectable() +export class NotificationService { + messages: Message[] = []; + public addMessage(message: Message) { + this.messages.push(message); + } + + public success(title: string, body: string) { + this.addMessage({ severity: 'success', detail: body, summary: title }); + } + + public info(title: string, body: string) { + this.addMessage({ severity: 'info', detail: body, summary: title }); + } + + public warning(title: string, body: string) { + this.addMessage({ severity: 'warning', detail: body, summary: title }); + } + + public error(title: string, body: string) { + this.addMessage({ severity: 'danger', detail: body, summary: title }); + } + + public clearMessages() { + this.messages = []; + } +} \ No newline at end of file diff --git a/Ombi/Ombi/app/services/request.service.ts b/Ombi/Ombi/app/services/request.service.ts index 4af6c70b7..fe8d188f0 100644 --- a/Ombi/Ombi/app/services/request.service.ts +++ b/Ombi/Ombi/app/services/request.service.ts @@ -5,6 +5,7 @@ import { Observable } from 'rxjs/Rx'; import { ServiceHelpers } from './service.helpers'; import { IRequestEngineResult } from '../interfaces/IRequestEngineResult'; import { ISearchMovieResult } from '../interfaces/ISearchMovieResult'; +import { IRequestModel } from '../interfaces/IRequestModel'; @Injectable() export class RequestService { @@ -15,4 +16,8 @@ export class RequestService { return this.http.post('/api/Request/Movie/', JSON.stringify(movie), ServiceHelpers.RequestOptions).map(ServiceHelpers.extractData); } + getAllRequests(): Observable { + return this.http.get('/api/request').map(ServiceHelpers.extractData); + } + } \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml index fa3c25e50..f97cdb039 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -11,11 +11,11 @@ before_build: - cmd: cd ombi/ombi - appveyor-retry dotnet restore - appveyor-retry npm install bower -g -- appveyor-retry npm install -g gulp +#- appveyor-retry npm install -g gulp - appveyor-retry npm install -g typescript - appveyor-retry npm install - appveyor-retry bower install -- gulp publish +#- gulp publish build_script: - dotnet build after_build: From 9d982cbcb99c677d56163407277cd889c9e78edb Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" Date: Fri, 7 Apr 2017 13:40:46 +0100 Subject: [PATCH 006/725] Matt is helping --- Ombi/Ombi/gulpfile.js | 61 ++++++++++++++++++++++++++---------- Ombi/Ombi/systemjs.config.ts | 10 ++---- appveyor.yml | 9 +++--- 3 files changed, 52 insertions(+), 28 deletions(-) diff --git a/Ombi/Ombi/gulpfile.js b/Ombi/Ombi/gulpfile.js index 08614359b..57dd86369 100644 --- a/Ombi/Ombi/gulpfile.js +++ b/Ombi/Ombi/gulpfile.js @@ -16,8 +16,10 @@ var filter = require('gulp-filter'); var systemJSBuilder = require('systemjs-builder'); var run = require('gulp-run'); +var wwwroot = './wwwroot'; + var paths = { - wwwroot: './wwwroot/', + wwwroot: wwwroot, npm: { // These will be resolved automatically and copied to output directory as its name, only works for pre-bundled modules e.g. angular src: [ '@angular/animations', @@ -125,8 +127,22 @@ var paths = { }, bundle: { // This is the config for the bundler, you shouldn't need to change this root: './', - dest: './lib/bundle.js', + dest: './lib/bundles/full.js', + libdest: './lib/bundles/lib.js', bundle: 'app/main.js', + app: 'app/**/*', + config: { + baseURL: wwwroot, + packages: { + '.': { + defaultExtension: 'js' + } + }, + paths: { + '*': 'lib/*', + 'app/*': 'app/*' + } + } } } @@ -229,26 +245,37 @@ gulp.task('sass', function () { .pipe(gulp.dest(path.join(paths.wwwroot, paths.sass.dest))) }); + +// This bundles the entire application and libraries for deployment gulp.task('bundle', function () { var builder = new systemJSBuilder(paths.bundle.root); - builder.config({ - baseURL: paths.wwwroot, - packages: { - '.': { - defaultExtension: 'js' - } - }, - paths: { - '*': 'lib/*', - 'app/*': 'app/*' - } - }); + builder.config(paths.bundle.config); + del.sync(path.join(paths.wwwroot, paths.bundle.dest), { force: true }); return builder.bundle(paths.bundle.bundle, path.join(paths.wwwroot, paths.bundle.dest), { sourceMaps: true }) }) +// This bundles only third party dependencies for development +gulp.task('bundlelib', function () { + var builder = new systemJSBuilder(paths.bundle.root); + builder.config(paths.bundle.config); + del.sync(path.join(paths.wwwroot, paths.bundle.libdest), { force: true }); + return builder.bundle(paths.bundle.bundle + ' - [' + paths.bundle.app + ']', path.join(paths.wwwroot, paths.bundle.libdest), { + sourceMaps: global.full + }) +}) + +gulp.task('clean', function () { + return del([ + paths.sass.dest, + paths.lib.dest, + paths.bundle.dest, + ...paths.modules.map(m => m.dest) + ].map(x => path.join(paths.wwwroot, x)), { force: true }); +}) + gulp.task('typescript', function () { return run('tsc').exec(); }); @@ -256,9 +283,9 @@ gulp.task('typescript', function () { gulp.task('fullvar', () => { global.full = true }); gulp.task('libs') gulp.task('copy', ['lib', 'libcss', 'libfonts', 'libimages', 'npm', 'modules']); -gulp.task('compile', ['sass']); -gulp.task('build', callback => runSequence('copy', 'compile', callback)); -gulp.task('full', callback => runSequence('build', callback)); +gulp.task('compile', callback => runSequence('copy', 'sass', callback)); +gulp.task('build', callback => runSequence('compile', 'bundlelib', callback)); +gulp.task('full', callback => runSequence('clean', 'compile', callback)); // Use this in a build server environment to compile and bundle everything gulp.task('publish', callback => runSequence('fullvar', 'full', 'typescript', 'bundle', callback)); diff --git a/Ombi/Ombi/systemjs.config.ts b/Ombi/Ombi/systemjs.config.ts index b31bed9a4..c007c8d9a 100644 --- a/Ombi/Ombi/systemjs.config.ts +++ b/Ombi/Ombi/systemjs.config.ts @@ -10,11 +10,7 @@ map: { app: '../app' } }) - if (config.bundle) { - System.import('bundle').then(() => { - System.import('/app/main'); - }) - } else { - System.import('/app/main') - } + System.import(config.bundle ? 'bundles/full' : 'bundles/lib').then(() => { + System.import('/app/main'); + }) }); \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml index f97cdb039..6aeb2aa9b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,6 +1,6 @@ version: 3.0.{build} configuration: Release -os: Visual Studio 2015 Preview +os: Visual Studio 2017 assembly_info: patch: true file: '**\AssemblyInfo.*' @@ -11,11 +11,12 @@ before_build: - cmd: cd ombi/ombi - appveyor-retry dotnet restore - appveyor-retry npm install bower -g -#- appveyor-retry npm install -g gulp -- appveyor-retry npm install -g typescript +- appveyor-retry npm install -g gulp +#- appveyor-retry npm install -g typescript - appveyor-retry npm install - appveyor-retry bower install -#- gulp publish +- gulp publish +- node --version build_script: - dotnet build after_build: From ecd7725cf6bfa04d353b972abeb59fc424ee496b Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" Date: Fri, 7 Apr 2017 13:45:14 +0100 Subject: [PATCH 007/725] wrong line --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 6aeb2aa9b..0a3ddc9d4 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -9,6 +9,7 @@ assembly_info: assembly_informational_version: '3.0.0' before_build: - cmd: cd ombi/ombi +- node --version - appveyor-retry dotnet restore - appveyor-retry npm install bower -g - appveyor-retry npm install -g gulp @@ -16,7 +17,6 @@ before_build: - appveyor-retry npm install - appveyor-retry bower install - gulp publish -- node --version build_script: - dotnet build after_build: From 831e19e283ed6155d212eee6c66dde33becc75d6 Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" Date: Fri, 7 Apr 2017 13:51:32 +0100 Subject: [PATCH 008/725] Update node --- appveyor.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/appveyor.yml b/appveyor.yml index 0a3ddc9d4..69f8f561b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -10,6 +10,8 @@ assembly_info: before_build: - cmd: cd ombi/ombi - node --version +- Update-NodeJsInstallation 7.8.0 +- node --version - appveyor-retry dotnet restore - appveyor-retry npm install bower -g - appveyor-retry npm install -g gulp From 4cf5a31fedd1a2ee22f75a45e07e99ce461bcc3a Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" Date: Fri, 7 Apr 2017 13:56:10 +0100 Subject: [PATCH 009/725] update node again... --- Ombi/Ombi/gulpfile.js | 6 +++--- appveyor.yml | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Ombi/Ombi/gulpfile.js b/Ombi/Ombi/gulpfile.js index 57dd86369..3c1037a78 100644 --- a/Ombi/Ombi/gulpfile.js +++ b/Ombi/Ombi/gulpfile.js @@ -153,7 +153,7 @@ gulp.task('npm', function () { streams.push( gulp.src(file) .pipe(gulpif(global.full, sourcemaps.init())) - //.pipe(gulpif(global.full, uglify({ source_map: true }))) + .pipe(gulpif(global.full, uglify({ source_map: true }))) .pipe(rename((path => { path.basename = module }))) .pipe(gulpif(global.full, sourcemaps.write('../maps'))) .pipe(gulp.dest(path.join(paths.wwwroot, paths.npm.dest))) @@ -168,7 +168,7 @@ gulp.task('lib', function () { streams.push( gulp.src(typeof module === "string" ? module : module.file) .pipe(gulpif(global.full, sourcemaps.init())) - //.pipe(gulpif(global.full, uglify({ source_map: true }))) + .pipe(gulpif(global.full, uglify({ source_map: true }))) .pipe(rename(function (path) { if (typeof module !== "string" && module.rename) { path.basename = module.rename; @@ -228,7 +228,7 @@ gulp.task('modules', function () { streams.push( gulp.src(module.src) .pipe(gulpif(global.full, sourcemaps.init())) - // .pipe(gulpif(global.full, uglify({ source_map: true }))) + .pipe(gulpif(global.full, uglify({ source_map: true }))) .pipe(gulpif(global.full, sourcemaps.write(`${module.name ? '.' : ''}./maps/${module.name ? module.name : ''}`))) .pipe(gulp.dest(path.join(paths.wwwroot, module.dest))) ); diff --git a/appveyor.yml b/appveyor.yml index 69f8f561b..5b5808779 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -8,10 +8,9 @@ assembly_info: assembly_file_version: '{version}' assembly_informational_version: '3.0.0' before_build: +- ps: Update-NodeJsInstallation 7.8.0 - cmd: cd ombi/ombi - node --version -- Update-NodeJsInstallation 7.8.0 -- node --version - appveyor-retry dotnet restore - appveyor-retry npm install bower -g - appveyor-retry npm install -g gulp From 24c4ae21925d6c28962d4afdd50e30b8e4eae74b Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" Date: Fri, 7 Apr 2017 14:00:24 +0100 Subject: [PATCH 010/725] put uglify back in! --- Ombi/Ombi/gulpfile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Ombi/Ombi/gulpfile.js b/Ombi/Ombi/gulpfile.js index 3c1037a78..493faf5e6 100644 --- a/Ombi/Ombi/gulpfile.js +++ b/Ombi/Ombi/gulpfile.js @@ -4,7 +4,7 @@ var gulp = require('gulp'); var sass = require('gulp-sass'); var changed = require('gulp-changed'); var rename = require('gulp-rename'); -//var uglify = require('gulp-uglify'); +var uglify = require('gulp-uglify'); var sourcemaps = require('gulp-sourcemaps'); var path = require('path'); var del = require('del'); From 7b6e5fd431844703766b52241a978e36ce9b3862 Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" Date: Fri, 7 Apr 2017 14:09:09 +0100 Subject: [PATCH 011/725] Move app into wwwroot --- Ombi/Ombi/.gitignore | 10 +- Ombi/Ombi/Ombi.csproj | 3698 ++++++++++++++++- .../Ombi/{ => wwwroot}/app/app.component.html | 0 Ombi/Ombi/{ => wwwroot}/app/app.component.ts | 0 Ombi/Ombi/{ => wwwroot}/app/app.module.ts | 0 Ombi/Ombi/{ => wwwroot}/app/config.ts | 0 .../app/errors/not-found.component.ts | 0 .../app/interfaces/IRequestEngineResult.ts | 0 .../app/interfaces/IRequestModel.ts | 0 .../app/interfaces/ISearchMovieResult.ts | 0 Ombi/Ombi/{ => wwwroot}/app/main.ts | 0 Ombi/Ombi/{ => wwwroot}/app/polyfills.ts | 0 .../app/requests/request.component.html | 0 .../app/requests/request.component.ts | 0 .../app/search/search.component.html | 0 .../app/search/search.component.ts | 0 .../app/services/notification.service.ts | 0 .../app/services/request.service.ts | 0 .../app/services/search.service.ts | 0 .../app/services/service.helpers.ts | 0 .../app/settings/ombi/ombi.component.html | 0 .../app/settings/ombi/ombi.component.ts | 0 .../app/settings/settings.module.ts | 0 23 files changed, 3704 insertions(+), 4 deletions(-) rename Ombi/Ombi/{ => wwwroot}/app/app.component.html (100%) rename Ombi/Ombi/{ => wwwroot}/app/app.component.ts (100%) rename Ombi/Ombi/{ => wwwroot}/app/app.module.ts (100%) rename Ombi/Ombi/{ => wwwroot}/app/config.ts (100%) rename Ombi/Ombi/{ => wwwroot}/app/errors/not-found.component.ts (100%) rename Ombi/Ombi/{ => wwwroot}/app/interfaces/IRequestEngineResult.ts (100%) rename Ombi/Ombi/{ => wwwroot}/app/interfaces/IRequestModel.ts (100%) rename Ombi/Ombi/{ => wwwroot}/app/interfaces/ISearchMovieResult.ts (100%) rename Ombi/Ombi/{ => wwwroot}/app/main.ts (100%) rename Ombi/Ombi/{ => wwwroot}/app/polyfills.ts (100%) rename Ombi/Ombi/{ => wwwroot}/app/requests/request.component.html (100%) rename Ombi/Ombi/{ => wwwroot}/app/requests/request.component.ts (100%) rename Ombi/Ombi/{ => wwwroot}/app/search/search.component.html (100%) rename Ombi/Ombi/{ => wwwroot}/app/search/search.component.ts (100%) rename Ombi/Ombi/{ => wwwroot}/app/services/notification.service.ts (100%) rename Ombi/Ombi/{ => wwwroot}/app/services/request.service.ts (100%) rename Ombi/Ombi/{ => wwwroot}/app/services/search.service.ts (100%) rename Ombi/Ombi/{ => wwwroot}/app/services/service.helpers.ts (100%) rename Ombi/Ombi/{ => wwwroot}/app/settings/ombi/ombi.component.html (100%) rename Ombi/Ombi/{ => wwwroot}/app/settings/ombi/ombi.component.ts (100%) rename Ombi/Ombi/{ => wwwroot}/app/settings/settings.module.ts (100%) diff --git a/Ombi/Ombi/.gitignore b/Ombi/Ombi/.gitignore index 71bffc9b8..577f921f9 100644 --- a/Ombi/Ombi/.gitignore +++ b/Ombi/Ombi/.gitignore @@ -1,6 +1,10 @@ -/app/**/*.js -/app/**/*.js.map -/wwwroot/** +/wwwroot/css/** +/wwwroot/fonts/** +/wwwroot/images/** +/wwwroot/lib/** +/wwwroot/maps/** +/wwwroot/app/**/*.js +/wwwroot/app/**/*.js.map # dependencies /node_modules diff --git a/Ombi/Ombi/Ombi.csproj b/Ombi/Ombi/Ombi.csproj index 9c3dab512..5c2250328 100644 --- a/Ombi/Ombi/Ombi.csproj +++ b/Ombi/Ombi/Ombi.csproj @@ -19,7 +19,3645 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -29,6 +3667,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Ombi/Ombi/app/app.component.html b/Ombi/Ombi/wwwroot/app/app.component.html similarity index 100% rename from Ombi/Ombi/app/app.component.html rename to Ombi/Ombi/wwwroot/app/app.component.html diff --git a/Ombi/Ombi/app/app.component.ts b/Ombi/Ombi/wwwroot/app/app.component.ts similarity index 100% rename from Ombi/Ombi/app/app.component.ts rename to Ombi/Ombi/wwwroot/app/app.component.ts diff --git a/Ombi/Ombi/app/app.module.ts b/Ombi/Ombi/wwwroot/app/app.module.ts similarity index 100% rename from Ombi/Ombi/app/app.module.ts rename to Ombi/Ombi/wwwroot/app/app.module.ts diff --git a/Ombi/Ombi/app/config.ts b/Ombi/Ombi/wwwroot/app/config.ts similarity index 100% rename from Ombi/Ombi/app/config.ts rename to Ombi/Ombi/wwwroot/app/config.ts diff --git a/Ombi/Ombi/app/errors/not-found.component.ts b/Ombi/Ombi/wwwroot/app/errors/not-found.component.ts similarity index 100% rename from Ombi/Ombi/app/errors/not-found.component.ts rename to Ombi/Ombi/wwwroot/app/errors/not-found.component.ts diff --git a/Ombi/Ombi/app/interfaces/IRequestEngineResult.ts b/Ombi/Ombi/wwwroot/app/interfaces/IRequestEngineResult.ts similarity index 100% rename from Ombi/Ombi/app/interfaces/IRequestEngineResult.ts rename to Ombi/Ombi/wwwroot/app/interfaces/IRequestEngineResult.ts diff --git a/Ombi/Ombi/app/interfaces/IRequestModel.ts b/Ombi/Ombi/wwwroot/app/interfaces/IRequestModel.ts similarity index 100% rename from Ombi/Ombi/app/interfaces/IRequestModel.ts rename to Ombi/Ombi/wwwroot/app/interfaces/IRequestModel.ts diff --git a/Ombi/Ombi/app/interfaces/ISearchMovieResult.ts b/Ombi/Ombi/wwwroot/app/interfaces/ISearchMovieResult.ts similarity index 100% rename from Ombi/Ombi/app/interfaces/ISearchMovieResult.ts rename to Ombi/Ombi/wwwroot/app/interfaces/ISearchMovieResult.ts diff --git a/Ombi/Ombi/app/main.ts b/Ombi/Ombi/wwwroot/app/main.ts similarity index 100% rename from Ombi/Ombi/app/main.ts rename to Ombi/Ombi/wwwroot/app/main.ts diff --git a/Ombi/Ombi/app/polyfills.ts b/Ombi/Ombi/wwwroot/app/polyfills.ts similarity index 100% rename from Ombi/Ombi/app/polyfills.ts rename to Ombi/Ombi/wwwroot/app/polyfills.ts diff --git a/Ombi/Ombi/app/requests/request.component.html b/Ombi/Ombi/wwwroot/app/requests/request.component.html similarity index 100% rename from Ombi/Ombi/app/requests/request.component.html rename to Ombi/Ombi/wwwroot/app/requests/request.component.html diff --git a/Ombi/Ombi/app/requests/request.component.ts b/Ombi/Ombi/wwwroot/app/requests/request.component.ts similarity index 100% rename from Ombi/Ombi/app/requests/request.component.ts rename to Ombi/Ombi/wwwroot/app/requests/request.component.ts diff --git a/Ombi/Ombi/app/search/search.component.html b/Ombi/Ombi/wwwroot/app/search/search.component.html similarity index 100% rename from Ombi/Ombi/app/search/search.component.html rename to Ombi/Ombi/wwwroot/app/search/search.component.html diff --git a/Ombi/Ombi/app/search/search.component.ts b/Ombi/Ombi/wwwroot/app/search/search.component.ts similarity index 100% rename from Ombi/Ombi/app/search/search.component.ts rename to Ombi/Ombi/wwwroot/app/search/search.component.ts diff --git a/Ombi/Ombi/app/services/notification.service.ts b/Ombi/Ombi/wwwroot/app/services/notification.service.ts similarity index 100% rename from Ombi/Ombi/app/services/notification.service.ts rename to Ombi/Ombi/wwwroot/app/services/notification.service.ts diff --git a/Ombi/Ombi/app/services/request.service.ts b/Ombi/Ombi/wwwroot/app/services/request.service.ts similarity index 100% rename from Ombi/Ombi/app/services/request.service.ts rename to Ombi/Ombi/wwwroot/app/services/request.service.ts diff --git a/Ombi/Ombi/app/services/search.service.ts b/Ombi/Ombi/wwwroot/app/services/search.service.ts similarity index 100% rename from Ombi/Ombi/app/services/search.service.ts rename to Ombi/Ombi/wwwroot/app/services/search.service.ts diff --git a/Ombi/Ombi/app/services/service.helpers.ts b/Ombi/Ombi/wwwroot/app/services/service.helpers.ts similarity index 100% rename from Ombi/Ombi/app/services/service.helpers.ts rename to Ombi/Ombi/wwwroot/app/services/service.helpers.ts diff --git a/Ombi/Ombi/app/settings/ombi/ombi.component.html b/Ombi/Ombi/wwwroot/app/settings/ombi/ombi.component.html similarity index 100% rename from Ombi/Ombi/app/settings/ombi/ombi.component.html rename to Ombi/Ombi/wwwroot/app/settings/ombi/ombi.component.html diff --git a/Ombi/Ombi/app/settings/ombi/ombi.component.ts b/Ombi/Ombi/wwwroot/app/settings/ombi/ombi.component.ts similarity index 100% rename from Ombi/Ombi/app/settings/ombi/ombi.component.ts rename to Ombi/Ombi/wwwroot/app/settings/ombi/ombi.component.ts diff --git a/Ombi/Ombi/app/settings/settings.module.ts b/Ombi/Ombi/wwwroot/app/settings/settings.module.ts similarity index 100% rename from Ombi/Ombi/app/settings/settings.module.ts rename to Ombi/Ombi/wwwroot/app/settings/settings.module.ts From 9305e3a66a3bddc01c0d1e5e02f352724d98eb9b Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" Date: Fri, 7 Apr 2017 14:15:07 +0100 Subject: [PATCH 012/725] stuff --- Ombi/Ombi/Ombi.csproj | 3705 +---------------------------------------- Ombi/Ombi/Startup.cs | 7 - 2 files changed, 1 insertion(+), 3711 deletions(-) diff --git a/Ombi/Ombi/Ombi.csproj b/Ombi/Ombi/Ombi.csproj index 5c2250328..8d1c47917 100644 --- a/Ombi/Ombi/Ombi.csproj +++ b/Ombi/Ombi/Ombi.csproj @@ -19,3710 +19,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/Ombi/Ombi/Startup.cs b/Ombi/Ombi/Startup.cs index defdcce82..bf16a1f69 100644 --- a/Ombi/Ombi/Startup.cs +++ b/Ombi/Ombi/Startup.cs @@ -59,13 +59,6 @@ namespace Ombi ContentTypeProvider = provider }); - app.UseStaticFiles(new StaticFileOptions - { - FileProvider = new PhysicalFileProvider( - Path.Combine(Directory.GetCurrentDirectory(), @"app")), - RequestPath = new PathString("/app"), - }); - app.UseMvc(routes => { routes.MapRoute( From 13b6bee0ebee7f95d7b6e968d7ca5825bfb19f08 Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" Date: Fri, 7 Apr 2017 14:37:44 +0100 Subject: [PATCH 013/725] bundling changes --- Ombi/Ombi/gulpfile.js | 19 +++---------------- Ombi/Ombi/systemjs.config.ts | 27 ++++++++++++--------------- 2 files changed, 15 insertions(+), 31 deletions(-) diff --git a/Ombi/Ombi/gulpfile.js b/Ombi/Ombi/gulpfile.js index 493faf5e6..6b9624dde 100644 --- a/Ombi/Ombi/gulpfile.js +++ b/Ombi/Ombi/gulpfile.js @@ -127,8 +127,7 @@ var paths = { }, bundle: { // This is the config for the bundler, you shouldn't need to change this root: './', - dest: './lib/bundles/full.js', - libdest: './lib/bundles/lib.js', + dest: './lib/bundle.js', bundle: 'app/main.js', app: 'app/**/*', config: { @@ -246,23 +245,12 @@ gulp.task('sass', function () { }); -// This bundles the entire application and libraries for deployment gulp.task('bundle', function () { var builder = new systemJSBuilder(paths.bundle.root); builder.config(paths.bundle.config); del.sync(path.join(paths.wwwroot, paths.bundle.dest), { force: true }); - return builder.bundle(paths.bundle.bundle, path.join(paths.wwwroot, paths.bundle.dest), { - sourceMaps: true - }) -}) - -// This bundles only third party dependencies for development -gulp.task('bundlelib', function () { - var builder = new systemJSBuilder(paths.bundle.root); - builder.config(paths.bundle.config); - del.sync(path.join(paths.wwwroot, paths.bundle.libdest), { force: true }); - return builder.bundle(paths.bundle.bundle + ' - [' + paths.bundle.app + ']', path.join(paths.wwwroot, paths.bundle.libdest), { + return builder.bundle(paths.bundle.bundle + (global.full ? '' : ' - [' + paths.bundle.app + ']'), path.join(paths.wwwroot, paths.bundle.dest), { sourceMaps: global.full }) }) @@ -281,10 +269,9 @@ gulp.task('typescript', function () { }); gulp.task('fullvar', () => { global.full = true }); -gulp.task('libs') gulp.task('copy', ['lib', 'libcss', 'libfonts', 'libimages', 'npm', 'modules']); gulp.task('compile', callback => runSequence('copy', 'sass', callback)); -gulp.task('build', callback => runSequence('compile', 'bundlelib', callback)); +gulp.task('build', callback => runSequence('compile', 'bundle', callback)); gulp.task('full', callback => runSequence('clean', 'compile', callback)); // Use this in a build server environment to compile and bundle everything diff --git a/Ombi/Ombi/systemjs.config.ts b/Ombi/Ombi/systemjs.config.ts index c007c8d9a..be7c0a005 100644 --- a/Ombi/Ombi/systemjs.config.ts +++ b/Ombi/Ombi/systemjs.config.ts @@ -1,16 +1,13 @@ -System.import('/app/config.js').then((module: any) => { - var config = module.config.systemJS; - System.config({ - baseURL: '/lib', - packages: { - '.': { - defaultExtension: 'js' - } - }, - map: { app: '../app' } - }) +System.config({ + baseURL: '/lib', + packages: { + '.': { + defaultExtension: 'js' + } + }, + map: { app: '../app' } +}) - System.import(config.bundle ? 'bundles/full' : 'bundles/lib').then(() => { - System.import('/app/main'); - }) -}); \ No newline at end of file +System.import('bundle').then(() => { + System.import('/app/main'); +}) \ No newline at end of file From d765bcbe76831c1e89776c6ff76d2730497ce375 Mon Sep 17 00:00:00 2001 From: Matt Jeanes Date: Fri, 7 Apr 2017 15:07:02 +0100 Subject: [PATCH 014/725] Remove unneeded bundle config With the new bundling config stuff this is no longer needed --- Ombi/Ombi/wwwroot/app/config.ts | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/Ombi/Ombi/wwwroot/app/config.ts b/Ombi/Ombi/wwwroot/app/config.ts index 7ae446190..0f365a170 100644 --- a/Ombi/Ombi/wwwroot/app/config.ts +++ b/Ombi/Ombi/wwwroot/app/config.ts @@ -14,14 +14,7 @@ if (envs[envVar]) { export var config = { envs: envs, - env: env, - systemJS: { - bundle: { - [envs.local]: false, - [envs.next]: true, - [envs.live]: true - }[env] - } + env: env } -export default config; \ No newline at end of file +export default config; From 905c4b895b590d5fcb1209afee0446fec55e57f1 Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" Date: Fri, 7 Apr 2017 16:03:45 +0100 Subject: [PATCH 015/725] redo dotnet publish targets --- appveyor.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 5b5808779..0110191f3 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -21,10 +21,10 @@ before_build: build_script: - dotnet build after_build: -- dotnet publish -c Release /p:AppRuntimeIdentifier=win10-x64 -- dotnet publish -c Release /p:AppRuntimeIdentifier=osx.10.12-x64 -- dotnet publish -c Release /p:AppRuntimeIdentifier=ubuntu.16.10-x64 -- dotnet publish -c Release /p:AppRuntimeIdentifier=debian.8-x64 + - dotnet publish -c Release -r win10-x64 + - dotnet publish -c Release -r osx.10.12-x64 + - dotnet publish -c Release -r ubuntu.16.10-x64 + - dotnet publish -c Release -r debian.8-x64 - cmd: >- 7z a Ombi_windows.zip %APPVEYOR_BUILD_FOLDER%\Ombi\Ombi\bin\Release\netcoreapp1.1\win10-x64\publish From b8244984782335ce21330bb4a21178e672bad463 Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" Date: Fri, 7 Apr 2017 16:31:02 +0100 Subject: [PATCH 016/725] fixed the yml --- Ombi/Ombi/Ombi.csproj | 4 ---- appveyor.yml | 8 ++++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/Ombi/Ombi/Ombi.csproj b/Ombi/Ombi/Ombi.csproj index 8d1c47917..167666ce3 100644 --- a/Ombi/Ombi/Ombi.csproj +++ b/Ombi/Ombi/Ombi.csproj @@ -18,10 +18,6 @@ - - - - diff --git a/appveyor.yml b/appveyor.yml index 0110191f3..69f0d1d28 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -21,10 +21,10 @@ before_build: build_script: - dotnet build after_build: - - dotnet publish -c Release -r win10-x64 - - dotnet publish -c Release -r osx.10.12-x64 - - dotnet publish -c Release -r ubuntu.16.10-x64 - - dotnet publish -c Release -r debian.8-x64 +- dotnet publish -c Release -r win10-x64 +- dotnet publish -c Release -r osx.10.12-x64 +- dotnet publish -c Release -r ubuntu.16.10-x64 +- dotnet publish -c Release -r debian.8-x64 - cmd: >- 7z a Ombi_windows.zip %APPVEYOR_BUILD_FOLDER%\Ombi\Ombi\bin\Release\netcoreapp1.1\win10-x64\publish From a284db90de72551f08b40b9d845cf17d66653d8b Mon Sep 17 00:00:00 2001 From: tidusjar Date: Fri, 7 Apr 2017 22:33:46 +0100 Subject: [PATCH 017/725] messing around with the settings --- Ombi/Ombi/Ombi.csproj | 18 +++++++++++++ Ombi/Ombi/wwwroot/app/app.component.html | 4 +-- .../app/settings/ombi/ombi.component.html | 8 +++++- .../app/settings/ombi/ombi.component.ts | 4 +-- .../wwwroot/app/settings/settings.module.ts | 10 ++++++- .../app/settings/settingsmenu.component.html | 1 + .../app/settings/settingsmenu.component.ts | 27 +++++++++++++++++++ 7 files changed, 66 insertions(+), 6 deletions(-) create mode 100644 Ombi/Ombi/wwwroot/app/settings/settingsmenu.component.html create mode 100644 Ombi/Ombi/wwwroot/app/settings/settingsmenu.component.ts diff --git a/Ombi/Ombi/Ombi.csproj b/Ombi/Ombi/Ombi.csproj index 167666ce3..990a3eccc 100644 --- a/Ombi/Ombi/Ombi.csproj +++ b/Ombi/Ombi/Ombi.csproj @@ -6,6 +6,24 @@ + + + PreserveNewest + + + PreserveNewest + + + + + PreserveNewest + + + + + + + diff --git a/Ombi/Ombi/wwwroot/app/app.component.html b/Ombi/Ombi/wwwroot/app/app.component.html index d75a0c200..e9d555ec4 100644 --- a/Ombi/Ombi/wwwroot/app/app.component.html +++ b/Ombi/Ombi/wwwroot/app/app.component.html @@ -1,4 +1,4 @@ - +
diff --git a/Ombi/Ombi/wwwroot/app/settings/ombi/ombi.component.html b/Ombi/Ombi/wwwroot/app/settings/ombi/ombi.component.html index 5f282702b..a71b39d11 100644 --- a/Ombi/Ombi/wwwroot/app/settings/ombi/ombi.component.html +++ b/Ombi/Ombi/wwwroot/app/settings/ombi/ombi.component.html @@ -1 +1,7 @@ - \ No newline at end of file + +Settings + + +Enabled: + +HostName: \ No newline at end of file diff --git a/Ombi/Ombi/wwwroot/app/settings/ombi/ombi.component.ts b/Ombi/Ombi/wwwroot/app/settings/ombi/ombi.component.ts index 0d4746eec..7805b9ebe 100644 --- a/Ombi/Ombi/wwwroot/app/settings/ombi/ombi.component.ts +++ b/Ombi/Ombi/wwwroot/app/settings/ombi/ombi.component.ts @@ -6,6 +6,6 @@ }) export class OmbiComponent { - - + enabled:boolean; + host:string; } \ No newline at end of file diff --git a/Ombi/Ombi/wwwroot/app/settings/settings.module.ts b/Ombi/Ombi/wwwroot/app/settings/settings.module.ts index 588cfb602..79ca414c9 100644 --- a/Ombi/Ombi/wwwroot/app/settings/settings.module.ts +++ b/Ombi/Ombi/wwwroot/app/settings/settings.module.ts @@ -5,6 +5,9 @@ import { RouterModule, Routes } from '@angular/router'; import { OmbiComponent } from './ombi/ombi.component' +import { SettingsMenuComponent } from './settingsmenu.component'; + +import { MenuModule, InputSwitchModule, InputTextModule } from 'primeng/primeng'; const routes: Routes = [ { path: 'Settings/Ombi', component: OmbiComponent } @@ -15,15 +18,20 @@ const routes: Routes = [ CommonModule, FormsModule, RouterModule.forChild(routes), + MenuModule, + InputSwitchModule, + InputTextModule, ], declarations: [ + SettingsMenuComponent, OmbiComponent ], exports: [ RouterModule ], providers: [ - ] + ], + }) export class SettingsModule { } \ No newline at end of file diff --git a/Ombi/Ombi/wwwroot/app/settings/settingsmenu.component.html b/Ombi/Ombi/wwwroot/app/settings/settingsmenu.component.html new file mode 100644 index 000000000..2f6f7043a --- /dev/null +++ b/Ombi/Ombi/wwwroot/app/settings/settingsmenu.component.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Ombi/Ombi/wwwroot/app/settings/settingsmenu.component.ts b/Ombi/Ombi/wwwroot/app/settings/settingsmenu.component.ts new file mode 100644 index 000000000..a3cfa409e --- /dev/null +++ b/Ombi/Ombi/wwwroot/app/settings/settingsmenu.component.ts @@ -0,0 +1,27 @@ +import { Component, OnInit } from '@angular/core'; +import { MenuItem } from 'primeng/primeng'; +@Component({ + selector: 'settings-menu', + moduleId: module.id, + templateUrl: './settingsmenu.component.html' +}) +export class SettingsMenuComponent implements OnInit { + private menu: MenuItem[]; + + ngOnInit() { + this.menu = [{ + label: 'File', + items: [ + { label: 'Ombi', icon: 'fa-plus', routerLink:"/Settings/Ombi" }, + { label: 'Open', icon: 'fa-download' } + ] + }, + { + label: 'Edit', + items: [ + { label: 'Undo', icon: 'fa-refresh' }, + { label: 'Redo', icon: 'fa-repeat' } + ] + }]; + } +} \ No newline at end of file From d4aaf002d59ba83735de15504f98050ea7edf8fa Mon Sep 17 00:00:00 2001 From: tidusjar Date: Fri, 7 Apr 2017 23:52:07 +0100 Subject: [PATCH 018/725] fixed build --- Ombi/Ombi/Ombi.csproj | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/Ombi/Ombi/Ombi.csproj b/Ombi/Ombi/Ombi.csproj index 990a3eccc..6c2e094de 100644 --- a/Ombi/Ombi/Ombi.csproj +++ b/Ombi/Ombi/Ombi.csproj @@ -7,17 +7,6 @@ - - PreserveNewest - - - PreserveNewest - - - - - PreserveNewest - From 20f493e4cb66bcf43ee9b6b5f8f84380d11211c6 Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" Date: Mon, 10 Apr 2017 17:29:59 +0100 Subject: [PATCH 019/725] #865 --- .../Engine/Interfaces/IRequestEngine.cs | 4 + Ombi/Ombi.Core/Engine/RequestEngine.cs | 40 ++- .../Models/Requests/IRequestService.cs | 4 +- .../Models/Requests/JsonRequestService.cs | 32 ++- Ombi/Ombi.Helpers/StringHelper.cs | 12 + .../Repository/IRequestRepository.cs | 1 + .../Repository/RequestJsonRepository.cs | 29 ++- ...piController.cs => BaseV1ApiController.cs} | 7 +- Ombi/Ombi/Controllers/RequestController.cs | 28 ++- Ombi/Ombi/Controllers/SearchController.cs | 2 +- Ombi/Ombi/Startup.cs | 4 +- Ombi/Ombi/gulpfile.js | 11 +- Ombi/Ombi/package.json | 7 +- Ombi/Ombi/wwwroot/app/app.module.ts | 5 +- .../wwwroot/app/interfaces/IRequestModel.ts | 5 + .../app/requests/request.component.html | 228 ++++++++++++++---- .../wwwroot/app/requests/request.component.ts | 82 ++++++- .../wwwroot/app/services/request.service.ts | 24 +- .../wwwroot/app/services/search.service.ts | 15 +- .../wwwroot/app/services/service.helpers.ts | 30 ++- 20 files changed, 477 insertions(+), 93 deletions(-) create mode 100644 Ombi/Ombi.Helpers/StringHelper.cs rename Ombi/Ombi/Controllers/{BaseApiController.cs => BaseV1ApiController.cs} (89%) diff --git a/Ombi/Ombi.Core/Engine/Interfaces/IRequestEngine.cs b/Ombi/Ombi.Core/Engine/Interfaces/IRequestEngine.cs index ace321444..c8391d9c1 100644 --- a/Ombi/Ombi.Core/Engine/Interfaces/IRequestEngine.cs +++ b/Ombi/Ombi.Core/Engine/Interfaces/IRequestEngine.cs @@ -11,5 +11,9 @@ namespace Ombi.Core.Engine Task RequestMovie(SearchMovieViewModel model); bool ShouldAutoApprove(RequestType requestType); Task> GetRequests(); + Task> GetRequests(int count, int position); + Task> SearchRequest(string search); + Task RemoveRequest(int requestId); + Task UpdateRequest(RequestViewModel request); } } \ No newline at end of file diff --git a/Ombi/Ombi.Core/Engine/RequestEngine.cs b/Ombi/Ombi.Core/Engine/RequestEngine.cs index f4f02e2f7..3cf325122 100644 --- a/Ombi/Ombi.Core/Engine/RequestEngine.cs +++ b/Ombi/Ombi.Core/Engine/RequestEngine.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Threading.Tasks; using Ombi.Core.Models.Requests; @@ -7,6 +8,7 @@ using Ombi.Core.Models.Search; using Ombi.Core.Requests.Models; using Ombi.Store.Entities; using Ombi.TheMovieDbApi; +using Ombi.Helpers; namespace Ombi.Core.Engine { @@ -233,7 +235,41 @@ namespace Ombi.Core.Engine public async Task> GetRequests() { var allRequests = await RequestService.GetAllAsync(); - var viewModel = allRequests.Select(movie => new RequestViewModel + var viewModel = MapToVm(allRequests); + return viewModel; + } + + public async Task> GetRequests(int count, int position) + { + var allRequests = await RequestService.GetAllAsync(count, position); + var viewModel = MapToVm(allRequests); + return viewModel; + } + public async Task> SearchRequest(string search) + { + var allRequests = await RequestService.GetAllAsync(); + var results = allRequests.Where(x => x.Title.Contains(search, CompareOptions.IgnoreCase)); + var viewModel = MapToVm(results); + return viewModel; + } + public async Task UpdateRequest(RequestViewModel request) + { + var allRequests = await RequestService.GetAllAsync(); + var results = allRequests.FirstOrDefault(x => x.Id == request.Id); + + var model = RequestService.UpdateRequest(results); + return MapToVm(new List{model}).FirstOrDefault(); + } + + public async Task RemoveRequest(int requestId) + { + await RequestService.DeleteRequestAsync(requestId); + } + + + private IEnumerable MapToVm(IEnumerable model) + { + return model.Select(movie => new RequestViewModel { ProviderId = movie.ProviderId, Type = movie.Type, @@ -259,8 +295,6 @@ namespace Ombi.Core.Engine //RootFolders = rootFolders.ToArray(), //CurrentRootPath = radarr.Enabled ? GetRootPath(movie.RootFolderSelected, radarr).Result : null }).ToList(); - return viewModel; } - } } \ No newline at end of file diff --git a/Ombi/Ombi.Core/Models/Requests/IRequestService.cs b/Ombi/Ombi.Core/Models/Requests/IRequestService.cs index 1cc56dea8..deb40703f 100644 --- a/Ombi/Ombi.Core/Models/Requests/IRequestService.cs +++ b/Ombi/Ombi.Core/Models/Requests/IRequestService.cs @@ -16,11 +16,13 @@ namespace Ombi.Core.Requests.Models Task CheckRequestAsync(int providerId); Task CheckRequestAsync(string musicId); void DeleteRequest(RequestModel request); + Task DeleteRequestAsync(int request); Task DeleteRequestAsync(RequestModel request); RequestModel Get(int id); IEnumerable GetAll(); Task> GetAllAsync(); + Task> GetAllAsync(int count, int position); Task GetAsync(int id); - RequestBlobs UpdateRequest(RequestModel model); + RequestModel UpdateRequest(RequestModel model); } } \ No newline at end of file diff --git a/Ombi/Ombi.Core/Models/Requests/JsonRequestService.cs b/Ombi/Ombi.Core/Models/Requests/JsonRequestService.cs index f5418e74a..db741bac7 100644 --- a/Ombi/Ombi.Core/Models/Requests/JsonRequestService.cs +++ b/Ombi/Ombi.Core/Models/Requests/JsonRequestService.cs @@ -92,11 +92,19 @@ namespace Ombi.Core.Models.Requests var blob = await Repo.GetAsync(request.Id).ConfigureAwait(false); Repo.Delete(blob); } + public async Task DeleteRequestAsync(int request) + { + var blob = await Repo.GetAsync(request).ConfigureAwait(false); + Repo.Delete(blob); + } - public RequestBlobs UpdateRequest(RequestModel model) + public RequestModel UpdateRequest(RequestModel model) { - var entity = new RequestBlobs { Type = model.Type, Content = ByteConverterHelper.ReturnBytes(model), ProviderId = model.ProviderId, Id = model.Id }; - return Repo.Update(entity); + var b = Repo.Get(model.Id); + b.Content = ByteConverterHelper.ReturnBytes(model); + var blob = Repo.Update(b); + return model; + } public RequestModel Get(int id) @@ -159,6 +167,24 @@ namespace Ombi.Core.Models.Requests return retVal; } + public async Task> GetAllAsync(int count, int position) + { + var blobs = await Repo.GetAllAsync(count, position).ConfigureAwait(false); + var retVal = new List(); + + foreach (var b in blobs) + { + if (b == null) + { + continue; + } + var model = ByteConverterHelper.ReturnObject(b.Content); + model.Id = b.Id; + retVal.Add(model); + } + return retVal; + } + public void BatchUpdate(IEnumerable model) { var entities = model.Select(m => new RequestBlobs { Type = m.Type, Content = ByteConverterHelper.ReturnBytes(m), ProviderId = m.ProviderId, Id = m.Id }).ToList(); diff --git a/Ombi/Ombi.Helpers/StringHelper.cs b/Ombi/Ombi.Helpers/StringHelper.cs new file mode 100644 index 000000000..c936490f3 --- /dev/null +++ b/Ombi/Ombi.Helpers/StringHelper.cs @@ -0,0 +1,12 @@ +using System.Globalization; + +namespace Ombi.Helpers +{ + public static class StringHelper + { + public static bool Contains(this string paragraph, string word, CompareOptions opts) + { + return CultureInfo.CurrentUICulture.CompareInfo.IndexOf(paragraph, word, CompareOptions.IgnoreCase) >= 0; + } + } +} \ No newline at end of file diff --git a/Ombi/Ombi.Store/Repository/IRequestRepository.cs b/Ombi/Ombi.Store/Repository/IRequestRepository.cs index 4e1ac2798..a24985c34 100644 --- a/Ombi/Ombi.Store/Repository/IRequestRepository.cs +++ b/Ombi/Ombi.Store/Repository/IRequestRepository.cs @@ -11,6 +11,7 @@ namespace Ombi.Store.Repository RequestBlobs Get(int id); IEnumerable GetAll(); Task> GetAllAsync(); + Task> GetAllAsync(int count, int position); Task GetAsync(int id); RequestBlobs Insert(RequestBlobs entity); Task InsertAsync(RequestBlobs entity); diff --git a/Ombi/Ombi.Store/Repository/RequestJsonRepository.cs b/Ombi/Ombi.Store/Repository/RequestJsonRepository.cs index f16b96d51..ba0472bac 100644 --- a/Ombi/Ombi.Store/Repository/RequestJsonRepository.cs +++ b/Ombi/Ombi.Store/Repository/RequestJsonRepository.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; @@ -61,6 +62,18 @@ namespace Ombi.Store.Repository //}, 5); //return item; } + public async Task> GetAllAsync(int count, int position) + { + //var key = "GetAll"; + //var item = await Cache.GetOrSetAsync(key, async () => + //{ + + var page = await Db.Requests.ToListAsync().ConfigureAwait(false); + return page.Skip(position).Take(count); + + //}, 5); + //return item; + } public RequestBlobs Get(int id) { @@ -99,9 +112,17 @@ namespace Ombi.Store.Repository public RequestBlobs Update(RequestBlobs entity) { - - return Db.Requests.Update(entity).Entity; - Db.SaveChanges(); + try + { + Db.SaveChanges(); + + return entity; + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } } diff --git a/Ombi/Ombi/Controllers/BaseApiController.cs b/Ombi/Ombi/Controllers/BaseV1ApiController.cs similarity index 89% rename from Ombi/Ombi/Controllers/BaseApiController.cs rename to Ombi/Ombi/Controllers/BaseV1ApiController.cs index f122c578b..87d776ae6 100644 --- a/Ombi/Ombi/Controllers/BaseApiController.cs +++ b/Ombi/Ombi/Controllers/BaseV1ApiController.cs @@ -1,7 +1,7 @@ #region Copyright // /************************************************************************ // Copyright (c) 2017 Jamie Rees -// File: BaseApiController.cs +// File: BaseV1ApiController.cs // Created By: Jamie Rees // // Permission is hereby granted, free of charge, to any person obtaining @@ -29,8 +29,9 @@ using Microsoft.AspNetCore.Mvc; namespace Ombi.Controllers { - [Route("api/[controller]")] - public class BaseApiController : Controller + [Route(ApiBase)] + public class BaseV1ApiController : Controller { + protected const string ApiBase = "api/v1/[controller]"; } } \ No newline at end of file diff --git a/Ombi/Ombi/Controllers/RequestController.cs b/Ombi/Ombi/Controllers/RequestController.cs index 6fa16fca6..d90268845 100644 --- a/Ombi/Ombi/Controllers/RequestController.cs +++ b/Ombi/Ombi/Controllers/RequestController.cs @@ -7,7 +7,7 @@ using Ombi.Core.Models.Search; namespace Ombi.Controllers { - public class RequestController : BaseApiController + public class RequestController : BaseV1ApiController { public RequestController(IRequestEngine engine) { @@ -22,10 +22,34 @@ namespace Ombi.Controllers return await RequestEngine.GetRequests(); } + [HttpGet("{count:int}/{position:int}", Name = "GetRequestsByCount")] + public async Task> GetRequests(int count, int position) + { + return await RequestEngine.GetRequests(count, position); + } + [HttpPost("movie")] - public async Task SearchMovie([FromBody]SearchMovieViewModel movie) + public async Task RequestMovie([FromBody]SearchMovieViewModel movie) { return await RequestEngine.RequestMovie(movie); } + + [HttpGet("search/{searchTerm}")] + public async Task> Search(string searchTerm) + { + return await RequestEngine.SearchRequest(searchTerm); + } + + [HttpDelete("{requestId:int}")] + public async Task DeleteRequest(int requestId) + { + await RequestEngine.RemoveRequest(requestId); + } + + [HttpPost] + public async Task UpdateRequest([FromBody]RequestViewModel model) + { + return await RequestEngine.UpdateRequest(model); + } } } diff --git a/Ombi/Ombi/Controllers/SearchController.cs b/Ombi/Ombi/Controllers/SearchController.cs index 45200cbd1..3001791a4 100644 --- a/Ombi/Ombi/Controllers/SearchController.cs +++ b/Ombi/Ombi/Controllers/SearchController.cs @@ -8,7 +8,7 @@ using Ombi.Core.Models.Search; namespace Ombi.Controllers { - public class SearchController : BaseApiController + public class SearchController : BaseV1ApiController { public SearchController(IMovieEngine movie) { diff --git a/Ombi/Ombi/Startup.cs b/Ombi/Ombi/Startup.cs index bf16a1f69..f418fcc0e 100644 --- a/Ombi/Ombi/Startup.cs +++ b/Ombi/Ombi/Startup.cs @@ -41,7 +41,7 @@ namespace Ombi { loggerFactory.AddConsole(Configuration.GetSection("Logging")); loggerFactory.AddDebug(); - + if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); @@ -60,7 +60,7 @@ namespace Ombi }); app.UseMvc(routes => - { + { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); diff --git a/Ombi/Ombi/gulpfile.js b/Ombi/Ombi/gulpfile.js index 6b9624dde..1e5496d7c 100644 --- a/Ombi/Ombi/gulpfile.js +++ b/Ombi/Ombi/gulpfile.js @@ -31,7 +31,7 @@ var paths = { '@angular/platform-browser-dynamic', '@angular/http', '@angular/router', - '@angular/forms' + '@angular/forms', ], dest: './lib' }, @@ -56,7 +56,7 @@ var paths = { './bower_components/PACE/pace.js', './node_modules/bootstrap/dist/js/bootstrap.js', './node_modules/tether/dist/js/tether.js', - './systemjs.config.js' + './systemjs.config.js', ], dest: './lib/' }, @@ -118,7 +118,12 @@ var paths = { name: 'primeng', src: './node_modules/primeng/**/*.js', dest: './lib/primeng/' - } + }, + { + name: "angular2-infinite-scroll", + src: ['./node_modules/angular2-infinite-scroll/**/*.js', '!./node_modules/angular2-infinite-scroll/bundles/**/*.js'], + dest:"./lib/angular2-infinite-scroll/" + }, ], sass: { // Simple sass->css compilation src: ['./Styles/**/*.scss', '!./Styles/primeng/**'], diff --git a/Ombi/Ombi/package.json b/Ombi/Ombi/package.json index ad2eb6014..02951242e 100644 --- a/Ombi/Ombi/package.json +++ b/Ombi/Ombi/package.json @@ -16,10 +16,12 @@ "@angular/router": "^4.0.0", "@types/jquery": "^2.0.33", "@types/systemjs": "^0.20.2", + "angular2-infinite-scroll": "^0.3.4", + "bootstrap": "3.3.6", "core-js": "^2.4.1", "del": "^2.2.2", "gulp": "~3.9.1", - "gulp-changed": "^1.3.0", + "gulp-changed": "^1.3.0", "gulp-clean-css": "^3.0.4", "gulp-filter": "^5.0.0", "gulp-if": "^2.0.2", @@ -39,7 +41,6 @@ "systemjs-builder": "^0.15.34", "tether": "^1.4.0", "typescript": "^2.2.1", - "zone.js": "^0.8.5", - "bootstrap": "3.3.6" + "zone.js": "^0.8.5" } } diff --git a/Ombi/Ombi/wwwroot/app/app.module.ts b/Ombi/Ombi/wwwroot/app/app.module.ts index c7a1dee52..8c7d4584b 100644 --- a/Ombi/Ombi/wwwroot/app/app.module.ts +++ b/Ombi/Ombi/wwwroot/app/app.module.ts @@ -8,6 +8,8 @@ import { AppComponent } from './app.component'; import { RouterModule, Routes } from '@angular/router'; import { HttpModule } from '@angular/http'; +import { InfiniteScrollModule } from 'angular2-infinite-scroll/angular2-infinite-scroll' + import { SearchComponent } from './search/search.component'; import { RequestComponent } from './requests/request.component'; import { PageNotFoundComponent } from './errors/not-found.component'; @@ -41,7 +43,8 @@ const routes: Routes = [ FormsModule, SettingsModule, DataTableModule, - SharedModule + SharedModule, + InfiniteScrollModule ], declarations: [ AppComponent, diff --git a/Ombi/Ombi/wwwroot/app/interfaces/IRequestModel.ts b/Ombi/Ombi/wwwroot/app/interfaces/IRequestModel.ts index 31db39bf7..c93a171c1 100644 --- a/Ombi/Ombi/wwwroot/app/interfaces/IRequestModel.ts +++ b/Ombi/Ombi/wwwroot/app/interfaces/IRequestModel.ts @@ -24,4 +24,9 @@ export enum RequestType { movie = 1, tvShow = 2 +} + +export interface IRequestsPageScroll { + count: number, + position:number } \ No newline at end of file diff --git a/Ombi/Ombi/wwwroot/app/requests/request.component.html b/Ombi/Ombi/wwwroot/app/requests/request.component.html index b2654cbdc..f743e16d4 100644 --- a/Ombi/Ombi/wwwroot/app/requests/request.component.html +++ b/Ombi/Ombi/wwwroot/app/requests/request.component.html @@ -1,46 +1,182 @@ -

Requests

- - - - - - - - - - - - - - -
-
-
- - - -
-
-
-
-
Type:
-
{{request.type}}
-
-
-
Status:
-
{{request.status}}
-
-
-
Approved:
-
{{request.approved}}
-
-
-
Available:
-
{{request.available}}
-
-
-
-
-
-
-
+

Requests

+ +
+
+ +
+
+
+ +
+ + +
+ + +
+
+ + poster + poster + +
+ +
+ +
+
+ Status: + {{request.status}} +
+ +
+ Request status: + Request Available + Processing Request + Request Denied + + Pending Approval + +
+
+ Denied: + +
+ + +
Release Date: {{request.releaseDate}}
+ + + +
Requested By: {{request.requestedUsers}}
+ +
Requested Date: {{request.requestedDate}}
+ +
+ + + + + + + +
+
+
+ +
+ + + +
+ + + + + + + + + + + +
+ + +
+ + + +
+
+
+ + + + + + + + + + +
+ + + + + +
+
+
+ + + + +
+
diff --git a/Ombi/Ombi/wwwroot/app/requests/request.component.ts b/Ombi/Ombi/wwwroot/app/requests/request.component.ts index 65a733b77..3f7d96e51 100644 --- a/Ombi/Ombi/wwwroot/app/requests/request.component.ts +++ b/Ombi/Ombi/wwwroot/app/requests/request.component.ts @@ -1,4 +1,10 @@ import { Component, OnInit } from '@angular/core'; +import { Subject } from 'rxjs/Subject'; +import 'rxjs/add/operator/debounceTime'; +import 'rxjs/add/operator/distinctUntilChanged'; +import 'rxjs/add/operator/map'; + + import 'rxjs/add/operator/debounceTime'; import 'rxjs/add/operator/distinctUntilChanged'; import 'rxjs/add/operator/map'; @@ -14,11 +20,83 @@ import { IRequestModel } from '../interfaces/IRequestModel'; providers: [RequestService] }) export class RequestComponent implements OnInit { - constructor(private requestService: RequestService) { } + constructor(private requestService: RequestService) { + this.searchChanged + .debounceTime(600) // Wait Xms afterthe last event before emitting last event + .distinctUntilChanged() // only emit if value is different from previous value + .subscribe(x => { + this.searchText = x as string; + if (this.searchText === "") { + this.resetSearch(); + return; + } + this.requestService.searchRequests(this.searchText).subscribe(x => this.requests = x); + }); + } requests: IRequestModel[]; + searchChanged: Subject = new Subject(); + searchText: string; + + private currentlyLoaded: number; + private amountToLoad : number; + ngOnInit() { - this.requestService.getAllRequests().subscribe(x => this.requests = x); + this.amountToLoad = 5; + this.currentlyLoaded = 5; + this.loadInit(); + } + + loadMore() { + this.requestService.getRequests(this.amountToLoad, this.currentlyLoaded + 1).subscribe(x => { + this.requests.push.apply(this.requests, x); + this.currentlyLoaded = this.currentlyLoaded + this.amountToLoad; + }); + } + + search(text: any) { + this.searchChanged.next(text.target.value); + } + + removeRequest(request: IRequestModel) { + this.requestService.removeRequest(request).subscribe(); + this.removeRequestFromUi(request); + } + + changeAvailability(request: IRequestModel, available: boolean) { + request.available = available; + this.updateRequest(request); + } + + approve(request: IRequestModel) { + request.approved = true; + this.updateRequest(request); + } + + deny(request: IRequestModel) { + request.approved = false; + request.denied = true; + this.updateRequest(request); + } + + private updateRequest(request: IRequestModel) { + this.requestService.updateRequest(request).subscribe(x => request = x); + } + + private loadInit() { + this.requestService.getRequests(this.amountToLoad, 0).subscribe(x => this.requests = x); + } + + private resetSearch() { + this.currentlyLoaded = 5; + this.loadInit(); + } + + private removeRequestFromUi(key : IRequestModel) { + var index = this.requests.indexOf(key, 0); + if (index > -1) { + this.requests.splice(index, 1); + } } } \ No newline at end of file diff --git a/Ombi/Ombi/wwwroot/app/services/request.service.ts b/Ombi/Ombi/wwwroot/app/services/request.service.ts index fe8d188f0..adcdd8667 100644 --- a/Ombi/Ombi/wwwroot/app/services/request.service.ts +++ b/Ombi/Ombi/wwwroot/app/services/request.service.ts @@ -8,16 +8,32 @@ import { ISearchMovieResult } from '../interfaces/ISearchMovieResult'; import { IRequestModel } from '../interfaces/IRequestModel'; @Injectable() -export class RequestService { - constructor(private http: Http) { +export class RequestService extends ServiceHelpers { + constructor(http: Http) { + super(http, '/api/v1/Request/'); } requestMovie(movie: ISearchMovieResult): Observable { - return this.http.post('/api/Request/Movie/', JSON.stringify(movie), ServiceHelpers.RequestOptions).map(ServiceHelpers.extractData); + return this.http.post(`${this.url}/Movie/`, JSON.stringify(movie), { headers: this.headers }).map(this.extractData); } getAllRequests(): Observable { - return this.http.get('/api/request').map(ServiceHelpers.extractData); + return this.http.get(this.url).map(this.extractData); } + getRequests(count: number, position: number): Observable { + return this.http.get(`${this.url}/${count}/${position}`).map(this.extractData); + } + + searchRequests(search: string): Observable { + return this.http.get(`${this.url}/search/${search}`).map(this.extractData); + } + + removeRequest(request: IRequestModel): Observable { + return this.http.delete(`${this.url}/${request.id}`).map(this.extractData); + } + + updateRequest(request: IRequestModel) : Observable { + return this.http.post(`${this.url}/`, JSON.stringify(request), { headers: this.headers }).map(this.extractData); + } } \ No newline at end of file diff --git a/Ombi/Ombi/wwwroot/app/services/search.service.ts b/Ombi/Ombi/wwwroot/app/services/search.service.ts index f684fa235..a9de2ddfe 100644 --- a/Ombi/Ombi/wwwroot/app/services/search.service.ts +++ b/Ombi/Ombi/wwwroot/app/services/search.service.ts @@ -6,24 +6,25 @@ import { ServiceHelpers } from './service.helpers'; import { ISearchMovieResult } from '../interfaces/ISearchMovieResult'; @Injectable() -export class SearchService { - constructor(private http: Http) { +export class SearchService extends ServiceHelpers { + constructor(http: Http) { + super(http, "/api/v1/search"); } searchMovie(searchTerm: string): Observable { - return this.http.get('/api/Search/Movie/' + searchTerm).map(ServiceHelpers.extractData); + return this.http.get(`${this.url}/Movie/` + searchTerm).map(this.extractData); } popularMovies(): Observable { - return this.http.get('/api/Search/Movie/Popular').map(ServiceHelpers.extractData); + return this.http.get(`${this.url}/Movie/Popular`).map(this.extractData); } upcomignMovies(): Observable { - return this.http.get('/api/Search/Movie/upcoming').map(ServiceHelpers.extractData); + return this.http.get(`${this.url}/Movie/upcoming`).map(this.extractData); } nowPlayingMovies(): Observable { - return this.http.get('/api/Search/Movie/nowplaying').map(ServiceHelpers.extractData); + return this.http.get(`${this.url}/Movie/nowplaying`).map(this.extractData); } topRatedMovies(): Observable { - return this.http.get('/api/Search/Movie/toprated').map(ServiceHelpers.extractData); + return this.http.get(`${this.url}/Movie/toprated`).map(this.extractData); } } \ No newline at end of file diff --git a/Ombi/Ombi/wwwroot/app/services/service.helpers.ts b/Ombi/Ombi/wwwroot/app/services/service.helpers.ts index b8b0176a6..f1d111c49 100644 --- a/Ombi/Ombi/wwwroot/app/services/service.helpers.ts +++ b/Ombi/Ombi/wwwroot/app/services/service.helpers.ts @@ -1,15 +1,29 @@ -import { Headers, RequestOptions, Response } from '@angular/http'; +import { Headers, Response, Http } from '@angular/http'; +import { Observable } from 'rxjs/Observable'; export class ServiceHelpers { - public static Headers = new Headers({ 'Content-Type': 'application/json' }); - public static RequestOptions = new RequestOptions({ - headers: ServiceHelpers.Headers - }); + constructor(protected http: Http, protected url: string) { + this.headers = new Headers(); + this.headers.append('Content-Type', 'application/json; charset=utf-8'); + } + + protected headers: Headers; - public static extractData(res: Response) { - console.log(res); - return res.json(); + protected extractData(res: Response) { + let body = res.json(); + //console.log('extractData', body || {}); + return body || {}; } + protected handleError(error: any) { + // In a real world app, we might use a remote logging infrastructure + // We'd also dig deeper into the error to get a better message + let errMsg = (error.message) ? error.message : + error.status ? `${error.status} - ${error.statusText}` : 'Server error'; + console.error(errMsg); // log to console instead + return Observable.throw(errMsg); + } + + } \ No newline at end of file From a21db34d603e05d8cbd71c4da001b6276798110c Mon Sep 17 00:00:00 2001 From: tidusjar Date: Mon, 10 Apr 2017 22:18:27 +0100 Subject: [PATCH 020/725] More on the search and requests page. It's almost there for movies. Need to add some filtering logic #865 --- .../Engine/Interfaces/IMovieEngine.cs | 1 + Ombi/Ombi.Core/Engine/MovieEngine.cs | 69 ++++++++--- Ombi/Ombi.Core/Engine/RequestEngine.cs | 16 +++ Ombi/Ombi/Controllers/SearchController.cs | 6 + Ombi/Ombi/Styles/base.scss | 2 +- Ombi/Ombi/gulpfile.js | 11 +- Ombi/Ombi/package.json | 1 + Ombi/Ombi/wwwroot/app/app.component.html | 6 +- .../app/requests/request.component.html | 117 +++++++++--------- .../wwwroot/app/requests/request.component.ts | 2 + .../wwwroot/app/search/search.component.html | 14 +-- .../wwwroot/app/search/search.component.ts | 32 ++++- .../wwwroot/app/services/search.service.ts | 3 + 13 files changed, 187 insertions(+), 93 deletions(-) diff --git a/Ombi/Ombi.Core/Engine/Interfaces/IMovieEngine.cs b/Ombi/Ombi.Core/Engine/Interfaces/IMovieEngine.cs index fcd397093..e4bc37ace 100644 --- a/Ombi/Ombi.Core/Engine/Interfaces/IMovieEngine.cs +++ b/Ombi/Ombi.Core/Engine/Interfaces/IMovieEngine.cs @@ -11,5 +11,6 @@ namespace Ombi.Core Task> ProcessMovieSearch(string search); Task> TopRatedMovies(); Task> UpcomingMovies(); + Task> LookupImdbInformation(IEnumerable movies); } } \ No newline at end of file diff --git a/Ombi/Ombi.Core/Engine/MovieEngine.cs b/Ombi/Ombi.Core/Engine/MovieEngine.cs index fa6eae68a..d9b3c9c31 100644 --- a/Ombi/Ombi.Core/Engine/MovieEngine.cs +++ b/Ombi/Ombi.Core/Engine/MovieEngine.cs @@ -23,6 +23,57 @@ namespace Ombi.Core.Engine private IRequestService RequestService { get; } private IMovieDbApi MovieApi { get; } + public async Task> LookupImdbInformation(IEnumerable movies) + { + var retVal = new List(); + Dictionary dbMovies = await RequestedMovies(); + foreach (var m in movies) + { + + var movieInfo = await MovieApi.GetMovieInformationWithVideo(m.Id); + var viewMovie = new SearchMovieViewModel + { + Adult = movieInfo.adult, + BackdropPath = movieInfo.backdrop_path, + Id = movieInfo.id, + OriginalLanguage = movieInfo.original_language, + OriginalTitle = movieInfo.original_title, + Overview = movieInfo.overview, + Popularity = movieInfo.popularity, + PosterPath = movieInfo.poster_path, + ReleaseDate = + string.IsNullOrEmpty(movieInfo.release_date) + ? DateTime.MinValue + : DateTime.Parse(movieInfo.release_date), + Title = movieInfo.title, + Video = movieInfo.video, + VoteAverage = movieInfo.vote_average, + VoteCount = movieInfo.vote_count, + ImdbId = movieInfo?.imdb_id, + Homepage = movieInfo?.homepage + }; + retVal.Add(viewMovie); + // TODO needs to be careful about this, it's adding extra time to search... + // https://www.themoviedb.org/talk/5807f4cdc3a36812160041f2 + //var videoId = movieInfo?.video ?? false + // ? movieInfo?.videos?.results?.FirstOrDefault()?.key + // : string.Empty; + + //viewMovie.Trailer = string.IsNullOrEmpty(videoId) + // ? string.Empty + // : $"https://www.youtube.com/watch?v={videoId}"; + if (dbMovies.ContainsKey(movieInfo.id) /*&& canSee*/) // compare to the requests db + { + var dbm = dbMovies[movieInfo.id]; + + viewMovie.Requested = true; + viewMovie.Approved = dbm.Approved; + viewMovie.Available = dbm.Available; + } + } + return retVal; + } + public async Task> ProcessMovieSearch(string search) { var api = new TheMovieDbApi.TheMovieDbApi(); @@ -103,25 +154,7 @@ namespace Ombi.Core.Engine }; viewMovies.Add(viewMovie); - var counter = 0; - if (counter <= 5) // Let's only do it for the first 5 items - { - var movieInfo = await MovieApi.GetMovieInformationWithVideo(movie.id); - - // TODO needs to be careful about this, it's adding extra time to search... - // https://www.themoviedb.org/talk/5807f4cdc3a36812160041f2 - viewMovie.ImdbId = movieInfo?.imdb_id; - viewMovie.Homepage = movieInfo?.homepage; - //var videoId = movieInfo?.video ?? false - // ? movieInfo?.videos?.results?.FirstOrDefault()?.key - // : string.Empty; - //viewMovie.Trailer = string.IsNullOrEmpty(videoId) - // ? string.Empty - // : $"https://www.youtube.com/watch?v={videoId}"; - - counter++; - } // var canSee = CanUserSeeThisRequest(viewMovie.Id, Security.HasPermissions(User, Permissions.UsersCanViewOnlyOwnRequests), dbMovies); diff --git a/Ombi/Ombi.Core/Engine/RequestEngine.cs b/Ombi/Ombi.Core/Engine/RequestEngine.cs index 3cf325122..2bb21d183 100644 --- a/Ombi/Ombi.Core/Engine/RequestEngine.cs +++ b/Ombi/Ombi.Core/Engine/RequestEngine.cs @@ -257,6 +257,22 @@ namespace Ombi.Core.Engine var allRequests = await RequestService.GetAllAsync(); var results = allRequests.FirstOrDefault(x => x.Id == request.Id); + results.Approved = request.Approved; + results.Available = request.Available; + results.Denied = request.Denied; + results.DeniedReason = request.DeniedReason; + //results.AdminNote = request.AdminNote; + results.ImdbId = request.ImdbId; + results.Episodes = request.Episodes?.ToList() ?? new List(); + results.IssueId = request.IssueId; + //results.Issues = request.Issues; + //results.OtherMessage = request.OtherMessage; + results.Overview = request.Overview; + results.PosterPath = request.PosterPath; + results.RequestedUsers = request.RequestedUsers?.ToList() ?? new List(); + //results.RootFolderSelected = request.RootFolderSelected; + + var model = RequestService.UpdateRequest(results); return MapToVm(new List{model}).FirstOrDefault(); } diff --git a/Ombi/Ombi/Controllers/SearchController.cs b/Ombi/Ombi/Controllers/SearchController.cs index 3001791a4..c7d48b8b9 100644 --- a/Ombi/Ombi/Controllers/SearchController.cs +++ b/Ombi/Ombi/Controllers/SearchController.cs @@ -23,6 +23,12 @@ namespace Ombi.Controllers return await MovieEngine.ProcessMovieSearch(searchTerm); } + [HttpPost("movie/extrainfo")] + public async Task> GetImdbInfo([FromBody]IEnumerable model) + { + return await MovieEngine.LookupImdbInformation(model); + } + [HttpGet("movie/popular")] public async Task> Popular() { diff --git a/Ombi/Ombi/Styles/base.scss b/Ombi/Ombi/Styles/base.scss index 095d7d2ab..7755b64e6 100644 --- a/Ombi/Ombi/Styles/base.scss +++ b/Ombi/Ombi/Styles/base.scss @@ -692,7 +692,7 @@ $border-radius: 10px; // Bootstrap overrides html { - font-size: 10px; + font-size: 16px; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); } body { diff --git a/Ombi/Ombi/gulpfile.js b/Ombi/Ombi/gulpfile.js index 1e5496d7c..c4697e4c7 100644 --- a/Ombi/Ombi/gulpfile.js +++ b/Ombi/Ombi/gulpfile.js @@ -32,6 +32,7 @@ var paths = { '@angular/http', '@angular/router', '@angular/forms', + ], dest: './lib' }, @@ -122,7 +123,7 @@ var paths = { { name: "angular2-infinite-scroll", src: ['./node_modules/angular2-infinite-scroll/**/*.js', '!./node_modules/angular2-infinite-scroll/bundles/**/*.js'], - dest:"./lib/angular2-infinite-scroll/" + dest: "./lib/angular2-infinite-scroll/" }, ], sass: { // Simple sass->css compilation @@ -273,11 +274,17 @@ gulp.task('typescript', function () { return run('tsc').exec(); }); +uglify().on('error', + function(err) { + gutil.log(gutil.colors.red('[Error]'), err.toString()); + this.emit('end'); + }); + gulp.task('fullvar', () => { global.full = true }); gulp.task('copy', ['lib', 'libcss', 'libfonts', 'libimages', 'npm', 'modules']); gulp.task('compile', callback => runSequence('copy', 'sass', callback)); gulp.task('build', callback => runSequence('compile', 'bundle', callback)); -gulp.task('full', callback => runSequence('clean', 'compile', callback)); +gulp.task('full', callback => runSequence(/*'clean',*/ 'compile', callback)); // Use this in a build server environment to compile and bundle everything gulp.task('publish', callback => runSequence('fullvar', 'full', 'typescript', 'bundle', callback)); diff --git a/Ombi/Ombi/package.json b/Ombi/Ombi/package.json index 02951242e..f74d68e3e 100644 --- a/Ombi/Ombi/package.json +++ b/Ombi/Ombi/package.json @@ -17,6 +17,7 @@ "@types/jquery": "^2.0.33", "@types/systemjs": "^0.20.2", "angular2-infinite-scroll": "^0.3.4", + "angular2-moment": "^1.3.3", "bootstrap": "3.3.6", "core-js": "^2.4.1", "del": "^2.2.2", diff --git a/Ombi/Ombi/wwwroot/app/app.component.html b/Ombi/Ombi/wwwroot/app/app.component.html index e9d555ec4..a75b30406 100644 --- a/Ombi/Ombi/wwwroot/app/app.component.html +++ b/Ombi/Ombi/wwwroot/app/app.component.html @@ -14,13 +14,13 @@
diff --git a/Ombi/Ombi/wwwroot/app/requests/request.component.html b/Ombi/Ombi/wwwroot/app/requests/request.component.html index f743e16d4..4c43df249 100644 --- a/Ombi/Ombi/wwwroot/app/requests/request.component.html +++ b/Ombi/Ombi/wwwroot/app/requests/request.component.html @@ -1,5 +1,5 @@ 

Requests

- +

Below you can see yours and all other requests, as well as their download and approval status.

@@ -26,8 +26,8 @@

@@ -38,11 +38,11 @@
Request status: - Request Available - Processing Request + Available + Processing Request Request Denied - Pending Approval + Pending Approval
@@ -51,8 +51,8 @@
-
Release Date: {{request.releaseDate}}
- +
Release Date: {{request.releaseDate | date}}
+
Requested By: {{request.requestedUsers}}
-
Requested Date: {{request.requestedDate}}
+
Requested Date: {{request.requestedDate | date}}
-
- +
- + + - - - -
- - + + {{#if_eq hasRootFolders true}}
- - +
-
-
+ {{/if_eq}} + --> - - - +
+
+ + +
+ + + +
+
+
+ +
+ +
+ +
+ + +
- + @@ -163,11 +168,11 @@ diff --git a/Ombi/Ombi/wwwroot/app/requests/request.component.ts b/Ombi/Ombi/wwwroot/app/requests/request.component.ts index 3f7d96e51..1ffd2f80b 100644 --- a/Ombi/Ombi/wwwroot/app/requests/request.component.ts +++ b/Ombi/Ombi/wwwroot/app/requests/request.component.ts @@ -66,11 +66,13 @@ export class RequestComponent implements OnInit { changeAvailability(request: IRequestModel, available: boolean) { request.available = available; + this.updateRequest(request); } approve(request: IRequestModel) { request.approved = true; + request.denied = false; this.updateRequest(request); } diff --git a/Ombi/Ombi/wwwroot/app/search/search.component.html b/Ombi/Ombi/wwwroot/app/search/search.component.html index 1ef35faf1..464e08e31 100644 --- a/Ombi/Ombi/wwwroot/app/search/search.component.html +++ b/Ombi/Ombi/wwwroot/app/search/search.component.html @@ -1,5 +1,5 @@ 

Search

-

Search Paragraph

+

Want to watch something that is not currently available?! No problem! Just search for it below and request it!


@@ -137,17 +137,15 @@ Release Date: {{result.releaseDate | date: 'dd/MM/yyyy'}} - @UI.Search_Available - - @UI.Search_Processing_Request - -
+ Available + Processing Request +
diff --git a/Ombi/Ombi/wwwroot/app/search/search.component.ts b/Ombi/Ombi/wwwroot/app/search/search.component.ts index 20a5a6a7a..fc61a6651 100644 --- a/Ombi/Ombi/wwwroot/app/search/search.component.ts +++ b/Ombi/Ombi/wwwroot/app/search/search.component.ts @@ -33,7 +33,13 @@ export class SearchComponent implements OnInit { this.clearResults(); return; } - this.searchService.searchMovie(this.searchText).subscribe(x => this.movieResults = x); + this.searchService.searchMovie(this.searchText).subscribe(x => { + this.movieResults = x; + + // Now let's load some exta info including IMDBId + // This way the search is fast at displaying results. + this.getExtaInfo(); + }); }); } @@ -66,19 +72,35 @@ export class SearchComponent implements OnInit { popularMovies() { this.clearResults(); - this.searchService.popularMovies().subscribe(x => this.movieResults = x); + this.searchService.popularMovies().subscribe(x => { + this.movieResults = x; + this.getExtaInfo(); + }); } nowPlayingMovies() { this.clearResults(); - this.searchService.nowPlayingMovies().subscribe(x => this.movieResults = x); + this.searchService.nowPlayingMovies().subscribe(x => { + this.movieResults = x; + this.getExtaInfo(); + }); } topRatedMovies() { this.clearResults(); - this.searchService.topRatedMovies().subscribe(x => this.movieResults = x); + this.searchService.topRatedMovies().subscribe(x => { + this.movieResults = x; + this.getExtaInfo(); + }); } upcomingMovies() { this.clearResults(); - this.searchService.upcomignMovies().subscribe(x => this.movieResults = x); + this.searchService.upcomignMovies().subscribe(x => { + this.movieResults = x; + this.getExtaInfo(); + }); + } + + private getExtaInfo() { + this.searchService.extraInfo(this.movieResults).subscribe(m => this.movieResults = m); } private clearResults() { diff --git a/Ombi/Ombi/wwwroot/app/services/search.service.ts b/Ombi/Ombi/wwwroot/app/services/search.service.ts index a9de2ddfe..f0abe17e0 100644 --- a/Ombi/Ombi/wwwroot/app/services/search.service.ts +++ b/Ombi/Ombi/wwwroot/app/services/search.service.ts @@ -27,4 +27,7 @@ export class SearchService extends ServiceHelpers { topRatedMovies(): Observable { return this.http.get(`${this.url}/Movie/toprated`).map(this.extractData); } + extraInfo(movies: ISearchMovieResult[]): Observable { + return this.http.post(`${this.url}/Movie/extrainfo`, JSON.stringify(movies), { headers: this.headers }).map(this.extractData); + } } \ No newline at end of file From 98fb15c2639a0d3af90db4d1654472b06222c615 Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" Date: Tue, 11 Apr 2017 16:36:26 +0100 Subject: [PATCH 021/725] auth --- .../Ombi.DependencyInjection/IocExtensions.cs | 14 ++ .../Ombi.DependencyInjection.csproj | 9 ++ Ombi/Ombi.Store/Ombi.Store.csproj | 2 + Ombi/Ombi/.gitignore | 2 +- Ombi/Ombi/Auth/CustomJwtDataFormat.cs | 72 +++++++++ Ombi/Ombi/Auth/TokenProviderMiddleware.cs | 148 ++++++++++++++++++ Ombi/Ombi/Auth/TokenProviderOptions.cs | 52 ++++++ Ombi/Ombi/Controllers/HomeController.cs | 25 +-- Ombi/Ombi/Controllers/RequestController.cs | 2 + Ombi/Ombi/Models/RequestResult.cs | 18 +++ Ombi/Ombi/Models/UserAuthModel.cs | 8 + Ombi/Ombi/Ombi.csproj | 33 ++++ Ombi/Ombi/Program.cs | 1 + Ombi/Ombi/Startup.Auth.cs | 97 ++++++++++++ Ombi/Ombi/Startup.cs | 20 ++- Ombi/Ombi/appsettings.json | 7 + Ombi/Ombi/gulpfile.js | 1 + Ombi/Ombi/package.json | 1 + Ombi/Ombi/typings/globals/globals.d.ts | 5 + Ombi/Ombi/typings/index.d.ts | 1 + Ombi/Ombi/wwwroot/app/app.component.ts | 3 +- Ombi/Ombi/wwwroot/app/app.module.ts | 20 ++- Ombi/Ombi/wwwroot/app/auth/IUserLogin.ts | 4 + Ombi/Ombi/wwwroot/app/auth/auth.guard.ts | 20 +++ Ombi/Ombi/wwwroot/app/auth/auth.module.ts | 18 +++ Ombi/Ombi/wwwroot/app/auth/auth.service.ts | 33 ++++ .../wwwroot/app/login/login.component.html | 32 ++++ .../Ombi/wwwroot/app/login/login.component.ts | 34 ++++ .../wwwroot/app/services/request.service.ts | 8 +- .../wwwroot/app/services/search.service.ts | 8 +- .../wwwroot/app/services/service.helpers.ts | 30 ++++ .../wwwroot/app/settings/settings.module.ts | 11 +- 32 files changed, 696 insertions(+), 43 deletions(-) create mode 100644 Ombi/Ombi/Auth/CustomJwtDataFormat.cs create mode 100644 Ombi/Ombi/Auth/TokenProviderMiddleware.cs create mode 100644 Ombi/Ombi/Auth/TokenProviderOptions.cs create mode 100644 Ombi/Ombi/Models/RequestResult.cs create mode 100644 Ombi/Ombi/Models/UserAuthModel.cs create mode 100644 Ombi/Ombi/Startup.Auth.cs create mode 100644 Ombi/Ombi/typings/globals/globals.d.ts create mode 100644 Ombi/Ombi/typings/index.d.ts create mode 100644 Ombi/Ombi/wwwroot/app/auth/IUserLogin.ts create mode 100644 Ombi/Ombi/wwwroot/app/auth/auth.guard.ts create mode 100644 Ombi/Ombi/wwwroot/app/auth/auth.module.ts create mode 100644 Ombi/Ombi/wwwroot/app/auth/auth.service.ts create mode 100644 Ombi/Ombi/wwwroot/app/login/login.component.html create mode 100644 Ombi/Ombi/wwwroot/app/login/login.component.ts diff --git a/Ombi/Ombi.DependencyInjection/IocExtensions.cs b/Ombi/Ombi.DependencyInjection/IocExtensions.cs index 189c58b3b..639d26f0e 100644 --- a/Ombi/Ombi.DependencyInjection/IocExtensions.cs +++ b/Ombi/Ombi.DependencyInjection/IocExtensions.cs @@ -1,5 +1,8 @@ using System; using System.Diagnostics.CodeAnalysis; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authorization; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Ombi.Core; using Ombi.Core.Engine; @@ -53,5 +56,16 @@ namespace Ombi.DependencyInjection services.AddTransient(); return services; } + + public static IServiceCollection RegisterIdentity(this IServiceCollection services) + { + services.AddAuthorization(auth => + { + auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder() + .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme) + .RequireAuthenticatedUser().Build()); + }); + return services; + } } } diff --git a/Ombi/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj b/Ombi/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj index 0be698b01..a98f5c6aa 100644 --- a/Ombi/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj +++ b/Ombi/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj @@ -4,11 +4,20 @@ netstandard1.4 + + + + + + + + ..\..\..\..\..\.nuget\packages\microsoft.aspnetcore.authorization\1.1.1\lib\netstandard1.3\Microsoft.AspNetCore.Authorization.dll + ..\..\..\..\..\.nuget\packages\microsoft.extensions.dependencyinjection.abstractions\1.1.0\lib\netstandard1.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll diff --git a/Ombi/Ombi.Store/Ombi.Store.csproj b/Ombi/Ombi.Store/Ombi.Store.csproj index 7d9da619e..d8eb4db16 100644 --- a/Ombi/Ombi.Store/Ombi.Store.csproj +++ b/Ombi/Ombi.Store/Ombi.Store.csproj @@ -6,6 +6,8 @@ + + diff --git a/Ombi/Ombi/.gitignore b/Ombi/Ombi/.gitignore index 577f921f9..ef724f488 100644 --- a/Ombi/Ombi/.gitignore +++ b/Ombi/Ombi/.gitignore @@ -17,5 +17,5 @@ /libpeerconnection.log npm-debug.log testem.log -/typings +#/typings /systemjs.config.js* diff --git a/Ombi/Ombi/Auth/CustomJwtDataFormat.cs b/Ombi/Ombi/Auth/CustomJwtDataFormat.cs new file mode 100644 index 000000000..de8eccaae --- /dev/null +++ b/Ombi/Ombi/Auth/CustomJwtDataFormat.cs @@ -0,0 +1,72 @@ +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/Ombi/Ombi/Auth/TokenProviderMiddleware.cs b/Ombi/Ombi/Auth/TokenProviderMiddleware.cs new file mode 100644 index 000000000..069cc7b56 --- /dev/null +++ b/Ombi/Ombi/Auth/TokenProviderMiddleware.cs @@ -0,0 +1,148 @@ +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.Models; + +namespace Ombi.Auth +{ + public class TokenProviderMiddleware + { + private readonly RequestDelegate _next; + private readonly TokenProviderOptions _options; + private readonly JsonSerializerSettings _serializerSettings; + + public TokenProviderMiddleware( + RequestDelegate next, + IOptions options) + { + _next = next; + + _options = options.Value; + ThrowIfInvalidOptions(_options); + + _serializerSettings = new JsonSerializerSettings + { + Formatting = Formatting.Indented + }; + } + + public Task Invoke(HttpContext context) + { + // If the request path doesn't match, skip + if (!context.Request.Path.Equals(_options.Path, StringComparison.Ordinal)) + { + return _next(context); + } + + // 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; // TODO use a stong type + + 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); + 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/Ombi/Ombi/Auth/TokenProviderOptions.cs b/Ombi/Ombi/Auth/TokenProviderOptions.cs new file mode 100644 index 000000000..3cdb356c1 --- /dev/null +++ b/Ombi/Ombi/Auth/TokenProviderOptions.cs @@ -0,0 +1,52 @@ +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; + +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 five minutes (300 seconds). + public TimeSpan Expiration { get; set; } = TimeSpan.FromMinutes(5); + + /// + /// 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/Ombi/Ombi/Controllers/HomeController.cs b/Ombi/Ombi/Controllers/HomeController.cs index c03c9f475..07a23e7f5 100644 --- a/Ombi/Ombi/Controllers/HomeController.cs +++ b/Ombi/Ombi/Controllers/HomeController.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc; namespace Ombi.Controllers { @@ -12,24 +8,5 @@ namespace Ombi.Controllers { return View(); } - - public IActionResult About() - { - ViewData["Message"] = "Your application description page."; - - return View(); - } - - public IActionResult Contact() - { - ViewData["Message"] = "Your contact page."; - - return View(); - } - - public IActionResult Error() - { - return View(); - } } } diff --git a/Ombi/Ombi/Controllers/RequestController.cs b/Ombi/Ombi/Controllers/RequestController.cs index d90268845..71e436122 100644 --- a/Ombi/Ombi/Controllers/RequestController.cs +++ b/Ombi/Ombi/Controllers/RequestController.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Ombi.Core.Engine; using Ombi.Core.Models.Requests; @@ -7,6 +8,7 @@ using Ombi.Core.Models.Search; namespace Ombi.Controllers { + [Authorize] public class RequestController : BaseV1ApiController { public RequestController(IRequestEngine engine) diff --git a/Ombi/Ombi/Models/RequestResult.cs b/Ombi/Ombi/Models/RequestResult.cs new file mode 100644 index 000000000..51c01d0d5 --- /dev/null +++ b/Ombi/Ombi/Models/RequestResult.cs @@ -0,0 +1,18 @@ +using System; + +namespace Ombi.Models +{ + public class RequestResult + { + public RequestState State { get; set; } + public string Msg { get; set; } + public Object Data { get; set; } + } + + public enum RequestState + { + Failed = -1, + NotAuth = 0, + Success = 1 + } +} \ No newline at end of file diff --git a/Ombi/Ombi/Models/UserAuthModel.cs b/Ombi/Ombi/Models/UserAuthModel.cs new file mode 100644 index 000000000..4e16af0b8 --- /dev/null +++ b/Ombi/Ombi/Models/UserAuthModel.cs @@ -0,0 +1,8 @@ +namespace Ombi.Models +{ + public class UserAuthModel + { + public string Username { get; set; } + public string Password { get; set; } + } +} \ No newline at end of file diff --git a/Ombi/Ombi/Ombi.csproj b/Ombi/Ombi/Ombi.csproj index 6c2e094de..efeae93f4 100644 --- a/Ombi/Ombi/Ombi.csproj +++ b/Ombi/Ombi/Ombi.csproj @@ -3,24 +3,48 @@ netcoreapp1.1 win10-x64;osx.10.12-x64;ubuntu.16.10-x64;debian.8-x64; + portable-net45+win8 + + auth - Copy.module.js + PreserveNewest + + + PreserveNewest + + + + + + + + + + + + + + + + + @@ -30,4 +54,13 @@ + + + + ..\..\..\..\..\..\..\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.6.1\System.Configuration.dll + + + ..\..\..\..\..\.nuget\packages\system.security.cryptography.csp\4.3.0\ref\netstandard1.3\System.Security.Cryptography.Csp.dll + +
diff --git a/Ombi/Ombi/Program.cs b/Ombi/Ombi/Program.cs index 1949a296a..d4867e9cb 100644 --- a/Ombi/Ombi/Program.cs +++ b/Ombi/Ombi/Program.cs @@ -11,6 +11,7 @@ namespace Ombi { public static void Main(string[] args) { + Console.Title = "Ombi"; var host = new WebHostBuilder() .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) diff --git a/Ombi/Ombi/Startup.Auth.cs b/Ombi/Ombi/Startup.Auth.cs new file mode 100644 index 000000000..00ad36eec --- /dev/null +++ b/Ombi/Ombi/Startup.Auth.cs @@ -0,0 +1,97 @@ +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; + +namespace Ombi +{ + public partial class Startup + { + public SymmetricSecurityKey signingKey; + private void ConfigureAuth(IApplicationBuilder app) + { + + var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Configuration.GetSection("TokenAuthentication:SecretKey").Value)); + + var tokenProviderOptions = new TokenProviderOptions + { + Path = Configuration.GetSection("TokenAuthentication:TokenPath").Value, + Audience = Configuration.GetSection("TokenAuthentication:Audience").Value, + Issuer = Configuration.GetSection("TokenAuthentication:Issuer").Value, + 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 = Configuration.GetSection("TokenAuthentication:Issuer").Value, + // Validate the JWT Audience (aud) claim + ValidateAudience = true, + ValidAudience = Configuration.GetSection("TokenAuthentication:Audience").Value, + // 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.UseCookieAuthentication(new CookieAuthenticationOptions + { + AutomaticAuthenticate = true, + AutomaticChallenge = true, + AuthenticationScheme = "Cookie", + CookieName = Configuration.GetSection("TokenAuthentication:CookieName").Value, + TicketDataFormat = new CustomJwtDataFormat( + SecurityAlgorithms.HmacSha256, + tokenValidationParameters) + }); + + app.UseMiddleware(Options.Create(tokenProviderOptions)); + } + + + private Task GetIdentity(string username, string password) + { + // DEMO CODE, DON NOT USE IN PRODUCTION!!! + if (username == "TEST" && password == "TEST123") + { + var claim = new ClaimsIdentity(new GenericIdentity(username, "Token"), + new[] + { + //new Claim(ClaimTypes.Role, "Admin"), + new Claim(ClaimTypes.Name, "Test"), + + }); + + claim.AddClaim(new Claim(ClaimsIdentity.DefaultRoleClaimType, "Admin", ClaimValueTypes.String)); + return Task.FromResult(claim); + } + if (username == "TEST2" && password == "TEST123") + { + return Task.FromResult(new ClaimsIdentity(new GenericIdentity(username, "Token"), new Claim[] { + new Claim(ClaimTypes.Role, "User"), + new Claim(ClaimTypes.Name, "Test2"), })); + } + + // Account doesn't exists + return Task.FromResult(null); + } + + } +} \ No newline at end of file diff --git a/Ombi/Ombi/Startup.cs b/Ombi/Ombi/Startup.cs index f418fcc0e..24ef16b31 100644 --- a/Ombi/Ombi/Startup.cs +++ b/Ombi/Ombi/Startup.cs @@ -1,5 +1,10 @@ -using System.IO; +using System; +using System.IO; +using System.Linq; +using System.Text; +using IdentityServer4.EntityFramework.DbContexts; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.StaticFiles; @@ -8,12 +13,16 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Logging; +using Microsoft.IdentityModel.Tokens; +using Newtonsoft.Json; +using Ombi.Auth; using Ombi.DependencyInjection; +using Ombi.Models; using Ombi.Store.Context; namespace Ombi { - public class Startup + public partial class Startup { public Startup(IHostingEnvironment env) { @@ -51,6 +60,10 @@ namespace Ombi app.UseExceptionHandler("/Home/Error"); } + + + ConfigureAuth(app); + var provider = new FileExtensionContentTypeProvider(); provider.Mappings[".map"] = "application/octet-stream"; @@ -70,5 +83,8 @@ namespace Ombi defaults: new { controller = "Home", action = "Index" }); }); } + + + } } diff --git a/Ombi/Ombi/appsettings.json b/Ombi/Ombi/appsettings.json index 5fff67bac..b0f80f89d 100644 --- a/Ombi/Ombi/appsettings.json +++ b/Ombi/Ombi/appsettings.json @@ -4,5 +4,12 @@ "LogLevel": { "Default": "Warning" } + }, + "TokenAuthentication": { + "SecretKey": "secretkey_secretkey123!", + "Issuer": "DemoIssuer", + "Audience": "DemoAudience", + "TokenPath": "/api/token/", + "CookieName": "access_token" } } diff --git a/Ombi/Ombi/gulpfile.js b/Ombi/Ombi/gulpfile.js index c4697e4c7..fead077d4 100644 --- a/Ombi/Ombi/gulpfile.js +++ b/Ombi/Ombi/gulpfile.js @@ -57,6 +57,7 @@ var paths = { './bower_components/PACE/pace.js', './node_modules/bootstrap/dist/js/bootstrap.js', './node_modules/tether/dist/js/tether.js', + './node_modules/angular2-jwt/angular2-jwt.js', './systemjs.config.js', ], dest: './lib/' diff --git a/Ombi/Ombi/package.json b/Ombi/Ombi/package.json index f74d68e3e..643b975b2 100644 --- a/Ombi/Ombi/package.json +++ b/Ombi/Ombi/package.json @@ -18,6 +18,7 @@ "@types/systemjs": "^0.20.2", "angular2-infinite-scroll": "^0.3.4", "angular2-moment": "^1.3.3", + "angular2-jwt": "0.2.0", "bootstrap": "3.3.6", "core-js": "^2.4.1", "del": "^2.2.2", diff --git a/Ombi/Ombi/typings/globals/globals.d.ts b/Ombi/Ombi/typings/globals/globals.d.ts new file mode 100644 index 000000000..b9c873dfc --- /dev/null +++ b/Ombi/Ombi/typings/globals/globals.d.ts @@ -0,0 +1,5 @@ +// Globals + +declare var module: any; +declare var require: any; +declare var localStorage: any; \ No newline at end of file diff --git a/Ombi/Ombi/typings/index.d.ts b/Ombi/Ombi/typings/index.d.ts new file mode 100644 index 000000000..d3394b68b --- /dev/null +++ b/Ombi/Ombi/typings/index.d.ts @@ -0,0 +1 @@ +/// \ No newline at end of file diff --git a/Ombi/Ombi/wwwroot/app/app.component.ts b/Ombi/Ombi/wwwroot/app/app.component.ts index 36b29087e..de5f83076 100644 --- a/Ombi/Ombi/wwwroot/app/app.component.ts +++ b/Ombi/Ombi/wwwroot/app/app.component.ts @@ -1,5 +1,6 @@ import { Component } from '@angular/core'; import { NotificationService } from './services/notification.service'; +import { AuthService } from './auth/auth.service'; @Component({ selector: 'ombi', @@ -8,5 +9,5 @@ import { NotificationService } from './services/notification.service'; }) export class AppComponent { - constructor(public notificationService: NotificationService) { }; + constructor(public notificationService: NotificationService, public authService : AuthService) { }; } \ No newline at end of file diff --git a/Ombi/Ombi/wwwroot/app/app.module.ts b/Ombi/Ombi/wwwroot/app/app.module.ts index 8c7d4584b..1e9835bda 100644 --- a/Ombi/Ombi/wwwroot/app/app.module.ts +++ b/Ombi/Ombi/wwwroot/app/app.module.ts @@ -12,12 +12,16 @@ import { InfiniteScrollModule } from 'angular2-infinite-scroll/angular2-infinite import { SearchComponent } from './search/search.component'; import { RequestComponent } from './requests/request.component'; +import { LoginComponent } from './login/login.component'; import { PageNotFoundComponent } from './errors/not-found.component'; // Services import { SearchService } from './services/search.service'; import { RequestService } from './services/request.service'; import { NotificationService } from './services/notification.service'; +import { AuthService } from './auth/auth.service'; +import { AuthGuard } from './auth/auth.guard'; +import { AuthModule } from './auth/auth.module'; // Modules import { SettingsModule } from './settings/settings.module'; @@ -28,8 +32,10 @@ import { DataTableModule, SharedModule } from 'primeng/primeng'; const routes: Routes = [ { path: '*', component: PageNotFoundComponent }, - { path: 'search', component: SearchComponent }, - { path: 'requests', component: RequestComponent }, + { path: '', redirectTo: '/search', pathMatch: 'full' }, + { path: 'search', component: SearchComponent, canActivate: [AuthGuard] }, + { path: 'requests', component: RequestComponent, canActivate: [AuthGuard] }, + { path: 'login', component: LoginComponent }, ]; @NgModule({ @@ -44,18 +50,22 @@ const routes: Routes = [ SettingsModule, DataTableModule, SharedModule, - InfiniteScrollModule + InfiniteScrollModule, + AuthModule ], declarations: [ AppComponent, PageNotFoundComponent, SearchComponent, - RequestComponent + RequestComponent, + LoginComponent ], providers: [ SearchService, RequestService, - NotificationService + NotificationService, + AuthService, + AuthGuard, ], bootstrap: [AppComponent] }) diff --git a/Ombi/Ombi/wwwroot/app/auth/IUserLogin.ts b/Ombi/Ombi/wwwroot/app/auth/IUserLogin.ts new file mode 100644 index 000000000..1d45bda19 --- /dev/null +++ b/Ombi/Ombi/wwwroot/app/auth/IUserLogin.ts @@ -0,0 +1,4 @@ +export interface IUserLogin { + username: string, + password:string +} \ No newline at end of file diff --git a/Ombi/Ombi/wwwroot/app/auth/auth.guard.ts b/Ombi/Ombi/wwwroot/app/auth/auth.guard.ts new file mode 100644 index 000000000..29d59fd6c --- /dev/null +++ b/Ombi/Ombi/wwwroot/app/auth/auth.guard.ts @@ -0,0 +1,20 @@ + +import { Injectable } from '@angular/core'; +import { Router } from '@angular/router'; +import { CanActivate } from '@angular/router'; +import { AuthService } from './auth.service'; + +@Injectable() +export class AuthGuard implements CanActivate { + + constructor(private auth: AuthService, private router: Router) { } + + canActivate() { + if (this.auth.loggedIn()) { + return true; + } else { + this.router.navigate(['login']); + return false; + } + } +} \ No newline at end of file diff --git a/Ombi/Ombi/wwwroot/app/auth/auth.module.ts b/Ombi/Ombi/wwwroot/app/auth/auth.module.ts new file mode 100644 index 000000000..db5e84ffd --- /dev/null +++ b/Ombi/Ombi/wwwroot/app/auth/auth.module.ts @@ -0,0 +1,18 @@ +import { NgModule } from '@angular/core'; +import { Http, RequestOptions } from '@angular/http'; +import { AuthHttp, AuthConfig } from 'angular2-jwt'; + +export function authHttpServiceFactory(http: Http, options: RequestOptions) { + return new AuthHttp(new AuthConfig(), http, options); +} + +@NgModule({ + providers: [ + { + provide: AuthHttp, + useFactory: authHttpServiceFactory, + deps: [Http, RequestOptions] + } + ] +}) +export class AuthModule {} \ No newline at end of file diff --git a/Ombi/Ombi/wwwroot/app/auth/auth.service.ts b/Ombi/Ombi/wwwroot/app/auth/auth.service.ts new file mode 100644 index 000000000..8bb2f69b3 --- /dev/null +++ b/Ombi/Ombi/wwwroot/app/auth/auth.service.ts @@ -0,0 +1,33 @@ + +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs/Rx'; + +import { ServiceHelpers } from '../services/service.helpers'; + +import { IUserLogin } from './IUserLogin'; + +import { tokenNotExpired } from 'angular2-jwt'; + +import { Http } from '@angular/http'; + +@Injectable() +export class AuthService extends ServiceHelpers { + constructor(http: Http) { + super(http, '/api/token'); + } + + login(login:IUserLogin) : Observable { + return this.http.post(`${this.url}/`, JSON.stringify(login), { headers: this.headers }) + .map(this.extractData); + + } + + loggedIn() { + return tokenNotExpired(); + } + + logout() { + localStorage.removeItem('id_token'); + } +} + diff --git a/Ombi/Ombi/wwwroot/app/login/login.component.html b/Ombi/Ombi/wwwroot/app/login/login.component.html new file mode 100644 index 000000000..7a01eb2a0 --- /dev/null +++ b/Ombi/Ombi/wwwroot/app/login/login.component.html @@ -0,0 +1,32 @@ +
+

Login

+
+

+ @UI.UserLogin_Paragraph +

+
+
+
+
+ +
+
+ +
+
+
+ +
+
+ +
+
+ +
+
+
+ + + +
+
\ No newline at end of file diff --git a/Ombi/Ombi/wwwroot/app/login/login.component.ts b/Ombi/Ombi/wwwroot/app/login/login.component.ts new file mode 100644 index 000000000..696a8cea1 --- /dev/null +++ b/Ombi/Ombi/wwwroot/app/login/login.component.ts @@ -0,0 +1,34 @@ +import { Component } from '@angular/core'; +import { Router } from '@angular/router'; + + +import { AuthService } from '../auth/auth.service'; +import { NotificationService } from '../services/notification.service'; + +@Component({ + selector: 'ombi', + moduleId: module.id, + templateUrl: './login.component.html', +}) +export class LoginComponent { + constructor(private authService: AuthService, private router: Router, private notify: NotificationService) { } + + + username: string; + password: string; + + + login(): void { + this.authService.login({ password: this.password, username: this.username }) + .subscribe(x => { + localStorage.setItem("id_token", x.access_token); + if (this.authService.loggedIn()) { + this.router.navigate(['search']); + } else { + this.notify.error("Could not log in", "Incorrect username or password"); + } + }, err => this.notify.error("Could not log in", "Incorrect username or password")); + + + } +} \ No newline at end of file diff --git a/Ombi/Ombi/wwwroot/app/services/request.service.ts b/Ombi/Ombi/wwwroot/app/services/request.service.ts index adcdd8667..76eb3232f 100644 --- a/Ombi/Ombi/wwwroot/app/services/request.service.ts +++ b/Ombi/Ombi/wwwroot/app/services/request.service.ts @@ -1,15 +1,15 @@ import { Injectable } from '@angular/core'; -import { Http } from '@angular/http'; +import { AuthHttp } from 'angular2-jwt'; import { Observable } from 'rxjs/Rx'; -import { ServiceHelpers } from './service.helpers'; +import { ServiceAuthHelpers } from './service.helpers'; import { IRequestEngineResult } from '../interfaces/IRequestEngineResult'; import { ISearchMovieResult } from '../interfaces/ISearchMovieResult'; import { IRequestModel } from '../interfaces/IRequestModel'; @Injectable() -export class RequestService extends ServiceHelpers { - constructor(http: Http) { +export class RequestService extends ServiceAuthHelpers { + constructor(http: AuthHttp) { super(http, '/api/v1/Request/'); } diff --git a/Ombi/Ombi/wwwroot/app/services/search.service.ts b/Ombi/Ombi/wwwroot/app/services/search.service.ts index f0abe17e0..8363a717f 100644 --- a/Ombi/Ombi/wwwroot/app/services/search.service.ts +++ b/Ombi/Ombi/wwwroot/app/services/search.service.ts @@ -1,13 +1,13 @@ import { Injectable } from '@angular/core'; -import { Http } from '@angular/http'; +import { AuthHttp } from 'angular2-jwt'; import { Observable } from 'rxjs/Rx'; -import { ServiceHelpers } from './service.helpers'; +import { ServiceAuthHelpers } from './service.helpers'; import { ISearchMovieResult } from '../interfaces/ISearchMovieResult'; @Injectable() -export class SearchService extends ServiceHelpers { - constructor(http: Http) { +export class SearchService extends ServiceAuthHelpers { + constructor(http: AuthHttp) { super(http, "/api/v1/search"); } diff --git a/Ombi/Ombi/wwwroot/app/services/service.helpers.ts b/Ombi/Ombi/wwwroot/app/services/service.helpers.ts index f1d111c49..4b5ce8c42 100644 --- a/Ombi/Ombi/wwwroot/app/services/service.helpers.ts +++ b/Ombi/Ombi/wwwroot/app/services/service.helpers.ts @@ -1,6 +1,9 @@ import { Headers, Response, Http } from '@angular/http'; import { Observable } from 'rxjs/Observable'; + +import { AuthHttp } from 'angular2-jwt'; + export class ServiceHelpers { constructor(protected http: Http, protected url: string) { @@ -26,4 +29,31 @@ export class ServiceHelpers { } +} + +export class ServiceAuthHelpers { + + constructor(protected http: AuthHttp, protected url: string) { + this.headers = new Headers(); + this.headers.append('Content-Type', 'application/json; charset=utf-8'); + } + + protected headers: Headers; + + protected extractData(res: Response) { + let body = res.json(); + //console.log('extractData', body || {}); + return body || {}; + } + + protected handleError(error: any) { + // In a real world app, we might use a remote logging infrastructure + // We'd also dig deeper into the error to get a better message + let errMsg = (error.message) ? error.message : + error.status ? `${error.status} - ${error.statusText}` : 'Server error'; + console.error(errMsg); // log to console instead + return Observable.throw(errMsg); + } + + } \ No newline at end of file diff --git a/Ombi/Ombi/wwwroot/app/settings/settings.module.ts b/Ombi/Ombi/wwwroot/app/settings/settings.module.ts index 79ca414c9..255b0cb89 100644 --- a/Ombi/Ombi/wwwroot/app/settings/settings.module.ts +++ b/Ombi/Ombi/wwwroot/app/settings/settings.module.ts @@ -3,6 +3,10 @@ import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { RouterModule, Routes } from '@angular/router'; +import { AuthService } from '../auth/auth.service'; +import { AuthGuard } from '../auth/auth.guard'; +import { AuthModule } from '../auth/auth.module'; + import { OmbiComponent } from './ombi/ombi.component' import { SettingsMenuComponent } from './settingsmenu.component'; @@ -10,7 +14,7 @@ import { SettingsMenuComponent } from './settingsmenu.component'; import { MenuModule, InputSwitchModule, InputTextModule } from 'primeng/primeng'; const routes: Routes = [ - { path: 'Settings/Ombi', component: OmbiComponent } + { path: 'Settings/Ombi', component: OmbiComponent, canActivate: [AuthGuard] } ]; @NgModule({ @@ -21,7 +25,7 @@ const routes: Routes = [ MenuModule, InputSwitchModule, InputTextModule, - + AuthModule ], declarations: [ SettingsMenuComponent, @@ -31,6 +35,9 @@ const routes: Routes = [ RouterModule ], providers: [ + + AuthService, + AuthGuard, ], }) From cf6b5c5138c519d05f6b0b827d080584abe67a28 Mon Sep 17 00:00:00 2001 From: tidusjar Date: Tue, 11 Apr 2017 22:22:46 +0100 Subject: [PATCH 022/725] #865 more for the authentication --- .../IdentityResolver/IUserIdentityManager.cs | 14 +++ .../IdentityResolver/UserIdentityManager.cs | 90 +++++++++++++++++++ Ombi/Ombi.Core/Ombi.Core.csproj | 1 + .../Ombi.DependencyInjection/IocExtensions.cs | 3 + Ombi/Ombi.Store/Context/IOmbiContext.cs | 1 + Ombi/Ombi.Store/Context/OmbiContext.cs | 1 + Ombi/Ombi.Store/Entities/User.cs | 48 ++++++++++ Ombi/Ombi.Store/Repository/IUserRepository.cs | 13 +++ Ombi/Ombi.Store/Repository/UserRepository.cs | 62 +++++++++++++ Ombi/Ombi/Auth/TokenProviderMiddleware.cs | 6 +- Ombi/Ombi/Auth/TokenProviderOptions.cs | 3 +- Ombi/Ombi/Startup.Auth.cs | 31 ++----- 12 files changed, 248 insertions(+), 25 deletions(-) create mode 100644 Ombi/Ombi.Core/IdentityResolver/IUserIdentityManager.cs create mode 100644 Ombi/Ombi.Core/IdentityResolver/UserIdentityManager.cs create mode 100644 Ombi/Ombi.Store/Entities/User.cs create mode 100644 Ombi/Ombi.Store/Repository/IUserRepository.cs create mode 100644 Ombi/Ombi.Store/Repository/UserRepository.cs diff --git a/Ombi/Ombi.Core/IdentityResolver/IUserIdentityManager.cs b/Ombi/Ombi.Core/IdentityResolver/IUserIdentityManager.cs new file mode 100644 index 000000000..78facfc3e --- /dev/null +++ b/Ombi/Ombi.Core/IdentityResolver/IUserIdentityManager.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Ombi.Store.Entities; + +namespace Ombi.Core.IdentityResolver +{ + public interface IUserIdentityManager + { + Task CreateUser(User user); + Task CredentialsValid(string username, string password); + Task GetUser(string username); + Task> GetUsers(); + } +} \ No newline at end of file diff --git a/Ombi/Ombi.Core/IdentityResolver/UserIdentityManager.cs b/Ombi/Ombi.Core/IdentityResolver/UserIdentityManager.cs new file mode 100644 index 000000000..4f26b7027 --- /dev/null +++ b/Ombi/Ombi.Core/IdentityResolver/UserIdentityManager.cs @@ -0,0 +1,90 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: UserIdentityManager.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.Security.Cryptography; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Cryptography.KeyDerivation; +using Ombi.Store.Entities; +using Ombi.Store.Repository; + +namespace Ombi.Core.IdentityResolver +{ + public class UserIdentityManager : IUserIdentityManager + { + public UserIdentityManager(IUserRepository userRepository) + { + UserRepository = userRepository; + } + + private IUserRepository UserRepository { get; } + + public async Task CredentialsValid(string username, string password) + { + var user = await UserRepository.GetUser(username); + var hashedPass = HashPassword(password); + + return hashedPass.Equals(user.Password); + } + + public async Task GetUser(string username) + { + return await UserRepository.GetUser(username); + } + + public async Task> GetUsers() + { + return await UserRepository.GetUsers(); + } + + public async Task CreateUser(User user) + { + user.Password = HashPassword(user.Password); + await UserRepository.CreateUser(user); + } + + private string HashPassword(string password) + { + // generate a 128-bit salt using a secure PRNG + byte[] salt = new byte[128 / 8]; + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(salt); + } + // derive a 256-bit subkey (use HMACSHA1 with 10,000 iterations) + var hashed = Convert.ToBase64String(KeyDerivation.Pbkdf2( + password: password, + salt: salt, + prf: KeyDerivationPrf.HMACSHA1, + iterationCount: 10000, + numBytesRequested: 256 / 8)); + + return hashed; + } + } +} \ No newline at end of file diff --git a/Ombi/Ombi.Core/Ombi.Core.csproj b/Ombi/Ombi.Core/Ombi.Core.csproj index 078f56b95..0312ace74 100644 --- a/Ombi/Ombi.Core/Ombi.Core.csproj +++ b/Ombi/Ombi.Core/Ombi.Core.csproj @@ -6,6 +6,7 @@ + diff --git a/Ombi/Ombi.DependencyInjection/IocExtensions.cs b/Ombi/Ombi.DependencyInjection/IocExtensions.cs index 639d26f0e..596b810f3 100644 --- a/Ombi/Ombi.DependencyInjection/IocExtensions.cs +++ b/Ombi/Ombi.DependencyInjection/IocExtensions.cs @@ -6,6 +6,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Ombi.Core; using Ombi.Core.Engine; +using Ombi.Core.IdentityResolver; using Ombi.Core.Models.Requests; using Ombi.Core.Requests.Models; using Ombi.Core.Settings; @@ -48,6 +49,7 @@ namespace Ombi.DependencyInjection services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddTransient(typeof(ISettingsService<>), typeof(SettingsServiceV2<>)); return services; } @@ -59,6 +61,7 @@ namespace Ombi.DependencyInjection public static IServiceCollection RegisterIdentity(this IServiceCollection services) { + services.AddTransient(); services.AddAuthorization(auth => { auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder() diff --git a/Ombi/Ombi.Store/Context/IOmbiContext.cs b/Ombi/Ombi.Store/Context/IOmbiContext.cs index 1d555253d..c176a3f99 100644 --- a/Ombi/Ombi.Store/Context/IOmbiContext.cs +++ b/Ombi/Ombi.Store/Context/IOmbiContext.cs @@ -12,5 +12,6 @@ namespace Ombi.Store.Context Task SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken)); DbSet Requests { get; set; } DbSet Settings { get; set; } + DbSet Users { get; set; } } } \ No newline at end of file diff --git a/Ombi/Ombi.Store/Context/OmbiContext.cs b/Ombi/Ombi.Store/Context/OmbiContext.cs index 9a97ac69f..57daca0cd 100644 --- a/Ombi/Ombi.Store/Context/OmbiContext.cs +++ b/Ombi/Ombi.Store/Context/OmbiContext.cs @@ -16,6 +16,7 @@ namespace Ombi.Store.Context } public DbSet Requests { get; set; } public DbSet Settings { get; set; } + public DbSet Users { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { diff --git a/Ombi/Ombi.Store/Entities/User.cs b/Ombi/Ombi.Store/Entities/User.cs new file mode 100644 index 000000000..534d062a2 --- /dev/null +++ b/Ombi/Ombi.Store/Entities/User.cs @@ -0,0 +1,48 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: Users.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.Security.Claims; + +namespace Ombi.Store.Entities +{ + public class User : Entity + { + public string Username { get; set; } + public string Alias { get; set; } + public Claim[] Claims { get; set; } + public string EmailAddress { get; set; } + public string Password { get; set; } + public UserType UserType { get; set; } + } + + public enum UserType + { + LocalUser = 1, + PlexUser = 2, + EmbyUser = 3, + } +} \ No newline at end of file diff --git a/Ombi/Ombi.Store/Repository/IUserRepository.cs b/Ombi/Ombi.Store/Repository/IUserRepository.cs new file mode 100644 index 000000000..468716486 --- /dev/null +++ b/Ombi/Ombi.Store/Repository/IUserRepository.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Ombi.Store.Entities; + +namespace Ombi.Store.Repository +{ + public interface IUserRepository + { + Task CreateUser(User user); + Task GetUser(string username); + Task> GetUsers(); + } +} \ No newline at end of file diff --git a/Ombi/Ombi.Store/Repository/UserRepository.cs b/Ombi/Ombi.Store/Repository/UserRepository.cs new file mode 100644 index 000000000..ddd73d272 --- /dev/null +++ b/Ombi/Ombi.Store/Repository/UserRepository.cs @@ -0,0 +1,62 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: UserRepository.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.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Ombi.Store.Context; +using Ombi.Store.Entities; + +namespace Ombi.Store.Repository +{ + public class UserRepository : IUserRepository + { + public UserRepository(IOmbiContext ctx) + { + Db = ctx; + } + + private IOmbiContext Db { get; } + + public async Task GetUser(string username) + { + return await Db.Users.FirstOrDefaultAsync(x => x.Username.ToLower() == username.ToLower()); + } + + public async Task CreateUser(User user) + { + Db.Users.Add(user); + await Db.SaveChangesAsync(); + } + + public async Task> GetUsers() + { + return await Db.Users.ToListAsync(); + } + } +} \ No newline at end of file diff --git a/Ombi/Ombi/Auth/TokenProviderMiddleware.cs b/Ombi/Ombi/Auth/TokenProviderMiddleware.cs index 069cc7b56..cf21abd08 100644 --- a/Ombi/Ombi/Auth/TokenProviderMiddleware.cs +++ b/Ombi/Ombi/Auth/TokenProviderMiddleware.cs @@ -8,7 +8,10 @@ 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 { @@ -23,7 +26,6 @@ namespace Ombi.Auth IOptions options) { _next = next; - _options = options.Value; ThrowIfInvalidOptions(_options); @@ -64,7 +66,7 @@ namespace Ombi.Auth userInfo = JsonConvert.DeserializeObject(body); } - var identity = await _options.IdentityResolver(userInfo.Username, userInfo.Password); + var identity = await _options.IdentityResolver(userInfo.Username, userInfo.Password, new UserIdentityManager(new UserRepository(new OmbiContext()))); if (identity == null) { context.Response.StatusCode = 400; diff --git a/Ombi/Ombi/Auth/TokenProviderOptions.cs b/Ombi/Ombi/Auth/TokenProviderOptions.cs index 3cdb356c1..bb8f7c9fd 100644 --- a/Ombi/Ombi/Auth/TokenProviderOptions.cs +++ b/Ombi/Ombi/Auth/TokenProviderOptions.cs @@ -5,6 +5,7 @@ using System.Security.Claims; using System.Security.Cryptography; using System.Threading.Tasks; using Microsoft.IdentityModel.Tokens; +using Ombi.Core.IdentityResolver; namespace Ombi.Auth { @@ -40,7 +41,7 @@ namespace Ombi.Auth /// /// Resolves a user identity given a username and password. /// - public Func> IdentityResolver { get; set; } + public Func> IdentityResolver { get; set; } /// /// Generates a random value (nonce) for each generated token. diff --git a/Ombi/Ombi/Startup.Auth.cs b/Ombi/Ombi/Startup.Auth.cs index 00ad36eec..21ba6d509 100644 --- a/Ombi/Ombi/Startup.Auth.cs +++ b/Ombi/Ombi/Startup.Auth.cs @@ -7,11 +7,13 @@ 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 { + public SymmetricSecurityKey signingKey; private void ConfigureAuth(IApplicationBuilder app) { @@ -66,32 +68,17 @@ namespace Ombi } - private Task GetIdentity(string username, string password) + private async Task GetIdentity(string username, string password, IUserIdentityManager userIdentityManager) { - // DEMO CODE, DON NOT USE IN PRODUCTION!!! - if (username == "TEST" && password == "TEST123") - { - var claim = new ClaimsIdentity(new GenericIdentity(username, "Token"), - new[] - { - //new Claim(ClaimTypes.Role, "Admin"), - new Claim(ClaimTypes.Name, "Test"), - - }); - - claim.AddClaim(new Claim(ClaimsIdentity.DefaultRoleClaimType, "Admin", ClaimValueTypes.String)); - return Task.FromResult(claim); - } - if (username == "TEST2" && password == "TEST123") + var validLogin = await userIdentityManager.CredentialsValid(username, password); + if (!validLogin) { - return Task.FromResult(new ClaimsIdentity(new GenericIdentity(username, "Token"), new Claim[] { - new Claim(ClaimTypes.Role, "User"), - new Claim(ClaimTypes.Name, "Test2"), })); + return null; } - // Account doesn't exists - return Task.FromResult(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 From 8e04061e4e11f051a59eb6b1b5e24fb3347df83a Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" Date: Wed, 12 Apr 2017 12:43:19 +0100 Subject: [PATCH 023/725] Fixed some issues around the identity --- .../IdentityResolver/IUserIdentityManager.cs | 8 ++-- .../IdentityResolver/UserIdentityManager.cs | 41 ++++++++++++++----- Ombi/Ombi.Core/Models/UserDto.cs | 23 +++++++++++ Ombi/Ombi.Core/Ombi.Core.csproj | 2 + .../Ombi.DependencyInjection/IocExtensions.cs | 2 + .../Ombi.DependencyInjection.csproj | 2 - Ombi/Ombi.Helpers/ClaimConverter.cs | 33 +++++++++++++++ Ombi/Ombi.Helpers/Ombi.Helpers.csproj | 9 ++++ .../Ombi.Mapping.Maps.csproj | 19 +++++++++ Ombi/Ombi.Mapping.Maps/OmbiProfile.cs | 15 +++++++ Ombi/Ombi.Mapping/AutoMapperProfile.cs | 20 +++++++++ Ombi/Ombi.Mapping/Ombi.Mapping.csproj | 19 +++++++++ Ombi/Ombi.Mapping/OmbiProfile.cs | 15 +++++++ Ombi/Ombi.Store/Entities/User.cs | 14 ++++++- Ombi/Ombi.Store/Ombi.Store.csproj | 4 +- Ombi/Ombi.sln | 14 ++++++- Ombi/Ombi/Auth/TokenProviderMiddleware.cs | 6 ++- Ombi/Ombi/Controllers/IdentityController.cs | 26 ++++++++++++ Ombi/Ombi/Ombi.csproj | 9 +--- Ombi/Ombi/Startup.Auth.cs | 21 +++++----- Ombi/Ombi/Startup.cs | 18 ++------ Ombi/Ombi/Views/Shared/_Layout.cshtml | 2 +- Ombi/Ombi/appsettings.json | 2 +- Ombi/Ombi/wwwroot/app/auth/auth.service.ts | 2 +- 24 files changed, 269 insertions(+), 57 deletions(-) create mode 100644 Ombi/Ombi.Core/Models/UserDto.cs create mode 100644 Ombi/Ombi.Helpers/ClaimConverter.cs create mode 100644 Ombi/Ombi.Mapping.Maps/Ombi.Mapping.Maps.csproj create mode 100644 Ombi/Ombi.Mapping.Maps/OmbiProfile.cs create mode 100644 Ombi/Ombi.Mapping/AutoMapperProfile.cs create mode 100644 Ombi/Ombi.Mapping/Ombi.Mapping.csproj create mode 100644 Ombi/Ombi.Mapping/OmbiProfile.cs create mode 100644 Ombi/Ombi/Controllers/IdentityController.cs diff --git a/Ombi/Ombi.Core/IdentityResolver/IUserIdentityManager.cs b/Ombi/Ombi.Core/IdentityResolver/IUserIdentityManager.cs index 78facfc3e..89c6d8ecd 100644 --- a/Ombi/Ombi.Core/IdentityResolver/IUserIdentityManager.cs +++ b/Ombi/Ombi.Core/IdentityResolver/IUserIdentityManager.cs @@ -1,14 +1,14 @@ using System.Collections.Generic; using System.Threading.Tasks; -using Ombi.Store.Entities; +using Ombi.Core.Models; namespace Ombi.Core.IdentityResolver { public interface IUserIdentityManager { - Task CreateUser(User user); + Task CreateUser(UserDto user); Task CredentialsValid(string username, string password); - Task GetUser(string username); - Task> GetUsers(); + Task GetUser(string username); + Task> GetUsers(); } } \ No newline at end of file diff --git a/Ombi/Ombi.Core/IdentityResolver/UserIdentityManager.cs b/Ombi/Ombi.Core/IdentityResolver/UserIdentityManager.cs index 4f26b7027..4b40a8788 100644 --- a/Ombi/Ombi.Core/IdentityResolver/UserIdentityManager.cs +++ b/Ombi/Ombi.Core/IdentityResolver/UserIdentityManager.cs @@ -29,7 +29,9 @@ using System; using System.Collections.Generic; using System.Security.Cryptography; using System.Threading.Tasks; +using AutoMapper; using Microsoft.AspNetCore.Cryptography.KeyDerivation; +using Ombi.Core.Models; using Ombi.Store.Entities; using Ombi.Store.Repository; @@ -37,38 +39,43 @@ namespace Ombi.Core.IdentityResolver { public class UserIdentityManager : IUserIdentityManager { - public UserIdentityManager(IUserRepository userRepository) + public UserIdentityManager(IUserRepository userRepository, IMapper mapper) { UserRepository = userRepository; + Mapper = mapper; } + private IMapper Mapper { get; } private IUserRepository UserRepository { get; } public async Task CredentialsValid(string username, string password) { var user = await UserRepository.GetUser(username); - var hashedPass = HashPassword(password); + var hash = HashPassword(password, user.Salt); - return hashedPass.Equals(user.Password); + return hash.HashedPass.Equals(user.Password); } - public async Task GetUser(string username) + public async Task GetUser(string username) { - return await UserRepository.GetUser(username); + return Mapper.Map(await UserRepository.GetUser(username)); } - public async Task> GetUsers() + public async Task> GetUsers() { - return await UserRepository.GetUsers(); + return Mapper.Map>(await UserRepository.GetUsers()); } - public async Task CreateUser(User user) + public async Task CreateUser(UserDto userDto) { - user.Password = HashPassword(user.Password); + var user = Mapper.Map(userDto); + var result = HashPassword(user.Password); + user.Password = result.HashedPass; + user.Salt = result.Salt; await UserRepository.CreateUser(user); } - private string HashPassword(string password) + private UserHash HashPassword(string password) { // generate a 128-bit salt using a secure PRNG byte[] salt = new byte[128 / 8]; @@ -76,6 +83,12 @@ namespace Ombi.Core.IdentityResolver { rng.GetBytes(salt); } + return HashPassword(password, salt); + } + + + private UserHash HashPassword(string password, byte[] salt) + { // derive a 256-bit subkey (use HMACSHA1 with 10,000 iterations) var hashed = Convert.ToBase64String(KeyDerivation.Pbkdf2( password: password, @@ -84,7 +97,13 @@ namespace Ombi.Core.IdentityResolver iterationCount: 10000, numBytesRequested: 256 / 8)); - return hashed; + return new UserHash { HashedPass = hashed, Salt = salt }; + } + + private class UserHash + { + public string HashedPass { get; set; } + public byte[] Salt { get; set; } } } } \ No newline at end of file diff --git a/Ombi/Ombi.Core/Models/UserDto.cs b/Ombi/Ombi.Core/Models/UserDto.cs new file mode 100644 index 000000000..3ca6b5cf7 --- /dev/null +++ b/Ombi/Ombi.Core/Models/UserDto.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using System.Security.Claims; + +namespace Ombi.Core.Models +{ + public class UserDto + { + public int Id { get; set; } + public string Username { get; set; } + public string Alias { get; set; } + public List Claims { get; set; } + public string EmailAddress { get; set; } + public string Password { get; set; } + public byte[] Salt { get; set; } + public UserType UserType { get; set; } + } + public enum UserType + { + LocalUser = 1, + PlexUser = 2, + EmbyUser = 3, + } +} \ No newline at end of file diff --git a/Ombi/Ombi.Core/Ombi.Core.csproj b/Ombi/Ombi.Core/Ombi.Core.csproj index 0312ace74..d6bb44f88 100644 --- a/Ombi/Ombi.Core/Ombi.Core.csproj +++ b/Ombi/Ombi.Core/Ombi.Core.csproj @@ -6,6 +6,8 @@ + + diff --git a/Ombi/Ombi.DependencyInjection/IocExtensions.cs b/Ombi/Ombi.DependencyInjection/IocExtensions.cs index 596b810f3..6ccaca0f8 100644 --- a/Ombi/Ombi.DependencyInjection/IocExtensions.cs +++ b/Ombi/Ombi.DependencyInjection/IocExtensions.cs @@ -25,6 +25,7 @@ namespace Ombi.DependencyInjection services.RegisterApi(); services.RegisterServices(); services.RegisterStore(); + services.RegisterIdentity(); return services; } @@ -56,6 +57,7 @@ namespace Ombi.DependencyInjection public static IServiceCollection RegisterServices(this IServiceCollection services) { services.AddTransient(); + return services; } diff --git a/Ombi/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj b/Ombi/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj index a98f5c6aa..1e12ffd1a 100644 --- a/Ombi/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj +++ b/Ombi/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj @@ -5,8 +5,6 @@ - - diff --git a/Ombi/Ombi.Helpers/ClaimConverter.cs b/Ombi/Ombi.Helpers/ClaimConverter.cs new file mode 100644 index 000000000..d8603b9a4 --- /dev/null +++ b/Ombi/Ombi.Helpers/ClaimConverter.cs @@ -0,0 +1,33 @@ +using System; +using System.Security.Claims; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Ombi.Helpers +{ + public class ClaimConverter : JsonConverter + { + public override bool CanConvert(Type objectType) + { + return (objectType == typeof(Claim)); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + JObject jo = JObject.Load(reader); + string type = (string)jo["Type"]; + string value = (string)jo["Value"]; + string valueType = (string)jo["ValueType"]; + string issuer = (string)jo["Issuer"]; + string originalIssuer = (string)jo["OriginalIssuer"]; + return new Claim(type, value, valueType, issuer, originalIssuer); + } + + public override bool CanWrite => false; + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/Ombi/Ombi.Helpers/Ombi.Helpers.csproj b/Ombi/Ombi.Helpers/Ombi.Helpers.csproj index 498e443c1..00920ab0c 100644 --- a/Ombi/Ombi.Helpers/Ombi.Helpers.csproj +++ b/Ombi/Ombi.Helpers/Ombi.Helpers.csproj @@ -8,4 +8,13 @@ + + + ..\..\..\..\..\..\..\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.6.1\System.IdentityModel.dll + + + ..\..\..\..\..\.nuget\packages\system.security.claims\4.3.0\ref\netstandard1.3\System.Security.Claims.dll + + + \ No newline at end of file diff --git a/Ombi/Ombi.Mapping.Maps/Ombi.Mapping.Maps.csproj b/Ombi/Ombi.Mapping.Maps/Ombi.Mapping.Maps.csproj new file mode 100644 index 000000000..19d3d3815 --- /dev/null +++ b/Ombi/Ombi.Mapping.Maps/Ombi.Mapping.Maps.csproj @@ -0,0 +1,19 @@ + + + + netstandard1.6 + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Ombi/Ombi.Mapping.Maps/OmbiProfile.cs b/Ombi/Ombi.Mapping.Maps/OmbiProfile.cs new file mode 100644 index 000000000..b79d9f760 --- /dev/null +++ b/Ombi/Ombi.Mapping.Maps/OmbiProfile.cs @@ -0,0 +1,15 @@ +using System; +using AutoMapper; + +namespace Ombi.Mapping.Maps +{ + public class OmbiProfile : Profile + { + public OmbiProfile() + { + // Add as many of these lines as you need to map your objects + + } + } + +} diff --git a/Ombi/Ombi.Mapping/AutoMapperProfile.cs b/Ombi/Ombi.Mapping/AutoMapperProfile.cs new file mode 100644 index 000000000..5b3423389 --- /dev/null +++ b/Ombi/Ombi.Mapping/AutoMapperProfile.cs @@ -0,0 +1,20 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace Ombi.Mapping +{ + public static class AutoMapperProfile + { + public static IServiceCollection AddOmbiMappingProfile(this IServiceCollection services) + { + var config = new AutoMapper.MapperConfiguration(cfg => + { + cfg.AddProfile(new OmbiProfile()); + }); + + var mapper = config.CreateMapper(); + services.AddSingleton(mapper); + + return services; + } + } +} \ No newline at end of file diff --git a/Ombi/Ombi.Mapping/Ombi.Mapping.csproj b/Ombi/Ombi.Mapping/Ombi.Mapping.csproj new file mode 100644 index 000000000..ce044ce17 --- /dev/null +++ b/Ombi/Ombi.Mapping/Ombi.Mapping.csproj @@ -0,0 +1,19 @@ + + + + netstandard1.6 + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Ombi/Ombi.Mapping/OmbiProfile.cs b/Ombi/Ombi.Mapping/OmbiProfile.cs new file mode 100644 index 000000000..6a2121106 --- /dev/null +++ b/Ombi/Ombi.Mapping/OmbiProfile.cs @@ -0,0 +1,15 @@ +using System; +using AutoMapper; +using Ombi.Core.Models; +using Ombi.Store.Entities; + +namespace Ombi.Mapping +{ + public class OmbiProfile : Profile + { + public OmbiProfile() + { + CreateMap().ReverseMap(); + } + } +} diff --git a/Ombi/Ombi.Store/Entities/User.cs b/Ombi/Ombi.Store/Entities/User.cs index 534d062a2..3b080da81 100644 --- a/Ombi/Ombi.Store/Entities/User.cs +++ b/Ombi/Ombi.Store/Entities/User.cs @@ -25,7 +25,12 @@ // ************************************************************************/ #endregion +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; using System.Security.Claims; +using Newtonsoft.Json; +using Ombi.Helpers; + namespace Ombi.Store.Entities { @@ -33,10 +38,17 @@ namespace Ombi.Store.Entities { public string Username { get; set; } public string Alias { get; set; } - public Claim[] Claims { get; set; } + public string ClaimsSerialized { get; set; } public string EmailAddress { get; set; } public string Password { get; set; } + public byte[] Salt { get; set; } public UserType UserType { get; set; } + + [NotMapped] + public List Claims { + get => JsonConvert.DeserializeObject>(ClaimsSerialized, new ClaimConverter()); + set => ClaimsSerialized = JsonConvert.SerializeObject(value, new ClaimConverter()); + } } public enum UserType diff --git a/Ombi/Ombi.Store/Ombi.Store.csproj b/Ombi/Ombi.Store/Ombi.Store.csproj index d8eb4db16..6a83ce47c 100644 --- a/Ombi/Ombi.Store/Ombi.Store.csproj +++ b/Ombi/Ombi.Store/Ombi.Store.csproj @@ -6,7 +6,6 @@ - @@ -15,4 +14,7 @@ + + + \ No newline at end of file diff --git a/Ombi/Ombi.sln b/Ombi/Ombi.sln index bd42c7e32..4519f2dc4 100644 --- a/Ombi/Ombi.sln +++ b/Ombi/Ombi.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26228.10 +VisualStudioVersion = 15.0.26403.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi", "Ombi\Ombi.csproj", "{C987AA67-AFE1-468F-ACD3-EAD5A48E1F6A}" EndProject @@ -26,6 +26,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Store", "Ombi.Store\Om EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.DependencyInjection", "Ombi.DependencyInjection\Ombi.DependencyInjection.csproj", "{B39E4558-C557-48E7-AA74-19C5CD809617}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Mapping", "Ombi.Mapping\Ombi.Mapping.csproj", "{63E63511-1C7F-4162-8F92-8F7391B3C8A3}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Mappers", "Mappers", "{025FB189-2FFB-4F43-A64B-6F1B5A0D2065}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DI", "DI", "{410F36CF-9C60-428A-B191-6FD90610991A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -60,6 +66,10 @@ Global {B39E4558-C557-48E7-AA74-19C5CD809617}.Debug|Any CPU.Build.0 = Debug|Any CPU {B39E4558-C557-48E7-AA74-19C5CD809617}.Release|Any CPU.ActiveCfg = Release|Any CPU {B39E4558-C557-48E7-AA74-19C5CD809617}.Release|Any CPU.Build.0 = Release|Any CPU + {63E63511-1C7F-4162-8F92-8F7391B3C8A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {63E63511-1C7F-4162-8F92-8F7391B3C8A3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {63E63511-1C7F-4162-8F92-8F7391B3C8A3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {63E63511-1C7F-4162-8F92-8F7391B3C8A3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -67,5 +77,7 @@ Global GlobalSection(NestedProjects) = preSolution {132DA282-5894-4570-8916-D8C18ED2CE84} = {9293CA11-360A-4C20-A674-B9E794431BF5} {EA31F915-31F9-4318-B521-1500CDF40DDF} = {9293CA11-360A-4C20-A674-B9E794431BF5} + {B39E4558-C557-48E7-AA74-19C5CD809617} = {410F36CF-9C60-428A-B191-6FD90610991A} + {63E63511-1C7F-4162-8F92-8F7391B3C8A3} = {025FB189-2FFB-4F43-A64B-6F1B5A0D2065} EndGlobalSection EndGlobal diff --git a/Ombi/Ombi/Auth/TokenProviderMiddleware.cs b/Ombi/Ombi/Auth/TokenProviderMiddleware.cs index cf21abd08..5fcf82375 100644 --- a/Ombi/Ombi/Auth/TokenProviderMiddleware.cs +++ b/Ombi/Ombi/Auth/TokenProviderMiddleware.cs @@ -20,10 +20,11 @@ namespace Ombi.Auth private readonly RequestDelegate _next; private readonly TokenProviderOptions _options; private readonly JsonSerializerSettings _serializerSettings; + private readonly IUserIdentityManager _identityManager; public TokenProviderMiddleware( RequestDelegate next, - IOptions options) + IOptions options, IUserIdentityManager manager) { _next = next; _options = options.Value; @@ -33,6 +34,7 @@ namespace Ombi.Auth { Formatting = Formatting.Indented }; + _identityManager = manager; } public Task Invoke(HttpContext context) @@ -66,7 +68,7 @@ namespace Ombi.Auth userInfo = JsonConvert.DeserializeObject(body); } - var identity = await _options.IdentityResolver(userInfo.Username, userInfo.Password, new UserIdentityManager(new UserRepository(new OmbiContext()))); + var identity = await _options.IdentityResolver(userInfo.Username, userInfo.Password, _identityManager); if (identity == null) { context.Response.StatusCode = 400; diff --git a/Ombi/Ombi/Controllers/IdentityController.cs b/Ombi/Ombi/Controllers/IdentityController.cs new file mode 100644 index 000000000..30b47310c --- /dev/null +++ b/Ombi/Ombi/Controllers/IdentityController.cs @@ -0,0 +1,26 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Ombi.Core.IdentityResolver; +using Ombi.Core.Models; + +namespace Ombi.Controllers +{ + [Authorize] + public class IdentityController : BaseV1ApiController + { + public IdentityController(IUserIdentityManager identity) + { + IdentityManager = identity; + } + + private IUserIdentityManager IdentityManager { get; } + + [HttpGet] + public async Task GetUser() + { + return await IdentityManager.GetUser(this.HttpContext.User.Identity.Name); + } + + } +} diff --git a/Ombi/Ombi/Ombi.csproj b/Ombi/Ombi/Ombi.csproj index efeae93f4..b8336f669 100644 --- a/Ombi/Ombi/Ombi.csproj +++ b/Ombi/Ombi/Ombi.csproj @@ -8,16 +8,11 @@ - - auth - Copy.module.js - PreserveNewest - PreserveNewest - @@ -33,8 +28,7 @@ - - + @@ -52,6 +46,7 @@ + diff --git a/Ombi/Ombi/Startup.Auth.cs b/Ombi/Ombi/Startup.Auth.cs index 21ba6d509..3005dc941 100644 --- a/Ombi/Ombi/Startup.Auth.cs +++ b/Ombi/Ombi/Startup.Auth.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Security.Claims; using System.Security.Principal; using System.Text; @@ -8,6 +9,7 @@ using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; using Ombi.Auth; using Ombi.Core.IdentityResolver; +using Ombi.Core.Models; namespace Ombi { @@ -53,23 +55,20 @@ namespace Ombi TokenValidationParameters = tokenValidationParameters }); - app.UseCookieAuthentication(new CookieAuthenticationOptions - { - AutomaticAuthenticate = true, - AutomaticChallenge = true, - AuthenticationScheme = "Cookie", - CookieName = Configuration.GetSection("TokenAuthentication:CookieName").Value, - TicketDataFormat = new CustomJwtDataFormat( - SecurityAlgorithms.HmacSha256, - tokenValidationParameters) - }); - app.UseMiddleware(Options.Create(tokenProviderOptions)); } private async Task GetIdentity(string username, string password, IUserIdentityManager userIdentityManager) { + //await userIdentityManager.CreateUser(new UserDto + //{ + // Username = "a", + // Password = "a", + // Claims = new List() { new Claim(ClaimTypes.Role, "Admin")}, + // UserType = UserType.LocalUser, + //}); + var validLogin = await userIdentityManager.CredentialsValid(username, password); if (!validLogin) { diff --git a/Ombi/Ombi/Startup.cs b/Ombi/Ombi/Startup.cs index 24ef16b31..9c153ba7a 100644 --- a/Ombi/Ombi/Startup.cs +++ b/Ombi/Ombi/Startup.cs @@ -1,24 +1,12 @@ -using System; -using System.IO; -using System.Linq; -using System.Text; -using IdentityServer4.EntityFramework.DbContexts; +using AutoMapper; using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.StaticFiles; -using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Logging; -using Microsoft.IdentityModel.Tokens; -using Newtonsoft.Json; -using Ombi.Auth; using Ombi.DependencyInjection; -using Ombi.Models; -using Ombi.Store.Context; +using Ombi.Mapping; namespace Ombi { @@ -42,6 +30,8 @@ namespace Ombi { // Add framework services. services.AddMvc(); + services.AddOmbiMappingProfile(); + services.AddAutoMapper(); services.RegisterDependencies(); // Ioc and EF } diff --git a/Ombi/Ombi/Views/Shared/_Layout.cshtml b/Ombi/Ombi/Views/Shared/_Layout.cshtml index 1cc395747..fafd4a355 100644 --- a/Ombi/Ombi/Views/Shared/_Layout.cshtml +++ b/Ombi/Ombi/Views/Shared/_Layout.cshtml @@ -3,7 +3,7 @@ - @ViewData["Title"] - Ombi + Ombi diff --git a/Ombi/Ombi/appsettings.json b/Ombi/Ombi/appsettings.json index b0f80f89d..f3d45d9cc 100644 --- a/Ombi/Ombi/appsettings.json +++ b/Ombi/Ombi/appsettings.json @@ -9,7 +9,7 @@ "SecretKey": "secretkey_secretkey123!", "Issuer": "DemoIssuer", "Audience": "DemoAudience", - "TokenPath": "/api/token/", + "TokenPath": "/api/v1/token/", "CookieName": "access_token" } } diff --git a/Ombi/Ombi/wwwroot/app/auth/auth.service.ts b/Ombi/Ombi/wwwroot/app/auth/auth.service.ts index 8bb2f69b3..98624fabe 100644 --- a/Ombi/Ombi/wwwroot/app/auth/auth.service.ts +++ b/Ombi/Ombi/wwwroot/app/auth/auth.service.ts @@ -13,7 +13,7 @@ import { Http } from '@angular/http'; @Injectable() export class AuthService extends ServiceHelpers { constructor(http: Http) { - super(http, '/api/token'); + super(http, '/api/v1/token'); } login(login:IUserLogin) : Observable { From 831c65f563e6505130fc70a015f472c4351f27a2 Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" Date: Wed, 12 Apr 2017 15:30:45 +0100 Subject: [PATCH 024/725] Settings for Ombi --- Ombi/Ombi.Core/Settings/ISettingsResolver.cs | 7 +++ .../Ombi.Core/Settings/Models/OmbiSettings.cs | 12 +++++ Ombi/Ombi.Core/Settings/SettingsResolver.cs | 21 ++++++++ .../Ombi.DependencyInjection/IocExtensions.cs | 1 + Ombi/Ombi/Controllers/SettingsController.cs | 37 ++++++++++++++ Ombi/Ombi/Ombi.csproj | 9 ++++ Ombi/Ombi/wwwroot/app/app.module.ts | 2 + .../wwwroot/app/services/settings.service.ts | 23 +++++++++ .../app/settings/interfaces/ISettings.ts | 11 ++++ .../app/settings/ombi/ombi.component.html | 51 +++++++++++++++++-- .../app/settings/ombi/ombi.component.ts | 22 ++++++-- 11 files changed, 188 insertions(+), 8 deletions(-) create mode 100644 Ombi/Ombi.Core/Settings/ISettingsResolver.cs create mode 100644 Ombi/Ombi.Core/Settings/Models/OmbiSettings.cs create mode 100644 Ombi/Ombi.Core/Settings/SettingsResolver.cs create mode 100644 Ombi/Ombi/Controllers/SettingsController.cs create mode 100644 Ombi/Ombi/wwwroot/app/services/settings.service.ts create mode 100644 Ombi/Ombi/wwwroot/app/settings/interfaces/ISettings.ts diff --git a/Ombi/Ombi.Core/Settings/ISettingsResolver.cs b/Ombi/Ombi.Core/Settings/ISettingsResolver.cs new file mode 100644 index 000000000..6c21878ae --- /dev/null +++ b/Ombi/Ombi.Core/Settings/ISettingsResolver.cs @@ -0,0 +1,7 @@ +namespace Ombi.Core.Settings +{ + public interface ISettingsResolver + { + ISettingsService Resolve(); + } +} \ No newline at end of file diff --git a/Ombi/Ombi.Core/Settings/Models/OmbiSettings.cs b/Ombi/Ombi.Core/Settings/Models/OmbiSettings.cs new file mode 100644 index 000000000..6294a43a4 --- /dev/null +++ b/Ombi/Ombi.Core/Settings/Models/OmbiSettings.cs @@ -0,0 +1,12 @@ +namespace Ombi.Core.Settings.Models +{ + public class OmbiSettings : Settings + { + public int Port { get; set; } + //public string BaseUrl { get; set; } + public bool CollectAnalyticData { get; set; } + public bool Wizard { get; set; } + + public string ApiKey { get; set; } + } +} \ No newline at end of file diff --git a/Ombi/Ombi.Core/Settings/SettingsResolver.cs b/Ombi/Ombi.Core/Settings/SettingsResolver.cs new file mode 100644 index 000000000..33c7cf2c2 --- /dev/null +++ b/Ombi/Ombi.Core/Settings/SettingsResolver.cs @@ -0,0 +1,21 @@ +using System; +using Microsoft.Extensions.DependencyInjection; + +namespace Ombi.Core.Settings +{ + public class SettingsResolver : ISettingsResolver + { + public SettingsResolver(IServiceProvider services) + { + _services = services; + } + + private readonly IServiceProvider _services; + + public ISettingsService Resolve() + { + var service = (ISettingsService)_services.GetService(typeof(ISettingsService)); + return service;; + } + } +} \ No newline at end of file diff --git a/Ombi/Ombi.DependencyInjection/IocExtensions.cs b/Ombi/Ombi.DependencyInjection/IocExtensions.cs index 6ccaca0f8..57fae18d5 100644 --- a/Ombi/Ombi.DependencyInjection/IocExtensions.cs +++ b/Ombi/Ombi.DependencyInjection/IocExtensions.cs @@ -51,6 +51,7 @@ namespace Ombi.DependencyInjection services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddTransient(typeof(ISettingsService<>), typeof(SettingsServiceV2<>)); return services; } diff --git a/Ombi/Ombi/Controllers/SettingsController.cs b/Ombi/Ombi/Controllers/SettingsController.cs new file mode 100644 index 000000000..db83c15b6 --- /dev/null +++ b/Ombi/Ombi/Controllers/SettingsController.cs @@ -0,0 +1,37 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Ombi.Core.Settings; +using Ombi.Core.Settings.Models; + +namespace Ombi.Controllers +{ + [Authorize(Roles = "Admin")] + public class SettingsController : BaseV1ApiController + { + public SettingsController(ISettingsResolver resolver) + { + SettingsResolver = resolver; + } + + private ISettingsResolver SettingsResolver { get; } + + [HttpGet("ombi")] + public async Task OmbiSettings() + { + var settings = SettingsResolver.Resolve(); + + return await settings.GetSettingsAsync(); + } + + [HttpPost("ombi")] + public async Task OmbiSettings([FromBody]OmbiSettings ombi) + { + var settings = SettingsResolver.Resolve(); + + return await settings.SaveSettingsAsync(ombi); + } + + + } +} diff --git a/Ombi/Ombi/Ombi.csproj b/Ombi/Ombi/Ombi.csproj index b8336f669..784ae0f46 100644 --- a/Ombi/Ombi/Ombi.csproj +++ b/Ombi/Ombi/Ombi.csproj @@ -16,6 +16,15 @@ + + + + PreserveNewest + + + + + diff --git a/Ombi/Ombi/wwwroot/app/app.module.ts b/Ombi/Ombi/wwwroot/app/app.module.ts index 1e9835bda..18ea25e3a 100644 --- a/Ombi/Ombi/wwwroot/app/app.module.ts +++ b/Ombi/Ombi/wwwroot/app/app.module.ts @@ -19,6 +19,7 @@ import { PageNotFoundComponent } from './errors/not-found.component'; import { SearchService } from './services/search.service'; import { RequestService } from './services/request.service'; import { NotificationService } from './services/notification.service'; +import { SettingsService } from './services/settings.service'; import { AuthService } from './auth/auth.service'; import { AuthGuard } from './auth/auth.guard'; import { AuthModule } from './auth/auth.module'; @@ -66,6 +67,7 @@ const routes: Routes = [ NotificationService, AuthService, AuthGuard, + SettingsService ], bootstrap: [AppComponent] }) diff --git a/Ombi/Ombi/wwwroot/app/services/settings.service.ts b/Ombi/Ombi/wwwroot/app/services/settings.service.ts new file mode 100644 index 000000000..0f353315e --- /dev/null +++ b/Ombi/Ombi/wwwroot/app/services/settings.service.ts @@ -0,0 +1,23 @@ +import { Injectable } from '@angular/core'; +import { AuthHttp } from 'angular2-jwt'; +import { Observable } from 'rxjs/Rx'; + +import { ServiceAuthHelpers } from './service.helpers'; +import { IOmbiSettings } from '../settings/interfaces/ISettings'; + + +@Injectable() +export class SettingsService extends ServiceAuthHelpers { + constructor(http: AuthHttp) { + super(http, '/api/v1/Settings/'); + } + + getOmbi(): Observable { + return this.http.get(this.url).map(this.extractData); + } + + saveOmbi(settings: IOmbiSettings): Observable { + return this.http.post(`${this.url}/Ombi/`, JSON.stringify(settings), { headers: this.headers }).map(this.extractData); + } + +} \ No newline at end of file diff --git a/Ombi/Ombi/wwwroot/app/settings/interfaces/ISettings.ts b/Ombi/Ombi/wwwroot/app/settings/interfaces/ISettings.ts new file mode 100644 index 000000000..5bb3deeac --- /dev/null +++ b/Ombi/Ombi/wwwroot/app/settings/interfaces/ISettings.ts @@ -0,0 +1,11 @@ +export interface ISettings { + id:number +} + +export interface IOmbiSettings extends ISettings { + port: number, +//baseUrl:string, + collectAnalyticData: boolean, + wizard: boolean, + apiKey:string +} \ No newline at end of file diff --git a/Ombi/Ombi/wwwroot/app/settings/ombi/ombi.component.html b/Ombi/Ombi/wwwroot/app/settings/ombi/ombi.component.html index a71b39d11..6750530dd 100644 --- a/Ombi/Ombi/wwwroot/app/settings/ombi/ombi.component.html +++ b/Ombi/Ombi/wwwroot/app/settings/ombi/ombi.component.html @@ -1,7 +1,50 @@ - -Settings +
+
+ Ombi Configuration +
+ +
+ +
+
+ You will have to restart after changing the port. + + +
+ +
+ + +
+
+
+ +
+
+
+
+
+ +
+
+ + +
+
+ +
+
+ +
+
+
+
\ No newline at end of file diff --git a/Ombi/Ombi/wwwroot/app/settings/ombi/ombi.component.ts b/Ombi/Ombi/wwwroot/app/settings/ombi/ombi.component.ts index 7805b9ebe..d1aba3a99 100644 --- a/Ombi/Ombi/wwwroot/app/settings/ombi/ombi.component.ts +++ b/Ombi/Ombi/wwwroot/app/settings/ombi/ombi.component.ts @@ -1,11 +1,25 @@ -import { Component } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; + +import { IOmbiSettings } from '../interfaces/ISettings' +import { SettingsService } from '../../services/settings.service'; + @Component({ selector: 'ombi', moduleId: module.id, templateUrl: './ombi.component.html', }) -export class OmbiComponent { +export class OmbiComponent implements OnInit { + + constructor(private settingsService: SettingsService) { } + + settings: IOmbiSettings; + + ngOnInit(): void { + this.settingsService.getOmbi().subscribe(x => this.settings = x); + } + - enabled:boolean; - host:string; + refreshApiKey() { + + } } \ No newline at end of file From dd5e1c52321531902f4bad65d6e1a4ab10b85887 Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" Date: Thu, 13 Apr 2017 16:29:03 +0100 Subject: [PATCH 025/725] Settings --- Ombi.UI/Views/UserWizard/Index.cshtml | 2 +- Ombi/Ombi.Api/Api.cs | 39 +++++- Ombi/Ombi.Api/ApiHelper.cs | 3 - Ombi/Ombi.Api/JsonContent.cs | 13 ++ Ombi/Ombi.Api/Ombi.Api.csproj | 2 +- Ombi/Ombi.Api/Request.cs | 73 +++++++++++ Ombi/Ombi.Core/Ombi.Core.csproj | 2 +- .../Settings/Models/External/EmbySettings.cs | 10 ++ .../Models/External/ExternalSettings.cs | 29 +++++ .../Settings/Models/External/PlexSettings.cs | 12 ++ Ombi/Ombi.Core/Settings/SettingsResolver.cs | 2 + Ombi/Ombi.Core/Settings/SettingsService.cs | 4 +- .../Ombi.DependencyInjection.csproj | 2 +- Ombi/Ombi.Helpers/Ombi.Helpers.csproj | 3 +- Ombi/Ombi.Helpers/StringCipher.cs | 14 ++ Ombi/Ombi.Helpers/UriHelper.cs | 123 ++++++++++++++++++ Ombi/Ombi.Store/Ombi.Store.csproj | 2 +- .../Repository/SettingsJsonRepository.cs | 2 - .../Ombi.TheMovieDbApi.csproj | 2 +- Ombi/Ombi.TheMovieDbApi/TheMovieDbApi.cs | 60 +++++---- Ombi/Ombi/.gitignore | 1 - Ombi/Ombi/Controllers/SettingsController.cs | 46 ++++++- Ombi/Ombi/Ombi.csproj | 73 ++++++++++- Ombi/Ombi/wwwroot/app/app.component.html | 2 +- Ombi/Ombi/wwwroot/app/app.module.ts | 4 +- .../wwwroot/app/services/settings.service.ts | 20 ++- .../app/settings/emby/emby.component.html | 58 +++++++++ .../app/settings/emby/emby.component.ts | 35 +++++ .../app/settings/interfaces/ISettings.ts | 21 +++ .../app/settings/ombi/ombi.component.html | 11 +- .../app/settings/ombi/ombi.component.ts | 20 ++- .../app/settings/plex/plex.component.html | 85 ++++++++++++ .../app/settings/plex/plex.component.ts | 41 ++++++ .../wwwroot/app/settings/settings.module.ts | 10 +- .../mediaserver/mediaserver.component.html | 17 +++ .../mediaserver/mediaserver.component.ts | 22 ++++ .../app/wizard/welcome/welcome.component.html | 17 +++ .../app/wizard/welcome/welcome.component.ts | 18 +++ Ombi/Ombi/wwwroot/app/wizard/wizard.module.ts | 31 +++++ Ombi/Ombi/wwwroot/images/emby-logo-dark.jpg | Bin 0 -> 23064 bytes Ombi/Ombi/wwwroot/images/emby-logo.png | Bin 0 -> 9967 bytes Ombi/Ombi/wwwroot/images/icons_16.png | Bin 0 -> 4020 bytes Ombi/Ombi/wwwroot/images/logo original.png | Bin 0 -> 26845 bytes Ombi/Ombi/wwwroot/images/logo.png | Bin 0 -> 25370 bytes .../wwwroot/images/plex-logo-reversed.png | Bin 0 -> 15665 bytes Ombi/Ombi/wwwroot/images/plex-logo.png | Bin 0 -> 15825 bytes Ombi/Ombi/wwwroot/images/slider_handles.png | Bin 0 -> 1945 bytes .../Ombi/wwwroot/images/slider_handles@2x.png | Bin 0 -> 3603 bytes .../images/ui-bg_flat_0_aaaaaa_40x100.png | Bin 0 -> 180 bytes .../images/ui-bg_flat_75_ffffff_40x100.png | Bin 0 -> 178 bytes .../images/ui-bg_glass_55_fbf9ee_1x400.png | Bin 0 -> 120 bytes .../images/ui-bg_glass_65_ffffff_1x400.png | Bin 0 -> 105 bytes .../images/ui-bg_glass_75_dadada_1x400.png | Bin 0 -> 111 bytes .../images/ui-bg_glass_75_e6e6e6_1x400.png | Bin 0 -> 110 bytes .../images/ui-bg_glass_95_fef1ec_1x400.png | Bin 0 -> 119 bytes .../ui-bg_highlight-soft_75_cccccc_1x100.png | Bin 0 -> 101 bytes .../images/ui-icons_222222_256x240.png | Bin 0 -> 4369 bytes .../images/ui-icons_2e83ff_256x240.png | Bin 0 -> 5355 bytes .../images/ui-icons_454545_256x240.png | Bin 0 -> 4369 bytes .../images/ui-icons_cd0a0a_256x240.png | Bin 0 -> 4369 bytes .../images/ui-icons_ffffff_256x240.png | Bin 0 -> 4369 bytes 61 files changed, 858 insertions(+), 73 deletions(-) create mode 100644 Ombi/Ombi.Api/JsonContent.cs create mode 100644 Ombi/Ombi.Api/Request.cs create mode 100644 Ombi/Ombi.Core/Settings/Models/External/EmbySettings.cs create mode 100644 Ombi/Ombi.Core/Settings/Models/External/ExternalSettings.cs create mode 100644 Ombi/Ombi.Core/Settings/Models/External/PlexSettings.cs create mode 100644 Ombi/Ombi.Helpers/UriHelper.cs create mode 100644 Ombi/Ombi/wwwroot/app/settings/emby/emby.component.html create mode 100644 Ombi/Ombi/wwwroot/app/settings/emby/emby.component.ts create mode 100644 Ombi/Ombi/wwwroot/app/settings/plex/plex.component.html create mode 100644 Ombi/Ombi/wwwroot/app/settings/plex/plex.component.ts create mode 100644 Ombi/Ombi/wwwroot/app/wizard/mediaserver/mediaserver.component.html create mode 100644 Ombi/Ombi/wwwroot/app/wizard/mediaserver/mediaserver.component.ts create mode 100644 Ombi/Ombi/wwwroot/app/wizard/welcome/welcome.component.html create mode 100644 Ombi/Ombi/wwwroot/app/wizard/welcome/welcome.component.ts create mode 100644 Ombi/Ombi/wwwroot/app/wizard/wizard.module.ts create mode 100644 Ombi/Ombi/wwwroot/images/emby-logo-dark.jpg create mode 100644 Ombi/Ombi/wwwroot/images/emby-logo.png create mode 100644 Ombi/Ombi/wwwroot/images/icons_16.png create mode 100644 Ombi/Ombi/wwwroot/images/logo original.png create mode 100644 Ombi/Ombi/wwwroot/images/logo.png create mode 100644 Ombi/Ombi/wwwroot/images/plex-logo-reversed.png create mode 100644 Ombi/Ombi/wwwroot/images/plex-logo.png create mode 100644 Ombi/Ombi/wwwroot/images/slider_handles.png create mode 100644 Ombi/Ombi/wwwroot/images/slider_handles@2x.png create mode 100644 Ombi/Ombi/wwwroot/images/ui-bg_flat_0_aaaaaa_40x100.png create mode 100644 Ombi/Ombi/wwwroot/images/ui-bg_flat_75_ffffff_40x100.png create mode 100644 Ombi/Ombi/wwwroot/images/ui-bg_glass_55_fbf9ee_1x400.png create mode 100644 Ombi/Ombi/wwwroot/images/ui-bg_glass_65_ffffff_1x400.png create mode 100644 Ombi/Ombi/wwwroot/images/ui-bg_glass_75_dadada_1x400.png create mode 100644 Ombi/Ombi/wwwroot/images/ui-bg_glass_75_e6e6e6_1x400.png create mode 100644 Ombi/Ombi/wwwroot/images/ui-bg_glass_95_fef1ec_1x400.png create mode 100644 Ombi/Ombi/wwwroot/images/ui-bg_highlight-soft_75_cccccc_1x100.png create mode 100644 Ombi/Ombi/wwwroot/images/ui-icons_222222_256x240.png create mode 100644 Ombi/Ombi/wwwroot/images/ui-icons_2e83ff_256x240.png create mode 100644 Ombi/Ombi/wwwroot/images/ui-icons_454545_256x240.png create mode 100644 Ombi/Ombi/wwwroot/images/ui-icons_cd0a0a_256x240.png create mode 100644 Ombi/Ombi/wwwroot/images/ui-icons_ffffff_256x240.png diff --git a/Ombi.UI/Views/UserWizard/Index.cshtml b/Ombi.UI/Views/UserWizard/Index.cshtml index 8ecf52d29..734a9e4a5 100644 --- a/Ombi.UI/Views/UserWizard/Index.cshtml +++ b/Ombi.UI/Views/UserWizard/Index.cshtml @@ -10,7 +10,7 @@ } @Html.LoadWizardAssets() - +
diff --git a/Ombi/Ombi.Api/Api.cs b/Ombi/Ombi.Api/Api.cs index 931598b50..824e8af9e 100644 --- a/Ombi/Ombi.Api/Api.cs +++ b/Ombi/Ombi.Api/Api.cs @@ -1,8 +1,5 @@ using System; -using System.IO; using System.Net.Http; -using System.Net.Http.Headers; -using System.Text; using System.Threading.Tasks; using Newtonsoft.Json; @@ -10,7 +7,7 @@ namespace Ombi.Api { public class Api { - public static JsonSerializerSettings Settings = new JsonSerializerSettings + private static readonly JsonSerializerSettings Settings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }; @@ -27,5 +24,39 @@ namespace Ombi.Api var receiveString = await response.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject(receiveString, Settings); } + + public async Task Request(Request request) + { + using (var httpClient = new HttpClient()) + { + using (var httpRequestMessage = new HttpRequestMessage(request.HttpMethod, request.FullUri)) + { + // Add the Json Body + if (request.JsonBody != null) + { + httpRequestMessage.Content = new JsonContent(request.JsonBody); + } + + // Add headers + foreach (var header in request.Headers) + { + httpRequestMessage.Headers.Add(header.Key, header.Value); + } + using (var httpResponseMessage = await httpClient.SendAsync(httpRequestMessage)) + { + if (!httpResponseMessage.IsSuccessStatusCode) + { + // Logging + } + // do something with the response + var data = httpResponseMessage.Content; + + + var receivedString = await data.ReadAsStringAsync(); + return JsonConvert.DeserializeObject(receivedString, Settings); + } + } + } + } } } diff --git a/Ombi/Ombi.Api/ApiHelper.cs b/Ombi/Ombi.Api/ApiHelper.cs index 61050909e..fa9d3e7ab 100644 --- a/Ombi/Ombi.Api/ApiHelper.cs +++ b/Ombi/Ombi.Api/ApiHelper.cs @@ -71,8 +71,5 @@ namespace Ombi.Api : $"{startingTag}{parameter}={value}"; return builder.Uri; } - - - } } \ No newline at end of file diff --git a/Ombi/Ombi.Api/JsonContent.cs b/Ombi/Ombi.Api/JsonContent.cs new file mode 100644 index 000000000..58a2f2363 --- /dev/null +++ b/Ombi/Ombi.Api/JsonContent.cs @@ -0,0 +1,13 @@ +using System.Net.Http; +using System.Text; +using Newtonsoft.Json; + +namespace Ombi.Api +{ + internal class JsonContent : StringContent + { + public JsonContent(object obj) : + base(JsonConvert.SerializeObject(obj), Encoding.UTF8, "application/json") + { } + } +} \ No newline at end of file diff --git a/Ombi/Ombi.Api/Ombi.Api.csproj b/Ombi/Ombi.Api/Ombi.Api.csproj index 80457725a..b3d86c156 100644 --- a/Ombi/Ombi.Api/Ombi.Api.csproj +++ b/Ombi/Ombi.Api/Ombi.Api.csproj @@ -1,7 +1,7 @@  - netstandard1.4 + netstandard1.6 diff --git a/Ombi/Ombi.Api/Request.cs b/Ombi/Ombi.Api/Request.cs new file mode 100644 index 000000000..a7a00501f --- /dev/null +++ b/Ombi/Ombi.Api/Request.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Text; +using Newtonsoft.Json; +using Newtonsoft.Json.Bson; + +namespace Ombi.Api +{ + public class Request + { + public Request(string endpoint, string baseUrl, HttpMethod http) + { + Endpoint = endpoint; + BaseUrl = baseUrl; + HttpMethod = http; + } + + public string Endpoint { get; } + public string BaseUrl { get; } + public HttpMethod HttpMethod { get; } + + private string FullUrl + { + get + { + var sb = new StringBuilder(); + sb.Append(!BaseUrl.EndsWith("/") ? string.Format("{0}/", BaseUrl) : BaseUrl); + sb.Append(Endpoint.StartsWith("/") ? Endpoint.Remove(0, 1) : Endpoint); + return sb.ToString(); + } + } + + private Uri _modified; + + public Uri FullUri + { + get => _modified != null ? _modified : new Uri(FullUrl); + set => _modified = value; + } + + public List> Headers { get; } = new List>(); + + public object JsonBody { get; set; } + + public bool IsValidUrl + { + get + { + try + { + // ReSharper disable once ObjectCreationAsStatement + new Uri(FullUrl); + return true; + } + catch (Exception) + { + return false; + } + } + } + + public void AddHeader(string key, string value) + { + Headers.Add(new KeyValuePair(key, value)); + } + + public void AddJsonBody(object obj) + { + JsonBody = obj; + } + } +} \ No newline at end of file diff --git a/Ombi/Ombi.Core/Ombi.Core.csproj b/Ombi/Ombi.Core/Ombi.Core.csproj index d6bb44f88..7bac40bb5 100644 --- a/Ombi/Ombi.Core/Ombi.Core.csproj +++ b/Ombi/Ombi.Core/Ombi.Core.csproj @@ -1,7 +1,7 @@  - netstandard1.4 + netstandard1.6 diff --git a/Ombi/Ombi.Core/Settings/Models/External/EmbySettings.cs b/Ombi/Ombi.Core/Settings/Models/External/EmbySettings.cs new file mode 100644 index 000000000..b68cbe9d8 --- /dev/null +++ b/Ombi/Ombi.Core/Settings/Models/External/EmbySettings.cs @@ -0,0 +1,10 @@ +namespace Ombi.Core.Settings.Models.External +{ + public sealed class EmbySettings : ExternalSettings + { + public bool Enable { get; set; } + public string ApiKey { get; set; } + public string AdministratorId { get; set; } + public bool EnableEpisodeSearching { get; set; } + } +} \ No newline at end of file diff --git a/Ombi/Ombi.Core/Settings/Models/External/ExternalSettings.cs b/Ombi/Ombi.Core/Settings/Models/External/ExternalSettings.cs new file mode 100644 index 000000000..cdaf3158c --- /dev/null +++ b/Ombi/Ombi.Core/Settings/Models/External/ExternalSettings.cs @@ -0,0 +1,29 @@ +using System; +using Newtonsoft.Json; +using Ombi.Helpers; + +namespace Ombi.Core.Settings.Models.External +{ + public abstract class ExternalSettings : Settings + { + public bool Ssl { get; set; } + public string SubDir { get; set; } + public string Ip { get; set; } + public int Port { get; set; } + + [JsonIgnore] + public virtual Uri FullUri + { + get + { + if (!string.IsNullOrEmpty(SubDir)) + { + var formattedSubDir = Ip.ReturnUriWithSubDir(Port, Ssl, SubDir); + return formattedSubDir; + } + var formatted = Ip.ReturnUri(Port, Ssl); + return formatted; + } + } + } +} \ No newline at end of file diff --git a/Ombi/Ombi.Core/Settings/Models/External/PlexSettings.cs b/Ombi/Ombi.Core/Settings/Models/External/PlexSettings.cs new file mode 100644 index 000000000..80c57af3b --- /dev/null +++ b/Ombi/Ombi.Core/Settings/Models/External/PlexSettings.cs @@ -0,0 +1,12 @@ +namespace Ombi.Core.Settings.Models.External +{ + public sealed class PlexSettings : ExternalSettings + { + + public bool Enable { get; set; } + public bool EnableEpisodeSearching { get; set; } + + public string PlexAuthToken { get; set; } + public string MachineIdentifier { get; set; } + } +} \ No newline at end of file diff --git a/Ombi/Ombi.Core/Settings/SettingsResolver.cs b/Ombi/Ombi.Core/Settings/SettingsResolver.cs index 33c7cf2c2..d0bff8d36 100644 --- a/Ombi/Ombi.Core/Settings/SettingsResolver.cs +++ b/Ombi/Ombi.Core/Settings/SettingsResolver.cs @@ -1,4 +1,6 @@ using System; +using System.Linq; +using System.Reflection; using Microsoft.Extensions.DependencyInjection; namespace Ombi.Core.Settings diff --git a/Ombi/Ombi.Core/Settings/SettingsService.cs b/Ombi/Ombi.Core/Settings/SettingsService.cs index c796710fa..552325e24 100644 --- a/Ombi/Ombi.Core/Settings/SettingsService.cs +++ b/Ombi/Ombi.Core/Settings/SettingsService.cs @@ -118,12 +118,12 @@ namespace Ombi.Core.Settings private string EncryptSettings(GlobalSettings settings) { - return StringCipher.Encrypt(settings.Content, settings.SettingsName); + return StringCipher.EncryptString(settings.Content, $"Ombiv3SettingsEncryptionPassword"); } private string DecryptSettings(GlobalSettings settings) { - return StringCipher.Decrypt(settings.Content, settings.SettingsName); + return StringCipher.DecryptString(settings.Content, $"Ombiv3SettingsEncryptionPassword"); } } } \ No newline at end of file diff --git a/Ombi/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj b/Ombi/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj index 1e12ffd1a..d776f14ed 100644 --- a/Ombi/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj +++ b/Ombi/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj @@ -1,7 +1,7 @@  - netstandard1.4 + netstandard1.6 diff --git a/Ombi/Ombi.Helpers/Ombi.Helpers.csproj b/Ombi/Ombi.Helpers/Ombi.Helpers.csproj index 00920ab0c..7cf7de058 100644 --- a/Ombi/Ombi.Helpers/Ombi.Helpers.csproj +++ b/Ombi/Ombi.Helpers/Ombi.Helpers.csproj @@ -1,10 +1,11 @@  - netstandard1.4 + netstandard1.6 + diff --git a/Ombi/Ombi.Helpers/StringCipher.cs b/Ombi/Ombi.Helpers/StringCipher.cs index df10fcb6a..f55238ca3 100644 --- a/Ombi/Ombi.Helpers/StringCipher.cs +++ b/Ombi/Ombi.Helpers/StringCipher.cs @@ -1,3 +1,4 @@ +using EasyCrypto; using System; using System.IO; using System.Security.Cryptography; @@ -81,5 +82,18 @@ namespace Ombi.Helpers } } } + + + public static string EncryptString(string text, string keyString) + { + var result = AesEncryption.EncryptWithPassword(text, keyString); + return result; + } + + public static string DecryptString(string cipherText, string keyString) + { + var result = AesEncryption.DecryptWithPassword(cipherText, keyString); + return result; + } } } diff --git a/Ombi/Ombi.Helpers/UriHelper.cs b/Ombi/Ombi.Helpers/UriHelper.cs new file mode 100644 index 000000000..83cd27e9d --- /dev/null +++ b/Ombi/Ombi.Helpers/UriHelper.cs @@ -0,0 +1,123 @@ +using System; + +namespace Ombi.Helpers +{ + public static class UriHelper + { + private const string Https = "Https"; + private const string Http = "Http"; + + public static Uri ReturnUri(this string val) + { + if (val == null) + { + throw new ApplicationSettingsException("The URI is null, please check your settings to make sure you have configured the applications correctly."); + } + try + { + var uri = new UriBuilder(); + + if (val.StartsWith("http://", StringComparison.Ordinal)) + { + uri = new UriBuilder(val); + } + else if (val.StartsWith("https://", StringComparison.Ordinal)) + { + uri = new UriBuilder(val); + } + else if (val.Contains(":")) + { + var split = val.Split(':', '/'); + int port; + int.TryParse(split[1], out port); + + uri = split.Length == 3 + ? new UriBuilder(Http, split[0], port, "/" + split[2]) + : new UriBuilder(Http, split[0], port); + } + else + { + uri = new UriBuilder(Http, val); + } + + return uri.Uri; + } + catch (Exception exception) + { + throw new Exception(exception.Message, exception); + } + } + + /// + /// Returns the URI. + /// + /// The value. + /// The port. + /// if set to true [SSL]. + /// The subdir. + /// + /// The URI is null, please check your settings to make sure you have configured the applications correctly. + /// + public static Uri ReturnUri(this string val, int port, bool ssl = default(bool)) + { + if (val == null) + { + throw new ApplicationSettingsException("The URI is null, please check your settings to make sure you have configured the applications correctly."); + } + try + { + var uri = new UriBuilder(); + + if (val.StartsWith("http://", StringComparison.Ordinal)) + { + var split = val.Split('/'); + uri = split.Length >= 4 ? new UriBuilder(Http, split[2], port, "/" + split[3]) : new UriBuilder(new Uri($"{val}:{port}")); + } + else if (val.StartsWith("https://", StringComparison.Ordinal)) + { + var split = val.Split('/'); + uri = split.Length >= 4 + ? new UriBuilder(Https, split[2], port, "/" + split[3]) + : new UriBuilder(Https, split[2], port); + } + else if (ssl) + { + uri = new UriBuilder(Https, val, port); + } + else + { + uri = new UriBuilder(Http, val, port); + } + + return uri.Uri; + } + catch (Exception exception) + { + throw new Exception(exception.Message, exception); + } + } + + public static Uri ReturnUriWithSubDir(this string val, int port, bool ssl, string subDir) + { + var uriBuilder = new UriBuilder(val); + if (ssl) + { + uriBuilder.Scheme = Https; + } + if (!string.IsNullOrEmpty(subDir)) + { + uriBuilder.Path = subDir; + } + uriBuilder.Port = port; + + return uriBuilder.Uri; + } + } + + public class ApplicationSettingsException : Exception + { + public ApplicationSettingsException(string s) : base(s) + { + } + } +} \ No newline at end of file diff --git a/Ombi/Ombi.Store/Ombi.Store.csproj b/Ombi/Ombi.Store/Ombi.Store.csproj index 6a83ce47c..ffc1d529a 100644 --- a/Ombi/Ombi.Store/Ombi.Store.csproj +++ b/Ombi/Ombi.Store/Ombi.Store.csproj @@ -1,7 +1,7 @@  - netstandard1.4 + netstandard1.6 diff --git a/Ombi/Ombi.Store/Repository/SettingsJsonRepository.cs b/Ombi/Ombi.Store/Repository/SettingsJsonRepository.cs index 987276a55..e5e0da498 100644 --- a/Ombi/Ombi.Store/Repository/SettingsJsonRepository.cs +++ b/Ombi/Ombi.Store/Repository/SettingsJsonRepository.cs @@ -65,7 +65,6 @@ namespace Ombi.Store.Repository public async Task UpdateAsync(GlobalSettings entity) { - Db.Settings.Update(entity); await Db.SaveChangesAsync(); } @@ -77,7 +76,6 @@ namespace Ombi.Store.Repository public void Update(GlobalSettings entity) { - Db.Settings.Update(entity); Db.SaveChanges(); } } diff --git a/Ombi/Ombi.TheMovieDbApi/Ombi.TheMovieDbApi.csproj b/Ombi/Ombi.TheMovieDbApi/Ombi.TheMovieDbApi.csproj index 16516740e..07b575cc8 100644 --- a/Ombi/Ombi.TheMovieDbApi/Ombi.TheMovieDbApi.csproj +++ b/Ombi/Ombi.TheMovieDbApi/Ombi.TheMovieDbApi.csproj @@ -1,7 +1,7 @@  - netstandard1.4 + netstandard1.6 diff --git a/Ombi/Ombi.TheMovieDbApi/TheMovieDbApi.cs b/Ombi/Ombi.TheMovieDbApi/TheMovieDbApi.cs index 33787f911..76342d915 100644 --- a/Ombi/Ombi.TheMovieDbApi/TheMovieDbApi.cs +++ b/Ombi/Ombi.TheMovieDbApi/TheMovieDbApi.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Net.Http; using System.Threading.Tasks; using Ombi.Api; using Ombi.TheMovieDbApi.Models; @@ -13,63 +12,62 @@ namespace Ombi.TheMovieDbApi Api = new Api.Api(); } private const string ApiToken = "b8eabaf5608b88d0298aa189dd90bf00"; - private static readonly Uri BaseUri = new Uri("http://api.themoviedb.org/3/"); - public Api.Api Api { get; } + private static readonly string BaseUri ="http://api.themoviedb.org/3/"; + private Api.Api Api { get; } public async Task GetMovieInformation(int movieId) { - var url = BaseUri.ChangePath("movie/{0}", movieId.ToString()); - url = AddHeaders(url); - return await Api.Get(url); + var request = new Request($"movie/{movieId}", BaseUri, HttpMethod.Get); + request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken); + + return await Api.Request(request); } public async Task GetMovieInformationWithVideo(int movieId) { - var url = BaseUri.ChangePath("movie/{0}", movieId.ToString()); - url = AddHeaders(url); - url = url.AddQueryParameter("append_to_response", "videos"); - return await Api.Get(url); + var request = new Request($"movie/{movieId}", BaseUri, HttpMethod.Get); + request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken); + request.FullUri = request.FullUri.AddQueryParameter("append_to_response", "videos"); + return await Api.Request(request); } public async Task> SearchMovie(string searchTerm) { - var url = BaseUri.ChangePath("search/movie/"); - url = AddHeaders(url); - url = url.AddQueryParameter("query", searchTerm); - return await Api.Get>(url); + var request = new Request($"search/movie", BaseUri, HttpMethod.Get); + request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken); + request.FullUri = request.FullUri.AddQueryParameter("query", searchTerm); + + return await Api.Request>(request); } public async Task> PopularMovies() { - var url = BaseUri.ChangePath("movie/popular"); - url = AddHeaders(url); - return await Api.Get>(url); + var request = new Request($"movie/popular", BaseUri, HttpMethod.Get); + request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken); + + return await Api.Request>(request); } public async Task> TopRated() { - var url = BaseUri.ChangePath("movie/top_rated"); - url = AddHeaders(url); - return await Api.Get>(url); + var request = new Request($"movie/top_rated", BaseUri, HttpMethod.Get); + request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken); + return await Api.Request>(request); } public async Task> Upcoming() { - var url = BaseUri.ChangePath("movie/upcoming"); - url = AddHeaders(url); - return await Api.Get>(url); + var request = new Request($"movie/upcoming", BaseUri, HttpMethod.Get); + request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken); + return await Api.Request>(request); } public async Task> NowPlaying() { - var url = BaseUri.ChangePath("movie/now_playing"); - url = AddHeaders(url); - return await Api.Get>(url); + var request = new Request($"movie/now_playing", BaseUri, HttpMethod.Get); + request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken); + return await Api.Request>(request); } - private Uri AddHeaders(Uri url) - { - return url.AddQueryParameter("api_key", ApiToken); - } } } diff --git a/Ombi/Ombi/.gitignore b/Ombi/Ombi/.gitignore index ef724f488..6c6136a36 100644 --- a/Ombi/Ombi/.gitignore +++ b/Ombi/Ombi/.gitignore @@ -1,6 +1,5 @@ /wwwroot/css/** /wwwroot/fonts/** -/wwwroot/images/** /wwwroot/lib/** /wwwroot/maps/** /wwwroot/app/**/*.js diff --git a/Ombi/Ombi/Controllers/SettingsController.cs b/Ombi/Ombi/Controllers/SettingsController.cs index db83c15b6..fdd5f4d31 100644 --- a/Ombi/Ombi/Controllers/SettingsController.cs +++ b/Ombi/Ombi/Controllers/SettingsController.cs @@ -1,8 +1,10 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Ombi.Core.Settings; using Ombi.Core.Settings.Models; +using Ombi.Core.Settings.Models.External; namespace Ombi.Controllers { @@ -19,19 +21,51 @@ namespace Ombi.Controllers [HttpGet("ombi")] public async Task OmbiSettings() { - var settings = SettingsResolver.Resolve(); - - return await settings.GetSettingsAsync(); + return await Get(); } [HttpPost("ombi")] public async Task OmbiSettings([FromBody]OmbiSettings ombi) { - var settings = SettingsResolver.Resolve(); + return await Save(ombi); + + } + + [HttpGet("plex")] + public async Task PlexSettings() + { + return await Get(); + } - return await settings.SaveSettingsAsync(ombi); + [HttpPost("plex")] + public async Task PlexSettings([FromBody]PlexSettings plex) + { + return await Save(plex); } + [HttpGet("emby")] + public async Task EmbySettings() + { + return await Get(); + } + [HttpPost("emby")] + public async Task EmbySettings([FromBody]EmbySettings emby) + { + return await Save(emby); + } + + + private async Task Get() + { + var settings = SettingsResolver.Resolve(); + return await settings.GetSettingsAsync(); + } + + private async Task Save(T settingsModel) + { + var settings = SettingsResolver.Resolve(); + return await settings.SaveSettingsAsync(settingsModel); + } } } diff --git a/Ombi/Ombi/Ombi.csproj b/Ombi/Ombi/Ombi.csproj index 784ae0f46..da1c749d6 100644 --- a/Ombi/Ombi/Ombi.csproj +++ b/Ombi/Ombi/Ombi.csproj @@ -16,19 +16,82 @@ - - - - PreserveNewest - + + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + + + PreserveNewest + + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + + + + + + + + diff --git a/Ombi/Ombi/wwwroot/app/app.component.html b/Ombi/Ombi/wwwroot/app/app.component.html index a75b30406..1d28e3abe 100644 --- a/Ombi/Ombi/wwwroot/app/app.component.html +++ b/Ombi/Ombi/wwwroot/app/app.component.html @@ -1,4 +1,4 @@ - +