mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-07-16 02:02:55 -07:00
Got plex oauth working! !wip
This commit is contained in:
parent
7bbcb9a626
commit
2fc4ef2613
22 changed files with 535 additions and 74 deletions
|
@ -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<PlexFriends> GetUsers(string authToken);
|
||||
Task<PlexAccount> GetAccount(string authToken);
|
||||
Task<PlexMetadata> GetRecentlyAdded(string authToken, string uri, string sectionId);
|
||||
Task<OAuthPin> CreatePin();
|
||||
Task<OAuthPin> GetPin(int pinId);
|
||||
Uri GetOAuthUrl(int pinId, string code, string applicationUrl, bool wizard);
|
||||
}
|
||||
}
|
27
src/Ombi.Api.Plex/Models/OAuth/OAuthPin.cs
Normal file
27
src/Ombi.Api.Plex/Models/OAuth/OAuthPin.cs
Normal file
|
@ -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; }
|
||||
}
|
||||
|
||||
}
|
|
@ -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<PlexSettings> settings)
|
||||
{
|
||||
Api = api;
|
||||
_plex = settings;
|
||||
}
|
||||
|
||||
private IApi Api { get; }
|
||||
private readonly ISettingsService<PlexSettings> _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<PlexMetadata>(request);
|
||||
}
|
||||
|
||||
public async Task<OAuthPin> CreatePin()
|
||||
{
|
||||
var request = new Request($"api/v2/pins", "https://plex.tv/", HttpMethod.Post);
|
||||
request.AddQueryString("strong", "true");
|
||||
AddHeaders(request);
|
||||
|
||||
return await Api.Request<OAuthPin>(request);
|
||||
}
|
||||
|
||||
public async Task<OAuthPin> GetPin(int pinId)
|
||||
{
|
||||
var request = new Request($"api/v2/pins/{pinId}", "https://plex.tv/", HttpMethod.Get);
|
||||
AddHeaders(request);
|
||||
|
||||
return await Api.Request<OAuthPin>(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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the required headers and also the authorization header
|
||||
/// </summary>
|
||||
|
@ -173,7 +246,7 @@ namespace Ombi.Api.Plex
|
|||
/// <param name="request"></param>
|
||||
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");
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
76
src/Ombi.Core/Authentication/PlexOAuthManager.cs
Normal file
76
src/Ombi.Core/Authentication/PlexOAuthManager.cs
Normal file
|
@ -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<CustomizationSettings> settings)
|
||||
{
|
||||
_api = api;
|
||||
_customizationSettingsService = settings;
|
||||
}
|
||||
|
||||
private readonly IPlexApi _api;
|
||||
private readonly ISettingsService<CustomizationSettings> _customizationSettingsService;
|
||||
|
||||
public async Task<OAuthPin> RequestPin()
|
||||
{
|
||||
var pin = await _api.CreatePin();
|
||||
return pin;
|
||||
}
|
||||
|
||||
public async Task<string> GetAccessTokenFromPin(int pinId)
|
||||
{
|
||||
var pin = await _api.GetPin(pinId);
|
||||
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<PlexAccount> GetAccount(string accessToken)
|
||||
{
|
||||
return await _api.GetAccount(accessToken);
|
||||
}
|
||||
|
||||
public async Task<Uri> 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;
|
||||
}
|
||||
}
|
||||
}
|
16
src/Ombi.Core/IPlexOAuthManager.cs
Normal file
16
src/Ombi.Core/IPlexOAuthManager.cs
Normal file
|
@ -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<string> GetAccessTokenFromPin(int pinId);
|
||||
Task<OAuthPin> RequestPin();
|
||||
Task<Uri> GetOAuthUrl(int pinId, string code);
|
||||
Uri GetWizardOAuthUrl(int pinId, string code, string websiteAddress);
|
||||
Task<PlexAccount> GetAccount(string accessToken);
|
||||
}
|
||||
}
|
|
@ -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<IRecentlyAddedEngine, RecentlyAddedEngine>();
|
||||
services.AddTransient<ITvSender, TvSender>();
|
||||
services.AddTransient<IMassEmailSender, MassEmailSender>();
|
||||
services.AddTransient<IPlexOAuthManager, PlexOAuthManager>();
|
||||
}
|
||||
public static void RegisterHttp(this IServiceCollection services)
|
||||
{
|
||||
|
|
|
@ -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<PlexServers> Servers { get; set; }
|
||||
|
||||
}
|
||||
|
||||
public class PlexServers : ExternalSettings
|
||||
|
|
|
@ -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<any> {
|
||||
return this.http.get<any>(`${this.url}/${pin}`, {headers: this.headers});
|
||||
}
|
||||
|
||||
public requiresPassword(login: IUserLogin): Observable<boolean> {
|
||||
return this.http.post<boolean>(`${this.url}/requirePassword`, JSON.stringify(login), {headers: this.headers});
|
||||
}
|
||||
|
|
|
@ -2,6 +2,10 @@
|
|||
user: IPlexUser;
|
||||
}
|
||||
|
||||
export interface IPlexOAuthAccessToken {
|
||||
accessToken: string;
|
||||
}
|
||||
|
||||
export interface IPlexUser {
|
||||
email: string;
|
||||
uuid: string;
|
||||
|
|
|
@ -4,3 +4,4 @@ export * from "./plex.service";
|
|||
export * from "./radarr.service";
|
||||
export * from "./sonarr.service";
|
||||
export * from "./tester.service";
|
||||
export * from "./plexoauth.service";
|
||||
|
|
|
@ -29,4 +29,8 @@ export class PlexService extends ServiceHelpers {
|
|||
public getFriends(): Observable<IUsersModel[]> {
|
||||
return this.http.get<IUsersModel[]>(`${this.url}Friends`, {headers: this.headers});
|
||||
}
|
||||
|
||||
public oAuth(wizard: boolean): Observable<any> {
|
||||
return this.http.get<any>(`${this.url}oauth/${wizard}`, {headers: this.headers});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<IPlexOAuthAccessToken> {
|
||||
return this.http.get<IPlexOAuthAccessToken>(`${this.url}${pin}`, {headers: this.headers});
|
||||
}
|
||||
}
|
|
@ -20,6 +20,13 @@
|
|||
<button (click)="requestAuthToken()" class="btn btn-primary-outline">Request Token <i class="fa fa-key"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div style="text-align: center; margin-top: 20px">
|
||||
<button (click)="oauth()" class="btn btn-sucess">Sign In With Plex <i class="fa fa-key"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -70,4 +70,12 @@ export class PlexComponent {
|
|||
});
|
||||
});
|
||||
}
|
||||
|
||||
public oauth() {
|
||||
this.plexService.oAuth(true).subscribe(x => {
|
||||
if(x.url) {
|
||||
window.location.href = x.url;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
14
src/Ombi/ClientApp/app/wizard/plex/plexoauth.component.html
Normal file
14
src/Ombi/ClientApp/app/wizard/plex/plexoauth.component.html
Normal file
|
@ -0,0 +1,14 @@
|
|||
|
||||
<img class="landing-header" src="/images/logo.png" width="300" />
|
||||
<div class="landing-block shadow">
|
||||
<div class="media">
|
||||
<div id="contentBody" class="media-body">
|
||||
<h4 class="media-heading landing-title">Plex Authentication</h4>
|
||||
<div class="form-group">
|
||||
<label for="username" class="control-label">Please Wait</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p-confirmDialog></p-confirmDialog>
|
76
src/Ombi/ClientApp/app/wizard/plex/plexoauth.component.ts
Normal file
76
src/Ombi/ClientApp/app/wizard/plex/plexoauth.component.ts
Normal file
|
@ -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"]);
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -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,
|
||||
],
|
||||
|
||||
})
|
||||
|
|
38
src/Ombi/Controllers/External/PlexController.cs
vendored
38
src/Ombi/Controllers/External/PlexController.cs
vendored
|
@ -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> plexSettings,
|
||||
ILogger<PlexController> logger)
|
||||
ILogger<PlexController> logger, IPlexOAuthManager manager)
|
||||
{
|
||||
PlexApi = plexApi;
|
||||
PlexSettings = plexSettings;
|
||||
_log = logger;
|
||||
_plexOAuthManager = manager;
|
||||
}
|
||||
|
||||
private IPlexApi PlexApi { get; }
|
||||
private ISettingsService<PlexSettings> PlexSettings { get; }
|
||||
private readonly ILogger<PlexController> _log;
|
||||
private readonly IPlexOAuthManager _plexOAuthManager;
|
||||
|
||||
/// <summary>
|
||||
/// 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<PlexServers> {
|
||||
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<IActionResult> 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()});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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> plexSettings,
|
||||
ILogger<PlexOAuthController> 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> _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<IActionResult> 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<PlexServers> {
|
||||
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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -142,7 +142,13 @@ namespace Ombi.Controllers
|
|||
[HttpGet("plex")]
|
||||
public async Task<PlexSettings> PlexSettings()
|
||||
{
|
||||
return await Get<PlexSettings>();
|
||||
var s = await Get<PlexSettings>();
|
||||
if (s.UniqueInstallCode.IsNullOrEmpty())
|
||||
{
|
||||
s.UniqueInstallCode = Guid.NewGuid().ToString("N");
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -25,18 +25,21 @@ namespace Ombi.Controllers
|
|||
[Produces("application/json")]
|
||||
public class TokenController
|
||||
{
|
||||
public TokenController(OmbiUserManager um, IOptions<TokenAuthentication> ta, IAuditRepository audit, ITokenRepository token)
|
||||
public TokenController(OmbiUserManager um, IOptions<TokenAuthentication> 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;
|
||||
|
||||
/// <summary>
|
||||
/// 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<Claim>
|
||||
{
|
||||
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<IActionResult> 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<Claim>
|
||||
{
|
||||
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<IActionResult> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the token.
|
||||
/// </summary>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue