]> git.basschouten.com Git - openhab-addons.git/blob
30c3976e244392982b48fadedeaab350455f1c30
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.enphase.internal.handler;
14
15 import java.net.HttpCookie;
16 import java.net.URI;
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;
23
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;
37
38 /**
39  * Envoy connector using Entrez portal to obtain an JWT access token.
40  *
41  * @author Joe Inkenbrandt - Initial contribution
42  * @author Hilbrand Bouwkamp - Refactored entrez specific code in it's own sub connector class
43  */
44 @NonNullByDefault
45 public class EnvoyEntrezConnector extends EnvoyConnector {
46
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";
51
52     private final Logger logger = LoggerFactory.getLogger(EnvoyEntrezConnector.class);
53
54     private final EntrezConnector entrezConnector;
55
56     private @Nullable String sessionKey;
57     private @Nullable String sessionId;
58     private String jwt = "";
59     private Instant jwtExpirationTime = Instant.now();
60
61     public EnvoyEntrezConnector(final HttpClient httpClient) {
62         super(httpClient, HTTPS);
63         entrezConnector = new EntrezConnector(httpClient);
64     }
65
66     @Override
67     public String setConfiguration(final EnvoyConfiguration configuration) {
68         final String message;
69
70         if (!configuration.autoJwt) {
71             message = check(configuration.jwt, "No autoJWT enabled, but jwt parameter is empty.");
72
73             if (message.isEmpty()) {
74                 jwt = configuration.jwt;
75             }
76         } else {
77             message = Stream
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(", "));
82
83         }
84         if (!message.isEmpty()) {
85             return message;
86         }
87         return super.setConfiguration(configuration);
88     }
89
90     private String check(final String property, final String message) {
91         return property.isBlank() ? message : "";
92     }
93
94     @Override
95     protected void constructRequest(final Request request) throws EnphaseException {
96         // Check if we need a new session ID
97         if (!checkSessionId()) {
98             sessionId = getNewSessionId();
99         }
100         logger.trace("Retrieving data from '{}' with sessionID '{}'", request.getURI(), sessionId);
101         request.cookie(new HttpCookie(sessionKey, sessionId));
102     }
103
104     private boolean checkSessionId() {
105         if (this.sessionId == null) {
106             return false;
107         }
108         final URI uri = URI.create(HTTPS + configuration.hostname + LOGIN_URL);
109
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;
113
114         try {
115             response = send(request);
116         } catch (final EnvoyConnectionException e) {
117             logger.debug("Session ID ({}) Check TimeoutException: {}", sessionId, e.getMessage());
118             return false;
119         }
120         if (response.getStatus() != 200) {
121             logger.debug("Session ID ({}) Home Response: {}", sessionId, response.getStatus());
122             return false;
123         }
124
125         logger.debug("Home Response: {}", response.getContentAsString());
126         return true;
127     }
128
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());
135             } else {
136                 new EntrezJwtInvalidException("Accesstoken expired. Configure new token or configure autoJwt.");
137             }
138         }
139         return loginWithJWT(jwt);
140     }
141
142     private boolean isExpired() {
143         return jwtExpirationTime.isBefore(Instant.now());
144     }
145
146     /**
147      * This function attempts to get a sessionId from the local gateway by submitting the JWT given.
148      *
149      * @return the sessionId or null of no session id could be retrieved.
150      */
151     private @Nullable String loginWithJWT(final String jwt) throws EnvoyConnectionException, EntrezJwtInvalidException {
152         final URI uri = URI.create(HTTPS + configuration.hostname + LOGIN_URL);
153
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);
158
159         if (response.getStatus() == 200 && response.getHeaders().contains(HttpHeader.SET_COOKIE)) {
160             final List<HttpCookie> cookies = HttpCookie.parse(response.getHeaders().get(HttpHeader.SET_COOKIE));
161
162             for (final HttpCookie c : cookies) {
163                 final String cookieKey = String.valueOf(c.getName()).toLowerCase(Locale.ROOT);
164
165                 if (cookieKey.startsWith(SESSION_COOKIE_NAME)) {
166                     logger.debug("Got SessionID: {}", c.getValue());
167                     sessionKey = cookieKey;
168                     return c.getValue();
169                 }
170             }
171             logger.debug(
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.");
175         }
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());
180     }
181 }