2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.enphase.internal.handler;
15 import java.net.HttpCookie;
17 import java.nio.charset.StandardCharsets;
18 import java.util.Base64;
19 import java.util.List;
20 import java.util.concurrent.ExecutionException;
21 import java.util.concurrent.TimeUnit;
22 import java.util.concurrent.TimeoutException;
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jetty.client.HttpClient;
26 import org.eclipse.jetty.client.api.ContentResponse;
27 import org.eclipse.jetty.client.api.Request;
28 import org.eclipse.jetty.client.util.FormContentProvider;
29 import org.eclipse.jetty.http.HttpHeader;
30 import org.eclipse.jetty.http.HttpMethod;
31 import org.eclipse.jetty.util.Fields;
32 import org.jsoup.Jsoup;
33 import org.jsoup.nodes.Document;
34 import org.jsoup.nodes.Element;
35 import org.jsoup.select.Elements;
36 import org.openhab.binding.enphase.internal.dto.EntrezJwtDTO;
37 import org.openhab.binding.enphase.internal.dto.EntrezJwtDTO.EntrezJwtBodyDTO;
38 import org.openhab.binding.enphase.internal.dto.EntrezJwtDTO.EntrezJwtHeaderDTO;
39 import org.openhab.binding.enphase.internal.exception.EntrezConnectionException;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
43 import com.google.gson.Gson;
44 import com.google.gson.GsonBuilder;
45 import com.google.gson.JsonSyntaxException;
48 * Connector logic for connecting to Entrez server
50 * @author Joe Inkenbrandt - Initial contribution
53 public class EntrezConnector {
55 private static final String SESSION_COOKIE_NAME = "SESSION";
56 private static final String ELEMENT_ID_JWT_TOKEN = "#JWTToken";
57 private static final String LOGIN_URL = "https://entrez.enphaseenergy.com/login";
58 private static final String TOKEN_URL = "https://entrez.enphaseenergy.com/entrez_tokens";
60 private final Logger logger = LoggerFactory.getLogger(EntrezConnector.class);
61 private final Gson gson = new GsonBuilder().create();
62 private final HttpClient httpClient;
64 private static final long CONNECT_TIMEOUT_SECONDS = 10;
66 public EntrezConnector(final HttpClient httpClient) {
67 this.httpClient = httpClient;
70 public String retrieveJwt(final String username, final String password, final String siteId, final String serialNum)
71 throws EntrezConnectionException {
72 final String session = login(username, password);
73 final Fields fields = new Fields();
74 fields.put("Site", siteId);
75 fields.put("serialNum", serialNum);
77 final URI uri = URI.create(TOKEN_URL);
78 logger.trace("Retrieving jwt from '{}'", uri);
79 final Request request = httpClient.newRequest(uri).method(HttpMethod.POST)
80 .cookie(new HttpCookie(SESSION_COOKIE_NAME, session)).content(new FormContentProvider(fields))
81 .timeout(CONNECT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
83 final ContentResponse response = send(request);
84 final String contentAsString = response.getContentAsString();
85 final Document document = Jsoup.parse(contentAsString);
86 final Elements elements = document.select(ELEMENT_ID_JWT_TOKEN);
87 final Element first = elements.first();
90 logger.debug("Could not select element '{}' in received data from entrez site. Received data: {}",
91 ELEMENT_ID_JWT_TOKEN, contentAsString);
92 throw new EntrezConnectionException("Could not parse data from entrez site");
97 public EntrezJwtDTO processJwt(final String jwt) throws EntrezConnectionException {
99 final String[] parts = jwt.split("\\.", 0);
100 if (parts.length < 2) {
101 logger.debug("Could not split data into 2 parts. Recevied data: {}", jwt);
102 throw new EntrezConnectionException("Could not parse data from entrez site");
104 final EntrezJwtHeaderDTO header = gson.fromJson(
105 new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8),
106 EntrezJwtHeaderDTO.class);
107 final EntrezJwtBodyDTO body = gson.fromJson(
108 new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8),
109 EntrezJwtBodyDTO.class);
111 return new EntrezJwtDTO(header, body);
112 } catch (JsonSyntaxException | IllegalArgumentException e) {
113 throw new EntrezConnectionException("Could not parse data from entrez site:", e);
117 private String login(final String username, final String password) throws EntrezConnectionException {
118 final Fields fields = new Fields();
119 fields.put("username", username);
120 fields.put("password", password);
122 final URI uri = URI.create(LOGIN_URL);
123 logger.trace("Retrieving session id from '{}'", uri);
124 final Request request = httpClient.newRequest(uri).method(HttpMethod.POST)
125 .content(new FormContentProvider(fields)).timeout(CONNECT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
126 final ContentResponse response = send(request);
128 if (response.getStatus() == 200 && response.getHeaders().contains(HttpHeader.SET_COOKIE)) {
129 final List<HttpCookie> cookies = HttpCookie.parse(response.getHeaders().get(HttpHeader.SET_COOKIE));
131 for (final HttpCookie c : cookies) {
132 if (SESSION_COOKIE_NAME.equals(c.getName())) {
137 logger.debug("Failed to login to Entrez portal. Portal returned status: {}. Response from Entrez portal: {}",
138 response.getStatus(), response.getContentAsString());
139 throw new EntrezConnectionException(
140 "Could not login to Entrez JWT Portal. Status code:" + response.getStatus());
143 private ContentResponse send(final Request request) throws EntrezConnectionException {
145 return request.send();
146 } catch (final InterruptedException e) {
147 Thread.currentThread().interrupt();
148 throw new EntrezConnectionException("Interrupted");
149 } catch (final TimeoutException e) {
150 logger.debug("TimeoutException: {}", e.getMessage());
151 throw new EntrezConnectionException("Connection timeout: ", e);
152 } catch (final ExecutionException e) {
153 logger.debug("ExecutionException: {}", e.getMessage(), e);
154 throw new EntrezConnectionException("Could not retrieve data: ", e.getCause());