Merge pull request #2401 from tidusjar/develop

Develop
pull/2420/head
Jamie 6 years ago committed by GitHub
commit e621398dde
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,5 +1,84 @@
# Changelog
## (unreleased)
### **New Features**
- Updated the Emby availability checker to bring it more in line with what we do with Plex. [TidusJar]
- Added the ability to impersonate a user when using the API Key. This allows people to use the API and request as a certain user. #2363. [Jamie Rees]
- Added more background images and it will loop through the available ones. [Jamie Rees]
- Added chunk hashing to resolve #2330. [Jamie Rees]
- Added API at /api/v1/status/info to get branch and version information #2331. [Jamie Rees]
- Update to .net 2.1.1. [Jamie]
### **Fixes**
- Fix #2322 caused by continue statement inside try catch block. [Anojh]
- Fixed #2367. [TidusJar]
- Fixed the issue where you could not delete a user #2365. [TidusJar]
- Another attempt to fix #2366. [Jamie Rees]
- Fixed the Plex OAuth warning. [Jamie]
- Revert "Fixed Plex OAuth, should no longer show Insecure warning" [Jamie Rees]
- Fixed Plex OAuth, should no longer show Insecure warning. [Jamie Rees]
- Fixed the View On Emby URL since the Link changed #2368. [Jamie Rees]
- Fixed the issue where episodes were not being marked as available in the search #2367. [Jamie Rees]
- Fixed #2371. [Jamie Rees]
- Fixed collection issues in Emby #2366. [Jamie Rees]
- Do not delete the Emby Information every time we run, let's keep the content now. [Jamie Rees]
- Emby Improvements: Batch up the amount we get from the server. [Jamie Rees]
- Log errors when they are uncaught. [Jamie Rees]
- Fix unclosed table tags causing overflow #2322. [Anojh]
- This should now fix #2350. [Jamie]
- Improve the validation around the Application URL. [Jamie Rees]
- Fixed #2341. [Jamie Rees]
- Stop spamming errors when FanArt doesn't have the image. [Jamie Rees]
- Fixed #2338. [Jamie Rees]
- Removed some logging statements. [Jamie Rees]
- Fixed the api key being case sensative #2350. [Jamie Rees]
- Improved the Emby API #2230 Thanks Luke! [Jamie Rees]
- Revert. [Jamie Rees]
- Fixed a small error in the Mobile Notification Provider. [Jamie Rees]
- Minor style tweaks. [Randall Bruder]
- Downgrade to .net core 2.0. [Jamie Rees]
- Downgrade Microsoft.AspNetCore.All package back to 2.0.8. [Jamie Rees]
- Removed old code. [Jamie Rees]
- Swap out the old way of validating the API key with a real middlewear this time. [Jamie Rees]
## v3.0.3421 (2018-06-23)
### **New Features**

@ -15,19 +15,19 @@ test: off
after_build:
- cmd: >-
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.0\windows.zip"
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.1\windows.zip"
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.0\osx.tar.gz"
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.1\osx.tar.gz"
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.0\linux.tar.gz"
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.1\linux.tar.gz"
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.0\linux-arm.tar.gz"
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.1\linux-arm.tar.gz"
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.0\windows-32bit.zip"
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.1\windows-32bit.zip"
# appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.0\linux-arm64.tar.gz"

@ -26,7 +26,7 @@ var csProj = "./src/Ombi/Ombi.csproj"; // Path to the project.csproj
var solutionFile = "Ombi.sln"; // Solution file if needed
GitVersion versionInfo = null;
var frameworkVer = "netcoreapp2.0";
var frameworkVer = "netcoreapp2.1";
var buildSettings = new DotNetCoreBuildSettings
{

@ -91,27 +91,31 @@ namespace Ombi.Api.Emby
request.AddContentHeader("Content-Type", "application/json");
}
public async Task<EmbyItemContainer<MovieInformation>> GetCollection(string mediaId, string apiKey, string userId, string baseUrl)
public async Task<EmbyItemContainer<EmbyMovie>> GetCollection(string mediaId, string apiKey, string userId, string baseUrl)
{
var request = new Request($"emby/users/{userId}/items?parentId={mediaId}", baseUrl, HttpMethod.Get);
AddHeaders(request, apiKey);
return await Api.Request<EmbyItemContainer<MovieInformation>>(request);
request.AddQueryString("Fields", "ProviderIds,Overview");
request.AddQueryString("VirtualItem", "False");
return await Api.Request<EmbyItemContainer<EmbyMovie>>(request);
}
public async Task<EmbyItemContainer<EmbyMovie>> GetAllMovies(string apiKey, string userId, string baseUri)
public async Task<EmbyItemContainer<EmbyMovie>> GetAllMovies(string apiKey, int startIndex, int count, string userId, string baseUri)
{
return await GetAll<EmbyMovie>("Movie", apiKey, userId, baseUri, true);
return await GetAll<EmbyMovie>("Movie", apiKey, userId, baseUri, true, startIndex, count);
}
public async Task<EmbyItemContainer<EmbyEpisodes>> GetAllEpisodes(string apiKey, string userId, string baseUri)
public async Task<EmbyItemContainer<EmbyEpisodes>> GetAllEpisodes(string apiKey, int startIndex, int count, string userId, string baseUri)
{
return await GetAll<EmbyEpisodes>("Episode", apiKey, userId, baseUri);
return await GetAll<EmbyEpisodes>("Episode", apiKey, userId, baseUri, false, startIndex, count);
}
public async Task<EmbyItemContainer<EmbySeries>> GetAllShows(string apiKey, string userId, string baseUri)
public async Task<EmbyItemContainer<EmbySeries>> GetAllShows(string apiKey, int startIndex, int count, string userId, string baseUri)
{
return await GetAll<EmbySeries>("Series", apiKey, userId, baseUri);
return await GetAll<EmbySeries>("Series", apiKey, userId, baseUri, false, startIndex, count);
}
public async Task<SeriesInformation> GetSeriesInformation(string mediaId, string apiKey, string userId, string baseUrl)
@ -145,7 +149,25 @@ namespace Ombi.Api.Emby
request.AddQueryString("IncludeItemTypes", type);
request.AddQueryString("Fields", includeOverview ? "ProviderIds,Overview" : "ProviderIds");
request.AddQueryString("VirtualItem","False");
request.AddQueryString("VirtualItem", "False");
AddHeaders(request, apiKey);
var obj = await Api.Request<EmbyItemContainer<T>>(request);
return obj;
}
private async Task<EmbyItemContainer<T>> GetAll<T>(string type, string apiKey, string userId, string baseUri, bool includeOverview, int startIndex, int count)
{
var request = new Request($"emby/users/{userId}/items", baseUri, HttpMethod.Get);
request.AddQueryString("Recursive", true.ToString());
request.AddQueryString("IncludeItemTypes", type);
request.AddQueryString("Fields", includeOverview ? "ProviderIds,Overview" : "ProviderIds");
request.AddQueryString("startIndex", startIndex.ToString());
request.AddQueryString("limit", count.ToString());
request.AddQueryString("VirtualItem", "False");
AddHeaders(request, apiKey);

@ -14,12 +14,17 @@ namespace Ombi.Api.Emby
Task<EmbyUser> LogIn(string username, string password, string apiKey, string baseUri);
Task<EmbyConnectUser> LoginConnectUser(string username, string password);
Task<EmbyItemContainer<EmbyMovie>> GetAllMovies(string apiKey, string userId, string baseUri);
Task<EmbyItemContainer<EmbyEpisodes>> GetAllEpisodes(string apiKey, string userId, string baseUri);
Task<EmbyItemContainer<EmbySeries>> GetAllShows(string apiKey, string userId, string baseUri);
Task<EmbyItemContainer<EmbyMovie>> GetAllMovies(string apiKey, int startIndex, int count, string userId,
string baseUri);
Task<EmbyItemContainer<MovieInformation>> GetCollection(string mediaId, string apiKey, string userId,
string baseUrl);
Task<EmbyItemContainer<EmbyEpisodes>> GetAllEpisodes(string apiKey, int startIndex, int count, string userId,
string baseUri);
Task<EmbyItemContainer<EmbySeries>> GetAllShows(string apiKey, int startIndex, int count, string userId,
string baseUri);
Task<EmbyItemContainer<EmbyMovie>> GetCollection(string mediaId,
string apiKey, string userId, string baseUrl);
Task<SeriesInformation> GetSeriesInformation(string mediaId, string apiKey, string userId, string baseUrl);
Task<MovieInformation> GetMovieInformation(string mediaId, string apiKey, string userId, string baseUrl);

@ -22,8 +22,7 @@ namespace Ombi.Api.Plex
Task<PlexFriends> GetUsers(string authToken);
Task<PlexAccount> GetAccount(string authToken);
Task<PlexMetadata> GetRecentlyAdded(string authToken, string uri, string sectionId);
Task<OAuthPin> CreatePin();
Task<OAuthPin> GetPin(int pinId);
Uri GetOAuthUrl(int pinId, string code, string applicationUrl, bool wizard);
Task<Uri> GetOAuthUrl(int pinId, string code, string applicationUrl, bool wizard);
}
}

@ -16,14 +16,16 @@ namespace Ombi.Api.Plex
{
public class PlexApi : IPlexApi
{
public PlexApi(IApi api, ISettingsService<CustomizationSettings> settings)
public PlexApi(IApi api, ISettingsService<CustomizationSettings> settings, ISettingsService<PlexSettings> p)
{
Api = api;
_custom = settings;
_plexSettings = p;
}
private IApi Api { get; }
private readonly ISettingsService<CustomizationSettings> _custom;
private readonly ISettingsService<PlexSettings> _plexSettings;
private string _app;
private string ApplicationName
@ -69,7 +71,7 @@ namespace Ombi.Api.Plex
};
var request = new Request(SignInUri, string.Empty, HttpMethod.Post);
AddHeaders(request);
await AddHeaders(request);
request.AddJsonBody(userModel);
var obj = await Api.Request<PlexAuthentication>(request);
@ -80,14 +82,14 @@ namespace Ombi.Api.Plex
public async Task<PlexStatus> GetStatus(string authToken, string uri)
{
var request = new Request(uri, string.Empty, HttpMethod.Get);
AddHeaders(request, authToken);
await AddHeaders(request, authToken);
return await Api.Request<PlexStatus>(request);
}
public async Task<PlexAccount> GetAccount(string authToken)
{
var request = new Request(GetAccountUri, string.Empty, HttpMethod.Get);
AddHeaders(request, authToken);
await AddHeaders(request, authToken);
return await Api.Request<PlexAccount>(request);
}
@ -95,7 +97,7 @@ namespace Ombi.Api.Plex
{
var request = new Request(ServerUri, string.Empty, HttpMethod.Get, ContentType.Xml);
AddHeaders(request, authToken);
await AddHeaders(request, authToken);
return await Api.Request<PlexServer>(request);
}
@ -103,14 +105,14 @@ namespace Ombi.Api.Plex
public async Task<PlexContainer> GetLibrarySections(string authToken, string plexFullHost)
{
var request = new Request("library/sections", plexFullHost, HttpMethod.Get);
AddHeaders(request, authToken);
await AddHeaders(request, authToken);
return await Api.Request<PlexContainer>(request);
}
public async Task<PlexContainer> GetLibrary(string authToken, string plexFullHost, string libraryId)
{
var request = new Request($"library/sections/{libraryId}/all", plexFullHost, HttpMethod.Get);
AddHeaders(request, authToken);
await AddHeaders(request, authToken);
return await Api.Request<PlexContainer>(request);
}
@ -128,21 +130,21 @@ namespace Ombi.Api.Plex
public async Task<PlexMetadata> GetEpisodeMetaData(string authToken, string plexFullHost, int ratingKey)
{
var request = new Request($"/library/metadata/{ratingKey}", plexFullHost, HttpMethod.Get);
AddHeaders(request, authToken);
await AddHeaders(request, authToken);
return await Api.Request<PlexMetadata>(request);
}
public async Task<PlexMetadata> GetMetadata(string authToken, string plexFullHost, int itemId)
{
var request = new Request($"library/metadata/{itemId}", plexFullHost, HttpMethod.Get);
AddHeaders(request, authToken);
await AddHeaders(request, authToken);
return await Api.Request<PlexMetadata>(request);
}
public async Task<PlexMetadata> GetSeasons(string authToken, string plexFullHost, int ratingKey)
{
var request = new Request($"library/metadata/{ratingKey}/children", plexFullHost, HttpMethod.Get);
AddHeaders(request, authToken);
await AddHeaders(request, authToken);
return await Api.Request<PlexMetadata>(request);
}
@ -161,9 +163,9 @@ namespace Ombi.Api.Plex
request.AddQueryString("type", "4");
AddLimitHeaders(request, start, retCount);
AddHeaders(request, authToken);
await AddHeaders(request, authToken);
return await Api.Request<PlexContainer>(request);
return await Api.Request<PlexContainer>(request);
}
/// <summary>
@ -174,8 +176,8 @@ namespace Ombi.Api.Plex
/// <returns></returns>
public async Task<PlexFriends> GetUsers(string authToken)
{
var request = new Request(string.Empty,FriendsUri, HttpMethod.Get, ContentType.Xml);
AddHeaders(request, authToken);
var request = new Request(string.Empty, FriendsUri, HttpMethod.Get, ContentType.Xml);
await AddHeaders(request, authToken);
return await Api.Request<PlexFriends>(request);
}
@ -183,43 +185,40 @@ namespace Ombi.Api.Plex
public async Task<PlexMetadata> GetRecentlyAdded(string authToken, string uri, string sectionId)
{
var request = new Request($"library/sections/{sectionId}/recentlyAdded", uri, HttpMethod.Get);
AddHeaders(request, authToken);
await AddHeaders(request, authToken);
AddLimitHeaders(request, 0, 50);
return await Api.Request<PlexMetadata>(request);
}
public async Task<OAuthPin> CreatePin()
{
var request = new Request($"api/v2/pins", "https://plex.tv/", HttpMethod.Post);
request.AddQueryString("strong", "true");
AddHeaders(request);
return await Api.Request<OAuthPin>(request);
}
public async Task<OAuthPin> GetPin(int pinId)
{
var request = new Request($"api/v2/pins/{pinId}", "https://plex.tv/", HttpMethod.Get);
AddHeaders(request);
await AddHeaders(request);
return await Api.Request<OAuthPin>(request);
}
public Uri GetOAuthUrl(int pinId, string code, string applicationUrl, bool wizard)
public async Task<Uri> GetOAuthUrl(int pinId, string code, string applicationUrl, bool wizard)
{
var request = new Request("auth#", "https://app.plex.tv", HttpMethod.Get);
AddHeaders(request);
var forwardUrl = wizard
? new Request($"Wizard/OAuth/{pinId}", applicationUrl, HttpMethod.Get)
await AddHeaders(request);
var forwardUrl = wizard
? new Request($"Wizard/OAuth/{pinId}", applicationUrl, HttpMethod.Get)
: new Request($"Login/OAuth/{pinId}", applicationUrl, HttpMethod.Get);
request.AddQueryString("forwardUrl", forwardUrl.FullUri.ToString());
request.AddQueryString("pinID", pinId.ToString());
request.AddQueryString("code", code);
request.AddQueryString("context[device][product]", "Ombi");
request.AddQueryString("context[device][product]", ApplicationName);
request.AddQueryString("context[device][environment]", "bundled");
request.AddQueryString("clientID", $"OmbiV3");
request.AddQueryString("context[device][layout]", "desktop");
request.AddQueryString("context[device][platform]", "Web");
request.AddQueryString("context[device][device]", "Ombi (Web)");
var s = await GetSettings();
await CheckInstallId(s);
request.AddQueryString("clientID", s.InstallId.ToString("N"));
if (request.FullUri.Fragment.Equals("#"))
{
@ -238,21 +237,25 @@ namespace Ombi.Api.Plex
/// </summary>
/// <param name="request"></param>
/// <param name="authToken"></param>
private void AddHeaders(Request request, string authToken)
private async Task AddHeaders(Request request, string authToken)
{
request.AddHeader("X-Plex-Token", authToken);
AddHeaders(request);
await AddHeaders(request);
}
/// <summary>
/// Adds the main required headers to the Plex Request
/// </summary>
/// <param name="request"></param>
private void AddHeaders(Request request)
private async Task AddHeaders(Request request)
{
request.AddHeader("X-Plex-Client-Identifier", $"OmbiV3");
var s = await GetSettings();
await CheckInstallId(s);
request.AddHeader("X-Plex-Client-Identifier", s.InstallId.ToString("N"));
request.AddHeader("X-Plex-Product", ApplicationName);
request.AddHeader("X-Plex-Version", "3");
request.AddHeader("X-Plex-Device", "Ombi (Web)");
request.AddHeader("X-Plex-Platform", "Web");
request.AddContentHeader("Content-Type", request.ContentType == ContentType.Json ? "application/json" : "application/xml");
request.AddHeader("Accept", "application/json");
}
@ -262,5 +265,19 @@ namespace Ombi.Api.Plex
request.AddHeader("X-Plex-Container-Start", from.ToString());
request.AddHeader("X-Plex-Container-Size", to.ToString());
}
private async Task CheckInstallId(PlexSettings s)
{
if (s.InstallId == null || s.InstallId == Guid.Empty)
{
s.InstallId = Guid.NewGuid();
await _plexSettings.SaveSettingsAsync(s);
}
}
private PlexSettings _settings;
private async Task<PlexSettings> GetSettings()
{
return _settings ?? (_settings = await _plexSettings.GetSettingsAsync());
}
}
}

@ -9,7 +9,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.1.1" />
</ItemGroup>
<ItemGroup>

@ -11,7 +11,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Options" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Options" Version="2.1.1" />
</ItemGroup>
<ItemGroup>

@ -9,9 +9,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.1.1" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="Polly" Version="5.8.0" />
<PackageReference Include="Polly" Version="6.1.0" />
<PackageReference Include="System.Xml.XmlSerializer" Version="4.3.0" />
</ItemGroup>

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
@ -9,7 +9,7 @@
<PackageReference Include="Nunit" Version="3.8.1" />
<PackageReference Include="NUnit.ConsoleRunner" Version="3.7.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.8.0" />
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.7.2"></packagereference>
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.8.0"></packagereference>
</ItemGroup>
<ItemGroup>

