From c55fc32c63bfc9947c3afb2cb6bac71d6beb7a2d Mon Sep 17 00:00:00 2001 From: Jamie Rees Date: Sun, 1 Jul 2018 22:16:12 +0100 Subject: [PATCH 1/4] Fixed Plex OAuth, should no longer show Insecure warning --- src/Ombi.Api.Plex/PlexApi.cs | 2 +- .../Authentication/PlexOAuthManager.cs | 13 ------- .../ClientApp/app/login/login.component.ts | 34 ++++++++++++++----- src/Ombi/Controllers/TokenController.cs | 4 +-- 4 files changed, 29 insertions(+), 24 deletions(-) diff --git a/src/Ombi.Api.Plex/PlexApi.cs b/src/Ombi.Api.Plex/PlexApi.cs index a16dee9ec..95c1c9d49 100644 --- a/src/Ombi.Api.Plex/PlexApi.cs +++ b/src/Ombi.Api.Plex/PlexApi.cs @@ -214,7 +214,7 @@ namespace Ombi.Api.Plex ? new Request($"Wizard/OAuth/{pinId}", applicationUrl, HttpMethod.Get) : new Request($"Login/OAuth/{pinId}", applicationUrl, HttpMethod.Get); - request.AddQueryString("forwardUrl", forwardUrl.FullUri.ToString()); + //request.AddQueryString("forwardUrl", forwardUrl.FullUri.ToString()); request.AddQueryString("pinID", pinId.ToString()); request.AddQueryString("code", code); request.AddQueryString("context[device][product]", "Ombi"); diff --git a/src/Ombi.Core/Authentication/PlexOAuthManager.cs b/src/Ombi.Core/Authentication/PlexOAuthManager.cs index 37ed7d2f7..887245579 100644 --- a/src/Ombi.Core/Authentication/PlexOAuthManager.cs +++ b/src/Ombi.Core/Authentication/PlexOAuthManager.cs @@ -34,19 +34,6 @@ namespace Ombi.Core.Authentication return string.Empty; } - if (pin.authToken.IsNullOrEmpty()) - { - // Looks like we do not have a pin yet, we should retry a few times. - var retryCount = 0; - var retryMax = 5; - var retryWaitMs = 1000; - while (pin.authToken.IsNullOrEmpty() && retryCount < retryMax) - { - retryCount++; - await Task.Delay(retryWaitMs); - pin = await _api.GetPin(pinId); - } - } return pin.authToken; } diff --git a/src/Ombi/ClientApp/app/login/login.component.ts b/src/Ombi/ClientApp/app/login/login.component.ts index 3447f84c3..e6386b8ce 100644 --- a/src/Ombi/ClientApp/app/login/login.component.ts +++ b/src/Ombi/ClientApp/app/login/login.component.ts @@ -40,6 +40,7 @@ export class LoginComponent implements OnDestroy, OnInit { } private timer: any; + private pinTimer: any; private errorBody: string; private errorValidation: string; @@ -124,18 +125,35 @@ 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; - } - }); + window.open(x.url, "_blank"); + this.pinTimer = setInterval(() => { + this.getPinResult(x.pinId); + }, 10000); + }); + + } + + public getPinResult(pinId: number) { + this.authService.oAuth(pinId).subscribe(x => { + if(x.access_token) { + localStorage.setItem("id_token", x.access_token); + + if (this.authService.loggedIn()) { + this.router.navigate(["search"]); + return; + } + } + + }, err => { + this.notify.error(err.statusText); + + this.router.navigate(["login"]); + }); } public ngOnDestroy() { clearInterval(this.timer); + clearInterval(this.pinTimer); } private cycleBackground() { diff --git a/src/Ombi/Controllers/TokenController.cs b/src/Ombi/Controllers/TokenController.cs index b45752af4..3d810d1d2 100644 --- a/src/Ombi/Controllers/TokenController.cs +++ b/src/Ombi/Controllers/TokenController.cs @@ -82,7 +82,7 @@ namespace Ombi.Controllers // 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); @@ -93,7 +93,7 @@ namespace Ombi.Controllers error = "Application URL has not been set" }); } - return new JsonResult(new { url = url.ToString() }); + return new JsonResult(new { url = url.ToString(), pinId = pin.id }); } return new UnauthorizedResult(); From cbf331cd09409b58d51506c18e0874aaac747c46 Mon Sep 17 00:00:00 2001 From: Jamie Rees Date: Mon, 2 Jul 2018 08:06:43 +0100 Subject: [PATCH 2/4] Revert "Fixed Plex OAuth, should no longer show Insecure warning" This reverts commit c55fc32c63bfc9947c3afb2cb6bac71d6beb7a2d. --- src/Ombi.Api.Plex/PlexApi.cs | 2 +- .../Authentication/PlexOAuthManager.cs | 13 +++++++ .../ClientApp/app/login/login.component.ts | 34 +++++-------------- src/Ombi/Controllers/TokenController.cs | 4 +-- 4 files changed, 24 insertions(+), 29 deletions(-) diff --git a/src/Ombi.Api.Plex/PlexApi.cs b/src/Ombi.Api.Plex/PlexApi.cs index 95c1c9d49..a16dee9ec 100644 --- a/src/Ombi.Api.Plex/PlexApi.cs +++ b/src/Ombi.Api.Plex/PlexApi.cs @@ -214,7 +214,7 @@ namespace Ombi.Api.Plex ? new Request($"Wizard/OAuth/{pinId}", applicationUrl, HttpMethod.Get) : new Request($"Login/OAuth/{pinId}", applicationUrl, HttpMethod.Get); - //request.AddQueryString("forwardUrl", forwardUrl.FullUri.ToString()); + request.AddQueryString("forwardUrl", forwardUrl.FullUri.ToString()); request.AddQueryString("pinID", pinId.ToString()); request.AddQueryString("code", code); request.AddQueryString("context[device][product]", "Ombi"); diff --git a/src/Ombi.Core/Authentication/PlexOAuthManager.cs b/src/Ombi.Core/Authentication/PlexOAuthManager.cs index 887245579..37ed7d2f7 100644 --- a/src/Ombi.Core/Authentication/PlexOAuthManager.cs +++ b/src/Ombi.Core/Authentication/PlexOAuthManager.cs @@ -34,6 +34,19 @@ namespace Ombi.Core.Authentication return string.Empty; } + if (pin.authToken.IsNullOrEmpty()) + { + // Looks like we do not have a pin yet, we should retry a few times. + var retryCount = 0; + var retryMax = 5; + var retryWaitMs = 1000; + while (pin.authToken.IsNullOrEmpty() && retryCount < retryMax) + { + retryCount++; + await Task.Delay(retryWaitMs); + pin = await _api.GetPin(pinId); + } + } return pin.authToken; } diff --git a/src/Ombi/ClientApp/app/login/login.component.ts b/src/Ombi/ClientApp/app/login/login.component.ts index e6386b8ce..3447f84c3 100644 --- a/src/Ombi/ClientApp/app/login/login.component.ts +++ b/src/Ombi/ClientApp/app/login/login.component.ts @@ -40,7 +40,6 @@ export class LoginComponent implements OnDestroy, OnInit { } private timer: any; - private pinTimer: any; private errorBody: string; private errorValidation: string; @@ -125,35 +124,18 @@ export class LoginComponent implements OnDestroy, OnInit { public oauth() { this.authService.login({usePlexOAuth: true, password:"",rememberMe:true,username:""}).subscribe(x => { - window.open(x.url, "_blank"); - this.pinTimer = setInterval(() => { - this.getPinResult(x.pinId); - }, 10000); - }); - - } - - public getPinResult(pinId: number) { - this.authService.oAuth(pinId).subscribe(x => { - if(x.access_token) { - localStorage.setItem("id_token", x.access_token); - - if (this.authService.loggedIn()) { - this.router.navigate(["search"]); - return; - } - } - - }, err => { - this.notify.error(err.statusText); - - this.router.navigate(["login"]); - }); + if (window.frameElement) { + // in frame + window.open(x.url, "_blank"); + } else { + // not in frame + window.location.href = x.url; + } + }); } public ngOnDestroy() { clearInterval(this.timer); - clearInterval(this.pinTimer); } private cycleBackground() { diff --git a/src/Ombi/Controllers/TokenController.cs b/src/Ombi/Controllers/TokenController.cs index 3d810d1d2..b45752af4 100644 --- a/src/Ombi/Controllers/TokenController.cs +++ b/src/Ombi/Controllers/TokenController.cs @@ -82,7 +82,7 @@ namespace Ombi.Controllers // 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); @@ -93,7 +93,7 @@ namespace Ombi.Controllers error = "Application URL has not been set" }); } - return new JsonResult(new { url = url.ToString(), pinId = pin.id }); + return new JsonResult(new { url = url.ToString() }); } return new UnauthorizedResult(); From 7bfc5ad63204cb2bb8d99c476a8d6eb2adf1ac1c Mon Sep 17 00:00:00 2001 From: Jamie Rees Date: Mon, 2 Jul 2018 08:44:32 +0100 Subject: [PATCH 3/4] Rework how we create the OAuth Pin !wip --- src/Ombi.Api.Plex/IPlexApi.cs | 2 +- src/Ombi.Api.Plex/PlexApi.cs | 80 ++++++++++++------- .../Authentication/PlexOAuthManager.cs | 6 +- src/Ombi.Core/IPlexOAuthManager.cs | 2 +- .../Settings/Models/External/PlexSettings.cs | 7 +- .../Controllers/External/PlexController.cs | 2 +- src/Ombi/Controllers/SettingsController.cs | 4 + 7 files changed, 69 insertions(+), 34 deletions(-) diff --git a/src/Ombi.Api.Plex/IPlexApi.cs b/src/Ombi.Api.Plex/IPlexApi.cs index cc61dfa5d..95aba2a63 100644 --- a/src/Ombi.Api.Plex/IPlexApi.cs +++ b/src/Ombi.Api.Plex/IPlexApi.cs @@ -24,6 +24,6 @@ namespace Ombi.Api.Plex Task GetRecentlyAdded(string authToken, string uri, string sectionId); Task CreatePin(); Task GetPin(int pinId); - Uri GetOAuthUrl(int pinId, string code, string applicationUrl, bool wizard); + Task GetOAuthUrl(int pinId, string code, string applicationUrl, bool wizard); } } \ No newline at end of file diff --git a/src/Ombi.Api.Plex/PlexApi.cs b/src/Ombi.Api.Plex/PlexApi.cs index a16dee9ec..a559bea07 100644 --- a/src/Ombi.Api.Plex/PlexApi.cs +++ b/src/Ombi.Api.Plex/PlexApi.cs @@ -16,14 +16,16 @@ namespace Ombi.Api.Plex { public class PlexApi : IPlexApi { - public PlexApi(IApi api, ISettingsService settings) + public PlexApi(IApi api, ISettingsService settings, ISettingsService p) { Api = api; _custom = settings; + _plexSettings = p; } private IApi Api { get; } private readonly ISettingsService _custom; + private readonly ISettingsService _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(request); @@ -80,14 +82,14 @@ namespace Ombi.Api.Plex public async Task 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(request); } public async Task GetAccount(string authToken) { var request = new Request(GetAccountUri, string.Empty, HttpMethod.Get); - AddHeaders(request, authToken); + await AddHeaders(request, authToken); return await Api.Request(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(request); } @@ -103,14 +105,14 @@ namespace Ombi.Api.Plex public async Task 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(request); } public async Task 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(request); } @@ -128,21 +130,21 @@ namespace Ombi.Api.Plex public async Task 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(request); } public async Task 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(request); } public async Task 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(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(request); + return await Api.Request(request); } /// @@ -174,8 +176,8 @@ namespace Ombi.Api.Plex /// public async Task 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(request); } @@ -183,7 +185,7 @@ namespace Ombi.Api.Plex public async Task 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(request); @@ -193,7 +195,7 @@ namespace Ombi.Api.Plex { var request = new Request($"api/v2/pins", "https://plex.tv/", HttpMethod.Post); request.AddQueryString("strong", "true"); - AddHeaders(request); + await AddHeaders(request); return await Api.Request(request); } @@ -201,17 +203,17 @@ namespace Ombi.Api.Plex public async Task 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(request); } - public Uri GetOAuthUrl(int pinId, string code, string applicationUrl, bool wizard) + public async Task 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) + var request = new Request("auth#!?", "https://app.plex.tv", 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()); @@ -219,7 +221,13 @@ namespace Ombi.Api.Plex request.AddQueryString("code", code); request.AddQueryString("context[device][product]", "Ombi"); 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][devuce]", "Ombi (Web)"); + + var s = await GetSettings(); + await CheckInstallId(s); + request.AddQueryString("clientID", s.InstallId.ToString("N")); if (request.FullUri.Fragment.Equals("#")) { @@ -238,21 +246,25 @@ namespace Ombi.Api.Plex /// /// /// - 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); } /// /// Adds the main required headers to the Plex Request /// /// - 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 +274,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 GetSettings() + { + return _settings ?? (_settings = await _plexSettings.GetSettingsAsync()); + } } } diff --git a/src/Ombi.Core/Authentication/PlexOAuthManager.cs b/src/Ombi.Core/Authentication/PlexOAuthManager.cs index 37ed7d2f7..c10015f33 100644 --- a/src/Ombi.Core/Authentication/PlexOAuthManager.cs +++ b/src/Ombi.Core/Authentication/PlexOAuthManager.cs @@ -58,14 +58,14 @@ namespace Ombi.Core.Authentication public async Task 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 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; } } diff --git a/src/Ombi.Core/IPlexOAuthManager.cs b/src/Ombi.Core/IPlexOAuthManager.cs index 9c4f0582e..57a7cfc83 100644 --- a/src/Ombi.Core/IPlexOAuthManager.cs +++ b/src/Ombi.Core/IPlexOAuthManager.cs @@ -10,7 +10,7 @@ namespace Ombi.Core.Authentication Task GetAccessTokenFromPin(int pinId); Task RequestPin(); Task GetOAuthUrl(int pinId, string code, string websiteAddress = null); - Uri GetWizardOAuthUrl(int pinId, string code, string websiteAddress); + Task GetWizardOAuthUrl(int pinId, string code, string websiteAddress); Task GetAccount(string accessToken); } } \ No newline at end of file diff --git a/src/Ombi.Settings/Settings/Models/External/PlexSettings.cs b/src/Ombi.Settings/Settings/Models/External/PlexSettings.cs index 3faba3e42..8fc8111f7 100644 --- a/src/Ombi.Settings/Settings/Models/External/PlexSettings.cs +++ b/src/Ombi.Settings/Settings/Models/External/PlexSettings.cs @@ -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; } + /// + /// This is the ClientId for OAuth + /// + public Guid InstallId { get; set; } public List Servers { get; set; } } diff --git a/src/Ombi/Controllers/External/PlexController.cs b/src/Ombi/Controllers/External/PlexController.cs index 4819ed8a0..3675040d3 100644 --- a/src/Ombi/Controllers/External/PlexController.cs +++ b/src/Ombi/Controllers/External/PlexController.cs @@ -195,7 +195,7 @@ namespace Ombi.Controllers.External else { var websiteAddress =$"{this.Request.Scheme}://{this.Request.Host}{this.Request.PathBase}"; - url = _plexOAuthManager.GetWizardOAuthUrl(pin.id, pin.code, websiteAddress); + url = await _plexOAuthManager.GetWizardOAuthUrl(pin.id, pin.code, websiteAddress); } if (url == null) diff --git a/src/Ombi/Controllers/SettingsController.cs b/src/Ombi/Controllers/SettingsController.cs index 895621ad3..fb0ec53e3 100644 --- a/src/Ombi/Controllers/SettingsController.cs +++ b/src/Ombi/Controllers/SettingsController.cs @@ -139,6 +139,10 @@ namespace Ombi.Controllers [HttpPost("plex")] public async Task PlexSettings([FromBody]PlexSettings plex) { + if (plex.InstallId == null || plex.InstallId == Guid.Empty) + { + plex.InstallId = Guid.NewGuid(); + } var result = await Save(plex); return result; } From 41da68b96182be4ad3ca473ffbd5a6593f591fb9 Mon Sep 17 00:00:00 2001 From: Jamie Date: Tue, 3 Jul 2018 12:33:21 +0100 Subject: [PATCH 4/4] Fixed the Plex OAuth warning --- src/Ombi.Api.Plex/IPlexApi.cs | 1 - src/Ombi.Api.Plex/PlexApi.cs | 15 ++-------- .../Authentication/PlexOAuthManager.cs | 6 ---- src/Ombi.Core/IPlexOAuthManager.cs | 2 -- src/Ombi/ClientApp/app/app.module.ts | 3 +- src/Ombi/ClientApp/app/auth/IUserLogin.ts | 5 +++- src/Ombi/ClientApp/app/interfaces/IPlex.ts | 10 +++++++ .../ClientApp/app/login/login.component.ts | 27 ++++++++++-------- .../app/services/applications/index.ts | 1 + .../app/services/applications/plex.service.ts | 6 ++-- .../services/applications/plextv.service.ts | 28 +++++++++++++++++++ .../app/services/settings.service.ts | 4 +++ .../app/wizard/plex/plex.component.ts | 25 +++++++++++------ .../Controllers/External/PlexController.cs | 13 ++++----- src/Ombi/Controllers/SettingsController.cs | 15 +++++++++- src/Ombi/Controllers/TokenController.cs | 6 ++-- src/Ombi/Models/PlexOAuthViewModel.cs | 10 +++++++ src/Ombi/Models/UserAuthModel.cs | 5 +++- src/Ombi/Startup.cs | 11 +++++++- 19 files changed, 134 insertions(+), 59 deletions(-) create mode 100644 src/Ombi/ClientApp/app/services/applications/plextv.service.ts create mode 100644 src/Ombi/Models/PlexOAuthViewModel.cs diff --git a/src/Ombi.Api.Plex/IPlexApi.cs b/src/Ombi.Api.Plex/IPlexApi.cs index 95aba2a63..2dd1a638f 100644 --- a/src/Ombi.Api.Plex/IPlexApi.cs +++ b/src/Ombi.Api.Plex/IPlexApi.cs @@ -22,7 +22,6 @@ namespace Ombi.Api.Plex Task GetUsers(string authToken); Task GetAccount(string authToken); Task GetRecentlyAdded(string authToken, string uri, string sectionId); - Task CreatePin(); Task GetPin(int pinId); Task GetOAuthUrl(int pinId, string code, string applicationUrl, bool wizard); } diff --git a/src/Ombi.Api.Plex/PlexApi.cs b/src/Ombi.Api.Plex/PlexApi.cs index a559bea07..4276f6203 100644 --- a/src/Ombi.Api.Plex/PlexApi.cs +++ b/src/Ombi.Api.Plex/PlexApi.cs @@ -191,15 +191,6 @@ namespace Ombi.Api.Plex return await Api.Request(request); } - public async Task CreatePin() - { - var request = new Request($"api/v2/pins", "https://plex.tv/", HttpMethod.Post); - request.AddQueryString("strong", "true"); - await AddHeaders(request); - - return await Api.Request(request); - } - public async Task GetPin(int pinId) { var request = new Request($"api/v2/pins/{pinId}", "https://plex.tv/", HttpMethod.Get); @@ -210,7 +201,7 @@ namespace Ombi.Api.Plex public async Task GetOAuthUrl(int pinId, string code, string applicationUrl, bool wizard) { - var request = new Request("auth#!?", "https://app.plex.tv", HttpMethod.Get); + var request = new Request("auth#", "https://app.plex.tv", HttpMethod.Get); await AddHeaders(request); var forwardUrl = wizard ? new Request($"Wizard/OAuth/{pinId}", applicationUrl, HttpMethod.Get) @@ -219,11 +210,11 @@ namespace Ombi.Api.Plex 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("context[device][layout]", "desktop"); request.AddQueryString("context[device][platform]", "Web"); - request.AddQueryString("context[device][devuce]", "Ombi (Web)"); + request.AddQueryString("context[device][device]", "Ombi (Web)"); var s = await GetSettings(); await CheckInstallId(s); diff --git a/src/Ombi.Core/Authentication/PlexOAuthManager.cs b/src/Ombi.Core/Authentication/PlexOAuthManager.cs index c10015f33..803176d74 100644 --- a/src/Ombi.Core/Authentication/PlexOAuthManager.cs +++ b/src/Ombi.Core/Authentication/PlexOAuthManager.cs @@ -20,12 +20,6 @@ namespace Ombi.Core.Authentication private readonly IPlexApi _api; private readonly ISettingsService _customizationSettingsService; - public async Task RequestPin() - { - var pin = await _api.CreatePin(); - return pin; - } - public async Task GetAccessTokenFromPin(int pinId) { var pin = await _api.GetPin(pinId); diff --git a/src/Ombi.Core/IPlexOAuthManager.cs b/src/Ombi.Core/IPlexOAuthManager.cs index 57a7cfc83..a5c0c44ff 100644 --- a/src/Ombi.Core/IPlexOAuthManager.cs +++ b/src/Ombi.Core/IPlexOAuthManager.cs @@ -1,14 +1,12 @@ 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 GetAccessTokenFromPin(int pinId); - Task RequestPin(); Task GetOAuthUrl(int pinId, string code, string websiteAddress = null); Task GetWizardOAuthUrl(int pinId, string code, string websiteAddress); Task GetAccount(string accessToken); diff --git a/src/Ombi/ClientApp/app/app.module.ts b/src/Ombi/ClientApp/app/app.module.ts index 1421b1117..257a07cd0 100644 --- a/src/Ombi/ClientApp/app/app.module.ts +++ b/src/Ombi/ClientApp/app/app.module.ts @@ -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], }) diff --git a/src/Ombi/ClientApp/app/auth/IUserLogin.ts b/src/Ombi/ClientApp/app/auth/IUserLogin.ts index 609055c8e..d0e4d374a 100644 --- a/src/Ombi/ClientApp/app/auth/IUserLogin.ts +++ b/src/Ombi/ClientApp/app/auth/IUserLogin.ts @@ -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 { diff --git a/src/Ombi/ClientApp/app/interfaces/IPlex.ts b/src/Ombi/ClientApp/app/interfaces/IPlex.ts index 823b80d32..ccc4e0300 100644 --- a/src/Ombi/ClientApp/app/interfaces/IPlex.ts +++ b/src/Ombi/ClientApp/app/interfaces/IPlex.ts @@ -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; } diff --git a/src/Ombi/ClientApp/app/login/login.component.ts b/src/Ombi/ClientApp/app/login/login.component.ts index 3447f84c3..7a2a605ce 100644 --- a/src/Ombi/ClientApp/app/login/login.component.ts +++ b/src/Ombi/ClientApp/app/login/login.component.ts @@ -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; + } + }); }); } diff --git a/src/Ombi/ClientApp/app/services/applications/index.ts b/src/Ombi/ClientApp/app/services/applications/index.ts index 98c61cf04..9fe4a5403 100644 --- a/src/Ombi/ClientApp/app/services/applications/index.ts +++ b/src/Ombi/ClientApp/app/services/applications/index.ts @@ -5,3 +5,4 @@ export * from "./radarr.service"; export * from "./sonarr.service"; export * from "./tester.service"; export * from "./plexoauth.service"; +export * from "./plextv.service"; diff --git a/src/Ombi/ClientApp/app/services/applications/plex.service.ts b/src/Ombi/ClientApp/app/services/applications/plex.service.ts index 53fd31f9d..296f79ddb 100644 --- a/src/Ombi/ClientApp/app/services/applications/plex.service.ts +++ b/src/Ombi/ClientApp/app/services/applications/plex.service.ts @@ -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(`${this.url}Friends`, {headers: this.headers}); } - public oAuth(wizard: boolean): Observable { - return this.http.get(`${this.url}oauth/${wizard}`, {headers: this.headers}); + public oAuth(wizard: IPlexOAuthViewModel): Observable { + return this.http.post(`${this.url}oauth`, JSON.stringify(wizard), {headers: this.headers}); } } diff --git a/src/Ombi/ClientApp/app/services/applications/plextv.service.ts b/src/Ombi/ClientApp/app/services/applications/plextv.service.ts new file mode 100644 index 000000000..58a756007 --- /dev/null +++ b/src/Ombi/ClientApp/app/services/applications/plextv.service.ts @@ -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 { + 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("https://plex.tv/api/v2/pins?strong=true", null, {headers}); + } + +} diff --git a/src/Ombi/ClientApp/app/services/settings.service.ts b/src/Ombi/ClientApp/app/services/settings.service.ts index 726c86d63..ff49e393c 100644 --- a/src/Ombi/ClientApp/app/services/settings.service.ts +++ b/src/Ombi/ClientApp/app/services/settings.service.ts @@ -95,6 +95,10 @@ export class SettingsService extends ServiceHelpers { return this.http.get(`${this.url}/Authentication`, {headers: this.headers}); } + public getClientId(): Observable { + return this.http.get(`${this.url}/clientid`, {headers: this.headers}); + } + public saveAuthentication(settings: IAuthenticationSettings): Observable { return this.http.post(`${this.url}/Authentication`, JSON.stringify(settings), {headers: this.headers}); } diff --git a/src/Ombi/ClientApp/app/wizard/plex/plex.component.ts b/src/Ombi/ClientApp/app/wizard/plex/plex.component.ts index 67bd672dc..81b5db0b4 100644 --- a/src/Ombi/ClientApp/app/wizard/plex/plex.component.ts +++ b/src/Ombi/ClientApp/app/wizard/plex/plex.component.ts @@ -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; + } + }); }); } } diff --git a/src/Ombi/Controllers/External/PlexController.cs b/src/Ombi/Controllers/External/PlexController.cs index 3675040d3..6ea37e9cc 100644 --- a/src/Ombi/Controllers/External/PlexController.cs +++ b/src/Ombi/Controllers/External/PlexController.cs @@ -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 OAuth(bool wizard) + public async Task 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 = await _plexOAuthManager.GetWizardOAuthUrl(pin.id, pin.code, websiteAddress); + url = await _plexOAuthManager.GetWizardOAuthUrl(wizard.Pin.id, wizard.Pin.code, websiteAddress); } if (url == null) diff --git a/src/Ombi/Controllers/SettingsController.cs b/src/Ombi/Controllers/SettingsController.cs index fb0ec53e3..50938942b 100644 --- a/src/Ombi/Controllers/SettingsController.cs +++ b/src/Ombi/Controllers/SettingsController.cs @@ -127,10 +127,23 @@ namespace Ombi.Controllers public async Task PlexSettings() { var s = await Get(); - return s; } + [HttpGet("clientid")] + [AllowAnonymous] + public async Task GetClientId() + { + var s = await Get(); + if (s.InstallId == Guid.Empty) + { + s.InstallId = Guid.NewGuid(); + // Save it + await PlexSettings(s); + } + return s.InstallId.ToString("N"); + } + /// /// Save the Plex settings. /// diff --git a/src/Ombi/Controllers/TokenController.cs b/src/Ombi/Controllers/TokenController.cs index b45752af4..aad367dbe 100644 --- a/src/Ombi/Controllers/TokenController.cs +++ b/src/Ombi/Controllers/TokenController.cs @@ -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 diff --git a/src/Ombi/Models/PlexOAuthViewModel.cs b/src/Ombi/Models/PlexOAuthViewModel.cs new file mode 100644 index 000000000..dfc2eda8f --- /dev/null +++ b/src/Ombi/Models/PlexOAuthViewModel.cs @@ -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; } + } +} \ No newline at end of file diff --git a/src/Ombi/Models/UserAuthModel.cs b/src/Ombi/Models/UserAuthModel.cs index 046119821..5f240699b 100644 --- a/src/Ombi/Models/UserAuthModel.cs +++ b/src/Ombi/Models/UserAuthModel.cs @@ -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; } } } \ No newline at end of file diff --git a/src/Ombi/Startup.cs b/src/Ombi/Startup.cs index 9e7b1b290..b8a841592 100644 --- a/src/Ombi/Startup.cs +++ b/src/Ombi/Startup.cs @@ -129,6 +129,12 @@ namespace Ombi //x.UseConsole(); }); + services.AddCors(o => o.AddPolicy("MyPolicy", builder => + { + builder.AllowAnyOrigin() + .AllowAnyMethod() + .AllowAnyHeader(); + })); // Build the intermediate service provider return services.BuildServiceProvider(); @@ -211,13 +217,16 @@ namespace Ombi app.UseMiddleware(); app.UseMiddleware(); + 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(