From 7bbcb9a6267df751c47d2b91e2ee5da0d8c0fe65 Mon Sep 17 00:00:00 2001 From: Jamie Rees Date: Wed, 18 Apr 2018 15:11:44 +0100 Subject: [PATCH 1/6] Made an attempt at PlexOAuth !wip --- src/Ombi/Controllers/PlexOAuthController.cs | 40 +++++++++ src/Ombi/Controllers/TokenController.cs | 95 ++++++++++++--------- src/Ombi/Models/UserAuthModel.cs | 1 + 3 files changed, 97 insertions(+), 39 deletions(-) create mode 100644 src/Ombi/Controllers/PlexOAuthController.cs diff --git a/src/Ombi/Controllers/PlexOAuthController.cs b/src/Ombi/Controllers/PlexOAuthController.cs new file mode 100644 index 000000000..8f33e37be --- /dev/null +++ b/src/Ombi/Controllers/PlexOAuthController.cs @@ -0,0 +1,40 @@ +using System.Net; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Http.Internal; +using System.IO; +using System.Text; + +namespace Ombi.Controllers +{ + [ApiExplorerSettings(IgnoreApi = true)] + [ApiV1] + [AllowAnonymous] + public class PlexOAuthController : Controller + { + + [HttpGet] + public IActionResult OAuthCallBack() + { + var bodyStr = ""; + var req = Request; + + // Allows using several time the stream in ASP.Net Core + req.EnableRewind(); + + // Arguments: Stream, Encoding, detect encoding, buffer size + // AND, the most important: keep stream opened + using (StreamReader reader + = new StreamReader(req.Body, Encoding.UTF8, true, 1024, true)) + { + bodyStr = reader.ReadToEnd(); + } + + // Rewind, so the core is not lost when it looks the body for the request + req.Body.Position = 0; + + // Do your work with bodyStr + return Ok(); + } + } +} diff --git a/src/Ombi/Controllers/TokenController.cs b/src/Ombi/Controllers/TokenController.cs index 18da61e3a..5d524b7ba 100644 --- a/src/Ombi/Controllers/TokenController.cs +++ b/src/Ombi/Controllers/TokenController.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; using System.Linq; +using System.Net.Http; using System.Security.Claims; using System.Text; using System.Threading.Tasks; @@ -9,6 +10,7 @@ using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; +using Ombi.Api; using Ombi.Core.Authentication; using Ombi.Helpers; using Ombi.Models; @@ -62,50 +64,65 @@ namespace Ombi.Controllers user.EmailLogin = true; } - // Verify Password - if (await _userManager.CheckPasswordAsync(user, model.Password)) + if (!model.UsePlexOAuth) { - var roles = await _userManager.GetRolesAsync(user); - - if (roles.Contains(OmbiRoles.Disabled)) + // Verify Password + if (await _userManager.CheckPasswordAsync(user, model.Password)) { - return new UnauthorizedResult(); + var roles = await _userManager.GetRolesAsync(user); + + if (roles.Contains(OmbiRoles.Disabled)) + { + return new UnauthorizedResult(); + } + + user.LastLoggedIn = DateTime.UtcNow; + await _userManager.UpdateAsync(user); + + var claims = new List + { + new Claim(JwtRegisteredClaimNames.Sub, user.UserName), + new Claim(ClaimTypes.NameIdentifier, user.Id), + new Claim(ClaimTypes.Name, user.UserName), + new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) + }; + claims.AddRange(roles.Select(role => new Claim("role", role))); + + var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_tokenAuthenticationOptions.SecretKey)); + var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); + + + var token = new JwtSecurityToken( + claims: claims, + expires: model.RememberMe ? DateTime.UtcNow.AddDays(7) : DateTime.UtcNow.AddHours(5), + signingCredentials: creds, + audience: "Ombi", issuer: "Ombi" + ); + var accessToken = new JwtSecurityTokenHandler().WriteToken(token); + if (model.RememberMe) + { + // Save the token so we can refresh it later + //await _token.CreateToken(new Tokens() {Token = accessToken, User = user}); + } + + return new JsonResult(new + { + access_token = accessToken, + expiration = token.ValidTo + }); } + } + else + { + // Plex OAuth + // Redirect them to Plex - user.LastLoggedIn = DateTime.UtcNow; - await _userManager.UpdateAsync(user); + var request = new Request("auth", "https://app.plex.tv", HttpMethod.Get); + request.AddQueryString("clientID", "OMBIv3"); + request.AddQueryString("forwardUrl", "http://localhost:5000"); + request.AddQueryString("context-device-product", "http://localhost:5000"); + return new RedirectResult("https://app.plex.tv/auth#?forwardUrl=http://localhost:5000/api/v1/plexoauth&clientID=OMBIv3&context%5Bdevice%5D%5Bproduct%5D=Ombi%20SSO"); - var claims = new List - { - new Claim(JwtRegisteredClaimNames.Sub, user.UserName), - new Claim(ClaimTypes.NameIdentifier, user.Id), - new Claim(ClaimTypes.Name, user.UserName), - new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) - }; - claims.AddRange(roles.Select(role => new Claim("role", role))); - - var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_tokenAuthenticationOptions.SecretKey)); - var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); - - - var token = new JwtSecurityToken( - claims: claims, - expires: model.RememberMe ? DateTime.UtcNow.AddDays(7) : DateTime.UtcNow.AddHours(5), - signingCredentials: creds, - audience: "Ombi", issuer:"Ombi" - ); - var accessToken = new JwtSecurityTokenHandler().WriteToken(token); - if (model.RememberMe) - { - // Save the token so we can refresh it later - //await _token.CreateToken(new Tokens() {Token = accessToken, User = user}); - } - - return new JsonResult(new - { - access_token = accessToken, - expiration = token.ValidTo - }); } return new UnauthorizedResult(); diff --git a/src/Ombi/Models/UserAuthModel.cs b/src/Ombi/Models/UserAuthModel.cs index 5b3d69a29..046119821 100644 --- a/src/Ombi/Models/UserAuthModel.cs +++ b/src/Ombi/Models/UserAuthModel.cs @@ -6,5 +6,6 @@ public string Password { get; set; } public bool RememberMe { get; set; } public bool UsePlexAdminAccount { get; set; } + public bool UsePlexOAuth { get; set; } } } \ No newline at end of file From 2fc4ef2613a1e26c30e24e4e8ed6205ba30d04f0 Mon Sep 17 00:00:00 2001 From: Jamie Rees Date: Thu, 19 Apr 2018 14:26:22 +0100 Subject: [PATCH 2/6] Got plex oauth working! !wip --- src/Ombi.Api.Plex/IPlexApi.cs | 7 +- src/Ombi.Api.Plex/Models/OAuth/OAuthPin.cs | 27 ++++ src/Ombi.Api.Plex/PlexApi.cs | 79 +++++++++- src/Ombi.Api/Request.cs | 4 +- .../Authentication/PlexOAuthManager.cs | 76 ++++++++++ src/Ombi.Core/IPlexOAuthManager.cs | 16 +++ src/Ombi.DependencyInjection/IocExtensions.cs | 2 + .../Settings/Models/External/PlexSettings.cs | 2 +- src/Ombi/ClientApp/app/auth/auth.service.ts | 4 + src/Ombi/ClientApp/app/interfaces/IPlex.ts | 4 + .../app/services/applications/index.ts | 1 + .../app/services/applications/plex.service.ts | 4 + .../applications/plexoauth.service.ts | 20 +++ .../app/wizard/plex/plex.component.html | 7 + .../app/wizard/plex/plex.component.ts | 8 ++ .../app/wizard/plex/plexoauth.component.html | 14 ++ .../app/wizard/plex/plexoauth.component.ts | 76 ++++++++++ .../ClientApp/app/wizard/wizard.module.ts | 5 + .../Controllers/External/PlexController.cs | 38 ++++- src/Ombi/Controllers/PlexOAuthController.cs | 72 +++++++--- src/Ombi/Controllers/SettingsController.cs | 8 +- src/Ombi/Controllers/TokenController.cs | 135 +++++++++++------- 22 files changed, 535 insertions(+), 74 deletions(-) create mode 100644 src/Ombi.Api.Plex/Models/OAuth/OAuthPin.cs create mode 100644 src/Ombi.Core/Authentication/PlexOAuthManager.cs create mode 100644 src/Ombi.Core/IPlexOAuthManager.cs create mode 100644 src/Ombi/ClientApp/app/services/applications/plexoauth.service.ts create mode 100644 src/Ombi/ClientApp/app/wizard/plex/plexoauth.component.html create mode 100644 src/Ombi/ClientApp/app/wizard/plex/plexoauth.component.ts diff --git a/src/Ombi.Api.Plex/IPlexApi.cs b/src/Ombi.Api.Plex/IPlexApi.cs index 0734262cf..cc61dfa5d 100644 --- a/src/Ombi.Api.Plex/IPlexApi.cs +++ b/src/Ombi.Api.Plex/IPlexApi.cs @@ -1,6 +1,8 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; using Ombi.Api.Plex.Models; using Ombi.Api.Plex.Models.Friends; +using Ombi.Api.Plex.Models.OAuth; using Ombi.Api.Plex.Models.Server; using Ombi.Api.Plex.Models.Status; @@ -20,5 +22,8 @@ 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); + Uri GetOAuthUrl(int pinId, string code, string applicationUrl, bool wizard); } } \ No newline at end of file diff --git a/src/Ombi.Api.Plex/Models/OAuth/OAuthPin.cs b/src/Ombi.Api.Plex/Models/OAuth/OAuthPin.cs new file mode 100644 index 000000000..e65cd91d4 --- /dev/null +++ b/src/Ombi.Api.Plex/Models/OAuth/OAuthPin.cs @@ -0,0 +1,27 @@ +using System; + +namespace Ombi.Api.Plex.Models.OAuth +{ + public class OAuthPin + { + public int id { get; set; } + public string code { get; set; } + public bool trusted { get; set; } + public string clientIdentifier { get; set; } + public Location location { get; set; } + public int expiresIn { get; set; } + public DateTime createdAt { get; set; } + public DateTime expiresAt { get; set; } + public string authToken { get; set; } + } + + public class Location + { + public string code { get; set; } + public string country { get; set; } + public string city { get; set; } + public string subdivisions { get; set; } + public string coordinates { get; set; } + } + +} \ No newline at end of file diff --git a/src/Ombi.Api.Plex/PlexApi.cs b/src/Ombi.Api.Plex/PlexApi.cs index e6c52d1df..5fb87aca7 100644 --- a/src/Ombi.Api.Plex/PlexApi.cs +++ b/src/Ombi.Api.Plex/PlexApi.cs @@ -1,20 +1,49 @@ -using System.Net.Http; +using System; +using System.Net.Http; +using System.Reflection; using System.Threading.Tasks; using Ombi.Api.Plex.Models; using Ombi.Api.Plex.Models.Friends; +using Ombi.Api.Plex.Models.OAuth; using Ombi.Api.Plex.Models.Server; using Ombi.Api.Plex.Models.Status; +using Ombi.Core.Settings; +using Ombi.Core.Settings.Models.External; +using Ombi.Helpers; namespace Ombi.Api.Plex { public class PlexApi : IPlexApi { - public PlexApi(IApi api) + public PlexApi(IApi api, ISettingsService settings) { Api = api; + _plex = settings; } private IApi Api { get; } + private readonly ISettingsService _plex; + + private string _clientId; + private string ClientIdSecret + { + get + { + if (string.IsNullOrEmpty(_clientId)) + { + var settings = _plex.GetSettings(); + if (settings.UniqueInstallCode.IsNullOrEmpty()) + { + settings.UniqueInstallCode = Guid.NewGuid().ToString("N"); + _plex.SaveSettings(settings); + } + + _clientId = settings.UniqueInstallCode; + } + + return _clientId; + } + } private const string SignInUri = "https://plex.tv/users/sign_in.json"; private const string FriendsUri = "https://plex.tv/pms/friends/all"; @@ -156,6 +185,50 @@ 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"); + 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); + AddHeaders(request); + + return await Api.Request(request); + } + + public 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) + : new Request($"api/v1/PlexOAuth/{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][environment]", "bundled"); + request.AddQueryString("clientID", $"OmbiV3{ClientIdSecret}"); + + if (request.FullUri.Fragment.Equals("#")) + { + var uri = request.FullUri.ToString(); + var withoutEnd = uri.Remove(uri.Length - 1, 1); + var startOfQueryLocation = withoutEnd.IndexOf('?'); + var better = withoutEnd.Insert(startOfQueryLocation, "#"); + request.FullUri = new Uri(better); + } + + return request.FullUri; + } + /// /// Adds the required headers and also the authorization header /// @@ -173,7 +246,7 @@ namespace Ombi.Api.Plex /// private void AddHeaders(Request request) { - request.AddHeader("X-Plex-Client-Identifier", $"OmbiV3"); + request.AddHeader("X-Plex-Client-Identifier", $"OmbiV3{ClientIdSecret}"); request.AddHeader("X-Plex-Product", "Ombi"); request.AddHeader("X-Plex-Version", "3"); request.AddContentHeader("Content-Type", request.ContentType == ContentType.Json ? "application/json" : "application/xml"); diff --git a/src/Ombi.Api/Request.cs b/src/Ombi.Api/Request.cs index e4120ed9c..89c3a7f2d 100644 --- a/src/Ombi.Api/Request.cs +++ b/src/Ombi.Api/Request.cs @@ -10,7 +10,7 @@ namespace Ombi.Api { public Request() { - + } public Request(string endpoint, string baseUrl, HttpMethod http, ContentType contentType = ContentType.Json) @@ -105,10 +105,10 @@ namespace Ombi.Api hasQuery = true; startingTag = builder.Query.Contains("?") ? "&" : "?"; } - builder.Query = hasQuery ? $"{builder.Query}{startingTag}{key}={value}" : $"{startingTag}{key}={value}"; + _modified = builder.Uri; } diff --git a/src/Ombi.Core/Authentication/PlexOAuthManager.cs b/src/Ombi.Core/Authentication/PlexOAuthManager.cs new file mode 100644 index 000000000..d3bab0a05 --- /dev/null +++ b/src/Ombi.Core/Authentication/PlexOAuthManager.cs @@ -0,0 +1,76 @@ +using System; +using System.Threading.Tasks; +using Ombi.Api.Plex; +using Ombi.Api.Plex.Models; +using Ombi.Api.Plex.Models.OAuth; +using Ombi.Core.Settings; +using Ombi.Helpers; +using Ombi.Settings.Settings.Models; + +namespace Ombi.Core.Authentication +{ + public class PlexOAuthManager : IPlexOAuthManager + { + public PlexOAuthManager(IPlexApi api, ISettingsService settings) + { + _api = api; + _customizationSettingsService = settings; + } + + 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); + if (pin.expiresAt < DateTime.UtcNow) + { + 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; + } + + public async Task GetAccount(string accessToken) + { + return await _api.GetAccount(accessToken); + } + + public async Task GetOAuthUrl(int pinId, string code) + { + var settings = await _customizationSettingsService.GetSettingsAsync(); + if (settings.ApplicationUrl.IsNullOrEmpty()) + { + return null; + } + + var url = _api.GetOAuthUrl(pinId, code, settings.ApplicationUrl, false); + return url; + } + + public Uri GetWizardOAuthUrl(int pinId, string code, string websiteAddress) + { + var url = _api.GetOAuthUrl(pinId, code, websiteAddress, true); + return url; + } + } +} \ No newline at end of file diff --git a/src/Ombi.Core/IPlexOAuthManager.cs b/src/Ombi.Core/IPlexOAuthManager.cs new file mode 100644 index 000000000..142d4162a --- /dev/null +++ b/src/Ombi.Core/IPlexOAuthManager.cs @@ -0,0 +1,16 @@ +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); + Uri GetWizardOAuthUrl(int pinId, string code, string websiteAddress); + Task GetAccount(string accessToken); + } +} \ No newline at end of file diff --git a/src/Ombi.DependencyInjection/IocExtensions.cs b/src/Ombi.DependencyInjection/IocExtensions.cs index e2f667465..68f4b7218 100644 --- a/src/Ombi.DependencyInjection/IocExtensions.cs +++ b/src/Ombi.DependencyInjection/IocExtensions.cs @@ -51,6 +51,7 @@ using Ombi.Store.Repository.Requests; using Ombi.Updater; using PlexContentCacher = Ombi.Schedule.Jobs.Plex; using Ombi.Api.Telegram; +using Ombi.Core.Authentication; using Ombi.Core.Processor; using Ombi.Schedule.Jobs.Plex.Interfaces; using Ombi.Schedule.Jobs.SickRage; @@ -82,6 +83,7 @@ namespace Ombi.DependencyInjection services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); } public static void RegisterHttp(this IServiceCollection services) { diff --git a/src/Ombi.Settings/Settings/Models/External/PlexSettings.cs b/src/Ombi.Settings/Settings/Models/External/PlexSettings.cs index 3fcde951a..dd92eba18 100644 --- a/src/Ombi.Settings/Settings/Models/External/PlexSettings.cs +++ b/src/Ombi.Settings/Settings/Models/External/PlexSettings.cs @@ -6,8 +6,8 @@ namespace Ombi.Core.Settings.Models.External public sealed class PlexSettings : Ombi.Settings.Settings.Models.Settings { public bool Enable { get; set; } + public string UniqueInstallCode { get; set; } public List Servers { get; set; } - } public class PlexServers : ExternalSettings diff --git a/src/Ombi/ClientApp/app/auth/auth.service.ts b/src/Ombi/ClientApp/app/auth/auth.service.ts index b9899c9a4..92b41ccd9 100644 --- a/src/Ombi/ClientApp/app/auth/auth.service.ts +++ b/src/Ombi/ClientApp/app/auth/auth.service.ts @@ -18,6 +18,10 @@ export class AuthService extends ServiceHelpers { return this.http.post(`${this.url}/`, JSON.stringify(login), {headers: this.headers}); } + public oAuth(pin: number): Observable { + return this.http.get(`${this.url}/${pin}`, {headers: this.headers}); + } + public requiresPassword(login: IUserLogin): Observable { return this.http.post(`${this.url}/requirePassword`, JSON.stringify(login), {headers: this.headers}); } diff --git a/src/Ombi/ClientApp/app/interfaces/IPlex.ts b/src/Ombi/ClientApp/app/interfaces/IPlex.ts index 7125ae4bc..823b80d32 100644 --- a/src/Ombi/ClientApp/app/interfaces/IPlex.ts +++ b/src/Ombi/ClientApp/app/interfaces/IPlex.ts @@ -2,6 +2,10 @@ user: IPlexUser; } +export interface IPlexOAuthAccessToken { + accessToken: string; +} + export interface IPlexUser { email: string; uuid: string; diff --git a/src/Ombi/ClientApp/app/services/applications/index.ts b/src/Ombi/ClientApp/app/services/applications/index.ts index 9433dfce0..98c61cf04 100644 --- a/src/Ombi/ClientApp/app/services/applications/index.ts +++ b/src/Ombi/ClientApp/app/services/applications/index.ts @@ -4,3 +4,4 @@ export * from "./plex.service"; export * from "./radarr.service"; export * from "./sonarr.service"; export * from "./tester.service"; +export * from "./plexoauth.service"; diff --git a/src/Ombi/ClientApp/app/services/applications/plex.service.ts b/src/Ombi/ClientApp/app/services/applications/plex.service.ts index c04a990e1..53fd31f9d 100644 --- a/src/Ombi/ClientApp/app/services/applications/plex.service.ts +++ b/src/Ombi/ClientApp/app/services/applications/plex.service.ts @@ -29,4 +29,8 @@ export class PlexService extends ServiceHelpers { public getFriends(): Observable { 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}); + } } diff --git a/src/Ombi/ClientApp/app/services/applications/plexoauth.service.ts b/src/Ombi/ClientApp/app/services/applications/plexoauth.service.ts new file mode 100644 index 000000000..59a884714 --- /dev/null +++ b/src/Ombi/ClientApp/app/services/applications/plexoauth.service.ts @@ -0,0 +1,20 @@ +import { PlatformLocation } from "@angular/common"; +import { HttpClient } from "@angular/common/http"; +import { Injectable } from "@angular/core"; + +import { Observable } from "rxjs/Rx"; + +import { ServiceHelpers } from "../service.helpers"; + +import { IPlexOAuthAccessToken } from "../../interfaces"; + +@Injectable() +export class PlexOAuthService extends ServiceHelpers { + constructor(http: HttpClient, public platformLocation: PlatformLocation) { + super(http, "/api/v1/PlexOAuth/", platformLocation); + } + + public oAuth(pin: number): Observable { + return this.http.get(`${this.url}${pin}`, {headers: this.headers}); + } +} diff --git a/src/Ombi/ClientApp/app/wizard/plex/plex.component.html b/src/Ombi/ClientApp/app/wizard/plex/plex.component.html index 2ba63c43e..6c4b846a7 100644 --- a/src/Ombi/ClientApp/app/wizard/plex/plex.component.html +++ b/src/Ombi/ClientApp/app/wizard/plex/plex.component.html @@ -20,6 +20,13 @@ + +
+
+ +
+
+ diff --git a/src/Ombi/ClientApp/app/wizard/plex/plex.component.ts b/src/Ombi/ClientApp/app/wizard/plex/plex.component.ts index 146d794fe..6bc21d628 100644 --- a/src/Ombi/ClientApp/app/wizard/plex/plex.component.ts +++ b/src/Ombi/ClientApp/app/wizard/plex/plex.component.ts @@ -70,4 +70,12 @@ export class PlexComponent { }); }); } + + public oauth() { + this.plexService.oAuth(true).subscribe(x => { + if(x.url) { + window.location.href = x.url; + } + }); + } } diff --git a/src/Ombi/ClientApp/app/wizard/plex/plexoauth.component.html b/src/Ombi/ClientApp/app/wizard/plex/plexoauth.component.html new file mode 100644 index 000000000..d60f1d1f0 --- /dev/null +++ b/src/Ombi/ClientApp/app/wizard/plex/plexoauth.component.html @@ -0,0 +1,14 @@ + + +
+
+
+