@ -20,12 +20,6 @@ namespace Ombi.Core.Authentication
private readonly IPlexApi _api;
private readonly ISettingsService<CustomizationSettings> _customizationSettingsService;
public async Task<OAuthPin> RequestPin()
{
var pin = await _api.CreatePin();
return pin;
}
public async Task<string> GetAccessTokenFromPin(int pinId)
{
var pin = await _api.GetPin(pinId);
@ -58,14 +52,14 @@ namespace Ombi.Core.Authentication
public async Task<Uri> GetOAuthUrl(int pinId, string code, string websiteAddress = null)
{
var settings = await _customizationSettingsService.GetSettingsAsync();
var url = _api.GetOAuthUrl(pinId, code, settings.ApplicationUrl.IsNullOrEmpty() ? websiteAddress : settings.ApplicationUrl, false);
var url = await _api.GetOAuthUrl(pinId, code, settings.ApplicationUrl.IsNullOrEmpty() ? websiteAddress : settings.ApplicationUrl, false);
return url;
}
public Uri GetWizardOAuthUrl(int pinId, string code, string websiteAddress)
public async Task<Uri> GetWizardOAuthUrl(int pinId, string code, string websiteAddress)
{
var url = _api.GetOAuthUrl(pinId, code, websiteAddress, true);
var url = await _api.GetOAuthUrl(pinId, code, websiteAddress, true);
return url;
}
}

@ -1,16 +1,14 @@
using System;
using System.Threading.Tasks;
using Ombi.Api.Plex.Models;
using Ombi.Api.Plex.Models.OAuth;
namespace Ombi.Core.Authentication
{
public interface IPlexOAuthManager
{
Task<string> GetAccessTokenFromPin(int pinId);
Task<OAuthPin> RequestPin();
Task<Uri> GetOAuthUrl(int pinId, string code, string websiteAddress = null);
Uri GetWizardOAuthUrl(int pinId, string code, string websiteAddress);
Task<Uri> GetWizardOAuthUrl(int pinId, string code, string websiteAddress);
Task<PlexAccount> GetAccount(string accessToken);
}
}

@ -12,9 +12,9 @@
<PackageReference Include="AutoMapper" Version="6.1.1" />
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="3.2.0" />
<PackageReference Include="Hangfire" Version="1.6.19" />
<PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="2.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Design" Version="1.1.5" />
<PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="2.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Design" Version="2.0.0-preview1-final" />
<PackageReference Include="MiniProfiler.AspNetCore" Version="4.0.0-alpha6-79" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="System.Diagnostics.Process" Version="4.3.0" />

@ -1,4 +1,6 @@
using System.Linq;
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Ombi.Core.Models.Search;
@ -59,10 +61,19 @@ namespace Ombi.Core.Rule.Rules.Search
{
EmbyEpisode epExists = null;
epExists = await allEpisodes.FirstOrDefaultAsync(x =>
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
x.Series.ProviderId == item.ProviderId.ToString());
if (item.HasImdb)
{
epExists = await allEpisodes.FirstOrDefaultAsync(e => e.EpisodeNumber == episode.EpisodeNumber && e.SeasonNumber == season.SeasonNumber
&& e.ImdbId == item.ImdbId);
} if (item.HasTvDb && epExists == null)
{
epExists = await allEpisodes.FirstOrDefaultAsync(e => e.EpisodeNumber == episode.EpisodeNumber && e.SeasonNumber == season.SeasonNumber
&& e.Series.TvDbId == item.TvDbId);
} if (item.HasTheMovieDb && epExists == null)
{
epExists = await allEpisodes.FirstOrDefaultAsync(e => e.EpisodeNumber == episode.EpisodeNumber && e.SeasonNumber == season.SeasonNumber
&& e.TheMovieDbId == item.TheMovieDbId);
}
if (epExists != null)
{

@ -255,7 +255,12 @@ namespace Ombi.Core.Senders
{
// We have the same amount of requests as all of the episodes in the season.
var existingSeason =
result.seasons.First(x => x.seasonNumber == season.SeasonNumber);
result.seasons.FirstOrDefault(x => x.seasonNumber == season.SeasonNumber);
if (existingSeason == null)
{
Logger.LogError("The sonarr ep count was the same as out request count, but could not match the season number {0}", season.SeasonNumber);
continue;
}
existingSeason.monitored = true;
seriesChanges = true;
}

@ -9,9 +9,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="2.0.4" />
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="2.0.4" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="2.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.1.1" />
</ItemGroup>
<ItemGroup>

@ -10,7 +10,7 @@ namespace Ombi.Helpers
public static string GetEmbyMediaUrl(string mediaId)
{
var url =
$"http://app.emby.media/itemdetails.html?id={mediaId}";
$"http://app.emby.media/#!/itemdetails.html?id={mediaId}";
return url;
}
}

@ -10,8 +10,8 @@
<ItemGroup>
<PackageReference Include="EasyCrypto" Version="3.3.2" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.1.1" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="Nito.AsyncEx" Version="5.0.0-pre-05" />
<PackageReference Include="System.Security.Claims" Version="4.3.0" />

@ -1,14 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Nunit" Version="3.8.1" />
<PackageReference Include="NUnit.ConsoleRunner" Version="3.7.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.8.0" />
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.7.2"></packagereference>
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.8.0"></packagereference>
<PackageReference Include="Moq" Version="4.7.99" />
</ItemGroup>

@ -10,7 +10,7 @@
<ItemGroup>
<PackageReference Include="Ensure.That" Version="7.0.0-pre32" />
<PackageReference Include="MailKit" Version="2.0.3" />
<PackageReference Include="MailKit" Version="2.0.5" />
</ItemGroup>
<ItemGroup>

@ -1,16 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore" Version="2.0.3" />
<PackageReference Include="Microsoft.AspNetCore" Version="2.1.2" />
<PackageReference Include="Moq" Version="4.7.99" />
<PackageReference Include="Nunit" Version="3.10.1" />
<PackageReference Include="NUnit.ConsoleRunner" Version="3.8.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.10.0" />
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.7.0"></packagereference>
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.8.0"></packagereference>
</ItemGroup>
<ItemGroup>

@ -121,24 +121,53 @@ namespace Ombi.Schedule.Jobs.Emby
foreach (var child in tv)
{
IQueryable<EmbyEpisode> seriesEpisodes;
if (child.ParentRequest.TvDbId > 0)
var useImdb = false;
var useTvDb = false;
if (child.ParentRequest.ImdbId.HasValue())
{
useImdb = true;
}
if (child.ParentRequest.TvDbId.ToString().HasValue())
{
seriesEpisodes = embyEpisodes.Where(x => x.Series.TvDbId == child.ParentRequest.TvDbId.ToString());
useTvDb = true;
}
else if(child.ParentRequest.ImdbId.HasValue())
var tvDbId = child.ParentRequest.TvDbId;
var imdbId = child.ParentRequest.ImdbId;
IQueryable<EmbyEpisode> seriesEpisodes = null;
if (useImdb)
{
seriesEpisodes = embyEpisodes.Where(x => x.Series.ImdbId == child.ParentRequest.ImdbId);
seriesEpisodes = embyEpisodes.Where(x => x.Series.ImdbId == imdbId.ToString());
}
else
if (useTvDb && (seriesEpisodes == null || !seriesEpisodes.Any()))
{
seriesEpisodes = embyEpisodes.Where(x => x.Series.TvDbId == tvDbId.ToString());
}
if (seriesEpisodes == null)
{
continue;
}
if (!seriesEpisodes.Any())
{
// Let's try and match the series by name
seriesEpisodes = embyEpisodes.Where(x =>
x.Series.Title.Equals(child.Title, StringComparison.CurrentCultureIgnoreCase));
}
foreach (var season in child.SeasonRequests)
{
foreach (var episode in season.Episodes)
{
if (episode.Available)
{
continue;
}
var foundEp = await seriesEpisodes.FirstOrDefaultAsync(
x => x.EpisodeNumber == episode.EpisodeNumber &&
x.SeasonNumber == episode.Season.SeasonNumber);
@ -160,9 +189,9 @@ namespace Ombi.Schedule.Jobs.Emby
{
DateTime = DateTime.Now,
NotificationType = NotificationType.RequestAvailable,
RequestId = child.ParentRequestId,
RequestId = child.Id,
RequestType = RequestType.TvShow,
Recipient = child.RequestedUser.Email,
Recipient = child.RequestedUser.Email
}));
}
}

