2 * Copyright (c) 2010-2024 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.time.Instant;
18 import java.util.List;
19 import java.util.Locale;
20 import java.util.concurrent.TimeUnit;
21 import java.util.stream.Collectors;
22 import java.util.stream.Stream;
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.eclipse.jetty.client.HttpClient;
27 import org.eclipse.jetty.client.api.ContentResponse;
28 import org.eclipse.jetty.client.api.Request;
29 import org.eclipse.jetty.http.HttpHeader;
30 import org.eclipse.jetty.http.HttpMethod;
31 import org.openhab.binding.enphase.internal.EnvoyConfiguration;
32 import org.openhab.binding.enphase.internal.exception.EnphaseException;
33 import org.openhab.binding.enphase.internal.exception.EntrezJwtInvalidException;
34 import org.openhab.binding.enphase.internal.exception.EnvoyConnectionException;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
39 * Envoy connector using Entrez portal to obtain an JWT access token.
41 * @author Joe Inkenbrandt - Initial contribution
42 * @author Hilbrand Bouwkamp - Refactored entrez specific code in it's own sub connector class
45 public class EnvoyEntrezConnector extends EnvoyConnector {
47 // private static final String SESSION = "session";
48 private static final String HTTPS = "https://";
49 private static final String LOGIN_URL = "/auth/check_jwt";
50 private static final String SESSION_COOKIE_NAME = "session";
52 private final Logger logger = LoggerFactory.getLogger(EnvoyEntrezConnector.class);
54 private final EntrezConnector entrezConnector;
56 private @Nullable String sessionKey;
57 private @Nullable String sessionId;
58 private String jwt = "";
59 private Instant jwtExpirationTime = Instant.now();
61 public EnvoyEntrezConnector(final HttpClient httpClient) {
62 super(httpClient, HTTPS);
63 entrezConnector = new EntrezConnector(httpClient);
67 public String setConfiguration(final EnvoyConfiguration configuration) {
70 if (!configuration.autoJwt) {
71 message = check(configuration.jwt, "No autoJWT enabled, but jwt parameter is empty.");
73 if (message.isEmpty()) {
74 jwt = configuration.jwt;
78 .of(check(configuration.username, "Username parameter is empty"),
79 check(configuration.password, "Password parameter is empty"),
80 check(configuration.siteName, "siteName parameter is empty"))
81 .filter(s -> !s.isEmpty()).collect(Collectors.joining(", "));
84 if (!message.isEmpty()) {
87 return super.setConfiguration(configuration);
90 private String check(final String property, final String message) {
91 return property.isBlank() ? message : "";
95 protected void constructRequest(final Request request) throws EnphaseException {
96 // Check if we need a new session ID
97 if (!checkSessionId()) {
98 sessionId = getNewSessionId();
100 logger.trace("Retrieving data from '{}' with sessionID '{}'", request.getURI(), sessionId);
101 request.cookie(new HttpCookie(sessionKey, sessionId));
104 private boolean checkSessionId() {
105 if (this.sessionId == null) {
108 final URI uri = URI.create(HTTPS + configuration.hostname + LOGIN_URL);
110 final Request request = httpClient.newRequest(uri).method(HttpMethod.GET)
111 .cookie(new HttpCookie(sessionKey, this.sessionId)).timeout(CONNECT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
112 final ContentResponse response;
115 response = send(request);
116 } catch (final EnvoyConnectionException e) {
117 logger.debug("Session ID ({}) Check TimeoutException: {}", sessionId, e.getMessage());
120 if (response.getStatus() != 200) {
121 logger.debug("Session ID ({}) Home Response: {}", sessionId, response.getStatus());
125 logger.debug("Home Response: {}", response.getContentAsString());
129 private @Nullable String getNewSessionId() throws EnphaseException {
130 if (jwt.isEmpty() || isExpired()) {
131 if (configuration.autoJwt) {
132 jwt = entrezConnector.retrieveJwt(configuration.username, configuration.password,
133 configuration.siteName, configuration.serialNumber);
134 jwtExpirationTime = Instant.ofEpochSecond(entrezConnector.processJwt(jwt).getBody().getExp());
136 new EntrezJwtInvalidException("Accesstoken expired. Configure new token or configure autoJwt.");
139 return loginWithJWT(jwt);
142 private boolean isExpired() {
143 return jwtExpirationTime.isBefore(Instant.now());
147 * This function attempts to get a sessionId from the local gateway by submitting the JWT given.
149 * @return the sessionId or null of no session id could be retrieved.
151 private @Nullable String loginWithJWT(final String jwt) throws EnvoyConnectionException, EntrezJwtInvalidException {
152 final URI uri = URI.create(HTTPS + configuration.hostname + LOGIN_URL);
154 // Authorization: Bearer
155 final Request request = httpClient.newRequest(uri).method(HttpMethod.GET)
156 .header("Authorization", "Bearer " + jwt).timeout(CONNECT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
157 final ContentResponse response = send(request);
159 if (response.getStatus() == 200 && response.getHeaders().contains(HttpHeader.SET_COOKIE)) {
160 final List<HttpCookie> cookies = HttpCookie.parse(response.getHeaders().get(HttpHeader.SET_COOKIE));
162 for (final HttpCookie c : cookies) {
163 final String cookieKey = String.valueOf(c.getName()).toLowerCase(Locale.ROOT);
165 if (cookieKey.startsWith(SESSION_COOKIE_NAME)) {
166 logger.debug("Got SessionID: {}", c.getValue());
167 sessionKey = cookieKey;
172 "Failed to find cookie with the JWT token from the Enphase portal. Maybe Enphase changed the website.");
173 throw new EntrezJwtInvalidException(
174 "Unable to obtain jwt key from Ephase website. Manully configuring the JWT might make it work. Please report this issue.");
176 logger.debug("Failed to login to Envoy. Evoy returned status: {}. Response from Envoy: {}",
177 response.getStatus(), response.getContentAsString());
178 throw new EntrezJwtInvalidException(
179 "Could not login to Envoy with the access token. Envoy returned status:" + response.getStatus());