Plex Authentication

+
+ +
+
+
+
+ + \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/wizard/plex/plexoauth.component.ts b/src/Ombi/ClientApp/app/wizard/plex/plexoauth.component.ts new file mode 100644 index 000000000..1397fe7f5 --- /dev/null +++ b/src/Ombi/ClientApp/app/wizard/plex/plexoauth.component.ts @@ -0,0 +1,76 @@ +import { Component, OnInit } from "@angular/core"; +import { ActivatedRoute, Router } from "@angular/router"; + +import { ConfirmationService } from "primeng/primeng"; + +import { PlexOAuthService, IdentityService, SettingsService } from "../../services"; +import { AuthService } from "./../../auth/auth.service"; + +@Component({ + templateUrl: "./plexoauth.component.html", +}) +export class PlexOAuthComponent implements OnInit { + public pinId: number; + + constructor(private route: ActivatedRoute, + private plexOauth: PlexOAuthService, + private confirmationService: ConfirmationService, + private identityService: IdentityService, + private settings: SettingsService, + private router: Router, + private auth: AuthService) { + + this.route.params + .subscribe((params: any) => { + this.pinId = params.pin; + }); + } + + ngOnInit(): void { + this.plexOauth.oAuth(this.pinId).subscribe(x => { + x.accessToken; + + this.confirmationService.confirm({ + message: "Do you want your Plex user to be the main admin account on Ombi?", + header: "Use Plex Account", + icon: "fa fa-check", + accept: () => { + this.identityService.createWizardUser({ + username: "", + password: "", + usePlexAdminAccount: true, + }).subscribe(x => { + if (x) { + this.auth.oAuth(this.pinId).subscribe(c => { + localStorage.setItem("id_token", c.access_token); + + // Mark that we have done the settings now + this.settings.getOmbi().subscribe(ombi => { + ombi.wizard = true; + + this.settings.saveOmbi(ombi).subscribe(x => { + this.settings.getUserManagementSettings().subscribe(usr => { + + usr.importPlexAdmin = true; + this.settings.saveUserManagementSettings(usr).subscribe(saved => { + this.router.navigate(["login"]); + }); + }); + + }); + }); + }); + } else { + //this.notificationService.error("Could not get the Plex Admin Information"); + return; + } + }); + }, + reject: () => { + this.router.navigate(["Wizard/CreateAdmin"]); + }, + }); + }); + } + +} diff --git a/src/Ombi/ClientApp/app/wizard/wizard.module.ts b/src/Ombi/ClientApp/app/wizard/wizard.module.ts index 96cbdddc1..7eae2e4f4 100644 --- a/src/Ombi/ClientApp/app/wizard/wizard.module.ts +++ b/src/Ombi/ClientApp/app/wizard/wizard.module.ts @@ -14,6 +14,8 @@ import { WelcomeComponent } from "./welcome/welcome.component"; import { EmbyService } from "../services"; import { PlexService } from "../services"; import { IdentityService } from "../services"; +import { PlexOAuthService } from "../services"; +import { PlexOAuthComponent } from "./plex/plexoauth.component"; const routes: Routes = [ { path: "", component: WelcomeComponent}, @@ -21,6 +23,7 @@ const routes: Routes = [ { path: "Plex", component: PlexComponent}, { path: "Emby", component: EmbyComponent}, { path: "CreateAdmin", component: CreateAdminComponent}, + { path: "OAuth/:pin", component: PlexOAuthComponent}, ]; @NgModule({ imports: [ @@ -33,6 +36,7 @@ const routes: Routes = [ WelcomeComponent, MediaServerComponent, PlexComponent, + PlexOAuthComponent, CreateAdminComponent, EmbyComponent, ], @@ -44,6 +48,7 @@ const routes: Routes = [ IdentityService, EmbyService, ConfirmationService, + PlexOAuthService, ], }) diff --git a/src/Ombi/Controllers/External/PlexController.cs b/src/Ombi/Controllers/External/PlexController.cs index 2d45d7565..cde78bc64 100644 --- a/src/Ombi/Controllers/External/PlexController.cs +++ b/src/Ombi/Controllers/External/PlexController.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.Logging; using Ombi.Api.Plex; using Ombi.Api.Plex.Models; using Ombi.Attributes; +using Ombi.Core.Authentication; using Ombi.Core.Settings; using Ombi.Core.Settings.Models.External; using Ombi.Helpers; @@ -21,16 +22,18 @@ namespace Ombi.Controllers.External public class PlexController : Controller { public PlexController(IPlexApi plexApi, ISettingsService plexSettings, - ILogger logger) + ILogger logger, IPlexOAuthManager manager) { PlexApi = plexApi; PlexSettings = plexSettings; _log = logger; + _plexOAuthManager = manager; } private IPlexApi PlexApi { get; } private ISettingsService PlexSettings { get; } private readonly ILogger _log; + private readonly IPlexOAuthManager _plexOAuthManager; /// /// Signs into the Plex API. @@ -66,6 +69,7 @@ namespace Ombi.Controllers.External _log.LogDebug("Adding first server"); settings.Enable = true; + settings.UniqueInstallCode = Guid.NewGuid().ToString("N"); settings.Servers = new List { new PlexServers { @@ -173,5 +177,37 @@ namespace Ombi.Controllers.External // Filter out any dupes return vm.DistinctBy(x => x.Id); } + + [HttpGet("oauth/{wizard:bool}")] + [AllowAnonymous] + public async Task OAuth(bool 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) + { + url = await _plexOAuthManager.GetOAuthUrl(pin.id, pin.code); + } + else + { + var websiteAddress =$"{this.Request.Scheme}://{this.Request.Host}{this.Request.PathBase}"; + url = _plexOAuthManager.GetWizardOAuthUrl(pin.id, pin.code, websiteAddress); + } + + if (url == null) + { + return new JsonResult(new + { + error = "Application URL has not been set" + }); + } + + return new JsonResult(new {url = url.ToString()}); + } } } diff --git a/src/Ombi/Controllers/PlexOAuthController.cs b/src/Ombi/Controllers/PlexOAuthController.cs index 8f33e37be..2aad2a2a9 100644 --- a/src/Ombi/Controllers/PlexOAuthController.cs +++ b/src/Ombi/Controllers/PlexOAuthController.cs @@ -1,9 +1,19 @@ -using System.Net; +using System; +using System.Collections.Generic; +using System.Net; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Http.Internal; using System.IO; +using System.Linq; using System.Text; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Ombi.Api.Plex; +using Ombi.Core.Authentication; +using Ombi.Core.Settings; +using Ombi.Core.Settings.Models.External; +using Ombi.Helpers; namespace Ombi.Controllers { @@ -12,29 +22,57 @@ namespace Ombi.Controllers [AllowAnonymous] public class PlexOAuthController : Controller { - - [HttpGet] - public IActionResult OAuthCallBack() + public PlexOAuthController(IPlexOAuthManager manager, IPlexApi plexApi, ISettingsService plexSettings, + ILogger log) { - var bodyStr = ""; - var req = Request; + _manager = manager; + _plexApi = plexApi; + _plexSettings = plexSettings; + _log = log; + } - // Allows using several time the stream in ASP.Net Core - req.EnableRewind(); + private readonly IPlexOAuthManager _manager; + private readonly IPlexApi _plexApi; + private readonly ISettingsService _plexSettings; + private readonly ILogger _log; - // Arguments: Stream, Encoding, detect encoding, buffer size - // AND, the most important: keep stream opened - using (StreamReader reader - = new StreamReader(req.Body, Encoding.UTF8, true, 1024, true)) + [HttpGet("{pinId:int}")] + public async Task OAuthWizardCallBack([FromRoute] int pinId) + { + var accessToken = await _manager.GetAccessTokenFromPin(pinId); + if (accessToken.IsNullOrEmpty()) { - bodyStr = reader.ReadToEnd(); + return Json(new + { + success = false, + error = "Authentication did not work. Please try again" + }); } + var settings = await _plexSettings.GetSettingsAsync(); + var server = await _plexApi.GetServer(accessToken); + var servers = server.Server.FirstOrDefault(); + if (servers == null) + { + _log.LogWarning("Looks like we can't find any Plex Servers"); + } + _log.LogDebug("Adding first server"); - // Rewind, so the core is not lost when it looks the body for the request - req.Body.Position = 0; + settings.Enable = true; + settings.Servers = new List { + new PlexServers + { + PlexAuthToken = accessToken, + Id = new Random().Next(), + Ip = servers?.LocalAddresses?.Split(new []{','}, StringSplitOptions.RemoveEmptyEntries)?.FirstOrDefault() ?? string.Empty, + MachineIdentifier = servers?.MachineIdentifier ?? string.Empty, + Port = int.Parse(servers?.Port ?? "0"), + Ssl = (servers?.Scheme ?? "http") != "http", + Name = "Server 1", + } + }; - // Do your work with bodyStr - return Ok(); + await _plexSettings.SaveSettingsAsync(settings); + return Json(new { accessToken }); } } } diff --git a/src/Ombi/Controllers/SettingsController.cs b/src/Ombi/Controllers/SettingsController.cs index 30469cd57..867a7517c 100644 --- a/src/Ombi/Controllers/SettingsController.cs +++ b/src/Ombi/Controllers/SettingsController.cs @@ -142,7 +142,13 @@ namespace Ombi.Controllers [HttpGet("plex")] public async Task PlexSettings() { - return await Get(); + var s = await Get(); + if (s.UniqueInstallCode.IsNullOrEmpty()) + { + s.UniqueInstallCode = Guid.NewGuid().ToString("N"); + } + + return s; } /// diff --git a/src/Ombi/Controllers/TokenController.cs b/src/Ombi/Controllers/TokenController.cs index 5d524b7ba..62e43635d 100644 --- a/src/Ombi/Controllers/TokenController.cs +++ b/src/Ombi/Controllers/TokenController.cs @@ -25,18 +25,21 @@ namespace Ombi.Controllers [Produces("application/json")] public class TokenController { - public TokenController(OmbiUserManager um, IOptions ta, IAuditRepository audit, ITokenRepository token) + public TokenController(OmbiUserManager um, IOptions ta, IAuditRepository audit, ITokenRepository token, + IPlexOAuthManager oAuthManager) { _userManager = um; _tokenAuthenticationOptions = ta.Value; _audit = audit; _token = token; + _plexOAuthManager = oAuthManager; } private readonly TokenAuthentication _tokenAuthenticationOptions; private readonly IAuditRepository _audit; private readonly ITokenRepository _token; private readonly OmbiUserManager _userManager; + private readonly IPlexOAuthManager _plexOAuthManager; /// /// Gets the token. @@ -69,65 +72,101 @@ namespace Ombi.Controllers // Verify Password if (await _userManager.CheckPasswordAsync(user, model.Password)) { - var roles = await _userManager.GetRolesAsync(user); - - if (roles.Contains(OmbiRoles.Disabled)) - { - return new UnauthorizedResult(); - } - - user.LastLoggedIn = DateTime.UtcNow; - await _userManager.UpdateAsync(user); - - var claims = new List - { - new Claim(JwtRegisteredClaimNames.Sub, user.UserName), - new Claim(ClaimTypes.NameIdentifier, user.Id), - new Claim(ClaimTypes.Name, user.UserName), - new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) - }; - claims.AddRange(roles.Select(role => new Claim("role", role))); - - var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_tokenAuthenticationOptions.SecretKey)); - var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); - - - var token = new JwtSecurityToken( - claims: claims, - expires: model.RememberMe ? DateTime.UtcNow.AddDays(7) : DateTime.UtcNow.AddHours(5), - signingCredentials: creds, - audience: "Ombi", issuer: "Ombi" - ); - var accessToken = new JwtSecurityTokenHandler().WriteToken(token); - if (model.RememberMe) - { - // Save the token so we can refresh it later - //await _token.CreateToken(new Tokens() {Token = accessToken, User = user}); - } - - return new JsonResult(new - { - access_token = accessToken, - expiration = token.ValidTo - }); + return await CreateToken(model.RememberMe, user); } } else { // Plex OAuth // Redirect them to Plex + // We need a PIN first + var pin = await _plexOAuthManager.RequestPin(); - var request = new Request("auth", "https://app.plex.tv", HttpMethod.Get); - request.AddQueryString("clientID", "OMBIv3"); - request.AddQueryString("forwardUrl", "http://localhost:5000"); - request.AddQueryString("context-device-product", "http://localhost:5000"); - return new RedirectResult("https://app.plex.tv/auth#?forwardUrl=http://localhost:5000/api/v1/plexoauth&clientID=OMBIv3&context%5Bdevice%5D%5Bproduct%5D=Ombi%20SSO"); - + //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); + if (url == null) + { + return new JsonResult(new + { + error = "Application URL has not been set" + }); + } + return new RedirectResult(url.ToString()); } return new UnauthorizedResult(); } + private async Task CreateToken(bool rememberMe, OmbiUser user) + { + var roles = await _userManager.GetRolesAsync(user); + + if (roles.Contains(OmbiRoles.Disabled)) + { + return new UnauthorizedResult(); + } + + user.LastLoggedIn = DateTime.UtcNow; + await _userManager.UpdateAsync(user); + + var claims = new List + { + new Claim(JwtRegisteredClaimNames.Sub, user.UserName), + new Claim(ClaimTypes.NameIdentifier, user.Id), + new Claim(ClaimTypes.Name, user.UserName), + new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) + }; + claims.AddRange(roles.Select(role => new Claim("role", role))); + + var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_tokenAuthenticationOptions.SecretKey)); + var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); + + + var token = new JwtSecurityToken( + claims: claims, + expires: rememberMe ? DateTime.UtcNow.AddDays(7) : DateTime.UtcNow.AddHours(5), + signingCredentials: creds, + audience: "Ombi", issuer: "Ombi" + ); + var accessToken = new JwtSecurityTokenHandler().WriteToken(token); + if (rememberMe) + { + // Save the token so we can refresh it later + //await _token.CreateToken(new Tokens() {Token = accessToken, User = user}); + } + + return new JsonResult(new + { + access_token = accessToken, + expiration = token.ValidTo + }); + } + + [HttpGet("{pinId:int}")] + public async Task OAuth(int pinId) + { + var accessToken = await _plexOAuthManager.GetAccessTokenFromPin(pinId); + + // Let's look for the users account + var account = await _plexOAuthManager.GetAccount(accessToken); + + // Get the ombi user + var user = await _userManager.FindByNameAsync(account.user.username); + + if (user == null) + { + // Could this be an email login? + user = await _userManager.FindByEmailAsync(account.user.email); + + if (user == null) + { + return new UnauthorizedResult(); + } + } + + return await CreateToken(true, user); + } + /// /// Refreshes the token. /// From 2cd9c4d9b123f4849c11304d5fd2e159553f32c7 Mon Sep 17 00:00:00 2001 From: Jamie Rees Date: Thu, 19 Apr 2018 22:15:20 +0100 Subject: [PATCH 3/6] frontend is all done !wip --- src/Ombi.Api.Plex/PlexApi.cs | 2 +- src/Ombi/ClientApp/app/app.module.ts | 3 ++ src/Ombi/ClientApp/app/auth/IUserLogin.ts | 1 + .../ClientApp/app/login/login.component.html | 12 ++++++ .../ClientApp/app/login/login.component.ts | 39 ++++++++++++++++++- .../app/login/loginoauth.component.html | 9 +++++ .../app/login/loginoauth.component.ts | 35 +++++++++++++++++ .../app/services/settings.service.ts | 4 ++ .../createadmin/createadmin.component.ts | 2 +- .../app/wizard/plex/plex.component.html | 2 +- .../app/wizard/plex/plex.component.ts | 24 ++++-------- .../app/wizard/plex/plexoauth.component.ts | 26 +++++-------- src/Ombi/Controllers/SettingsController.cs | 10 +++++ src/Ombi/Controllers/TokenController.cs | 29 +++++++------- 14 files changed, 146 insertions(+), 52 deletions(-) create mode 100644 src/Ombi/ClientApp/app/login/loginoauth.component.html create mode 100644 src/Ombi/ClientApp/app/login/loginoauth.component.ts diff --git a/src/Ombi.Api.Plex/PlexApi.cs b/src/Ombi.Api.Plex/PlexApi.cs index 5fb87aca7..6d814adf3 100644 --- a/src/Ombi.Api.Plex/PlexApi.cs +++ b/src/Ombi.Api.Plex/PlexApi.cs @@ -208,7 +208,7 @@ namespace Ombi.Api.Plex AddHeaders(request); var forwardUrl = wizard ? new Request($"Wizard/OAuth/{pinId}", applicationUrl, HttpMethod.Get) - : new Request($"api/v1/PlexOAuth/{pinId}", applicationUrl, HttpMethod.Get); + : new Request($"Login/OAuth/{pinId}", applicationUrl, HttpMethod.Get); request.AddQueryString("forwardUrl", forwardUrl.FullUri.ToString()); request.AddQueryString("pinID", pinId.ToString()); diff --git a/src/Ombi/ClientApp/app/app.module.ts b/src/Ombi/ClientApp/app/app.module.ts index 0936b13cd..1421b1117 100644 --- a/src/Ombi/ClientApp/app/app.module.ts +++ b/src/Ombi/ClientApp/app/app.module.ts @@ -24,6 +24,7 @@ import { CookieComponent } from "./auth/cookie.component"; import { PageNotFoundComponent } from "./errors/not-found.component"; import { LandingPageComponent } from "./landingpage/landingpage.component"; import { LoginComponent } from "./login/login.component"; +import { LoginOAuthComponent } from "./login/loginoauth.component"; import { ResetPasswordComponent } from "./login/resetpassword.component"; import { TokenResetPasswordComponent } from "./login/tokenresetpassword.component"; @@ -41,6 +42,7 @@ const routes: Routes = [ { path: "*", component: PageNotFoundComponent }, { path: "", redirectTo: "/search", pathMatch: "full" }, { path: "login", component: LoginComponent }, + { path: "Login/OAuth/:pin", component: LoginOAuthComponent }, { path: "login/:landing", component: LoginComponent }, { path: "reset", component: ResetPasswordComponent }, { path: "token", component: TokenResetPasswordComponent }, @@ -116,6 +118,7 @@ export function HttpLoaderFactory(http: HttpClient, platformLocation: PlatformLo ResetPasswordComponent, TokenResetPasswordComponent, CookieComponent, + LoginOAuthComponent, ], providers: [ NotificationService, diff --git a/src/Ombi/ClientApp/app/auth/IUserLogin.ts b/src/Ombi/ClientApp/app/auth/IUserLogin.ts index af152a6b1..609055c8e 100644 --- a/src/Ombi/ClientApp/app/auth/IUserLogin.ts +++ b/src/Ombi/ClientApp/app/auth/IUserLogin.ts @@ -2,6 +2,7 @@ username: string; password: string; rememberMe: boolean; + usePlexOAuth: boolean; } export interface ILocalUser { diff --git a/src/Ombi/ClientApp/app/login/login.component.html b/src/Ombi/ClientApp/app/login/login.component.html index 5fc0150ff..528fe9917 100644 --- a/src/Ombi/ClientApp/app/login/login.component.html +++ b/src/Ombi/ClientApp/app/login/login.component.html @@ -7,11 +7,13 @@ include the remember me checkbox
+

+
+
+ + + diff --git a/src/Ombi/ClientApp/app/login/login.component.ts b/src/Ombi/ClientApp/app/login/login.component.ts index 661850e86..cde2ef085 100644 --- a/src/Ombi/ClientApp/app/login/login.component.ts +++ b/src/Ombi/ClientApp/app/login/login.component.ts @@ -25,9 +25,39 @@ export class LoginComponent implements OnDestroy, OnInit { public form: FormGroup; public customizationSettings: ICustomizationSettings; public authenticationSettings: IAuthenticationSettings; + public plexEnabled: boolean; public background: any; public landingFlag: boolean; public baseUrl: string; + + public get showLoginForm(): boolean { + if(this.customizationSettings.applicationUrl && this.plexEnabled) { + this.loginWithOmbi = false; + return false; + } + if(!this.customizationSettings.applicationUrl || !this.plexEnabled) { + + this.loginWithOmbi = true; + return true; + } + if(this.loginWithOmbi) { + return true; + } + + + this.loginWithOmbi = true; + return true; + } + public loginWithOmbi: boolean = false; + + public get appName(): string { + if(this.customizationSettings.applicationName) { + return this.customizationSettings.applicationName; + } else { + return "Ombi"; + } + } + private timer: any; private errorBody: string; @@ -68,6 +98,7 @@ export class LoginComponent implements OnDestroy, OnInit { public ngOnInit() { this.settingsService.getAuthentication().subscribe(x => this.authenticationSettings = x); this.settingsService.getCustomization().subscribe(x => this.customizationSettings = x); + this.settingsService.getStatusPlex().subscribe(x => this.plexEnabled = 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 + ")"); }); @@ -90,7 +121,7 @@ export class LoginComponent implements OnDestroy, OnInit { return; } const value = form.value; - const user = { password: value.password, username: value.username, rememberMe:value.rememberMe }; + const user = { password: value.password, username: value.username, rememberMe: value.rememberMe, usePlexOAuth: false }; this.authService.requiresPassword(user).subscribe(x => { if(x && this.authenticationSettings.allowNoPassword) { // Looks like this user requires a password @@ -111,6 +142,12 @@ export class LoginComponent implements OnDestroy, OnInit { }); } + public oauth() { + this.authService.login({usePlexOAuth: true, password:"",rememberMe:true,username:""}).subscribe(x => { + window.location.href = x.url; + }) + } + public ngOnDestroy() { clearInterval(this.timer); } diff --git a/src/Ombi/ClientApp/app/login/loginoauth.component.html b/src/Ombi/ClientApp/app/login/loginoauth.component.html new file mode 100644 index 000000000..e80f1239f --- /dev/null +++ b/src/Ombi/ClientApp/app/login/loginoauth.component.html @@ -0,0 +1,9 @@ + +
+ +
+
+ +
+
+
diff --git a/src/Ombi/ClientApp/app/login/loginoauth.component.ts b/src/Ombi/ClientApp/app/login/loginoauth.component.ts new file mode 100644 index 000000000..a0a9a9414 --- /dev/null +++ b/src/Ombi/ClientApp/app/login/loginoauth.component.ts @@ -0,0 +1,35 @@ +import { Component, OnInit } from "@angular/core"; +import { ActivatedRoute, Router } from "@angular/router"; + +import { AuthService } from "../auth/auth.service"; + +@Component({ + templateUrl: "./loginoauth.component.html" +}) +export class LoginOAuthComponent implements OnInit { + public pin: number; + + constructor(private authService: AuthService, private router: Router, + private route: ActivatedRoute) { + this.route.params + .subscribe((params: any) => { + this.pin = params.pin; + + }); + } + + ngOnInit(): void { + this.auth(); + } + + public auth() { + this.authService.oAuth(this.pin).subscribe(x => { + localStorage.setItem("id_token", x.access_token); + + if (this.authService.loggedIn()) { + this.router.navigate(["search"]); + } + + }); + } +} diff --git a/src/Ombi/ClientApp/app/services/settings.service.ts b/src/Ombi/ClientApp/app/services/settings.service.ts index f6f770a19..61a85874e 100644 --- a/src/Ombi/ClientApp/app/services/settings.service.ts +++ b/src/Ombi/ClientApp/app/services/settings.service.ts @@ -71,6 +71,10 @@ export class SettingsService extends ServiceHelpers { return this.http.get(`${this.url}/Plex/`, {headers: this.headers}); } + public getStatusPlex(): Observable { + return this.http.get(`${this.url}/Plexstatus/`, {headers: this.headers}); + } + public savePlex(settings: IPlexSettings): Observable { return this.http.post(`${this.url}/Plex/`, JSON.stringify(settings), {headers: this.headers}); } diff --git a/src/Ombi/ClientApp/app/wizard/createadmin/createadmin.component.ts b/src/Ombi/ClientApp/app/wizard/createadmin/createadmin.component.ts index 481c96a99..07f47f265 100644 --- a/src/Ombi/ClientApp/app/wizard/createadmin/createadmin.component.ts +++ b/src/Ombi/ClientApp/app/wizard/createadmin/createadmin.component.ts @@ -21,7 +21,7 @@ export class CreateAdminComponent { this.identityService.createWizardUser({username: this.username, password: this.password, usePlexAdminAccount: false}).subscribe(x => { if (x) { // Log me in. - this.auth.login({ username: this.username, password: this.password, rememberMe:false }).subscribe(c => { + this.auth.login({ username: this.username, password: this.password, rememberMe: false, usePlexOAuth:false }).subscribe(c => { localStorage.setItem("id_token", c.access_token); diff --git a/src/Ombi/ClientApp/app/wizard/plex/plex.component.html b/src/Ombi/ClientApp/app/wizard/plex/plex.component.html index 6c4b846a7..5d69c435b 100644 --- a/src/Ombi/ClientApp/app/wizard/plex/plex.component.html +++ b/src/Ombi/ClientApp/app/wizard/plex/plex.component.html @@ -23,7 +23,7 @@
- +
diff --git a/src/Ombi/ClientApp/app/wizard/plex/plex.component.ts b/src/Ombi/ClientApp/app/wizard/plex/plex.component.ts index 6bc21d628..2f5b2dd79 100644 --- a/src/Ombi/ClientApp/app/wizard/plex/plex.component.ts +++ b/src/Ombi/ClientApp/app/wizard/plex/plex.component.ts @@ -1,7 +1,6 @@ import { Component } from "@angular/core"; import { Router } from "@angular/router"; -import { ConfirmationService } from "primeng/primeng"; import { PlexService } from "../../services"; import { IdentityService, NotificationService, SettingsService } from "../../services"; @@ -17,7 +16,6 @@ export class PlexComponent { constructor(private plexService: PlexService, private router: Router, private notificationService: NotificationService, - private confirmationService: ConfirmationService, private identityService: IdentityService, private settings: SettingsService, private auth: AuthService) { } @@ -28,25 +26,21 @@ export class PlexComponent { this.notificationService.error("Username or password was incorrect. Could not authenticate with Plex."); return; } - this.confirmationService.confirm({ - message: "Do you want your Plex user to be the main admin account on Ombi?", - header: "Use Plex Account", - icon: "fa fa-check", - accept: () => { + this.identityService.createWizardUser({ username: "", password: "", usePlexAdminAccount: true, - }).subscribe(x => { - if (x) { - this.auth.login({ username: this.login, password: this.password, rememberMe:false }).subscribe(c => { + }).subscribe(y => { + if (y) { + this.auth.login({ username: this.login, password: this.password, rememberMe: false, usePlexOAuth: false }).subscribe(c => { localStorage.setItem("id_token", c.access_token); // Mark that we have done the settings now this.settings.getOmbi().subscribe(ombi => { ombi.wizard = true; - this.settings.saveOmbi(ombi).subscribe(x => { + this.settings.saveOmbi(ombi).subscribe(s => { this.settings.getUserManagementSettings().subscribe(usr => { usr.importPlexAdmin = true; @@ -63,12 +57,8 @@ export class PlexComponent { return; } }); - }, - reject: () => { - this.router.navigate(["Wizard/CreateAdmin"]); - }, - }); - }); + } + ); } public oauth() { diff --git a/src/Ombi/ClientApp/app/wizard/plex/plexoauth.component.ts b/src/Ombi/ClientApp/app/wizard/plex/plexoauth.component.ts index 1397fe7f5..7d0d0bf9d 100644 --- a/src/Ombi/ClientApp/app/wizard/plex/plexoauth.component.ts +++ b/src/Ombi/ClientApp/app/wizard/plex/plexoauth.component.ts @@ -1,8 +1,6 @@ import { Component, OnInit } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router"; -import { ConfirmationService } from "primeng/primeng"; - import { PlexOAuthService, IdentityService, SettingsService } from "../../services"; import { AuthService } from "./../../auth/auth.service"; @@ -14,7 +12,6 @@ export class PlexOAuthComponent implements OnInit { constructor(private route: ActivatedRoute, private plexOauth: PlexOAuthService, - private confirmationService: ConfirmationService, private identityService: IdentityService, private settings: SettingsService, private router: Router, @@ -28,19 +25,18 @@ export class PlexOAuthComponent implements OnInit { ngOnInit(): void { this.plexOauth.oAuth(this.pinId).subscribe(x => { - x.accessToken; + if(!x.accessToken) { + return; + // RETURN + } - this.confirmationService.confirm({ - message: "Do you want your Plex user to be the main admin account on Ombi?", - header: "Use Plex Account", - icon: "fa fa-check", - accept: () => { + this.identityService.createWizardUser({ username: "", password: "", usePlexAdminAccount: true, - }).subscribe(x => { - if (x) { + }).subscribe(u => { + if (u) { this.auth.oAuth(this.pinId).subscribe(c => { localStorage.setItem("id_token", c.access_token); @@ -48,7 +44,7 @@ export class PlexOAuthComponent implements OnInit { this.settings.getOmbi().subscribe(ombi => { ombi.wizard = true; - this.settings.saveOmbi(ombi).subscribe(x => { + this.settings.saveOmbi(ombi).subscribe(s => { this.settings.getUserManagementSettings().subscribe(usr => { usr.importPlexAdmin = true; @@ -65,11 +61,7 @@ export class PlexOAuthComponent implements OnInit { return; } }); - }, - reject: () => { - this.router.navigate(["Wizard/CreateAdmin"]); - }, - }); + }); } diff --git a/src/Ombi/Controllers/SettingsController.cs b/src/Ombi/Controllers/SettingsController.cs index 867a7517c..1f0d62147 100644 --- a/src/Ombi/Controllers/SettingsController.cs +++ b/src/Ombi/Controllers/SettingsController.cs @@ -151,6 +151,16 @@ namespace Ombi.Controllers return s; } + [HttpGet("plexstatus")] + [AllowAnonymous] + public async Task PlexStatusSettings() + { + var s = await Get(); + + + return s.Enable; + } + /// /// Save the Plex settings. /// diff --git a/src/Ombi/Controllers/TokenController.cs b/src/Ombi/Controllers/TokenController.cs index 62e43635d..624267989 100644 --- a/src/Ombi/Controllers/TokenController.cs +++ b/src/Ombi/Controllers/TokenController.cs @@ -49,26 +49,27 @@ namespace Ombi.Controllers [HttpPost] public async Task GetToken([FromBody] UserAuthModel model) { - await _audit.Record(AuditType.None, AuditArea.Authentication, + if (!model.UsePlexOAuth) + { + await _audit.Record(AuditType.None, AuditArea.Authentication, $"UserName {model.Username} attempting to authenticate"); - var user = await _userManager.FindByNameAsync(model.Username); - - if (user == null) - { - // Could this be an email login? - user = await _userManager.FindByEmailAsync(model.Username); + var user = await _userManager.FindByNameAsync(model.Username); if (user == null) { - return new UnauthorizedResult(); + // Could this be an email login? + user = await _userManager.FindByEmailAsync(model.Username); + + if (user == null) + { + return new UnauthorizedResult(); + } + + user.EmailLogin = true; } - user.EmailLogin = true; - } - if (!model.UsePlexOAuth) - { // Verify Password if (await _userManager.CheckPasswordAsync(user, model.Password)) { @@ -91,7 +92,7 @@ namespace Ombi.Controllers error = "Application URL has not been set" }); } - return new RedirectResult(url.ToString()); + return new JsonResult(new { url = url.ToString() }); } return new UnauthorizedResult(); @@ -183,7 +184,7 @@ namespace Ombi.Controllers { return new UnauthorizedResult(); } - + throw new NotImplementedException(); } From 5531af462cc67189f6a5d37f1c388dd230ea1fac Mon Sep 17 00:00:00 2001 From: Jamie Rees Date: Thu, 19 Apr 2018 22:18:25 +0100 Subject: [PATCH 4/6] Made the app name customizable for the OAUth !wip --- src/Ombi.Api.Plex/PlexApi.cs | 34 +++++++++++-------- .../Settings/Models/External/PlexSettings.cs | 1 - 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/Ombi.Api.Plex/PlexApi.cs b/src/Ombi.Api.Plex/PlexApi.cs index 6d814adf3..a16dee9ec 100644 --- a/src/Ombi.Api.Plex/PlexApi.cs +++ b/src/Ombi.Api.Plex/PlexApi.cs @@ -10,38 +10,42 @@ using Ombi.Api.Plex.Models.Status; using Ombi.Core.Settings; using Ombi.Core.Settings.Models.External; using Ombi.Helpers; +using Ombi.Settings.Settings.Models; namespace Ombi.Api.Plex { public class PlexApi : IPlexApi { - public PlexApi(IApi api, ISettingsService settings) + public PlexApi(IApi api, ISettingsService settings) { Api = api; - _plex = settings; + _custom = settings; } private IApi Api { get; } - private readonly ISettingsService _plex; + private readonly ISettingsService _custom; - private string _clientId; - private string ClientIdSecret + private string _app; + private string ApplicationName { get { - if (string.IsNullOrEmpty(_clientId)) + if (string.IsNullOrEmpty(_app)) { - var settings = _plex.GetSettings(); - if (settings.UniqueInstallCode.IsNullOrEmpty()) + var settings = _custom.GetSettings(); + if (settings.ApplicationName.IsNullOrEmpty()) { - settings.UniqueInstallCode = Guid.NewGuid().ToString("N"); - _plex.SaveSettings(settings); + _app = "Ombi"; + } + else + { + _app = settings.ApplicationName; } - _clientId = settings.UniqueInstallCode; + return _app; } - return _clientId; + return _app; } } @@ -215,7 +219,7 @@ namespace Ombi.Api.Plex request.AddQueryString("code", code); request.AddQueryString("context[device][product]", "Ombi"); request.AddQueryString("context[device][environment]", "bundled"); - request.AddQueryString("clientID", $"OmbiV3{ClientIdSecret}"); + request.AddQueryString("clientID", $"OmbiV3"); if (request.FullUri.Fragment.Equals("#")) { @@ -246,8 +250,8 @@ namespace Ombi.Api.Plex /// private void AddHeaders(Request request) { - request.AddHeader("X-Plex-Client-Identifier", $"OmbiV3{ClientIdSecret}"); - request.AddHeader("X-Plex-Product", "Ombi"); + request.AddHeader("X-Plex-Client-Identifier", $"OmbiV3"); + request.AddHeader("X-Plex-Product", ApplicationName); request.AddHeader("X-Plex-Version", "3"); request.AddContentHeader("Content-Type", request.ContentType == ContentType.Json ? "application/json" : "application/xml"); request.AddHeader("Accept", "application/json"); diff --git a/src/Ombi.Settings/Settings/Models/External/PlexSettings.cs b/src/Ombi.Settings/Settings/Models/External/PlexSettings.cs index dd92eba18..3faba3e42 100644 --- a/src/Ombi.Settings/Settings/Models/External/PlexSettings.cs +++ b/src/Ombi.Settings/Settings/Models/External/PlexSettings.cs @@ -6,7 +6,6 @@ namespace Ombi.Core.Settings.Models.External public sealed class PlexSettings : Ombi.Settings.Settings.Models.Settings { public bool Enable { get; set; } - public string UniqueInstallCode { get; set; } public List Servers { get; set; } } From f05a9a5090ecc68ba56522c7ecf453e3dc97b549 Mon Sep 17 00:00:00 2001 From: Jamie Rees Date: Fri, 20 Apr 2018 10:41:40 +0100 Subject: [PATCH 5/6] Removed no longer needed stuff and fixed linting !wip --- src/Ombi/ClientApp/app/login/login.component.ts | 4 +--- src/Ombi/ClientApp/app/login/loginoauth.component.ts | 4 ++-- src/Ombi/ClientApp/app/wizard/plex/plex.component.ts | 5 ++--- src/Ombi/ClientApp/app/wizard/plex/plexoauth.component.ts | 7 +++---- src/Ombi/Controllers/External/PlexController.cs | 1 - src/Ombi/Controllers/SettingsController.cs | 4 ---- 6 files changed, 8 insertions(+), 17 deletions(-) diff --git a/src/Ombi/ClientApp/app/login/login.component.ts b/src/Ombi/ClientApp/app/login/login.component.ts index cde2ef085..3c2636b2f 100644 --- a/src/Ombi/ClientApp/app/login/login.component.ts +++ b/src/Ombi/ClientApp/app/login/login.component.ts @@ -44,7 +44,6 @@ export class LoginComponent implements OnDestroy, OnInit { return true; } - this.loginWithOmbi = true; return true; } @@ -145,7 +144,7 @@ export class LoginComponent implements OnDestroy, OnInit { public oauth() { this.authService.login({usePlexOAuth: true, password:"",rememberMe:true,username:""}).subscribe(x => { window.location.href = x.url; - }) + }); } public ngOnDestroy() { @@ -161,5 +160,4 @@ export class LoginComponent implements OnDestroy, OnInit { .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 + ")"); }); } - } diff --git a/src/Ombi/ClientApp/app/login/loginoauth.component.ts b/src/Ombi/ClientApp/app/login/loginoauth.component.ts index a0a9a9414..03922d8b3 100644 --- a/src/Ombi/ClientApp/app/login/loginoauth.component.ts +++ b/src/Ombi/ClientApp/app/login/loginoauth.component.ts @@ -4,7 +4,7 @@ import { ActivatedRoute, Router } from "@angular/router"; import { AuthService } from "../auth/auth.service"; @Component({ - templateUrl: "./loginoauth.component.html" + templateUrl: "./loginoauth.component.html", }) export class LoginOAuthComponent implements OnInit { public pin: number; @@ -18,7 +18,7 @@ export class LoginOAuthComponent implements OnInit { }); } - ngOnInit(): void { + public ngOnInit(): void { this.auth(); } diff --git a/src/Ombi/ClientApp/app/wizard/plex/plex.component.ts b/src/Ombi/ClientApp/app/wizard/plex/plex.component.ts index 2f5b2dd79..bafe67756 100644 --- a/src/Ombi/ClientApp/app/wizard/plex/plex.component.ts +++ b/src/Ombi/ClientApp/app/wizard/plex/plex.component.ts @@ -1,7 +1,6 @@ import { Component } from "@angular/core"; import { Router } from "@angular/router"; - import { PlexService } from "../../services"; import { IdentityService, NotificationService, SettingsService } from "../../services"; import { AuthService } from "./../../auth/auth.service"; @@ -27,7 +26,7 @@ export class PlexComponent { return; } - this.identityService.createWizardUser({ + this.identityService.createWizardUser({ username: "", password: "", usePlexAdminAccount: true, @@ -57,7 +56,7 @@ export class PlexComponent { return; } }); - } + }, ); } diff --git a/src/Ombi/ClientApp/app/wizard/plex/plexoauth.component.ts b/src/Ombi/ClientApp/app/wizard/plex/plexoauth.component.ts index 7d0d0bf9d..25517541c 100644 --- a/src/Ombi/ClientApp/app/wizard/plex/plexoauth.component.ts +++ b/src/Ombi/ClientApp/app/wizard/plex/plexoauth.component.ts @@ -1,7 +1,7 @@ import { Component, OnInit } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router"; -import { PlexOAuthService, IdentityService, SettingsService } from "../../services"; +import { IdentityService, PlexOAuthService, SettingsService } from "../../services"; import { AuthService } from "./../../auth/auth.service"; @Component({ @@ -23,15 +23,14 @@ export class PlexOAuthComponent implements OnInit { }); } - ngOnInit(): void { + public ngOnInit(): void { this.plexOauth.oAuth(this.pinId).subscribe(x => { if(!x.accessToken) { return; // RETURN } - - this.identityService.createWizardUser({ + this.identityService.createWizardUser({ username: "", password: "", usePlexAdminAccount: true, diff --git a/src/Ombi/Controllers/External/PlexController.cs b/src/Ombi/Controllers/External/PlexController.cs index cde78bc64..4819ed8a0 100644 --- a/src/Ombi/Controllers/External/PlexController.cs +++ b/src/Ombi/Controllers/External/PlexController.cs @@ -69,7 +69,6 @@ namespace Ombi.Controllers.External _log.LogDebug("Adding first server"); settings.Enable = true; - settings.UniqueInstallCode = Guid.NewGuid().ToString("N"); settings.Servers = new List { new PlexServers { diff --git a/src/Ombi/Controllers/SettingsController.cs b/src/Ombi/Controllers/SettingsController.cs index 1f0d62147..0a515d9cb 100644 --- a/src/Ombi/Controllers/SettingsController.cs +++ b/src/Ombi/Controllers/SettingsController.cs @@ -143,10 +143,6 @@ namespace Ombi.Controllers public async Task PlexSettings() { var s = await Get(); - if (s.UniqueInstallCode.IsNullOrEmpty()) - { - s.UniqueInstallCode = Guid.NewGuid().ToString("N"); - } return s; } From e980ac6fc540595a5798c128ec2440a25bbe19e5 Mon Sep 17 00:00:00 2001 From: Jamie Rees Date: Fri, 20 Apr 2018 10:48:25 +0100 Subject: [PATCH 6/6] Fixed styling !wip --- src/Ombi/ClientApp/app/wizard/plex/plex.component.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Ombi/ClientApp/app/wizard/plex/plex.component.html b/src/Ombi/ClientApp/app/wizard/plex/plex.component.html index 5d69c435b..a48c0f73e 100644 --- a/src/Ombi/ClientApp/app/wizard/plex/plex.component.html +++ b/src/Ombi/ClientApp/app/wizard/plex/plex.component.html @@ -17,13 +17,13 @@ Please note we do not store this information, we only store your Plex Authorization Token that will allow Ombi to view your media and friends
- +
- +

OR

- +