@ -68,40 +68,96 @@ namespace Ombi.Schedule.Jobs.Emby
if (!ValidateSettings(server))
return;
await _repo.ExecuteSql("DELETE FROM EmbyEpisode");
await _repo.ExecuteSql("DELETE FROM EmbyContent");
//await _repo.ExecuteSql("DELETE FROM EmbyEpisode");
//await _repo.ExecuteSql("DELETE FROM EmbyContent");
var movies = await _api.GetAllMovies(server.ApiKey, 0, 200, server.AdministratorId, server.FullUri);
var totalCount = movies.TotalRecordCount;
var processed = 1;
var movies = await _api.GetAllMovies(server.ApiKey, server.AdministratorId, server.FullUri);
var mediaToAdd = new HashSet<EmbyContent>();
foreach (var movie in movies.Items)
while (processed < totalCount)
{
// Regular movie
await ProcessMovies(movie, mediaToAdd);
foreach (var movie in movies.Items)
{
if (movie.Type.Equals("boxset", StringComparison.InvariantCultureIgnoreCase))
{
var movieInfo =
await _api.GetCollection(movie.Id, server.ApiKey, server.AdministratorId, server.FullUri);
foreach (var item in movieInfo.Items)
{
await ProcessMovies(item, mediaToAdd);
}
processed++;
}
else
{
processed++;
// Regular movie
await ProcessMovies(movie, mediaToAdd);
}
}
// Get the next batch
movies = await _api.GetAllMovies(server.ApiKey, processed, 200, server.AdministratorId, server.FullUri);
await _repo.AddRange(mediaToAdd);
mediaToAdd.Clear();
}
// TV Time
var tv = await _api.GetAllShows(server.ApiKey, server.AdministratorId, server.FullUri);
foreach (var tvShow in tv.Items)
{
if (string.IsNullOrEmpty(tvShow.ProviderIds?.Tvdb))
// TV Time
var tv = await _api.GetAllShows(server.ApiKey, 0, 200, server.AdministratorId, server.FullUri);
var totalTv = tv.TotalRecordCount;
processed = 1;
while (processed < totalTv)
{
foreach (var tvShow in tv.Items)
{
Log.Error("Provider Id on tv {0} is null", tvShow.Name);
continue;
}
try
{
var existingTv = await _repo.GetByEmbyId(tvShow.Id);
if (existingTv == null)
mediaToAdd.Add(new EmbyContent
processed++;
if (string.IsNullOrEmpty(tvShow.ProviderIds?.Tvdb))
{
_logger.LogInformation("Provider Id on tv {0} is null", tvShow.Name);
continue;
}
var existingTv = await _repo.GetByEmbyId(tvShow.Id);
if (existingTv == null)
{
_logger.LogDebug("Adding new TV Show {0}", tvShow.Name);
mediaToAdd.Add(new EmbyContent
{
TvDbId = tvShow.ProviderIds?.Tvdb,
ImdbId = tvShow.ProviderIds?.Imdb,
TheMovieDbId = tvShow.ProviderIds?.Tmdb,
Title = tvShow.Name,
Type = EmbyMediaType.Series,
EmbyId = tvShow.Id,
Url = EmbyHelper.GetEmbyMediaUrl(tvShow.Id),
AddedAt = DateTime.UtcNow
});
}
else
{
_logger.LogDebug("We already have TV Show {0}", tvShow.Name);
}
}
catch (Exception)
{
TvDbId = tvShow.ProviderIds?.Tvdb,
ImdbId = tvShow.ProviderIds?.Imdb,
TheMovieDbId = tvShow.ProviderIds?.Tmdb,
Title = tvShow.Name,
Type = EmbyMediaType.Series,
EmbyId = tvShow.Id,
Url = EmbyHelper.GetEmbyMediaUrl(tvShow.Id),
AddedAt = DateTime.UtcNow
});
throw;
}
}
// Get the next batch
tv = await _api.GetAllShows(server.ApiKey, processed, 200, server.AdministratorId, server.FullUri);
await _repo.AddRange(mediaToAdd);
mediaToAdd.Clear();
}
if (mediaToAdd.Any())
@ -112,8 +168,10 @@ namespace Ombi.Schedule.Jobs.Emby
{
// Check if it exists
var existingMovie = await _repo.GetByEmbyId(movieInfo.Id);
if (existingMovie == null)
var alreadyGoingToAdd = content.Any(x => x.EmbyId == movieInfo.Id);
if (existingMovie == null && !alreadyGoingToAdd)
{
_logger.LogDebug("Adding new movie {0}", movieInfo.Name);
content.Add(new EmbyContent
{
ImdbId = movieInfo.ProviderIds.Imdb,
@ -124,6 +182,12 @@ namespace Ombi.Schedule.Jobs.Emby
Url = EmbyHelper.GetEmbyMediaUrl(movieInfo.Id),
AddedAt = DateTime.UtcNow,
});
}
else
{
// we have this
_logger.LogDebug("We already have movie {0}", movieInfo.Name);
}
}
private bool ValidateSettings(EmbyServers server)

@ -73,37 +73,51 @@ namespace Ombi.Schedule.Jobs.Emby
private async Task CacheEpisodes(EmbyServers server)
{
var allEpisodes = await _api.GetAllEpisodes(server.ApiKey, server.AdministratorId, server.FullUri);
var epToAdd = new List<EmbyEpisode>();
foreach (var ep in allEpisodes.Items)
var allEpisodes = await _api.GetAllEpisodes(server.ApiKey, 0, 200, server.AdministratorId, server.FullUri);
var total = allEpisodes.TotalRecordCount;
var processed = 1;
var epToAdd = new HashSet<EmbyEpisode>();
while (processed < total)
{
// Let's make sure we have the parent request, stop those pesky forign key errors,
// Damn me having data integrity
var parent = await _repo.GetByEmbyId(ep.SeriesId);
if (parent == null)
foreach (var ep in allEpisodes.Items)
{
_logger.LogInformation("The episode {0} does not relate to a series, so we cannot save this", ep.Name);
continue;
}
processed++;
// Let's make sure we have the parent request, stop those pesky forign key errors,
// Damn me having data integrity
var parent = await _repo.GetByEmbyId(ep.SeriesId);
if (parent == null)
{
_logger.LogInformation("The episode {0} does not relate to a series, so we cannot save this",
ep.Name);
continue;
}
var existingEpisode = await _repo.GetEpisodeByEmbyId(ep.Id);
if (existingEpisode == null)
{
// add it
epToAdd.Add(new EmbyEpisode
var existingEpisode = await _repo.GetEpisodeByEmbyId(ep.Id);
// Make sure it's not in the hashset too
var existingInList = epToAdd.Any(x => x.EmbyId == ep.Id);
if (existingEpisode == null && !existingInList)
{
EmbyId = ep.Id,
EpisodeNumber = ep.IndexNumber,
SeasonNumber = ep.ParentIndexNumber,
ParentId = ep.SeriesId,
TvDbId = ep.ProviderIds.Tvdb,
TheMovieDbId = ep.ProviderIds.Tmdb,
ImdbId = ep.ProviderIds.Imdb,
Title = ep.Name,
AddedAt = DateTime.UtcNow
});
_logger.LogDebug("Adding new episode {0} to parent {1}", ep.Name, ep.SeriesName);
// add it
epToAdd.Add(new EmbyEpisode
{
EmbyId = ep.Id,
EpisodeNumber = ep.IndexNumber,
SeasonNumber = ep.ParentIndexNumber,
ParentId = ep.SeriesId,
TvDbId = ep.ProviderIds.Tvdb,
TheMovieDbId = ep.ProviderIds.Tmdb,
ImdbId = ep.ProviderIds.Imdb,
Title = ep.Name,
AddedAt = DateTime.UtcNow
});
}
}
await _repo.AddRange(epToAdd);
epToAdd.Clear();
allEpisodes = await _api.GetAllEpisodes(server.ApiKey, processed, 200, server.AdministratorId, server.FullUri);
}
if (epToAdd.Any())

@ -492,41 +492,42 @@ namespace Ombi.Schedule.Jobs.Ombi
var orderedTv = series.OrderByDescending(x => x.AddedAt);
foreach (var t in orderedTv)
{
try
if (!t.HasTvDb)
{
if (!t.HasTvDb)
// We may need to use themoviedb for the imdbid or their own id to get info
if (t.HasTheMovieDb)
{
// We may need to use themoviedb for the imdbid or their own id to get info
if (t.HasTheMovieDb)
int.TryParse(t.TheMovieDbId, out var movieId);
var externals = await _movieApi.GetTvExternals(movieId);
if (externals == null || externals.tvdb_id <= 0)
{
int.TryParse(t.TheMovieDbId, out var movieId);
var externals = await _movieApi.GetTvExternals(movieId);
if (externals == null || externals.tvdb_id <= 0)
{
continue;
}
t.TvDbId = externals.tvdb_id.ToString();
continue;
}
// WE could check the below but we need to get the moviedb and then perform the above, let the metadata job figure this out.
//else if(t.HasImdb)
//{
// // Check the imdbid
// var externals = await _movieApi.Find(t.ImdbId, ExternalSource.imdb_id);
// if (externals?.tv_results == null || externals.tv_results.Length <= 0)
// {
// continue;
// }
// t.TvDbId = externals.tv_results.FirstOrDefault()..ToString();
//}
t.TvDbId = externals.tvdb_id.ToString();
}
// WE could check the below but we need to get the moviedb and then perform the above, let the metadata job figure this out.
//else if(t.HasImdb)
//{
// // Check the imdbid
// var externals = await _movieApi.Find(t.ImdbId, ExternalSource.imdb_id);
// if (externals?.tv_results == null || externals.tv_results.Length <= 0)
// {
// continue;
// }
// t.TvDbId = externals.tv_results.FirstOrDefault()..ToString();
//}
int.TryParse(t.TvDbId, out var tvdbId);
var info = await _tvApi.ShowLookupByTheTvDbId(tvdbId);
if (info == null)
{
continue;
}
}
int.TryParse(t.TvDbId, out var tvdbId);
var info = await _tvApi.ShowLookupByTheTvDbId(tvdbId);
if (info == null)
{
continue;
}
try
{
var banner = info.image?.original;
if (!string.IsNullOrEmpty(banner))
{
@ -588,7 +589,7 @@ namespace Ombi.Schedule.Jobs.Ombi
{
AddGenres(sb, $"Genres: {string.Join(", ", info.genres.Select(x => x.ToString()).ToArray())}");
}
}
catch (Exception e)
{
@ -676,20 +677,20 @@ namespace Ombi.Schedule.Jobs.Ombi
var orderedTv = series.OrderByDescending(x => x.AddedAt);
foreach (var t in orderedTv)
{
try
if (!t.TvDbId.HasValue())
{
if (!t.TvDbId.HasValue())
{
continue;
}
continue;
}
int.TryParse(t.TvDbId, out var tvdbId);
var info = await _tvApi.ShowLookupByTheTvDbId(tvdbId);
if (info == null)
{
continue;
}
int.TryParse(t.TvDbId, out var tvdbId);
var info = await _tvApi.ShowLookupByTheTvDbId(tvdbId);
if (info == null)
{
continue;
}
try
{
var banner = info.image?.original;
if (!string.IsNullOrEmpty(banner))
{
@ -752,7 +753,7 @@ namespace Ombi.Schedule.Jobs.Ombi
{
AddGenres(sb, $"Genres: {string.Join(", ", info.genres.Select(x => x.ToString()).ToArray())}");
}
}
catch (Exception e)
{

@ -79,11 +79,16 @@ namespace Ombi.Schedule.Jobs.Plex
{
seriesEpisodes = plexEpisodes.Where(x => x.Series.ImdbId == imdbId.ToString());
}
if (useTvDb)
if (useTvDb && (seriesEpisodes == null || !seriesEpisodes.Any()) )
{
seriesEpisodes = plexEpisodes.Where(x => x.Series.TvDbId == tvDbId.ToString());
}
if (seriesEpisodes == null)
{
continue;
}
if (!seriesEpisodes.Any())
{
// Let's try and match the series by name

@ -9,7 +9,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.1.1" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
</ItemGroup>

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using Ombi.Settings.Settings.Models.External;
namespace Ombi.Core.Settings.Models.External
@ -6,6 +7,10 @@ namespace Ombi.Core.Settings.Models.External
public sealed class PlexSettings : Ombi.Settings.Settings.Models.Settings
{
public bool Enable { get; set; }
/// <summary>
/// This is the ClientId for OAuth
/// </summary>
public Guid InstallId { get; set; }
public List<PlexServers> Servers { get; set; }
}

@ -183,7 +183,7 @@ namespace Ombi.Store.Context
notificationToAdd = new NotificationTemplates
{
NotificationType = notificationType,
Message = "Hello! You {Title} on {ApplicationName}! This is now available! :)",
Message = "Hello! Your request for {Title} on {ApplicationName}! This is now available! :)",
Subject = "{ApplicationName}: {Title} is now available!",
Agent = agent,
Enabled = true,

@ -0,0 +1,981 @@
// <auto-generated />
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Storage.Internal;
using Ombi.Helpers;
using Ombi.Store.Context;
using Ombi.Store.Entities;
using Ombi.Store.Entities.Requests;
using System;
namespace Ombi.Store.Migrations
{
[DbContext(typeof(OmbiContext))]
[Migration("20180703200952_EmbyUrlFix")]
partial class EmbyUrlFix
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.0.3-rtm-10026");
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Name")
.HasMaxLength(256);
b.Property<string>("NormalizedName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasName("RoleNameIndex");
b.ToTable("AspNetRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("RoleId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider");
b.Property<string>("ProviderKey");
b.Property<string>("ProviderDisplayName");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("RoleId");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("LoginProvider");
b.Property<string>("Name");
b.Property<string>("Value");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens");
});
modelBuilder.Entity("Ombi.Store.Entities.ApplicationConfiguration", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("Type");
b.Property<string>("Value");
b.HasKey("Id");
b.ToTable("ApplicationConfiguration");
});
modelBuilder.Entity("Ombi.Store.Entities.Audit", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("AuditArea");
b.Property<int>("AuditType");
b.Property<DateTime>("DateTime");
b.Property<string>("Description");
b.Property<string>("User");
b.HasKey("Id");
b.ToTable("Audit");
});
modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("TheMovieDbId");
b.HasKey("Id");
b.ToTable("CouchPotatoCache");
});
modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<string>("EmbyId")
.IsRequired();
b.Property<string>("ImdbId");
b.Property<string>("ProviderId");
b.Property<string>("TheMovieDbId");
b.Property<string>("Title");
b.Property<string>("TvDbId");
b.Property<int>("Type");
b.Property<string>("Url");
b.HasKey("Id");
b.ToTable("EmbyContent");
});
modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<string>("EmbyId");
b.Property<int>("EpisodeNumber");
b.Property<string>("ImdbId");
b.Property<string>("ParentId");
b.Property<string>("ProviderId");
b.Property<int>("SeasonNumber");
b.Property<string>("TheMovieDbId");
b.Property<string>("Title");
b.Property<string>("TvDbId");
b.HasKey("Id");
b.HasIndex("ParentId");
b.ToTable("EmbyEpisode");
});
modelBuilder.Entity("Ombi.Store.Entities.GlobalSettings", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Content");
b.Property<string>("SettingsName");
b.HasKey("Id");
b.ToTable("GlobalSettings");
});
modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("Agent");
b.Property<bool>("Enabled");
b.Property<string>("Message");
b.Property<int>("NotificationType");
b.Property<string>("Subject");
b.HasKey("Id");
b.ToTable("NotificationTemplates");
});
modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<string>("PlayerId");
b.Property<string>("UserId");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("NotificationUserId");
});
modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("AccessFailedCount");
b.Property<string>("Alias");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Email")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed");
b.Property<string>("EmbyConnectUserId");
b.Property<int?>("EpisodeRequestLimit");
b.Property<DateTime?>("LastLoggedIn");
b.Property<bool>("LockoutEnabled");
b.Property<DateTimeOffset?>("LockoutEnd");
b.Property<int?>("MovieRequestLimit");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256);
b.Property<string>("NormalizedUserName")
.HasMaxLength(256);
b.Property<string>("PasswordHash");
b.Property<string>("PhoneNumber");
b.Property<bool>("PhoneNumberConfirmed");
b.Property<string>("ProviderUserId");
b.Property<string>("SecurityStamp");
b.Property<bool>("TwoFactorEnabled");
b.Property<string>("UserAccessToken");
b.Property<string>("UserName")
.HasMaxLength(256);
b.Property<int>("UserType");
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasName("UserNameIndex");
b.ToTable("AspNetUsers");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("EpisodeNumber");
b.Property<int>("GrandparentKey");
b.Property<int>("Key");
b.Property<int>("ParentKey");
b.Property<int>("SeasonNumber");
b.Property<string>("Title");
b.HasKey("Id");
b.HasIndex("GrandparentKey");
b.ToTable("PlexEpisode");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("ParentKey");
b.Property<int>("PlexContentId");
b.Property<int?>("PlexServerContentId");
b.Property<int>("SeasonKey");
b.Property<int>("SeasonNumber");
b.HasKey("Id");
b.HasIndex("PlexServerContentId");
b.ToTable("PlexSeasonsContent");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<string>("ImdbId");
b.Property<int>("Key");
b.Property<string>("Quality");
b.Property<string>("ReleaseYear");
b.Property<string>("TheMovieDbId");
b.Property<string>("Title");
b.Property<string>("TvDbId");
b.Property<int>("Type");
b.Property<string>("Url");
b.HasKey("Id");
b.ToTable("PlexServerContent");
});
modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<bool>("HasFile");
b.Property<int>("TheMovieDbId");
b.HasKey("Id");
b.ToTable("RadarrCache");
});
modelBuilder.Entity("Ombi.Store.Entities.RecentlyAddedLog", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<int>("ContentId");
b.Property<int>("ContentType");
b.Property<int?>("EpisodeNumber");
b.Property<int?>("SeasonNumber");
b.Property<int>("Type");
b.HasKey("Id");
b.ToTable("RecentlyAddedLog");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<bool>("Approved");
b.Property<bool>("Available");
b.Property<bool?>("Denied");
b.Property<string>("DeniedReason");
b.Property<int?>("IssueId");
b.Property<int>("ParentRequestId");
b.Property<int>("RequestType");
b.Property<DateTime>("RequestedDate");
b.Property<string>("RequestedUserId");
b.Property<int>("SeriesType");
b.Property<string>("Title");
b.HasKey("Id");
b.HasIndex("ParentRequestId");
b.HasIndex("RequestedUserId");
b.ToTable("ChildRequests");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueCategory", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Value");
b.HasKey("Id");
b.ToTable("IssueCategory");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Comment");
b.Property<DateTime>("Date");
b.Property<int?>("IssuesId");
b.Property<string>("UserId");
b.HasKey("Id");
b.HasIndex("IssuesId");
b.HasIndex("UserId");
b.ToTable("IssueComments");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Description");
b.Property<int>("IssueCategoryId");
b.Property<int?>("IssueId");
b.Property<string>("ProviderId");
b.Property<int?>("RequestId");
b.Property<int>("RequestType");
b.Property<DateTime?>("ResovledDate");
b.Property<int>("Status");
b.Property<string>("Subject");
b.Property<string>("Title");
b.Property<string>("UserReportedId");
b.HasKey("Id");
b.HasIndex("IssueCategoryId");
b.HasIndex("IssueId");
b.HasIndex("UserReportedId");
b.ToTable("Issues");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<bool>("Approved");
b.Property<bool>("Available");
b.Property<string>("Background");
b.Property<bool?>("Denied");
b.Property<string>("DeniedReason");
b.Property<DateTime?>("DigitalReleaseDate");
b.Property<string>("ImdbId");
b.Property<int?>("IssueId");
b.Property<string>("Overview");
b.Property<string>("PosterPath");
b.Property<int>("QualityOverride");
b.Property<DateTime>("ReleaseDate");
b.Property<int>("RequestType");
b.Property<DateTime>("RequestedDate");
b.Property<string>("RequestedUserId");
b.Property<int>("RootPathOverride");
b.Property<string>("Status");
b.Property<int>("TheMovieDbId");
b.Property<string>("Title");
b.HasKey("Id");
b.HasIndex("RequestedUserId");
b.ToTable("MovieRequests");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("EpisodeCount");
b.Property<DateTime>("RequestDate");
b.Property<int>("RequestId");
b.Property<int>("RequestType");
b.Property<string>("UserId");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("RequestLog");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Background");
b.Property<string>("ImdbId");
b.Property<string>("Overview");
b.Property<string>("PosterPath");
b.Property<int?>("QualityOverride");
b.Property<DateTime>("ReleaseDate");
b.Property<int?>("RootFolder");
b.Property<string>("Status");
b.Property<string>("Title");
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("TvRequests");
});
modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("RequestId");
b.Property<int>("RequestType");
b.Property<string>("UserId");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("RequestSubscription");
});
modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("SickRageCache");
});
modelBuilder.Entity("Ombi.Store.Entities.SickRageEpisodeCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("EpisodeNumber");
b.Property<int>("SeasonNumber");
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("SickRageEpisodeCache");
});
modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("SonarrCache");
});
modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("EpisodeNumber");
b.Property<bool>("HasFile");
b.Property<int>("SeasonNumber");
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("SonarrEpisodeCache");
});
modelBuilder.Entity("Ombi.Store.Entities.Tokens", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Token");
b.Property<string>("UserId");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("Tokens");
});
modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AirDate");
b.Property<bool>("Approved");
b.Property<bool>("Available");
b.Property<int>("EpisodeNumber");
b.Property<bool>("Requested");
b.Property<int>("SeasonId");
b.Property<string>("Title");
b.Property<string>("Url");
b.HasKey("Id");
b.HasIndex("SeasonId");
b.ToTable("EpisodeRequests");
});
modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("ChildRequestId");
b.Property<int>("SeasonNumber");
b.HasKey("Id");
b.HasIndex("ChildRequestId");
b.ToTable("SeasonRequests");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Ombi.Store.Entities.OmbiUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b =>
{
b.HasOne("Ombi.Store.Entities.EmbyContent", "Series")
.WithMany("Episodes")
.HasForeignKey("ParentId")
.HasPrincipalKey("EmbyId");
});
modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser", "User")
.WithMany("NotificationUserIds")
.HasForeignKey("UserId");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b =>
{
b.HasOne("Ombi.Store.Entities.PlexServerContent", "Series")
.WithMany("Episodes")
.HasForeignKey("GrandparentKey")
.HasPrincipalKey("Key")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b =>
{
b.HasOne("Ombi.Store.Entities.PlexServerContent")
.WithMany("Seasons")
.HasForeignKey("PlexServerContentId");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b =>
{
b.HasOne("Ombi.Store.Entities.Requests.TvRequests", "ParentRequest")
.WithMany("ChildRequests")
.HasForeignKey("ParentRequestId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser")
.WithMany()
.HasForeignKey("RequestedUserId");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b =>
{
b.HasOne("Ombi.Store.Entities.Requests.Issues", "Issues")
.WithMany("Comments")
.HasForeignKey("IssuesId");
b.HasOne("Ombi.Store.Entities.OmbiUser", "User")
.WithMany()
.HasForeignKey("UserId");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b =>
{
b.HasOne("Ombi.Store.Entities.Requests.IssueCategory", "IssueCategory")
.WithMany()
.HasForeignKey("IssueCategoryId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Ombi.Store.Entities.Requests.ChildRequests")
.WithMany("Issues")
.HasForeignKey("IssueId");
b.HasOne("Ombi.Store.Entities.Requests.MovieRequests")
.WithMany("Issues")
.HasForeignKey("IssueId");
b.HasOne("Ombi.Store.Entities.OmbiUser", "UserReported")
.WithMany()
.HasForeignKey("UserReportedId");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser")
.WithMany()
.HasForeignKey("RequestedUserId");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser", "User")
.WithMany()
.HasForeignKey("UserId");
});
modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser", "User")
.WithMany()
.HasForeignKey("UserId");
});
modelBuilder.Entity("Ombi.Store.Entities.Tokens", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser", "User")
.WithMany()
.HasForeignKey("UserId");
});
modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b =>
{
b.HasOne("Ombi.Store.Repository.Requests.SeasonRequests", "Season")
.WithMany("Episodes")
.HasForeignKey("SeasonId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b =>
{
b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", "ChildRequest")
.WithMany("SeasonRequests")
.HasForeignKey("ChildRequestId")
.OnDelete(DeleteBehavior.Cascade);
});
#pragma warning restore 612, 618
}
}
}

