From 41da68b96182be4ad3ca473ffbd5a6593f591fb9 Mon Sep 17 00:00:00 2001 From: Jamie Date: Tue, 3 Jul 2018 12:33:21 +0100 Subject: [PATCH] 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(