[plex] Use https for local connections (#15306)

Signed-off-by: Michael Lobstein <michael.lobstein@gmail.com>
This commit is contained in:
mlobstein 2023-07-26 16:44:57 -05:00 committed by GitHub
parent e1c9213cfc
commit ca8c843d05
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 57 additions and 20 deletions

View File

@ -17,13 +17,17 @@ import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.util.Base64; import java.util.Base64;
import java.util.Properties; import java.util.Properties;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
@ -129,11 +133,12 @@ public class PlexApiConnector {
*/ */
public @Nullable MediaContainer getSessionData() { public @Nullable MediaContainer getSessionData() {
try { try {
String url = "http://" + host + ":" + String.valueOf(port) + "/status/sessions" + "?X-Plex-Token=" + token; String url = "https://" + host + ":" + String.valueOf(port) + "/status/sessions" + "?X-Plex-Token=" + token;
logger.debug("Getting session data '{}'", url); logger.debug("Getting session data '{}'", url);
MediaContainer mediaContainer = doHttpRequest("GET", url, getClientHeaders(), MediaContainer.class); MediaContainer mediaContainer = getFromXml(doHttpRequest("GET", url, getClientHeaders(), false),
MediaContainer.class);
return mediaContainer; return mediaContainer;
} catch (IOException e) { } catch (IOException | InterruptedException | TimeoutException | ExecutionException e) {
logger.debug("An exception occurred while polling the PLEX Server: '{}'", e.getMessage()); logger.debug("An exception occurred while polling the PLEX Server: '{}'", e.getMessage());
return null; return null;
} }
@ -151,7 +156,7 @@ public class PlexApiConnector {
} }
/** /**
* This method will get an X-Token from the PLEX server if one is not provided in the bridge config * This method will get an X-Token from the PLEX.tv server if one is not provided in the bridge config
* and use this in the communication with the plex server * and use this in the communication with the plex server
*/ */
public void getToken() { public void getToken() {
@ -161,8 +166,8 @@ public class PlexApiConnector {
headers.put("Authorization", "Basic " + authString); headers.put("Authorization", "Basic " + authString);
try { try {
user = doHttpRequest("POST", SIGNIN_URL, headers, User.class); user = getFromXml(doHttpRequest("POST", SIGNIN_URL, headers, true), User.class);
} catch (IOException e) { } catch (IOException | InterruptedException | TimeoutException | ExecutionException e) {
logger.debug("An exception occurred while fetching PLEX user token :'{}'", e.getMessage(), e); logger.debug("An exception occurred while fetching PLEX user token :'{}'", e.getMessage(), e);
throw new ConfigurationException("Error occurred while fetching PLEX user token, please check config"); throw new ConfigurationException("Error occurred while fetching PLEX user token, please check config");
} }
@ -176,11 +181,12 @@ public class PlexApiConnector {
} }
/** /**
* This method will get the Api information from the PLEX servers. * This method will get the Api information from the PLEX.tv servers.
*/ */
public boolean getApi() { public boolean getApi() {
try { try {
MediaContainer api = doHttpRequest("GET", API_URL, getClientHeaders(), MediaContainer.class); MediaContainer api = getFromXml(doHttpRequest("GET", API_URL, getClientHeaders(), true),
MediaContainer.class);
logger.debug("MediaContainer {}", api.getSize()); logger.debug("MediaContainer {}", api.getSize());
if (api.getDevice() != null) { if (api.getDevice() != null) {
for (Device tmpDevice : api.getDevice()) { for (Device tmpDevice : api.getDevice()) {
@ -198,28 +204,58 @@ public class PlexApiConnector {
} }
} }
return false; return false;
} catch (IOException e) { } catch (IOException | InterruptedException | TimeoutException | ExecutionException e) {
logger.debug("An exception occurred while fetching API :'{}'", e.getMessage(), e); logger.debug("An exception occurred while fetching API :'{}'", e.getMessage(), e);
} }
return false; return false;
} }
/** /**
* Make an HTTP request and return the class object that was used when calling. * Make an HTTP request and return the response as a string.
* *
* @param <T> Class being used(dto)
* @param method GET/POST * @param method GET/POST
* @param url What URL to call * @param url What URL to call
* @param headers Additional headers that will be used * @param headers Additional headers that will be used
* @param type class type for the XML parsing * @param verify Flag to indicate if ssl certificate checking should be done
* @return Returns a class object from the data returned by the call * @return Returns a string of the http response
* @throws IOException * @throws IOException
* @throws ExecutionException
* @throws TimeoutException
* @throws InterruptedException
*/ */
private <T> T doHttpRequest(String method, String url, Properties headers, Class<T> type) throws IOException { private String doHttpRequest(String method, String url, Properties headers, boolean verify)
String response = HttpUtil.executeUrl(method, url, headers, null, null, REQUEST_TIMEOUT_MS); throws IOException, InterruptedException, TimeoutException, ExecutionException {
final String response;
if (verify) {
// Requests sent to the PLEX.tv servers should use certificate checking
response = HttpUtil.executeUrl(method, url, headers, null, null, REQUEST_TIMEOUT_MS);
} else {
// Requests sent to the local server need to bypass certificate checking via the custom httpClient
final Request request = httpClient.newRequest(url).method(HttpUtil.createHttpMethod(method));
for (String httpHeaderKey : headers.stringPropertyNames()) {
if (httpHeaderKey.equalsIgnoreCase(HttpHeader.USER_AGENT.toString())) {
request.agent(headers.getProperty(httpHeaderKey));
} else {
request.header(httpHeaderKey, headers.getProperty(httpHeaderKey));
}
}
final ContentResponse res = request.send();
response = res.getContentAsString();
}
logger.debug("HTTP response: {}", response);
return response;
}
/**
* Return the class object that was represented by the xml input string.
*
* @param response The xml response to parse
* @param type Class type for the XML parsing
* @return Returns a class object from the input sring
*/
private <T> T getFromXml(String response, Class<T> type) {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
T obj = (T) xStream.fromXML(response); T obj = (T) xStream.fromXML(response);
logger.debug("HTTP response {}", response);
return obj; return obj;
} }
@ -230,7 +266,7 @@ public class PlexApiConnector {
*/ */
private Properties getClientHeaders() { private Properties getClientHeaders() {
Properties headers = new Properties(); Properties headers = new Properties();
headers.put(HttpHeader.USER_AGENT, "openHAB / PLEX binding "); // + VERSION); headers.put(HttpHeader.USER_AGENT, "openHAB/" + org.openhab.core.OpenHAB.getVersion() + " PLEX binding");
headers.put("X-Plex-Client-Identifier", CLIENT_ID); headers.put("X-Plex-Client-Identifier", CLIENT_ID);
headers.put("X-Plex-Product", "openHAB"); headers.put("X-Plex-Product", "openHAB");
headers.put("X-Plex-Version", ""); headers.put("X-Plex-Version", "");
@ -377,11 +413,11 @@ public class PlexApiConnector {
if (commandPath != null) { if (commandPath != null) {
try { try {
String url = "http://" + host + ":" + String.valueOf(port) + commandPath; String url = "https://" + host + ":" + String.valueOf(port) + commandPath;
Properties headers = getClientHeaders(); Properties headers = getClientHeaders();
headers.put("X-Plex-Target-Client-Identifier", playerID); headers.put("X-Plex-Target-Client-Identifier", playerID);
HttpUtil.executeUrl("GET", url, headers, null, null, REQUEST_TIMEOUT_MS); doHttpRequest("GET", url, headers, false);
} catch (IOException e) { } catch (IOException | InterruptedException | TimeoutException | ExecutionException e) {
logger.debug("An exception occurred trying to send command '{}' to the player: {}", commandPath, logger.debug("An exception occurred trying to send command '{}' to the player: {}", commandPath,
e.getMessage()); e.getMessage());
} }

View File

@ -90,6 +90,7 @@ public class PlexServerHandler extends BaseBridgeHandler implements PlexUpdateLi
try { try {
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server(); SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
sslContextFactory.setEndpointIdentificationAlgorithm(null); sslContextFactory.setEndpointIdentificationAlgorithm(null);
sslContextFactory.setTrustAll(true);
HttpClient localHttpClient = httpClient = httpClientFactory.createHttpClient(httpClientName, HttpClient localHttpClient = httpClient = httpClientFactory.createHttpClient(httpClientName,
sslContextFactory); sslContextFactory);
localHttpClient.start(); localHttpClient.start();