[magentatv] Adapt to new Telekom OAuth flow (required to retrieve the userId) (#10267)

Signed-off-by: Markus Michels <markus7017@gmail.com>
This commit is contained in:
Markus Michels 2021-03-16 21:54:55 +01:00 committed by GitHub
parent 754751c19b
commit c582dda1d5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 232 additions and 155 deletions

View File

@ -117,7 +117,7 @@ For security reasons the credentials are automatically deleted from the thing co
| |key |String |Send key code to the receiver (see code table below) | | |key |String |Send key code to the receiver (see code table below) |
| |mute |Switch |Mute volume (mute the speaker) | | |mute |Switch |Mute volume (mute the speaker) |
|status |playMode |String |Current play mode - this info is not reliable | |status |playMode |String |Current play mode - this info is not reliable |
| |channelCode |Number  |The channel code from the EPG. | | |channelCode |Number |The channel code from the EPG. |
|program |title |String |Title of the running program or video being played | |program |title |String |Title of the running program or video being played |
| |text |String |Some description (as reported by the receiver, could be empty) | | |text |String |Some description (as reported by the receiver, could be empty) |
| |start |DateTime |Time when the program started | | |start |DateTime |Time when the program started |

View File

@ -20,18 +20,15 @@ import java.io.InputStreamReader;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import javax.ws.rs.client.ClientBuilder;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.magentatv.internal.network.MagentaTVOAuth; import org.openhab.binding.magentatv.internal.network.MagentaTVOAuth;
import org.openhab.core.io.console.Console; import org.openhab.core.io.console.Console;
import org.openhab.core.io.console.extensions.AbstractConsoleCommandExtension; import org.openhab.core.io.console.extensions.AbstractConsoleCommandExtension;
import org.openhab.core.io.console.extensions.ConsoleCommandExtension; import org.openhab.core.io.console.extensions.ConsoleCommandExtension;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference; import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -47,14 +44,12 @@ public class MagentaTVConsoleHandler extends AbstractConsoleCommandExtension {
private static final String CMD_LOGIN = "login"; private static final String CMD_LOGIN = "login";
private final Logger logger = LoggerFactory.getLogger(MagentaTVConsoleHandler.class); private final Logger logger = LoggerFactory.getLogger(MagentaTVConsoleHandler.class);
private final MagentaTVOAuth oauth = new MagentaTVOAuth(); private final MagentaTVOAuth oauth;
@Reference(cardinality = ReferenceCardinality.OPTIONAL)
private @Nullable ClientBuilder injectedClientBuilder;
@Activate @Activate
public MagentaTVConsoleHandler() { public MagentaTVConsoleHandler(@Reference HttpClientFactory httpClientFactory) {
super(BINDING_ID, "Interact with the " + BINDING_ID + " integration."); super(BINDING_ID, "Interact with the " + BINDING_ID + " integration.");
oauth = new MagentaTVOAuth(httpClientFactory.getCommonHttpClient());
} }
@Override @Override

View File

@ -19,10 +19,12 @@ import java.util.Map;
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.openhab.binding.magentatv.internal.MagentaTVDeviceManager.MagentaTVDevice; import org.openhab.binding.magentatv.internal.MagentaTVDeviceManager.MagentaTVDevice;
import org.openhab.binding.magentatv.internal.handler.MagentaTVHandler; import org.openhab.binding.magentatv.internal.handler.MagentaTVHandler;
import org.openhab.binding.magentatv.internal.network.MagentaTVNetwork; import org.openhab.binding.magentatv.internal.network.MagentaTVNetwork;
import org.openhab.binding.magentatv.internal.network.MagentaTVPoweroffListener; import org.openhab.binding.magentatv.internal.network.MagentaTVPoweroffListener;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.openhab.core.net.HttpServiceUtil; import org.openhab.core.net.HttpServiceUtil;
import org.openhab.core.net.NetworkAddressService; import org.openhab.core.net.NetworkAddressService;
import org.openhab.core.thing.Thing; import org.openhab.core.thing.Thing;
@ -51,6 +53,7 @@ public class MagentaTVHandlerFactory extends BaseThingHandlerFactory {
private final MagentaTVNetwork network = new MagentaTVNetwork(); private final MagentaTVNetwork network = new MagentaTVNetwork();
private final MagentaTVDeviceManager manager; private final MagentaTVDeviceManager manager;
private final HttpClient httpClient;
private @Nullable MagentaTVPoweroffListener upnpListener; private @Nullable MagentaTVPoweroffListener upnpListener;
private boolean servletInitialized = false; private boolean servletInitialized = false;
@ -64,11 +67,11 @@ public class MagentaTVHandlerFactory extends BaseThingHandlerFactory {
@Activate @Activate
public MagentaTVHandlerFactory(@Reference NetworkAddressService networkAddressService, public MagentaTVHandlerFactory(@Reference NetworkAddressService networkAddressService,
@Reference MagentaTVDeviceManager manager, ComponentContext componentContext, @Reference HttpClientFactory httpClientFactory, @Reference MagentaTVDeviceManager manager,
Map<String, String> configProperties) throws IOException { ComponentContext componentContext, Map<String, String> configProperties) throws IOException {
super.activate(componentContext); super.activate(componentContext);
this.manager = manager; this.manager = manager;
this.httpClient = httpClientFactory.getCommonHttpClient();
try { try {
logger.debug("Initialize network access"); logger.debug("Initialize network access");
System.setProperty("java.net.preferIPv4Stack", "true"); System.setProperty("java.net.preferIPv4Stack", "true");
@ -99,7 +102,7 @@ public class MagentaTVHandlerFactory extends BaseThingHandlerFactory {
logger.debug("Create thing type {}", thing.getThingTypeUID().getAsString()); logger.debug("Create thing type {}", thing.getThingTypeUID().getAsString());
if (THING_TYPE_RECEIVER.equals(thingTypeUID)) { if (THING_TYPE_RECEIVER.equals(thingTypeUID)) {
return new MagentaTVHandler(manager, thing, network); return new MagentaTVHandler(manager, thing, network, httpClient);
} }
return null; return null;

View File

@ -23,6 +23,7 @@ import java.util.HashMap;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.magentatv.internal.MagentaTVException; import org.openhab.binding.magentatv.internal.MagentaTVException;
import org.openhab.binding.magentatv.internal.config.MagentaTVDynamicConfig; import org.openhab.binding.magentatv.internal.config.MagentaTVDynamicConfig;
import org.openhab.binding.magentatv.internal.network.MagentaTVHttp; import org.openhab.binding.magentatv.internal.network.MagentaTVHttp;
@ -44,7 +45,7 @@ public class MagentaTVControl {
private final MagentaTVNetwork network; private final MagentaTVNetwork network;
private final MagentaTVHttp http = new MagentaTVHttp(); private final MagentaTVHttp http = new MagentaTVHttp();
private final MagentaTVOAuth oauth = new MagentaTVOAuth(); private final MagentaTVOAuth oauth;
private final MagentaTVDynamicConfig config; private final MagentaTVDynamicConfig config;
private boolean initialized = false; private boolean initialized = false;
private String thingId = ""; private String thingId = "";
@ -52,11 +53,13 @@ public class MagentaTVControl {
public MagentaTVControl() { public MagentaTVControl() {
config = new MagentaTVDynamicConfig(); config = new MagentaTVDynamicConfig();
network = new MagentaTVNetwork(); network = new MagentaTVNetwork();
oauth = new MagentaTVOAuth(new HttpClient());
} }
public MagentaTVControl(MagentaTVDynamicConfig config, MagentaTVNetwork network) { public MagentaTVControl(MagentaTVDynamicConfig config, MagentaTVNetwork network, HttpClient httpClient) {
thingId = config.getFriendlyName(); thingId = config.getFriendlyName();
this.network = network; this.network = network;
this.oauth = new MagentaTVOAuth(httpClient);
this.config = config; this.config = config;
this.config.setTerminalID(computeMD5(network.getLocalMAC().toUpperCase() + config.getUDN())); this.config.setTerminalID(computeMD5(network.getLocalMAC().toUpperCase() + config.getUDN()));
this.config.setLocalIP(network.getLocalIP()); this.config.setLocalIP(network.getLocalIP());
@ -391,7 +394,8 @@ public class MagentaTVControl {
// direct key code // direct key code
return key; return key;
} }
return KEY_MAP.getOrDefault(key, ""); String code = KEY_MAP.get(key);
return code != null ? code : "";
} }
/** /**

View File

@ -33,6 +33,7 @@ import javax.measure.Unit;
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.openhab.binding.magentatv.internal.MagentaTVDeviceManager; import org.openhab.binding.magentatv.internal.MagentaTVDeviceManager;
import org.openhab.binding.magentatv.internal.MagentaTVException; import org.openhab.binding.magentatv.internal.MagentaTVException;
import org.openhab.binding.magentatv.internal.MagentaTVGsonDTO.MRPayEvent; import org.openhab.binding.magentatv.internal.MagentaTVGsonDTO.MRPayEvent;
@ -88,6 +89,7 @@ public class MagentaTVHandler extends BaseThingHandler implements MagentaTVListe
private final Gson gson; private final Gson gson;
protected final MagentaTVNetwork network; protected final MagentaTVNetwork network;
protected final MagentaTVDeviceManager manager; protected final MagentaTVDeviceManager manager;
private final HttpClient httpClient;
protected MagentaTVControl control = new MagentaTVControl(); protected MagentaTVControl control = new MagentaTVControl();
private String thingId = ""; private String thingId = "";
@ -102,10 +104,12 @@ public class MagentaTVHandler extends BaseThingHandler implements MagentaTVListe
* @param thing * @param thing
* @param bindingConfig * @param bindingConfig
*/ */
public MagentaTVHandler(MagentaTVDeviceManager manager, Thing thing, MagentaTVNetwork network) { public MagentaTVHandler(MagentaTVDeviceManager manager, Thing thing, MagentaTVNetwork network,
HttpClient httpClient) {
super(thing); super(thing);
this.manager = manager; this.manager = manager;
this.network = network; this.network = network;
this.httpClient = httpClient;
gson = new GsonBuilder().registerTypeAdapter(OauthCredentials.class, new MRProgramInfoEventInstanceCreator()) gson = new GsonBuilder().registerTypeAdapter(OauthCredentials.class, new MRProgramInfoEventInstanceCreator())
.registerTypeAdapter(OAuthTokenResponse.class, new MRProgramStatusInstanceCreator()) .registerTypeAdapter(OAuthTokenResponse.class, new MRProgramStatusInstanceCreator())
.registerTypeAdapter(OAuthAuthenticateResponse.class, new MRShortProgramInfoInstanceCreator()) .registerTypeAdapter(OAuthAuthenticateResponse.class, new MRShortProgramInfoInstanceCreator())
@ -150,7 +154,7 @@ public class MagentaTVHandler extends BaseThingHandler implements MagentaTVListe
} }
config.setMacAddress(macAddress); config.setMacAddress(macAddress);
} }
control = new MagentaTVControl(config, network); control = new MagentaTVControl(config, network, httpClient);
config.updateNetwork(control.getConfig()); // get network parameters from control config.updateNetwork(control.getConfig()); // get network parameters from control
// Check for emoty credentials (e.g. missing in .things file) // Check for emoty credentials (e.g. missing in .things file)
@ -185,7 +189,7 @@ public class MagentaTVHandler extends BaseThingHandler implements MagentaTVListe
} }
/** /**
* This routine is called every time the Thing configuration has been changed. * This routine is called every time the Thing configuration has been changed
*/ */
@Override @Override
public void handleConfigurationUpdate(Map<String, Object> configurationParameters) { public void handleConfigurationUpdate(Map<String, Object> configurationParameters) {

View File

@ -20,7 +20,6 @@ import java.net.NetworkInterface;
import java.net.SocketException; import java.net.SocketException;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import org.apache.commons.net.util.SubnetUtils;
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.openhab.binding.magentatv.internal.MagentaTVException; import org.openhab.binding.magentatv.internal.MagentaTVException;
@ -76,34 +75,6 @@ public class MagentaTVNetwork {
"Unable to get local IP / MAC address, check network settings in openHAB system configuration!"); "Unable to get local IP / MAC address, check network settings in openHAB system configuration!");
} }
/**
* Checks if client ip equals or is in range of ip networks provided by
* semicolon separated list
*
* @param clientIp in numeric form like "192.168.0.10"
* @param ipList like "127.0.0.1;192.168.0.0/24;10.0.0.0/8"
* @return true if client ip from the list os ips and networks
*/
public static boolean isIpInSubnet(String clientIp, String ipList) {
if (ipList.isEmpty()) {
// No ip address provided
return true;
}
String[] subnetMasks = ipList.split(";");
for (String subnetMask : subnetMasks) {
subnetMask = subnetMask.trim();
if (clientIp.equals(subnetMask)) {
return true;
}
if (subnetMask.contains("/")) {
if (new SubnetUtils(subnetMask).getInfo().isInRange(clientIp)) {
return true;
}
}
}
return false;
}
@Nullable @Nullable
public NetworkInterface getLocalInterface() { public NetworkInterface getLocalInterface() {
return localInterface; return localInterface;

View File

@ -13,21 +13,34 @@
package org.openhab.binding.magentatv.internal.network; package org.openhab.binding.magentatv.internal.network;
import static org.openhab.binding.magentatv.internal.MagentaTVBindingConstants.*; import static org.openhab.binding.magentatv.internal.MagentaTVBindingConstants.*;
import static org.openhab.binding.magentatv.internal.MagentaTVUtil.substringAfterLast; import static org.openhab.binding.magentatv.internal.MagentaTVUtil.*;
import java.io.ByteArrayInputStream; import java.io.UnsupportedEncodingException;
import java.io.IOException; import java.net.HttpCookie;
import java.io.InputStream;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.Charset; import java.nio.charset.StandardCharsets;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties; import java.util.Properties;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.ws.rs.HttpMethod; import javax.ws.rs.HttpMethod;
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.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.util.StringContentProvider;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpStatus;
import org.openhab.binding.magentatv.internal.MagentaTVException; import org.openhab.binding.magentatv.internal.MagentaTVException;
import org.openhab.binding.magentatv.internal.MagentaTVGsonDTO.OAuthAuthenticateResponse; import org.openhab.binding.magentatv.internal.MagentaTVGsonDTO.OAuthAuthenticateResponse;
import org.openhab.binding.magentatv.internal.MagentaTVGsonDTO.OAuthAuthenticateResponseInstanceCreator; import org.openhab.binding.magentatv.internal.MagentaTVGsonDTO.OAuthAuthenticateResponseInstanceCreator;
@ -37,7 +50,6 @@ import org.openhab.binding.magentatv.internal.MagentaTVGsonDTO.OauthCredentials;
import org.openhab.binding.magentatv.internal.MagentaTVGsonDTO.OauthCredentialsInstanceCreator; import org.openhab.binding.magentatv.internal.MagentaTVGsonDTO.OauthCredentialsInstanceCreator;
import org.openhab.binding.magentatv.internal.MagentaTVGsonDTO.OauthKeyValue; import org.openhab.binding.magentatv.internal.MagentaTVGsonDTO.OauthKeyValue;
import org.openhab.binding.magentatv.internal.handler.MagentaTVControl; import org.openhab.binding.magentatv.internal.handler.MagentaTVControl;
import org.openhab.core.io.net.http.HttpUtil;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -50,7 +62,7 @@ import com.google.gson.GsonBuilder;
* *
* @author Markus Michels - Initial contribution * @author Markus Michels - Initial contribution
* *
* Deutsche Telekom uses a OAuth-based authentication to access the EPG portal. The * Deutsche Telekom uses an OAuth-based authentication to access the EPG portal. The
* communication between the MR and the remote app requires a pairing before the receiver could be * communication between the MR and the remote app requires a pairing before the receiver could be
* controlled by sending keys etc. The so called userID is not directly derived from any local parameters * controlled by sending keys etc. The so called userID is not directly derived from any local parameters
* (like terminalID as a has from the mac address), but will be returned as a result from the OAuth * (like terminalID as a has from the mac address), but will be returned as a result from the OAuth
@ -63,9 +75,12 @@ import com.google.gson.GsonBuilder;
@NonNullByDefault @NonNullByDefault
public class MagentaTVOAuth { public class MagentaTVOAuth {
private final Logger logger = LoggerFactory.getLogger(MagentaTVOAuth.class); private final Logger logger = LoggerFactory.getLogger(MagentaTVOAuth.class);
final Gson gson; private HttpClient httpClient;
private final Gson gson;
private List<HttpCookie> cookies = new ArrayList<>();
public MagentaTVOAuth() { public MagentaTVOAuth(HttpClient httpClient) {
this.httpClient = httpClient;
gson = new GsonBuilder().registerTypeAdapter(OauthCredentials.class, new OauthCredentialsInstanceCreator()) gson = new GsonBuilder().registerTypeAdapter(OauthCredentials.class, new OauthCredentialsInstanceCreator())
.registerTypeAdapter(OAuthTokenResponse.class, new OAuthTokenResponseInstanceCreator()) .registerTypeAdapter(OAuthTokenResponse.class, new OAuthTokenResponseInstanceCreator())
.registerTypeAdapter(OAuthAuthenticateResponse.class, new OAuthAuthenticateResponseInstanceCreator()) .registerTypeAdapter(OAuthAuthenticateResponse.class, new OAuthAuthenticateResponseInstanceCreator())
@ -78,124 +93,209 @@ public class MagentaTVOAuth {
throw new MagentaTVException("Credentials for OAuth missing, check thing config!"); throw new MagentaTVException("Credentials for OAuth missing, check thing config!");
} }
String step = "initialize";
String url = ""; String url = "";
Properties httpHeader; Properties httpHeader = initHttpHeader();
String postData = ""; String postData = "";
String httpResponse = ""; String httpResponse = "";
InputStream dataStream = null;
// OAuth autentication results // OAuth autentication results
String oAuthScope = ""; String oAuthScope = "";
String oAuthService = ""; String oAuthService = "";
String epghttpsurl = ""; String epghttpsurl = "";
String retcode = "";
String retmsg = "";
// Get credentials
url = OAUTH_GET_CRED_URL + ":" + OAUTH_GET_CRED_PORT + OAUTH_GET_CRED_URI;
httpHeader.setProperty(HttpHeader.HOST.toString(), substringAfterLast(OAUTH_GET_CRED_URL, "/"));
httpResponse = httpRequest(HttpMethod.GET, url, httpHeader, "");
OauthCredentials cred = gson.fromJson(httpResponse, OauthCredentials.class);
epghttpsurl = getString(cred.epghttpsurl);
if (epghttpsurl.isEmpty()) {
throw new MagentaTVException("Unable to determine EPG url");
}
if (!epghttpsurl.contains("/EPG")) {
epghttpsurl = epghttpsurl + "/EPG";
}
logger.debug("OAuth: epghttpsurl = {}", epghttpsurl);
// get OAuth data from response
if (cred.sam3Para != null) {
for (OauthKeyValue si : cred.sam3Para) {
logger.trace("sam3Para.{} = {}", si.key, si.value);
if (si.key.equalsIgnoreCase("oAuthScope")) {
oAuthScope = si.value;
} else if (si.key.equalsIgnoreCase("SAM3ServiceURL")) {
oAuthService = si.value;
}
}
}
if (oAuthScope.isEmpty() || oAuthService.isEmpty()) {
throw new MagentaTVException("OAuth failed: Can't get Scope and Service: " + httpResponse);
}
// Get OAuth token (New flow based on WebTV)
String userId = "";
String terminalId = UUID.randomUUID().toString();
String cnonce = MagentaTVControl.computeMD5(terminalId);
url = oAuthService + "/oauth2/tokens";
postData = MessageFormat.format(
"password={0}&scope={1}+offline_access&grant_type=password&username={2}&x_telekom.access_token.format=CompactToken&x_telekom.access_token.encoding=text%2Fbase64&client_id=10LIVESAM30000004901NGTVWEB0000000000000",
urlEncode(accountPassword), oAuthScope, urlEncode(accountName));
url = oAuthService + "/oauth2/tokens";
httpResponse = httpRequest(HttpMethod.POST, url, httpHeader, postData);
OAuthTokenResponse resp = gson.fromJson(httpResponse, OAuthTokenResponse.class);
if (resp.accessToken.isEmpty()) {
String errorMessage = MessageFormat.format("Unable to authenticate: accountName={0}, rc={1} ({2})",
accountName, getString(resp.errorDescription), getString(resp.error));
logger.warn("{}", errorMessage);
throw new MagentaTVException(errorMessage);
}
logger.debug("OAuth: Access Token retrieved");
// General authentication
logger.debug("OAuth: Generating CSRF token");
url = "https://api.prod.sngtv.magentatv.de/EPG/JSON/Authenticate";
httpHeader = initHttpHeader();
httpHeader.setProperty(HttpHeader.HOST.toString(), "api.prod.sngtv.magentatv.de");
httpHeader.setProperty("Origin", "https://web.magentatv.de");
httpHeader.setProperty(HttpHeader.REFERER.toString(), "https://web.magentatv.de/");
postData = "{\"areaid\":\"1\",\"cnonce\":\"" + cnonce + "\",\"mac\":\"" + terminalId
+ "\",\"preSharedKeyID\":\"NGTV000001\",\"subnetId\":\"4901\",\"templatename\":\"NGTV\",\"terminalid\":\""
+ terminalId
+ "\",\"terminaltype\":\"WEB-MTV\",\"terminalvendor\":\"WebTV\",\"timezone\":\"Europe/Berlin\",\"usergroup\":\"-1\",\"userType\":3,\"utcEnable\":1}";
httpResponse = httpRequest(HttpMethod.POST, url, httpHeader, postData);
String csrf = "";
for (HttpCookie c : cookies) { // get CRSF Token
String value = c.getValue();
if (value.contains("CSRFSESSION")) {
csrf = substringBetween(value, "CSRFSESSION" + "=", ";");
}
}
if (csrf.isEmpty()) {
throw new MagentaTVException("OAuth: Unable to get CSRF token!");
}
// Final step: Retrieve userId
url = "https://api.prod.sngtv.magentatv.de/EPG/JSON/DTAuthenticate";
httpHeader = initHttpHeader();
httpHeader.setProperty(HttpHeader.HOST.toString(), "api.prod.sngtv.magentatv.de");
httpHeader.setProperty("Origin", "https://web.magentatv.de");
httpHeader.setProperty(HttpHeader.REFERER.toString(), "https://web.magentatv.de/");
httpHeader.setProperty("X_CSRFToken", csrf);
postData = "{\"areaid\":\"1\",\"cnonce\":\"" + cnonce + "\",\"mac\":\"" + terminalId + "\","
+ "\"preSharedKeyID\":\"NGTV000001\",\"subnetId\":\"4901\",\"templatename\":\"NGTV\","
+ "\"terminalid\":\"" + terminalId + "\",\"terminaltype\":\"WEB-MTV\",\"terminalvendor\":\"WebTV\","
+ "\"timezone\":\"Europe/Berlin\",\"usergroup\":\"\",\"userType\":\"1\",\"utcEnable\":1,"
+ "\"accessToken\":\"" + resp.accessToken
+ "\",\"caDeviceInfo\":[{\"caDeviceId\":\"4ef4d933-9a43-41d3-9e3a-84979f22c9eb\","
+ "\"caDeviceType\":8}],\"connectType\":1,\"osversion\":\"Mac OS 10.15.7\",\"softwareVersion\":\"1.33.4.3\","
+ "\"terminalDetail\":[{\"key\":\"GUID\",\"value\":\"" + terminalId + "\"},"
+ "{\"key\":\"HardwareSupplier\",\"value\":\"WEB-MTV\"},{\"key\":\"DeviceClass\",\"value\":\"TV\"},"
+ "{\"key\":\"DeviceStorage\",\"value\":0},{\"key\":\"DeviceStorageSize\",\"value\":0}]}";
httpResponse = httpRequest(HttpMethod.POST, url, httpHeader, postData);
OAuthAuthenticateResponse authResp = gson.fromJson(httpResponse, OAuthAuthenticateResponse.class);
if (authResp.userID.isEmpty()) {
String errorMessage = MessageFormat.format("Unable to authenticate: accountName={0}, rc={1} {2}",
accountName, getString(authResp.retcode), getString(authResp.desc));
logger.warn("{}", errorMessage);
throw new MagentaTVException(errorMessage);
}
userId = getString(authResp.userID);
if (userId.isEmpty()) {
throw new MagentaTVException("No userID received!");
}
String hashedUserID = MagentaTVControl.computeMD5(userId).toUpperCase();
logger.trace("done, userID = {}", hashedUserID);
return hashedUserID;
}
private String httpRequest(String method, String url, Properties headers, String data) throws MagentaTVException {
String result = "";
try { try {
step = "get credentials"; Request request = httpClient.newRequest(url).method(method).timeout(NETWORK_TIMEOUT_MS,
httpHeader = initHttpHeader(); TimeUnit.MILLISECONDS);
url = OAUTH_GET_CRED_URL + ":" + OAUTH_GET_CRED_PORT + OAUTH_GET_CRED_URI; for (Enumeration<?> e = headers.keys(); e.hasMoreElements();) {
httpHeader.setProperty(HEADER_HOST, substringAfterLast(OAUTH_GET_CRED_URL, "/")); String key = (String) e.nextElement();
logger.trace("{} from {}", step, url); String val = (String) headers.get(key);
httpResponse = HttpUtil.executeUrl(HttpMethod.GET, url, httpHeader, null, null, NETWORK_TIMEOUT_MS); request.header(key, val);
logger.trace("http response = {}", httpResponse);
OauthCredentials cred = gson.fromJson(httpResponse, OauthCredentials.class);
epghttpsurl = getString(cred.epghttpsurl);
if (epghttpsurl.isEmpty()) {
throw new MagentaTVException("Unable to determine EPG url");
} }
if (!epghttpsurl.contains("/EPG")) { if (method.equals(HttpMethod.POST)) {
epghttpsurl = epghttpsurl + "/EPG"; fillPostData(request, data);
} }
logger.debug("epghttpsurl = {}", epghttpsurl); if (cookies.size() > 0) {
// Add cookies
String cookieValue = "";
for (HttpCookie c : cookies) {
cookieValue = cookieValue + substringBefore(c.getValue(), ";") + "; ";
}
request.header("Cookie", substringBeforeLast(cookieValue, ";"));
}
logger.debug("OAuth: HTTP Request\n\tHTTP {} {}\n\tData={}", method, url, data.isEmpty() ? "<none>" : data);
logger.trace("\n\tHeaders={}\tCookies={}", request.getHeaders(), request.getCookies());
// get OAuth data from response ContentResponse contentResponse = request.send();
if (cred.sam3Para != null) { result = contentResponse.getContentAsString().replace("\t", "").replace("\r\n", "").trim();
for (OauthKeyValue si : cred.sam3Para) { int status = contentResponse.getStatus();
logger.trace("sam3Para.{} = {}", si.key, si.value); logger.debug("OAuth: HTTP Response\n\tStatus={} {}\n\tData={}", status, contentResponse.getReason(),
if (si.key.equalsIgnoreCase("oAuthScope")) { result.isEmpty() ? "<none>" : result);
oAuthScope = si.value; logger.trace("\n\tHeaders={}", contentResponse.getHeaders());
} else if (si.key.equalsIgnoreCase("SAM3ServiceURL")) {
oAuthService = si.value; // validate response, API errors are reported as Json
} HttpFields responseHeaders = contentResponse.getHeaders();
for (HttpField f : responseHeaders) {
if (f.getName().equals("Set-Cookie")) {
HttpCookie c = new HttpCookie(f.getName(), f.getValue());
cookies.add(c);
} }
} }
if (oAuthScope.isEmpty() || oAuthService.isEmpty()) { if (status != HttpStatus.OK_200) {
throw new MagentaTVException("OAuth failed: Can't get Scope and Service: " + httpResponse); String error = "HTTP reqaest failed for URL " + url + ", Code=" + contentResponse.getReason() + "("
+ status + ")";
throw new MagentaTVException(error);
} }
} catch (ExecutionException | InterruptedException | TimeoutException e) {
// Get OAuth token String error = "HTTP reqaest failed for URL " + url;
step = "get token"; logger.info("{}", error, e);
url = oAuthService + "/oauth2/tokens"; throw new MagentaTVException(e, error);
logger.debug("{} from {}", step, url);
String userId = "";
String uuid = UUID.randomUUID().toString();
String cnonce = MagentaTVControl.computeMD5(uuid);
// New flow based on WebTV
postData = MessageFormat.format(
"password={0}&scope={1}+offline_access&grant_type=password&username={2}&x_telekom.access_token.format=CompactToken&x_telekom.access_token.encoding=text%2Fbase64&client_id=10LIVESAM30000004901NGTVWEB0000000000000",
URLEncoder.encode(accountPassword, UTF_8), oAuthScope, URLEncoder.encode(accountName, UTF_8));
url = oAuthService + "/oauth2/tokens";
dataStream = new ByteArrayInputStream(postData.getBytes(Charset.forName("UTF-8")));
httpResponse = HttpUtil.executeUrl(HttpMethod.POST, url, httpHeader, dataStream, null, NETWORK_TIMEOUT_MS);
logger.trace("http response={}", httpResponse);
OAuthTokenResponse resp = gson.fromJson(httpResponse, OAuthTokenResponse.class);
if (resp.accessToken.isEmpty()) {
String errorMessage = MessageFormat.format("Unable to authenticate: accountName={0}, rc={1} ({2})",
accountName, getString(resp.errorDescription), getString(resp.error));
logger.warn("{}", errorMessage);
throw new MagentaTVException(errorMessage);
}
uuid = "t_" + MagentaTVControl.computeMD5(accountName);
url = "https://web.magentatv.de/EPG/JSON/DTAuthenticate?SID=user&T=Mac_chrome_81";
postData = "{\"userType\":1,\"terminalid\":\"" + uuid + "\",\"mac\":\"" + uuid + "\""
+ ",\"terminaltype\":\"MACWEBTV\",\"utcEnable\":1,\"timezone\":\"Europe/Berlin\","
+ "\"terminalDetail\":[{\"key\":\"GUID\",\"value\":\"" + uuid + "\"},"
+ "{\"key\":\"HardwareSupplier\",\"value\":\"\"},{\"key\":\"DeviceClass\",\"value\":\"PC\"},"
+ "{\"key\":\"DeviceStorage\",\"value\":\"1\"},{\"key\":\"DeviceStorageSize\",\"value\":\"\"}],"
+ "\"softwareVersion\":\"\",\"osversion\":\"\",\"terminalvendor\":\"Unknown\","
+ "\"caDeviceInfo\":[{\"caDeviceType\":6,\"caDeviceId\":\"" + uuid + "\"}]," + "\"accessToken\":\""
+ resp.accessToken + "\",\"preSharedKeyID\":\"PC01P00002\",\"cnonce\":\"" + cnonce + "\"}";
dataStream = new ByteArrayInputStream(postData.getBytes(Charset.forName("UTF-8")));
logger.debug("HTTP POST {}, postData={}", url, postData);
httpResponse = HttpUtil.executeUrl(HttpMethod.POST, url, httpHeader, dataStream, null, NETWORK_TIMEOUT_MS);
logger.trace("http response={}", httpResponse);
OAuthAuthenticateResponse authResp = gson.fromJson(httpResponse, OAuthAuthenticateResponse.class);
if (authResp.userID.isEmpty()) {
String errorMessage = MessageFormat.format("Unable to authenticate: accountName={0}, rc={1} {2}",
accountName, getString(authResp.retcode), getString(authResp.desc));
logger.warn("{}", errorMessage);
throw new MagentaTVException(errorMessage);
}
userId = getString(authResp.userID);
if (userId.isEmpty()) {
throw new MagentaTVException("No userID received!");
}
String hashedUserID = MagentaTVControl.computeMD5(userId).toUpperCase();
logger.trace("done, userID = {}", hashedUserID);
return hashedUserID;
} catch (IOException e) {
throw new MagentaTVException(e,
"Unable to authenticate {0}: {1} failed; serviceURL={2}, rc={3}/{4}, response={5}", accountName,
step, oAuthService, retcode, retmsg, httpResponse);
} }
return result;
} }
private Properties initHttpHeader() { private Properties initHttpHeader() {
Properties httpHeader = new Properties(); Properties httpHeader = new Properties();
httpHeader.setProperty(HEADER_USER_AGENT, OAUTH_USER_AGENT); httpHeader.setProperty(HttpHeader.ACCEPT.toString(), "*/*");
httpHeader.setProperty(HEADER_ACCEPT, "*/*"); httpHeader.setProperty(HttpHeader.ACCEPT_LANGUAGE.toString(), "en-US,en;q=0.9,de;q=0.8");
httpHeader.setProperty(HEADER_LANGUAGE, "de-de"); httpHeader.setProperty(HttpHeader.CACHE_CONTROL.toString(), "no-cache");
httpHeader.setProperty(HEADER_CACHE_CONTROL, "no-cache");
return httpHeader; return httpHeader;
} }
private void fillPostData(Request request, String data) {
if (!data.isEmpty()) {
StringContentProvider postData;
if (request.getHeaders().contains(HttpHeader.CONTENT_TYPE)) {
String contentType = request.getHeaders().get(HttpHeader.CONTENT_TYPE);
postData = new StringContentProvider(contentType, data, StandardCharsets.UTF_8);
} else {
boolean json = data.startsWith("{");
postData = new StringContentProvider(json ? "application/json" : "application/x-www-form-urlencoded",
data, StandardCharsets.UTF_8);
}
request.content(postData);
request.header(HttpHeader.CONTENT_LENGTH, Long.toString(postData.getLength()));
}
}
private String getString(@Nullable String value) { private String getString(@Nullable String value) {
return value != null ? value : ""; return value != null ? value : "";
} }
private String urlEncode(String url) {
try {
return URLEncoder.encode(url, UTF_8);
} catch (UnsupportedEncodingException e) {
logger.warn("OAuth: Unable to URL encode string {}", url, e);
return "";
}
}
} }

View File

@ -9,8 +9,8 @@ thing-type.magentatv.receiver.description = Media Receiver zum Epmfang von Magen
# Thing configuration # Thing configuration
thing-type.config.magentatv.receiver.ipAddress.label = IP-Adresse thing-type.config.magentatv.receiver.ipAddress.label = IP-Adresse
thing-type.config.magentatv.ipAddress.description = IP Adresse des Media Receivers thing-type.config.magentatv.ipAddress.description = IP Adresse des Media Receivers
thing-type.config.magentatv.receiver.userId.label = UID thing-type.config.magentatv.receiver.userId.label = User ID
thing-type.config.magentatv.receiver.userId.description = Technische Benutzerkennung (User ID), siehe Dokumentation; wird automatisch gefüllt, wenn Login-Name und Passwort angegeben sind. thing-type.config.magentatv.receiver.userId.description = Technische Benutzerkennung, siehe Dokumentation
thing-type.config.magentatv.receiver.accountName.label = Login-Name thing-type.config.magentatv.receiver.accountName.label = Login-Name
thing-type.config.magentatv.receiver.accountName.description = Login-Name (E-Mail) zur Anmeldung im Telekom Kundencenter thing-type.config.magentatv.receiver.accountName.description = Login-Name (E-Mail) zur Anmeldung im Telekom Kundencenter
thing-type.config.magentatv.receiver.accountPassword.label = Passwort thing-type.config.magentatv.receiver.accountPassword.label = Passwort

View File

@ -20,8 +20,8 @@
</channel-group> </channel-group>
</channel-groups> </channel-groups>
<representation-property>macAddress</representation-property> <representation-property>macAddress</representation-property>
<config-description uri="thing-type:magentatv:receiver"> <config-description uri="thing-type:magentatv:receiver">
<parameter name="ipAddress" type="text"> <parameter name="ipAddress" type="text">
<label>Device IP Address</label> <label>Device IP Address</label>
@ -40,7 +40,7 @@
</parameter> </parameter>
<parameter name="userId" type="text"> <parameter name="userId" type="text">
<label>User ID</label> <label>User ID</label>
<description>Technical User ID required for pairing process, auto-filled when account credentials are given</description> <description>Technical User ID required for pairing process</description>
</parameter> </parameter>
<parameter name="udn" type="text"> <parameter name="udn" type="text">
<label>Unique Device Name</label> <label>Unique Device Name</label>