Fixed: Plex server authentication

This commit is contained in:
Mark McDowall 2014-04-14 22:07:43 -07:00
parent af75813347
commit 5bc820efed
9 changed files with 202 additions and 94 deletions

View File

@ -33,79 +33,6 @@ namespace NzbDrone.Core.Test.NotificationTests
};
}
[Test]
public void GetSectionKeys_should_return_single_section_key_when_only_one_show_section()
{
var response = "<MediaContainer size=\"1\" mediaTagPrefix=\"/system/bundle/media/flags/\" mediaTagVersion=\"1329809559\" title1=\"Plex Library\" identifier=\"com.plexapp.plugins.library\"><Directory refreshing=\"0\" key=\"5\" type=\"show\" title=\"TV Shows\" art=\"/:/resources/show-fanart.jpg\" agent=\"com.plexapp.agents.thetvdb\" scanner=\"Plex Series Scanner\" language=\"en\" updatedAt=\"1329810350\"><Location path=\"C:/Test/TV\"/></Directory></MediaContainer>";
Stream stream = new MemoryStream(ASCIIEncoding.Default.GetBytes(response));
Mocker.GetMock<IHttpProvider>().Setup(s => s.DownloadStream("http://localhost:32400/library/sections", null))
.Returns(stream);
var result = Mocker.Resolve<PlexService>().GetSectionKeys(new PlexServerSettings { Host = "localhost", Port = 32400 });
result.Should().HaveCount(1);
result.First().Should().Be(5);
}
[Test]
public void GetSectionKeys_should_return_single_section_key_when_only_one_show_section_with_other_sections()
{
var response = "<MediaContainer size=\"1\" mediaTagPrefix=\"/system/bundle/media/flags/\" mediaTagVersion=\"1329809559\" title1=\"Plex Library\" identifier=\"com.plexapp.plugins.library\"><Directory refreshing=\"0\" key=\"5\" type=\"show\" title=\"TV Shows\" art=\"/:/resources/show-fanart.jpg\" agent=\"com.plexapp.agents.thetvdb\" scanner=\"Plex Series Scanner\" language=\"en\" updatedAt=\"1329810350\"><Location path=\"C:/Test/TV\"/></Directory><Directory refreshing=\"0\" key=\"7\" type=\"movie\" title=\"TV Shows\" art=\"/:/resources/show-fanart.jpg\" agent=\"com.plexapp.agents.thetvdb\" scanner=\"Plex Series Scanner\" language=\"en\" updatedAt=\"1329810350\"><Location path=\"C:/Test/TV\"/></Directory></MediaContainer>";
Stream stream = new MemoryStream(ASCIIEncoding.Default.GetBytes(response));
Mocker.GetMock<IHttpProvider>().Setup(s => s.DownloadStream("http://localhost:32400/library/sections", null))
.Returns(stream);
var result = Mocker.Resolve<PlexService>().GetSectionKeys(new PlexServerSettings { Host = "localhost", Port = 32400 });
result.Should().HaveCount(1);
result.First().Should().Be(5);
}
[Test]
public void GetSectionKeys_should_return_multiple_section_keys_when_there_are_multiple_show_sections()
{
var response = "<MediaContainer size=\"1\" mediaTagPrefix=\"/system/bundle/media/flags/\" mediaTagVersion=\"1329809559\" title1=\"Plex Library\" identifier=\"com.plexapp.plugins.library\"><Directory refreshing=\"0\" key=\"5\" type=\"show\" title=\"TV Shows\" art=\"/:/resources/show-fanart.jpg\" agent=\"com.plexapp.agents.thetvdb\" scanner=\"Plex Series Scanner\" language=\"en\" updatedAt=\"1329810350\"><Location path=\"C:/Test/TV\"/></Directory><Directory refreshing=\"0\" key=\"6\" type=\"show\" title=\"TV Shows\" art=\"/:/resources/show-fanart.jpg\" agent=\"com.plexapp.agents.thetvdb\" scanner=\"Plex Series Scanner\" language=\"en\" updatedAt=\"1329810350\"><Location path=\"C:/Test/TV\"/></Directory></MediaContainer>";
Stream stream = new MemoryStream(ASCIIEncoding.Default.GetBytes(response));
Mocker.GetMock<IHttpProvider>().Setup(s => s.DownloadStream("http://localhost:32400/library/sections", null))
.Returns(stream);
var result = Mocker.Resolve<PlexService>().GetSectionKeys(new PlexServerSettings { Host = "localhost", Port = 32400 });
result.Should().HaveCount(2);
result.First().Should().Be(5);
result.Last().Should().Be(6);
}
[Test]
public void UpdateSection_should_update_section()
{
var response = "";
Stream stream = new MemoryStream(ASCIIEncoding.Default.GetBytes(response));
Mocker.GetMock<IHttpProvider>().Setup(s => s.DownloadString("http://localhost:32400/library/sections/5/refresh"))
.Returns(response);
Mocker.Resolve<PlexService>().UpdateSection(new PlexServerSettings { Host = "localhost", Port = 32400 }, 5);
}
[Test]
public void Notify_should_send_notification()
{

View File

@ -0,0 +1,9 @@
using System;
namespace NzbDrone.Core.Notifications.Plex
{
public class PlexError
{
public String Error { get; set; }
}
}

View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NzbDrone.Common.Exceptions;
namespace NzbDrone.Core.Notifications.Plex
{
public class PlexException : NzbDroneException
{
public PlexException(string message) : base(message)
{
}
public PlexException(string message, params object[] args) : base(message, args)
{
}
}
}

View File

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
namespace NzbDrone.Core.Notifications.Plex
{
public class PlexSection
{
public Int32 Id { get; set; }
public String Path { get; set; }
}
public class PlexDirectory
{
public String Type { get; set; }
[JsonProperty("_children")]
public List<PlexSection> Sections { get; set; }
}
public class PlexMediaContainer
{
[JsonProperty("_children")]
public List<PlexDirectory> Directories { get; set; }
}
}

View File

@ -0,0 +1,121 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Newtonsoft.Json.Linq;
using NzbDrone.Common;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Serializer;
using RestSharp;
namespace NzbDrone.Core.Notifications.Plex
{
public interface IPlexServerProxy
{
List<PlexSection> GetTvSections(PlexServerSettings settings);
void Update(int sectionId, PlexServerSettings settings);
}
public class PlexServerProxy : IPlexServerProxy
{
private readonly ICached<String> _authCache;
public PlexServerProxy(ICacheManager cacheManager)
{
_authCache = cacheManager.GetCache<String>(GetType(), "authCache");
}
public List<PlexSection> GetTvSections(PlexServerSettings settings)
{
var request = GetPlexServerRequest("library/sections", Method.GET, settings);
var client = GetPlexServerClient(settings);
var response = client.Execute(request);
return Json.Deserialize<PlexMediaContainer>(response.Content)
.Directories
.Where(d => d.Type == "show")
.SelectMany(d => d.Sections)
.ToList();
}
public void Update(int sectionId, PlexServerSettings settings)
{
var resource = String.Format("library/sections/{2}/refresh");
var request = GetPlexServerRequest(resource, Method.GET, settings);
var client = GetPlexServerClient(settings);
var response = client.Execute(request);
}
private String Authenticate(string username, string password)
{
var request = GetMyPlexRequest("users/sign_in.json", Method.POST);
var client = GetMyPlexClient(username, password);
var response = client.Execute(request);
CheckForError(response.Content);
var user = Json.Deserialize<PlexUser>(JObject.Parse(response.Content).SelectToken("user").ToString());
_authCache.Set(username, user.AuthenticationToken);
return user.AuthenticationToken;
}
private RestClient GetMyPlexClient(string username, string password)
{
var client = new RestClient("https://my.plexapp.com");
client.Authenticator = new HttpBasicAuthenticator(username, password);
return client;
}
private RestRequest GetMyPlexRequest(string resource, Method method)
{
var request = new RestRequest(resource, method);
request.AddHeader("X-Plex-Platform", "Windows");
request.AddHeader("X-Plex-Platform-Version", "7");
request.AddHeader("X-Plex-Provides", "player");
request.AddHeader("X-Plex-Client-Identifier", "AB6CCCC7-5CF5-4523-826A-B969E0FFD8A0");
request.AddHeader("X-Plex-Product", "PlexWMC");
request.AddHeader("X-Plex-Version", "0");
return request;
}
private RestClient GetPlexServerClient(PlexServerSettings settings)
{
return new RestClient(String.Format("http://{0}:{1}", settings.Host, settings.Port));
}
private RestRequest GetPlexServerRequest(string resource, Method method, PlexServerSettings settings)
{
var request = new RestRequest(resource, method);
request.AddHeader("Accept", "application/json");
if (!settings.Username.IsNullOrWhiteSpace())
{
request.AddParameter("X-Plex-Token", GetAuthenticationToken(settings.Username, settings.Password));
}
return request;
}
private string GetAuthenticationToken(string username, string password)
{
return _authCache.Get(username, () => Authenticate(username, password));
}
private void CheckForError(string response)
{
var error = Json.Deserialize<PlexError>(response);
if (error != null && !error.Error.IsNullOrWhiteSpace())
{
throw new PlexException(error.Error);
}
}
}
}

View File

@ -33,7 +33,7 @@ namespace NzbDrone.Core.Notifications.Plex
[FieldDefinition(2, Label = "Username")]
public String Username { get; set; }
[FieldDefinition(3, Label = "Password")]
[FieldDefinition(3, Label = "Password", Type = FieldType.Password)]
public String Password { get; set; }
[FieldDefinition(4, Label = "Update Library", Type = FieldType.Checkbox)]

View File

@ -18,11 +18,13 @@ namespace NzbDrone.Core.Notifications.Plex
public class PlexService : IPlexService, IExecute<TestPlexClientCommand>, IExecute<TestPlexServerCommand>
{
private readonly IHttpProvider _httpProvider;
private readonly IPlexServerProxy _plexServerProxy;
private readonly Logger _logger;
public PlexService(IHttpProvider httpProvider, Logger logger)
public PlexService(IHttpProvider httpProvider, IPlexServerProxy plexServerProxy, Logger logger)
{
_httpProvider = httpProvider;
_plexServerProxy = plexServerProxy;
_logger = logger;
}
@ -45,7 +47,7 @@ namespace NzbDrone.Core.Notifications.Plex
{
_logger.Debug("Sending Update Request to Plex Server");
var sections = GetSectionKeys(settings);
sections.ForEach(s => UpdateSection(settings, s));
sections.ForEach(s => UpdateSection(s, settings));
}
catch(Exception ex)
@ -55,26 +57,21 @@ namespace NzbDrone.Core.Notifications.Plex
}
}
public List<int> GetSectionKeys(PlexServerSettings settings)
private List<int> GetSectionKeys(PlexServerSettings settings)
{
_logger.Debug("Getting sections from Plex host: {0}", settings.Host);
var url = String.Format("http://{0}:{1}/library/sections", settings.Host, settings.Port);
var xmlStream = _httpProvider.DownloadStream(url, GetCredentials(settings));
var xDoc = XDocument.Load(xmlStream);
var mediaContainer = xDoc.Descendants("MediaContainer").FirstOrDefault();
var directories = mediaContainer.Descendants("Directory").Where(x => x.Attribute("type").Value == "show");
return directories.Select(d => Int32.Parse(d.Attribute("key").Value)).ToList();
return _plexServerProxy.GetTvSections(settings).Select(s => s.Id).ToList();
}
public void UpdateSection(PlexServerSettings settings, int key)
private void UpdateSection(int key, PlexServerSettings settings)
{
_logger.Debug("Updating Plex host: {0}, Section: {1}", settings.Host, key);
var url = String.Format("http://{0}:{1}/library/sections/{2}/refresh", settings.Host, settings.Port, key);
_httpProvider.DownloadString(url, GetCredentials(settings));
_plexServerProxy.Update(key, settings);
}
public string SendCommand(string host, int port, string command, string username, string password)
private string SendCommand(string host, int port, string command, string username, string password)
{
var url = String.Format("http://{0}:{1}/xbmcCmds/xbmcHttp?command={2}", host, port, command);
@ -86,13 +83,6 @@ namespace NzbDrone.Core.Notifications.Plex
return _httpProvider.DownloadString(url);
}
private NetworkCredential GetCredentials(PlexServerSettings settings)
{
if (settings.Username.IsNullOrWhiteSpace()) return null;
return new NetworkCredential(settings.Username, settings.Password);
}
public void Execute(TestPlexClientCommand message)
{
_logger.Debug("Sending Test Notifcation to Plex Client: {0}", message.Host);

View File

@ -0,0 +1,11 @@
using System;
using Newtonsoft.Json;
namespace NzbDrone.Core.Notifications.Plex
{
public class PlexUser
{
[JsonProperty("authentication_token")]
public String AuthenticationToken { get; set; }
}
}

View File

@ -377,6 +377,11 @@
<Compile Include="Notifications\NotificationFactory.cs" />
<Compile Include="Notifications\NotificationService.cs" />
<Compile Include="Notifications\DownloadMessage.cs" />
<Compile Include="Notifications\Plex\PlexError.cs" />
<Compile Include="Notifications\Plex\PlexException.cs" />
<Compile Include="Notifications\Plex\PlexSection.cs" />
<Compile Include="Notifications\Plex\PlexServerProxy.cs" />
<Compile Include="Notifications\Plex\PlexUser.cs" />
<Compile Include="Notifications\PushBullet\PushBullet.cs" />
<Compile Include="Notifications\PushBullet\PushBulletProxy.cs" />
<Compile Include="Notifications\PushBullet\PushBulletSettings.cs" />