@ -0,0 +1,20 @@
using Microsoft.EntityFrameworkCore.Migrations;
using System;
using System.Collections.Generic;
namespace Ombi.Store.Migrations
{
public partial class EmbyUrlFix : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(
@"UPDATE EmbyContent SET Url = replace( Url, 'http://app.emby.media/itemdetails.html', 'http://app.emby.media/#!/itemdetails.html' ) WHERE Url LIKE 'http://app.emby.media/itemdetails.html%';");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}

@ -10,10 +10,10 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="2.0.4" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="2.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="2.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="2.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.1" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="1.1.9" />
</ItemGroup>

@ -7,12 +7,12 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="2.0.3" />
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="2.1.1" />
<PackageReference Include="Moq" Version="4.7.99" />
<PackageReference Include="Nunit" Version="3.8.1" />
<PackageReference Include="NUnit.ConsoleRunner" Version="3.7.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.8.0" />
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.7.0"></packagereference>
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.8.0"></packagereference>
</ItemGroup>
<ItemGroup>

@ -3,7 +3,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<RuntimeIdentifiers>win10-x64;win10-x86;osx-x64;ubuntu-x64;debian.8-x64;centos.7-x64;linux-x64;linux-arm;linux-arm64;</RuntimeIdentifiers>
<TargetFramework>netcoreapp2.0</TargetFramework>
<TargetFramework>netcoreapp2.1</TargetFramework>
<AssemblyVersion>3.0.0.0</AssemblyVersion>
<FileVersion>3.0.0.0</FileVersion>
<Version></Version>
@ -12,14 +12,14 @@
<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.1.1-beta" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.1.1" />
<PackageReference Include="Serilog" Version="2.6.0-dev-00892" />
<PackageReference Include="Serilog.Extensions.Logging" Version="2.0.2" />
<PackageReference Include="Serilog.Sinks.File" Version="3.2.0" />

@ -94,9 +94,31 @@ namespace Ombi
}
else
{
var identity = new GenericIdentity("API");
var principal = new GenericPrincipal(identity, new[] { "Admin", "ApiUser" });
context.User = principal;
// Check if we have a UserName header if so we can impersonate that user
if (context.Request.Headers.Keys.Contains("UserName", StringComparer.InvariantCultureIgnoreCase))
{
var username = context.Request.Headers["UserName"].FirstOrDefault();
var um = context.RequestServices.GetService<OmbiUserManager>();
var user = await um.Users.FirstOrDefaultAsync(x =>
x.UserName.Equals(username, StringComparison.InvariantCultureIgnoreCase));
if (user == null)
{
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
await context.Response.WriteAsync("Invalid User");
await next.Invoke(context);
}
var roles = await um.GetRolesAsync(user);
var identity = new GenericIdentity(user.UserName);
var principal = new GenericPrincipal(identity, roles.ToArray());
context.User = principal;
}
else
{
var identity = new GenericIdentity("API");
var principal = new GenericPrincipal(identity, new[] { "Admin", "ApiUser" });
context.User = principal;
}
await next.Invoke(context);
}
}

@ -36,7 +36,7 @@ import { ImageService } from "./services";
import { LandingPageService } from "./services";
import { NotificationService } from "./services";
import { SettingsService } from "./services";
import { IssuesService, JobService, StatusService } from "./services";
import { IssuesService, JobService, PlexTvService, StatusService } from "./services";
const routes: Routes = [
{ path: "*", component: PageNotFoundComponent },
@ -133,6 +133,7 @@ export function HttpLoaderFactory(http: HttpClient, platformLocation: PlatformLo
CookieService,
JobService,
IssuesService,
PlexTvService,
],
bootstrap: [AppComponent],
})

@ -1,8 +1,11 @@
export interface IUserLogin {
import { IPlexPin } from "../interfaces";
export interface IUserLogin {
username: string;
password: string;
rememberMe: boolean;
usePlexOAuth: boolean;
plexTvPin: IPlexPin;
}
export interface ILocalUser {

@ -2,6 +2,16 @@
user: IPlexUser;
}
export interface IPlexPin {
id: number;
code: string;
}
export interface IPlexOAuthViewModel {
wizard: boolean;
pin: IPlexPin;
}
export interface IPlexOAuthAccessToken {
accessToken: string;
}

@ -6,7 +6,7 @@ import { TranslateService } from "@ngx-translate/core";
import { PlatformLocation } from "@angular/common";
import { AuthService } from "../auth/auth.service";
import { IAuthenticationSettings, ICustomizationSettings } from "../interfaces";
import { NotificationService } from "../services";
import { NotificationService, PlexTvService } from "../services";
import { SettingsService } from "../services";
import { StatusService } from "../services";
@ -40,13 +40,14 @@ export class LoginComponent implements OnDestroy, OnInit {
}
private timer: any;
private clientId: string;
private errorBody: string;
private errorValidation: string;
constructor(private authService: AuthService, private router: Router, private notify: NotificationService, private status: StatusService,
private fb: FormBuilder, private settingsService: SettingsService, private images: ImageService, private sanitizer: DomSanitizer,
private route: ActivatedRoute, private location: PlatformLocation, private readonly translate: TranslateService) {
private route: ActivatedRoute, private location: PlatformLocation, private translate: TranslateService, private plexTv: PlexTvService) {
this.route.params
.subscribe((params: any) => {
this.landingFlag = params.landing;
@ -78,6 +79,7 @@ export class LoginComponent implements OnDestroy, OnInit {
public ngOnInit() {
this.settingsService.getAuthentication().subscribe(x => this.authenticationSettings = x);
this.settingsService.getClientId().subscribe(x => this.clientId = x);
this.settingsService.getCustomization().subscribe(x => this.customizationSettings = x);
this.images.getRandomBackground().subscribe(x => {
this.background = this.sanitizer.bypassSecurityTrustStyle("linear-gradient(-10deg, transparent 20%, rgba(0,0,0,0.7) 20.0%, rgba(0,0,0,0.7) 80.0%, transparent 80%),url(" + x.url + ")");
@ -101,7 +103,7 @@ export class LoginComponent implements OnDestroy, OnInit {
return;
}
const value = form.value;
const user = { password: value.password, username: value.username, rememberMe: value.rememberMe, usePlexOAuth: false };
const user = { password: value.password, username: value.username, rememberMe: value.rememberMe, usePlexOAuth: false, plexTvPin: { id: 0, code: ""} };
this.authService.requiresPassword(user).subscribe(x => {
if(x && this.authenticationSettings.allowNoPassword) {
// Looks like this user requires a password
@ -123,14 +125,17 @@ export class LoginComponent implements OnDestroy, OnInit {
}
public oauth() {
this.authService.login({usePlexOAuth: true, password:"",rememberMe:true,username:""}).subscribe(x => {
if (window.frameElement) {
// in frame
window.open(x.url, "_blank");
} else {
// not in frame
window.location.href = x.url;
}
this.plexTv.GetPin(this.clientId, this.appName).subscribe(pin => {
this.authService.login({usePlexOAuth: true, password:"",rememberMe:true,username:"", plexTvPin: pin}).subscribe(x => {
if (window.frameElement) {
// in frame
window.open(x.url, "_blank");
} else {
// not in frame
window.location.href = x.url;
}
});
});
}

@ -5,3 +5,4 @@ export * from "./radarr.service";
export * from "./sonarr.service";
export * from "./tester.service";
export * from "./plexoauth.service";
export * from "./plextv.service";

@ -6,7 +6,7 @@ import { Observable } from "rxjs/Rx";
import { ServiceHelpers } from "../service.helpers";
import { IPlexAuthentication, IPlexLibResponse, IPlexServer, IPlexServerViewModel, IUsersModel } from "../../interfaces";
import { IPlexAuthentication, IPlexLibResponse, IPlexOAuthViewModel,IPlexServer, IPlexServerViewModel, IUsersModel } from "../../interfaces";
@Injectable()
export class PlexService extends ServiceHelpers {
@ -30,7 +30,7 @@ export class PlexService extends ServiceHelpers {
return this.http.get<IUsersModel[]>(`${this.url}Friends`, {headers: this.headers});
}
public oAuth(wizard: boolean): Observable<any> {
return this.http.get<any>(`${this.url}oauth/${wizard}`, {headers: this.headers});
public oAuth(wizard: IPlexOAuthViewModel): Observable<any> {
return this.http.post<any>(`${this.url}oauth`, JSON.stringify(wizard), {headers: this.headers});
}
}

@ -0,0 +1,28 @@
import { PlatformLocation } from "@angular/common";
import { HttpClient, HttpHeaders } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { Observable } from "rxjs/Rx";
import { IPlexPin } from "../../interfaces";
@Injectable()
export class PlexTvService {
constructor(private http: HttpClient, public platformLocation: PlatformLocation) {
}
public GetPin(clientId: string, applicationName: string): Observable<IPlexPin> {
const headers = new HttpHeaders({"Content-Type":"application/json",
"X-Plex-Client-Identifier": clientId,
"X-Plex-Product": applicationName,
"X-Plex-Version": "3",
"X-Plex-Device": "Ombi (Web)",
"X-Plex-Platform": "Web",
"Accept": "application/json",
});
return this.http.post<IPlexPin>("https://plex.tv/api/v2/pins?strong=true", null, {headers});
}
}

@ -95,6 +95,10 @@ export class SettingsService extends ServiceHelpers {
return this.http.get<IAuthenticationSettings>(`${this.url}/Authentication`, {headers: this.headers});
}
public getClientId(): Observable<string> {
return this.http.get<string>(`${this.url}/clientid`, {headers: this.headers});
}
public saveAuthentication(settings: IAuthenticationSettings): Observable<boolean> {
return this.http.post<boolean>(`${this.url}/Authentication`, JSON.stringify(settings), {headers: this.headers});
}

@ -80,10 +80,10 @@ export class UserManagementComponent implements OnInit {
if(anyRoles) {
x.claims = this.availableClaims;
}
if(this.bulkEpisodeLimit && this.bulkEpisodeLimit > 0) {
if(this.bulkEpisodeLimit) {
x.episodeRequestLimit = this.bulkEpisodeLimit;
}
if(this.bulkMovieLimit && this.bulkMovieLimit > 0) {
if(this.bulkMovieLimit) {
x.movieRequestLimit = this.bulkMovieLimit;
}
this.identityService.updateUser(x).subscribe(y => {

@ -1,20 +1,27 @@
import { Component } from "@angular/core";
import { Component, OnInit } from "@angular/core";
import { Router } from "@angular/router";
import { PlexService } from "../../services";
import { PlexService, PlexTvService, SettingsService } from "../../services";
import { IdentityService, NotificationService } from "../../services";
@Component({
templateUrl: "./plex.component.html",
})
export class PlexComponent {
export class PlexComponent implements OnInit {
public login: string;
public password: string;
private clientId: string;
constructor(private plexService: PlexService, private router: Router,
private notificationService: NotificationService,
private identityService: IdentityService) { }
private identityService: IdentityService, private plexTv: PlexTvService,
private settingsService: SettingsService) { }
public ngOnInit(): void {
this.settingsService.getClientId().subscribe(x => this.clientId = x);
}
public requestAuthToken() {
this.plexService.logIn(this.login, this.password).subscribe(x => {
@ -40,10 +47,12 @@ export class PlexComponent {
}
public oauth() {
this.plexService.oAuth(true).subscribe(x => {
if(x.url) {
window.location.href = x.url;
}
this.plexTv.GetPin(this.clientId, "Ombi").subscribe(pin => {
this.plexService.oAuth({wizard: true, pin}).subscribe(x => {
if(x.url) {
window.location.href = x.url;
}
});
});
}
}

@ -12,6 +12,7 @@ using Ombi.Core.Authentication;
using Ombi.Core.Settings;
using Ombi.Core.Settings.Models.External;
using Ombi.Helpers;
using Ombi.Models;
using Ombi.Models.External;
namespace Ombi.Controllers.External
@ -177,25 +178,23 @@ namespace Ombi.Controllers.External
return vm.DistinctBy(x => x.Id);
}
[HttpGet("oauth/{wizard:bool}")]
[HttpPost("oauth")]
[AllowAnonymous]
public async Task<IActionResult> OAuth(bool wizard)
public async Task<IActionResult> OAuth([FromBody]PlexOAuthViewModel wizard)
{
//https://app.plex.tv/auth#?forwardUrl=http://google.com/&clientID=Ombi-Test&context%5Bdevice%5D%5Bproduct%5D=Ombi%20SSO&pinID=798798&code=4lgfd
// Plex OAuth
// Redirect them to Plex
// We need a PIN first
var pin = await _plexOAuthManager.RequestPin();
Uri url;
if (!wizard)
if (!wizard.Wizard)
{
url = await _plexOAuthManager.GetOAuthUrl(pin.id, pin.code);
url = await _plexOAuthManager.GetOAuthUrl(wizard.Pin.id, wizard.Pin.code);
}
else
{
var websiteAddress =$"{this.Request.Scheme}://{this.Request.Host}{this.Request.PathBase}";
url = _plexOAuthManager.GetWizardOAuthUrl(pin.id, pin.code, websiteAddress);
url = await _plexOAuthManager.GetWizardOAuthUrl(wizard.Pin.id, wizard.Pin.code, websiteAddress);
}
if (url == null)

@ -57,7 +57,9 @@ namespace Ombi.Controllers
ISettingsService<PlexSettings> settings,
IRepository<RequestLog> requestLog,
IRepository<Issues> issues,
IRepository<IssueComments> issueComments)
IRepository<IssueComments> issueComments,
IRepository<NotificationUserId> notificationRepository,
IRepository<RequestSubscription> subscriptionRepository)
{
UserManager = user;
Mapper = mapper;
@ -75,6 +77,8 @@ namespace Ombi.Controllers
_requestLogRepository = requestLog;
_issueCommentsRepository = issueComments;
OmbiSettings = ombiSettings;
_requestSubscriptionRepository = subscriptionRepository;
_notificationRepository = notificationRepository;
}
private OmbiUserManager UserManager { get; }
@ -93,6 +97,8 @@ namespace Ombi.Controllers
private readonly IRepository<Issues> _issuesRepository;
private readonly IRepository<IssueComments> _issueCommentsRepository;
private readonly IRepository<RequestLog> _requestLogRepository;
private readonly IRepository<NotificationUserId> _notificationRepository;
private readonly IRepository<RequestSubscription> _requestSubscriptionRepository;
/// <summary>
@ -568,6 +574,19 @@ namespace Ombi.Controllers
{
await _issueCommentsRepository.DeleteRange(issueComments);
}
// Delete the Subscriptions and mobile notification ids
var subs = _requestSubscriptionRepository.GetAll().Where(x => x.UserId == userId);
var mobileIds = _notificationRepository.GetAll().Where(x => x.UserId == userId);
if (subs.Any())
{
await _requestSubscriptionRepository.DeleteRange(subs);
}
if (mobileIds.Any())
{
await _notificationRepository.DeleteRange(mobileIds);
}
var result = await UserManager.DeleteAsync(userToDelete);
if (result.Succeeded)

@ -127,10 +127,23 @@ namespace Ombi.Controllers
public async Task<PlexSettings> PlexSettings()
{
var s = await Get<PlexSettings>();
return s;
}
[HttpGet("clientid")]
[AllowAnonymous]
public async Task<string> GetClientId()
{
var s = await Get<PlexSettings>();
if (s.InstallId == Guid.Empty)
{
s.InstallId = Guid.NewGuid();
// Save it
await PlexSettings(s);
}
return s.InstallId.ToString("N");
}
/// <summary>
/// Save the Plex settings.
/// </summary>
@ -139,6 +152,10 @@ namespace Ombi.Controllers
[HttpPost("plex")]
public async Task<bool> PlexSettings([FromBody]PlexSettings plex)
{
if (plex.InstallId == null || plex.InstallId == Guid.Empty)
{
plex.InstallId = Guid.NewGuid();
}
var result = await Save(plex);
return result;
}

@ -80,12 +80,10 @@ namespace Ombi.Controllers
{
// Plex OAuth
// Redirect them to Plex
// We need a PIN first
var pin = await _plexOAuthManager.RequestPin();
var websiteAddress = $"{this.Request.Scheme}://{this.Request.Host}{this.Request.PathBase}";
//https://app.plex.tv/auth#?forwardUrl=http://google.com/&clientID=Ombi-Test&context%5Bdevice%5D%5Bproduct%5D=Ombi%20SSO&pinID=798798&code=4lgfd
var url = await _plexOAuthManager.GetOAuthUrl(pin.id, pin.code, websiteAddress);
var url = await _plexOAuthManager.GetOAuthUrl(model.PlexTvPin.id, model.PlexTvPin.code, websiteAddress);
if (url == null)
{
return new JsonResult(new

@ -0,0 +1,10 @@
using Ombi.Api.Plex.Models.OAuth;
namespace Ombi.Models
{
public class PlexOAuthViewModel
{
public bool Wizard { get; set; }
public OAuthPin Pin { get; set; }
}
}

@ -1,4 +1,6 @@
namespace Ombi.Models
using Ombi.Api.Plex.Models.OAuth;
namespace Ombi.Models
{
public class UserAuthModel
{
@ -7,5 +9,6 @@
public bool RememberMe { get; set; }
public bool UsePlexAdminAccount { get; set; }
public bool UsePlexOAuth { get; set; }
public OAuthPin PlexTvPin { get; set; }
}
}

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<TargetFramework>netcoreapp2.1</TargetFramework>
<RuntimeIdentifiers>win10-x64;win10-x86;osx-x64;ubuntu-x64;debian.8-x64;centos.7-x64;linux-x64;linux-arm;linux-arm64;</RuntimeIdentifiers>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>
@ -67,10 +67,10 @@
<PackageReference Include="Hangfire.RecurringJobExtensions" Version="1.1.6" />
<PackageReference Include="Hangfire.SQLite" Version="1.4.2" />
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.8" />
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.2" />
<PackageReference Include="Microsoft.VisualStudio.Web.BrowserLink" Version="2.0.3" />
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.1.1" />
<PackageReference Include="Microsoft.VisualStudio.Web.BrowserLink" Version="2.1.1" />
<PackageReference Include="MiniProfiler.AspNetCore.Mvc" Version="4.0.0-alpha6-79" />
<PackageReference Include="ncrontab" Version="3.3.0" />
<PackageReference Include="Serilog" Version="2.6.0-dev-00892" />
@ -78,7 +78,7 @@
<PackageReference Include="Serilog.Sinks.File" Version="3.2.0" />
<PackageReference Include="Serilog.Sinks.RollingFile" Version="3.3.1-dev-00771" />
<PackageReference Include="Serilog.Sinks.SQLite" Version="3.8.3" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="2.4.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="3.0.0" />
<PackageReference Include="System.Security.Cryptography.Csp" Version="4.3.0" />
</ItemGroup>

@ -127,6 +127,12 @@ namespace Ombi
//x.UseConsole();
});
services.AddCors(o => o.AddPolicy("MyPolicy", builder =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
}));
// Build the intermediate service provider
return services.BuildServiceProvider();
@ -209,13 +215,16 @@ namespace Ombi
app.UseMiddleware<ErrorHandlingMiddleware>();
app.UseMiddleware<ApiKeyMiddlewear>();
app.UseCors("MyPolicy");
//app.ApiKeyMiddlewear(app.ApplicationServices);
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});
app.UseMvc(routes =>
{
routes.MapRoute(

@ -1,29 +1,38 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Security.Principal;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.PlatformAbstractions;
using Microsoft.IdentityModel.Tokens;
using Ombi.Config;
using Ombi.Core.Authentication;
using Ombi.Core.Settings;
using Ombi.Helpers;
using Ombi.Models.Identity;
using Ombi.Settings.Settings.Models;
using Swashbuckle.AspNetCore.Swagger;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace Ombi
{
public class AddRequiredHeaderParameter : IOperationFilter
{
public void Apply(Operation operation, OperationFilterContext context)
{
if (operation.Parameters == null)
operation.Parameters = new List<IParameter>();
operation.Parameters.Add(new NonBodyParameter
{
Name = "ApiKey",
In = "header",
Type = "apiKey",
});
}
}
public static class StartupExtensions
{
public static void AddSwagger(this IServiceCollection services)
@ -35,17 +44,37 @@ namespace Ombi
{
Version = "v1",
Title = "Ombi Api",
Description = "The API for Ombi, most of these calls require an auth token that you can get from calling POST:\"/api/v1/token\" with the body of: \n {\n\"username\":\"YOURUSERNAME\",\n\"password\":\"YOURPASSWORD\"\n} \n" +
"You can then use the returned token in the JWT Token field e.g. \"Bearer Token123xxff\"",
Contact = new Contact
{
Email = "tidusjar@gmail.com",
Name = "Jamie Rees",
Url = "https://www.ombi.io/"
}
});
var security = new Dictionary<string, IEnumerable<string>>
{
//{"Bearer", new string[] { }},
{"ApiKey", new string[] { }},
};
//c.AddSecurityDefinition("Bearer", new ApiKeyScheme
//{
// Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"",
// Name = "Authorization",
// In = "header",
// Type = "apiKey"
//});
c.AddSecurityDefinition("ApiKey", new ApiKeyScheme
{
Description = "API Key provided by Ombi. Example: \"ApiKey: {token}\"",
Name = "ApiKey",
In = "header",
Type = "apiKey"
});
c.AddSecurityRequirement(security);
c.CustomSchemaIds(x => x.FullName);
var basePath = PlatformServices.Default.Application.ApplicationBasePath;
c.OperationFilter<AddRequiredHeaderParameter>();
var basePath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
var xmlPath = Path.Combine(basePath, "Swagger.xml");
try
{
@ -55,13 +84,7 @@ namespace Ombi
{
Console.WriteLine(e);
}
c.AddSecurityDefinition("Bearer", new JwtBearer
{
Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"",
Name = "Authorization",
In = "header",
Type = "apiKey",
});
c.OperationFilter<SwaggerOperationFilter>();
c.DescribeAllParametersInCamelCase();

Loading…
Cancel
Save